Skip to Content
Technical Articles
Author's profile photo Armin Hatting

Cloud Foundry Event Mesh Instance Sharing

Introduction

In this post I briefly show how you can use an event mesh instance of foreign subaccounts/spaces in Java.

I also show how you can store and use sensitive data with the Cloud Foundry Credential Store service.

The question of sharing an event mesh instance over several subaccounts becomes relevant when following the account structure recommended in the guide “Best Practises For SAP BTP”:

https://help.sap.com/viewer/df50977d8bfa4c9a8a063ddb37113c43/Cloud/en-US/b5a6b58694784d0c9f4ff85f9b7336dd.html

In our case, the relevant subset of the BTP account structure could look like this (additional subaccounts & directory exist):

Test Sample App in Event Mesh Subaccount

Sample App:
https://github.com/SAP-samples/event-mesh-client-java-samples

First I test the sample app in the space, where the event mesh instance is located. In this case it is a space in the Event Mesh Subaccount.
I had to change some coding, to make things work:

  1. Change xbem.client version to 2.2.0 in pom.xml (version 2.2.2 is not available yet)
  2. Change event mesh service name in manifest.yml to event-mesh-dev
  3. Change MessagingServiceConfig.java and comment the custom authentication (not supported in 2.2.0)

 

// Custom provided authentication request, it is not mandatory. Emjapi can requests token from the client info.
// TokenRequest tokenRequest = new TokenRequest();
// settings.setAuthenticationRequest(tokenRequest::requestToken);
  1. Add a namespace variable to MessagingServiceRestController. This namespace is needed for correct mapping of the path variable to the queue name:
private static final String NAMESPACE = "<QUEUE_NAMESPACE>";
...
@PostMapping(MESSAGE_REST_PATH)
public ResponseEntity<String> sendMessage(@RequestBody String message, @PathVariable String queueName) throws MessagingException {
    try {
        queueName = decodeValue(queueName);
    } catch (UnsupportedEncodingException e1) {
        return ResponseEntity.badRequest().body("Unable to decode the queuename");
    }

    // add namespace
    queueName = NAMESPACE + queueName;
...

Note: I’m only testing the p2p part of the sample app.

After deployment you can test the service in postman:

Instance Sharing Options

Set Service Binding Environment Variables manually

Each service binding creates specific environment variables in the provided runtime.
You can “fake” a service binding by manually setting the environment variables and accessing them in your code.

In this example you can copy the value of the service key of your event mesh instance and paste it in the manifest.yml as an environment variable.
Additionally, you can delete the event-mesh service binding.

Then, adjust the MessageServiceConfig to use your new environment variable enterprise-messaging:

@Configuration
public class MessagingServiceConfig {

	@Bean
	public MessagingServiceFactory getMessagingServiceFactory() throws JsonParseException, JsonMappingException, IOException {
		String credentials = System.getenv("enterprise-messaging");
		Map<String, Object> credentialsMap = new ObjectMapper().readValue(credentials, HashMap.class);
		return MessagingServiceFactoryCreator.createFactoryFromCredentials(credentialsMap);
	}
...

You are now independent of the Cloud Foundry subaccount and space where the event-mesh instance is running. Thus, the app can run in every other space.

Using Credential Store Service for the Event Mesh Service Key

The approach above has some disadvantages. For example:

  • Credentials are stored on coding level
  • You might accidentally push credentials to git
  • You are not independent of the environment because the credentials are different for each environment

To tackle these problems you can use the Credential Store service. With this service you can manage passwords and keys centrally in the SAP BTP Cockpit.
The approach is as follows:

  1. Create Credential Store service instance
  2. Store event-mesh service key
  3. Bind the application to the Credential Store service
  4. In the application, get the service key from the Credential Store service

Create Credential Store Service Instance

In the space where your application is suppose to run create a service instance:

Store Event Mesh Service Key

After creation choose “Manage Instance” and store your event-mesh service key under section “Credential Store” (You have to Base64 encode the key first):

Bind the Application to the Credential Store Service

Bind the application in your manifest.yml (or mta.yml respectively) and delete your service key information:

Get the Service Key from the Credential Store Service

I implemented two additional functions getCredentialStoreKey and decryptPayload.

getCredentialStoreKey: Calls the Credential Store Service API and uses service binding environment variables as credentials.

decryptPayload: Decrypts the response of the REST call by using the private key of the service binding.

Full code here:

@Configuration
public class MessagingServiceConfig {

    @Bean
    public MessagingServiceFactory getMessagingServiceFactory() throws JsonParseException, JsonMappingException, IOException {
    String encodedKeyValue = JsonPath.read(getCredentialStoreKey(), "$.value");
    byte[] decodedKeyValue = Base64.getDecoder().decode(encodedKeyValue);
    Map<String, Object> credentialsMap = new ObjectMapper().readValue(decodedKeyValue, HashMap.class);
    return MessagingServiceFactoryCreator.createFactoryFromCredentials(credentialsMap);
    }

    @Bean
    public MessagingServiceJmsConnectionFactory getMessagingServiceJmsConnectionFactory(MessagingServiceFactory messagingServiceFactory) {
    try {
        /*
            * The settings object is preset with default values (see JavaDoc) and can be adjusted. The settings aren't required and depend on the use-case. Note: a connection will be closed after an idle time of 5 minutes.
            */
        MessagingServiceJmsSettings settings = new MessagingServiceJmsSettings();
        settings.setFailoverMaxReconnectAttempts(5); // use -1 for unlimited attempts
        settings.setFailoverInitialReconnectDelay(3000);
        settings.setFailoverReconnectDelay(3000);
        settings.setJmsRequestTimeout(30000);
        settings.setAmqpIdleTimeout(-1);

        // Custom provided authentication request, it is not mandatory. Emjapi can requests token from the client info.
        // TokenRequest tokenRequest = new TokenRequest();
        // settings.setAuthenticationRequest(tokenRequest::requestToken);

        return messagingServiceFactory.createConnectionFactory(MessagingServiceJmsConnectionFactory.class, settings);
    } catch (MessagingException e) {
        throw new IllegalStateException("Unable to create the Connection Factory", e);
    }
    }

    private String decryptPayload(String responsePayload) {
    final AlgorithmConstraints CONTENT_ENCRYPTION_ALGORITHM_CONSTRAINTS = new AlgorithmConstraints(ConstraintType.PERMIT, ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
    final AlgorithmConstraints KEY_ENCRYPTION_ALGORITHM_CONSTRAINTS = new AlgorithmConstraints(ConstraintType.PERMIT, KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);

    // get service binding environment variables
    final String vcapServicesJson = System.getenv("VCAP_SERVICES");

    String PRIVATE_KEY = JsonPath.read(vcapServicesJson, "$.credstore[0].credentials.encryption.client_private_key");
    String decryptedPayload = null;
    try {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] binaryKey = Base64.getDecoder().decode(PRIVATE_KEY);
        RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(binaryKey));
        JsonWebEncryption jwe = new JsonWebEncryption();
        jwe.setAlgorithmConstraints(KEY_ENCRYPTION_ALGORITHM_CONSTRAINTS);
        jwe.setContentEncryptionAlgorithmConstraints(CONTENT_ENCRYPTION_ALGORITHM_CONSTRAINTS);
        jwe.setKey(privateKey);
        jwe.setCompactSerialization(responsePayload);
        Long iat = jwe.getHeaders().getLongHeaderValue("iat");
        System.out.println("iat:" + iat);
        decryptedPayload = jwe.getPayload();
        System.out.println(decryptedPayload);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return decryptedPayload;
    }

    private String getCredentialStoreKey() {
    String result = null;
    try {

        // get service binding environment variables
        final String vcapServicesJson = System.getenv("VCAP_SERVICES");
        String USERNAME = JsonPath.read(vcapServicesJson, "$.credstore[0].credentials.username");
        String PASSWORD = JsonPath.read(vcapServicesJson, "$.credstore[0].credentials.password");
        String URL = JsonPath.read(vcapServicesJson, "$.credstore[0].credentials.url");
        String encodedCredentials = Base64.getEncoder().encodeToString((USERNAME + ":" + PASSWORD).getBytes());
        final CloseableHttpClient httpClient = HttpClients.createDefault();

        HttpGet request = new HttpGet(URL + "/key?name=event-mesh-service-key");

        // add request headers
        request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + encodedCredentials);
        request.addHeader("sapcp-credstore-namespace", "<CRED_STORE_NAMESPACE>");

        try (CloseableHttpResponse response = httpClient.execute(request)) {

            // Get HttpResponse Status
            System.out.println(response.getStatusLine().toString());
            HttpEntity entity = response.getEntity();

            if (entity != null) {
                // return it as a String
                String responseBody = EntityUtils.toString(entity);
                System.out.println(responseBody);
                result = decryptPayload(responseBody);
            }
        }

    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
    }
}

Result

As a conclusion you archived two goals:

  1. You are independent of the space/subaccount where your event-mesh instance is located.
  2. You have a central key store managed in the BTP Cockpit.

Links

Best Practises SAP BTP:

https://help.sap.com/viewer/df50977d8bfa4c9a8a063ddb37113c43/Cloud/en-US/b5a6b58694784d0c9f4ff85f9b7336dd.html

Documentation Event Mesh:

https://help.sap.com/viewer/bf82e6b26456494cbdd197057c09979f/Cloud/en-US/df532e8735eb4322b00bfc7e42f84e8d.html

Documentation Credentials Store:

https://help.sap.com/viewer/601525c6e5604e4192451d5e7328fa3c/Cloud/en-US/02e8f7d1016740b8adf68690f36df142.html

Decrypt Payload Example:

https://help.sap.com/viewer/601525c6e5604e4192451d5e7328fa3c/Cloud/en-US/6bdd6c29887f48f69940af679f520e45.html

Assigned tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Christoph Ueberle
      Christoph Ueberle

      Thank you very much Armin for the indepth article! Helped me a great deal.

      Author's profile photo Tobias Griebe
      Tobias Griebe

      Great article, explaining how it all works together.

      You could also add a tag for "SAP Event Mesh", as it is very interesting for this audience as well.