Messaging Service

The PubSub+ Messaging API for Go provides the MessagingService interface, which makes it easy to connect to an event broker. The MessagingService interface handles all the functionality for interacting with a PubSub+ event broker. To create a MessagingService instance, you must first configure a ServicePropertyMap with the information required to establish a connection to the event broker, including the host details and the authentication scheme.

Creating a Property Map

ServicePropertyMap (accessed through the config package which is included in the PubSub+ Go API) can have a number of properties, however the ServicePropertyMap must contain the keys config.TransportLayerPropertyHost and config.ServicePropertyVPNName. The ServicePropertyMap is passed to the MessagingServiceBuilder to configure the connection to the event broker. There are five categories of properties that can be configured in a ServicePropertyMap using the config package:.

  • config.ServiceProperty(required for the VPN property)
  • config.TransportLayerProperty (required for HOST property )
  • config.AuthenticationProperty
  • config.ClientProperty
  • config.TransportLayerSecurityProperty

For more information, see the PubSub+ Messaging API for Go reference.

The code below shows how to use a ServicePropertyMap for establishing a connection to an event broker using basic authentication:

package main

import (
	"fmt"
	"os"
	"os/signal"
	"strconv"
	"time"

	"solace.dev/go/messaging"
	"solace.dev/go/messaging/pkg/solace/config"
	"solace.dev/go/messaging/pkg/solace/resource"
)					
...
...
// Configuration parameters
brokerConfig := config.ServicePropertyMap{
config.TransportLayerPropertyHost:                 "tcps://messaging.solace.cloud:55443",
config.ServicePropertyVPNName:                     "MyVPN",
config.AuthenticationPropertySchemeBasicPassword:  "MyPassword123",
config.AuthenticationPropertySchemeBasicUserName:  "MyUsername",
}

Alternatively if you're running samples, the configuration you want to use can be passed into the client application via a JSON file. The following sample code shows how you can use the UnmarshalJSON() function to parse JSON data and use it to create a ServicePropertyMap:

var configMap config.ServicePropertyMap
data,err := os.ReadFile("/path/to/config.json")  // ReadFile reads the named file and returns the contents.
error := json.Unmarshal(data, &configMap)         // Parses the JSON-encoded data and stores the result in the value pointed to by 2nd paramater.
if error != nil {
panic(error)
}
// Use FromConfigurationProvider(configMap) to use JSON data when creating your MessagingService instance

Establishing a Connection to an Event Broker

When the necessary properties have been set and stored in a ServicePropertyMap, you can use the messaging package to call NewMessagingServiceBuilder() function. This function returns an instance of MessagingServiceBuilder that you call the Build() function on to return a MessagingService instance. A MessagingService instance allows the API to establish a connection to the event broker. The following is a list of commonly used functions to create the necessary MessagingService instance:

  • messaging.NewMessagingServiceBuilder()
  • WithAuthenticationStrategy(AuthenticationStrategy authenticationProvider)
  • FromConfigurationProvider(provider config.ServicePropertiesConfigurationProvider)
  • Build()

For more information, see the PubSub+ Messaging API for Go reference.

After you create the MessagingService instance, you call the Connect() function on it to connect to the event broker.

The following sample code shows how to create a simple MessagingService instance and connect it to an event broker:

messagingService, err := messaging.NewMessagingServiceBuilder().      // Returns an instance of solace.MessagingServiceBuilder.
FromConfigurationProvider(brokerConfig).                              // An example configuration provider is ServicePropertyMap
WithTransportSecurityStrategy(config.NewTransportSecurityStrategy().  // Configures the resulting messaging service with the specified transport security strategy.
WithCertificateValidation(false, true, "./trust_store", "")).         // With Certificate Validation. Note: Make sure you have a trust_store directory with the .pem file stored in it.
WithAuthenticationStrategy(config.BasicUserNamePasswordAuthentication("myUsername", "myPassword123")). // Configures the resulting messaging service with the specified authentication configuration.
Build()     // Creates and returns the built MessagingService instance based on the provided configuration.				
if err != nil {
	panic(err)
}

if err := messagingService.Connect(); err != nil {   // Connects the messaging service to the event broker. This function blocks until the connection attempt is completed.
	panic(err)
}	

Using Transport Layer Security

