Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
arminhatting
Participant

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/b5a6b58694784d0c9f4ff85f9b7...

Documentation Event Mesh:

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

Documentation Credentials Store:

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

Decrypt Payload Example:

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

2 Comments
Labels in this area