Transport Layer Security (TLS) allows for encrypted authentication and data transmission between the PubSub+ Go API and a PubSub+ event broker. The PubSub+ Go API supports Transport Layer Security versions TLS 1.0, TLS 1.1, and TLS 1.2 . The recommended version to use is the most recent version of TLS. Secure Socket Layer (SSL) protocol, version 3 (SSLv3) is also supported. We don't recommend that you use SSL unless it's required for backwards compatibility.

You can use WithTransportSecurityStrategy() with the config package to configure the TLS connection properties to use, or whether or not to disable certificate validation entirely. When you use TLS, you must always use the secure TCP  protocol (tcps or https) in setting the config.TransportLayerPropertyHost property for your connection, for example:

brokerConfig := config.ServicePropertyMap{
	config.TransportLayerPropertyHost:  "tcps://messaging.solace.cloud:55443",
	config.ServicePropertyVPNName:      "MyVPN",
}

We recommend using certificate validation when configuring your messaging service. The PubSub+ Go API's config package provides the following function for configuring certificate validation:

  • WithCertificateValidation(ignoreExpiration bool, validateServerName bool, trustStoreFilePath string, trustedCommonNameList string)

    • ignoreExpiration— When set to true, expired certificates are accepted.
    • validateServerName— When set to true, certificates without the matching host are not accepted.
    • trustStoreFilePath— The location of the trust store files. If an empty string is passed, no file path will be set.
    • trustedCommonNameList— A comma-separated list of acceptable common names for matching with server certificates. An empty string will match no names.

The following sample code shows the recommended security setup for client applications when you use TLS:

messagingService0, err := messaging.NewMessagingServiceBuilder().
FromConfigurationProvider(brokerConfig).
WithTransportSecurityStrategy(
	config.NewTransportSecurityStrategy().                         // creates a default transport security strategy. Properties can be overwritten by calling configuration functions.
	WithCertificateValidation(false, true, "./trust_store", "").   // Configures TLS validation on certificates. By default, validation is performed.
		WithExcludedProtocols(                                  // Specifies the list of SSL or TLS protocols to not use. 
		config.TransportSecurityProtocolTLSv1,                  // Excludes dated protocols TLSv1, TSLv1_1 and SSLv3.
		config.TransportSecurityProtocolTLSv1_1,
		config.TransportSecurityProtocolSSLv3)).
WithAuthenticationStrategy(config.BasicUserNamePasswordAuthentication("myUserName123", "myPassword123")).
Build()	

There are also a number of functions that you may find useful to configure the TLS connection using the WithTransportSecurityStrategy() function. Here are three functions commonly used to configure the TLS connection:

  • WithCipherSuites()—The list of cipher suites used when negotiating the TLS connection can be configured. You can configure the PubSub+ Go API to limit it to use a set of stronger ciphers and to help guarantee a more secure connection between the client applications and event brokers.

    Cipher suites are a useful method of encrypting communication through a TLS handshake and offer improved security between applications connected through the event broker. For a more detailed explanation and a list of supported cipher suites see Cipher Suites.

    The following code sample shows how to use the WithCipherSuites() function:

    messagingService00, err := messaging.NewMessagingServiceBuilder().
    FromConfigurationProvider(brokerConfig).
    WithTransportSecurityStrategy(
    config.NewTransportSecurityStrategy().
    WithCipherSuites("CipherSuite1, CipherSuite2").   // Comma separated list that configures cipher suites to use.
    WithAuthenticationStrategy(config.ClientCertificateAuthentication("certFile", "keyFile", "keyPassword")).
    Build()	 

  • WithExcludedProtocols()—You may not want to use specific protocols to connect with microservices. For example, you may not want to use legacy protocols. Use this function to specify the Secure Socket Layer (SSL) and Transport Layer Security (TLS) protocols not to use. Here is sample code that shows you how to exclude a specific protocol:

    messagingService00, err := messaging.NewMessagingServiceBuilder().
    FromConfigurationProvider(brokerConfig).
    WithTransportSecurityStrategy(
    config.NewTransportSecurityStrategy().
    WithExcludedProtocols(config.TransportSecurityProtocolTLSv1).   // Specifies the list of SSL or TLS protocols to not use.
    WithAuthenticationStrategy(config.ClientCertificateAuthentication("certFile", "keyFile", "keyPassword")).
    Build()        

  • WithoutCertificateValidation()—This function configures your TLS connection not to validate server certificates.

    Only use this function in development environments. We recommend that you never use this function in production environments because it creates a security vulnerability.

    The following sample code shows how to use the WithoutCertificateValidation() function:

    messagingService, err := messaging.NewMessagingServiceBuilder().
    FromConfigurationProvider(brokerConfig).
    WithTransportSecurityStrategy(
    config.NewTransportSecurityStrategy().
    WithoutCertificateValidation().   // Configures TLS to not validate the server certificate configured on the remote broker.
    WithAuthenticationStrategy(config.ClientCertificateAuthentication("certFile", "keyFile", "keyPassword")).
    Build()        

You can also configure the aspects of the TLS connection using TransportLayerSecurityProperty in the ServicePropertyMap. The TLS connection can be configured using various fields found in config.TransportLayerSecurityProperty. We recommend that you use the default settings (set to true and enabled) to ensure secure connections for the following properties:

  • config.TransportLayerSecurityPropertyCertRejectExpired
  • config.TransportLayerSecurityPropertyCertValidateServername
  • config.TransportLayerSecurityPropertyCertValidated

For more information about the functions and properties, see the PubSub+ Messaging API for Go reference.

Authentication

The PubSub+ Messing API for Go supports a number of authentication schemes (or strategies). that you can choose from. The scheme that you choose may depend on the credentials that the connecting client is required to provide. You can use one of the following authentication schemes:

Basic Authentication

Basic authentication is the default client authentication scheme which allows a client to authenticate with an event broker using a client username and password. To specify basic authentication, create an instance of a MessagingService and specify the following as the parameter for the WithAuthenticationStrategy() function:

  • config.BasicUserNamePasswordAuthentication("myUsername", "myPassword")

For details see the Messaging API for Go reference.

The following sample code shows how to use basic authentication:

messagingService, err := messaging.NewMessagingServiceBuilder().      // Returns an instance of solace.MessagingServiceBuilder.
FromConfigurationProvider(brokerConfig).                              // An example configuration provider is ServicePropertyMap
WithAuthenticationStrategy(config.BasicUserNamePasswordAuthentication("myUsername", "myPassword123")). // Configures the resulting messaging service with the specified authentication configuration.
Build()     // Creates and returns the built MessagingService instance based on the provided configuration.		

Kerberos Authentication

The PubSub+ Go API provides support for Kerberos Authentication. Connecting using this function requires you to load a Kerberos Keytab on the broker (see Managing Event Broker Files) and Kerberos authentication must be configured and enabled for any Message VPNs that Kerberos-authenticated clients connect to.

. Call the WithAuthenticationStrategy() function and pass the following as the parameters:

  • config.KerberosAuthentication(serviceName string) If the service name is not required, an empty string can be passed to the serviceName argument.

For additional details, see the Messaging API for Go reference.

The following sample code shows how to use Kerboros for authentication:

messagingService, err := messaging.NewMessagingServiceBuilder().              // Returns an instance of solace.MessagingServiceBuilder.
FromConfigurationProvider(brokerConfig).				       // An example configuration provider is ServicePropertyMap
WithAuthenticationStrategy(config.KerberosAuthentication("serviceName")).     // Configures the resulting messaging service with the specified authentication configuration.
Build()	   // Creates and returns the built MessagingService instance based on the provided configuration.	

Client Certificate Authentication

To use the Client certificate authentication scheme, the following steps are required:

  • Configure the host event broker to use TLS connections (see Using Transport Layer Security).

  • Your application must connect to the broker using TLS.

  • Enable Client certificate verification on the Message VPN that the application uses to connect.

  • The client-side certificate must be present in a keystore file and configured using the following:

    • config.ClientCertificateAuthentication(certificateFile, keyFile, keyPassword string)

For additional details see the PubSub+ Messaging API for Go reference.

The following sample code shows how to configure client certificate authentication:

messagingService, err := messaging.NewMessagingServiceBuilder().              // Returns an instance of solace.MessagingServiceBuilder.
FromConfigurationProvider(brokerConfig).				       // An example configuration provider is ServicePropertyMap
WithTransportSecurityStrategy(config.NewTransportSecurityStrategy().          // Configures the resulting messaging service with the specified transport security strategy.
WithCertificateValidation(false, true, "./trust_store", "")).                 // With Certificate Validation. Note: Make sure you have a trust_store directory with the .pem file stored in it.).
WithAuthenticationStrategy(config.ClientCertificateAuthentication("certFile", "keyFile", "keyPassword")).  // Configures the resulting messaging service with the specified authentication configuration.
Build()	   // Creates and returns the built MessagingService based on the provided configuration.			

OAuth 2.0 Authentication

OAuth2.0 is an open standard for access delegation and authorization. It is commonly used as a mechanism to grant websites or applications access to users' information on other websites without giving them access to sensitive credentials. The OAuth authentication scheme allows access through the use of tokens issued to third-party clients by an authorization server that provides access to Message VPNs on PubSub+ event brokers. To use OAuth 2.0 authentication, configure the host event broker to use TLS connections (see Using Transport Layer Security) and make sure your application connects to the event broker using TLS. For more information, see OAuth Authentication.

The PubSub+ Go API supports different fields that can be sent to the event broker:

  • accessToken—a String for applications to make requests for data access

    and/or

    idToken—a String for Open ID Connect (OIDC) connections

  • issuerIdentifier—(Optional) a String to identify the appropriate OAuth provider configuration

OAuth authentication requires an accessToken, an idToken, or both to be enabled using this function:

  • config.OAuth2Authentication(accessToken string, oidcIDToken string, issuerIdentifier string)
    • At least one of accessToken or oidcIDToken must be provided. If any of the parameters is not required, an empty string can be passed.

For additional details, refer to the PubSub+ Messaging API for Go reference.

The following sample code shows how to use OAuth authentication with OpenID Connect (OIDC):

/* Configure service access to use a Open ID connect authentication with an ID token and an optional access token. */
messagingService, err := messaging.NewMessagingServiceBuilder().              // Returns an instance of solace.MessagingServiceBuilder.
FromConfigurationProvider(brokerConfig).				       // An example configuration provider is ServicePropertyMap
WithTransportSecurityStrategy(config.NewTransportSecurityStrategy().          // Configures the resulting messaging service with the specified transport security strategy.
WithCertificateValidation(false, true, "./trust_store", "")).                 // With Certificate Validation. Note: Make sure you have a trust_store directory with the .pem file stored in it.).
WithAuthenticationStrategy(config.OAuth2Authentication("accessToken", "oidcIDToken", "")).  // Configures the resulting messaging service with the specified authentication configuration.
Build()	   // Creates and returns the built MessagingService instance based on the provided configuration.		.
/* Configure service access to use OAuth 2 authentication with an access token and an optional issuer identifier. */
messagingService, err := messaging.NewMessagingServiceBuilder().              // Returns an instance of solace.MessagingServiceBuilder.
FromConfigurationProvider(brokerConfig).				       // An example configuration provider is ServicePropertyMap
WithTransportSecurityStrategy(config.NewTransportSecurityStrategy().          // Configures the resulting messaging service with the specified transport security strategy.
WithCertificateValidation(false, true, "./trust_store", "")).                 // With Certificate Validation. Note: Make sure you have a trust_store directory with the .pem file stored in it.).
WithAuthenticationStrategy(config.OAuth2Authentication("accessToken", "", "issuerIdentifier")).  // Configures the resulting messaging service with the specified authentication configuration.
Build()	   // Creates and returns the built MessagingService instance based on the provided configuration.		

Refreshing Expired OAuth Tokens

By default, event brokers disconnect clients when their tokens expire (see Disconnect on Token Expiration). When a client session is disconnected, the client application tries to reconnect a number of times using the same OAuth token based on the RECONNECTION_ATTEMPTS property. If the connection can't be reestablished due to token expiration, the client application must recreate the session with all its subscriptions.

To update the OAuth token, use the UpdateProperty(property config.ServiceProperty, value interface{}) method, which allows you to set a modifable service property after the creation of the MessagingService instance. The first parameter is one of the following strings and the second parameter is the token:

Modifiable service properties may not update immediately and may require the next reconnection attempt to update.

Refreshing the expired token can happen while:

  • the client application is connected. In this case, the client contacts the authentication server to refresh the token and modifies the session to use the updated token the next time the API connects to the event broker.

  • the client application is reconnecting. The reconnecting event includes a diagnostic subCode. If this subCode is Login Failure, this may indicate that your token has expired. In this case, the API tries to reconnect (using the expired token). The client then contacts the authentication server to refresh the token and modifies the session to use the updated token the next time the API attempts to reconnect to the event broker.

In general, it is better if the client application is aware of potential token expiry and refreshes the token before it expires.

When the client application's session is reconnected, the Go API re-applies the client application's direct subscriptions. If there is a change in the ACLs as a result of the refreshed token, the subscriptions may be rejected by the event broker.