Technical Articles
SAP Cloud Foundry Credential Store Service
Hi security-fans! π
Some time ago I wrote a blogpost about βTimesheet Management with CAP & Trello β±οΈβ. In this scenario there were some Trello API keys that had to be stored and obviously consumed inside the core of the application. I was eager to use the SAP Cloud Foundry Credential Store Service to store these keys in a secure way in the SAP Cloud Platform.
Sad as it was, I had to put aside the idea of using the Credential Store Service. This because I was not able to create a service instance with the available service plan βproxyβ. I missed some required parameters and I had no idea which ones those were. The SAP Help Documentation was also referring to the βstandardβ service plan instead of the βproxyβ one. I was totally confused so I searched for another solution. I could try to store those API Keys inside the appropriate destinations, but I chose to store them inside a βUser provided Instanceβ to get to know this functionality as well.
More information about this issue can be found here:
SAP Cloud Platform Credential Store (Cloud Foundry) – Service plan does not exist – SAP Q&A
But recently I saw that the βtrialβ service plan for the Credential Store Service is available, with a brief and very clear description. Which made me want to try out this service once more. π
More information about the SAP Cloud Platform Credential Store can be found here in the SAP Help Documentation: Credential Store – SAP Help Portal
Creating the Credential Store Service Instance
I do like to use the βcf cliβ to create and maintain my Cloud Foundry services. Which is what Iβm about to use to create our Credential Store Service Instance. Once the service instance has been created, we will have to switch to the SAP Cloud platform Cockpit to continue the configuration of the service. This because there is no functionality to create namespaces and credentials (more about those later in this blog) inside the service instance via the cli. At least not that I am aware of.
Run the following command to create your Credential Store Service instance called βCredentialStoreServiceInstanceβ with the βtrialβ service plan:
cf create-service credstore trial CredentialStoreServiceInstance
This service instance could obviously have been created via the SCP Cockpit as well.
The last thing we need to do is to create a service key in our Credential Store Service Instance to consume the service afterwards. Run the following command to create the service key called βCredentialStoreServiceInstanceServiceKeyβ:
cf create-service-key CredentialStoreServiceInstance CredentialStoreServiceInstanceServiceKey
But now we will have to switch to the SCP Cockpit anyway, to continue our configuration.
Configuring the Credential Store Service Instance
Go to your Cloud Foundry Space and select the created βCredentialStoreServiceInstanceβ from your service instances:
We do not have to bind any application at this point, but we do see our earlier created service key. What we want to do is to create a namespace and add a credential to it. To do so, press the “Manage Instance” button.
Once you pressed this button you will be able to access and manage the Credential Store Service instance.
Choose the βCredential Storeβ tab in the left-hand-menu and press the βCreate Namespaceβ button.
A namespace is nothing more or less than a collection to logically group your credentials.
When you want to create a namespace you immediately must add a credential to it.
This credential can be one of the below options:
- Password (text)
- Key (binary)
Let us select the βPasswordβ option for this namespace and hit the βNextβ button.
This brings us to the following configuration-form:
Provide a name for our namespace: βCredentialStoreNameSpaceβ.
Specify a name which will be used later to call our required credential: βMyCredentialβ
Choose to “Enter Value“.
Enter the password twice βtest123!βand provide a username βuser1β. Leave the metadata blank and continue by pressing the βCreateβ button.
As you can see the namespace has been created successfully:
When you select the namespace, you will get an overview of all the credentials it contains:
Now that we created our Credential Store Service Instance and added a namespace and credential to it, we are ready to consume this service and credentials inside our applications.
Letβs get coding!
Create a Credential Store Consumption Application
As many of you already know I am a big fan of the SAP Business Application Studio. Which means I will be building and testing this application in the SAP BAS.
Let us create a basic multitarget application by executing the following Yeoman command and name it βCredentialStoreConsumptionβ:
yo basic-multitarget-application
Once the project has been generated, bind the credential store service instance to it. Using the BAS command pallet (CTRL + Shift + P) with the βbind a service to locally run application bind a service to locally run applicationβ option. Continue by selecting the βCredentialStoreConsumptionβ project, to store the β.envβ file. Finish the process by selecting the Credential Store Service Instance.
You will see a β.envβ file has been created inside your project. Rename this file to βdefault-env.jsonβ and correct the content to a valid JSON. If you are also working via the BAS you will see in the SCP Cockpit a new service key called βkeyβ was generated because of this action.
Your βdefault-env.jsonβ file should look like this:
{
"VCAP_SERVICES": {
"credstore": [
{
"name": "CredentialStoreServiceInstance",
"instance_name": "CredentialStoreServiceInstance",
"label": "credstore",
"tags": [
"credstore",
"securestore",
"keystore",
"credentials",
"endpoint:",
"org:",
"space:"
],
"plan": "trial",
"credentials": {
"encryption": {
"client_private_key": "",
"server_public_key": ""
},
"parameters": {
"authorization": {
"default_permissions": [
"create",
"decrypt",
"delete",
"encrypt",
"info",
"list",
"namespaces",
"read",
"update"
],
"namespace_permissions": {
"\u003cnamespace\u003e": [
"create",
"decrypt",
"delete",
"encrypt",
"list",
"read",
"update"
]
}
}
},
"password": "",
"service_key_name": "key",
"url": "",
"username": ""
}
}
]
}
}
Do note the password and username displayed in this configuration is not the password βtest123!β that we created earlier, neither is username the βuser1β.
These basic authentication values are used to consume the Credential Store Rest API.
Consuming the Credential Store Rest API
We could be using Postman for this matter but let us use the SAP BAS build in http-client to consume the Rest API. Like I mentioned before the credentials in the βdefault-env.jsonβ file in the Credential Store Service Instance Configuration, are not our created credentials. These credentials will be used to perform basic authentication on the rest API.
More information about this authentication can be found here:
Authentication – SAP Help Portal
Create a file called βgetPasswordMyCredential.httpβ inside your project:
touch getPasswordMyCredential.http
Add the following content to it:
GET /api/v1/credentials/password?name=MyCredential HTTP/1.1
Host: credstore.cfapps.eu10.hana.ondemand.com
sapcp-credstore-namespace: CredentialStoreNameSpace
Authorization: Basic <base64 encoding of username and password joined by a colon>
We pass the host to it, and we query the API for the required password (name of your password). The default required header βsapcp-credstore-namespaceβ is passed as well with the value equal to your namespace name. Finally, you pass the basic authentication in the Authorization header. Combination of username:password in base64 format encoded. All values can be found in the βdefault-env.jsonβ file under your Credential Store Service.
As you can see, we received our credential (encrypted) successfully:
Consuming the Credential Store in Node.js
In real-life productive scenarios, I believe we will not be calling this APIs without doing something with the response. Let us build a tiny Node.js application to consume our stored credentials.
The code I will be reusing is coming from the SAP Help Documentation right here:
Code Example (NodeJS) – SAP Help Portal
It is a nice example on how to consume the Credential Store Service via Node.js.
I will just make some tiny adjustments to it, to run our app and retrieve the credentials.
Inside our created project we execute the βnpm initβ command to initialize NPM and we proceed with all the default values:
npm init
Once initialized add the following script to the βpackage.jsonβ file to run the Node app.
"start": "node index.js"
Continue by creating the βindex.jsβ file with the following command:
touch index.js
Add the following code from the SAP Help Documentation to it:
const fetch = require('node-fetch');
const jose = require('node-jose');
const xsenv = require("@sap/xsenv");
function checkStatus(response) {
if (!response.ok) throw Error("Unexpected status code: " + response.status);
return response;
}
async function decryptPayload(privateKey, payload) {
const key = await jose.JWK.asKey(
`-----BEGIN PRIVATE KEY-----${privateKey}-----END PRIVATE KEY-----`,
"pem",
{ alg: "RSA-OAEP-256", enc: "A256GCM" }
);
const decrypt = await jose.JWE.createDecrypt(key).decrypt(payload);
const result = decrypt.plaintext.toString();
return result;
}
function headers(binding, namespace, init) {
const result = new fetch.Headers(init);
result.set("Authorization", `Basic ${Buffer.from(`${binding.username}:${binding.password}`).toString("base64")}`);
result.set("sapcp-credstore-namespace", namespace);
return result;
}
async function fetchAndDecrypt(privateKey, url, method, headers, body) {
const result = await fetch(url, { method, headers, body })
.then(checkStatus)
.then(response => response.text())
.then(payload => decryptPayload(privateKey, payload))
.then(JSON.parse);
return result;
}
async function readCredential(binding, namespace, type, name) {
return fetchAndDecrypt(
binding.encryption.client_private_key,
`${binding.url}/${type}?name=${encodeURIComponent(name)}`,
"get",
headers(binding, namespace)
);
}
(async () => {
xsenv.loadEnv();
const binding = xsenv.getServices({ credstore: { tag: 'credstore' } }).credstore;
console.log(await readCredential(binding, "CredentialStoreNameSpace", "password", "MyCredential"));
})();
The deletion and creation of credentials I left out of the example, but I did add the β@sap/xsenvβ package to read out my βdefault-env.jsonβ configuration:
const xsenv = require("@sap/xsenv");
With this addition I load the βdefault-env.jsonβ file:
xsenv.loadEnv();
I retrieve the Credential Store Service information from it:
const binding = xsenv.getServices({ credstore: { tag: 'credstore' } }).credstore;
Once the configuration is read, the credentials can be requested and logged to the console:
console.log(await readCredential(binding, "CredentialStoreNameSpace", "password", "MyCredential"));
The function βreadCredentialβ will take a few arguments:
- yourBinding/CredentialStoreConfig
- YourCredentialStoreNameSpace
- passwordOrKey
- nameOfThePasswordOrKey
When you would read through the code, you would see that the request is build and fetched using the appropriate NPM packages. Once the result has been received it will also be decrypted by the βnode-joseβ NPM package.
Install the three required packages inside the project by executing the following command:
npm i node-fetch node-jose @sap/xsenv
Run the project by executing the following command:
npm run start
As you can see our credential has been retrieved successfully and it is ready to be used for whatever request that has to be performed:
Wrap up
In this blogpost we had a look at the SAP Cloud Platform Credential Store Service. We created a Service Instance using the βtrialβ service plan. Once our service instance was created, we configured a namespace and added a credential to it of the type βpasswordβ.
Once the service instance was completely setup, we initialized a basic multitarget application in the SAP Business Application Studio with our Credential Store service bound to the it.Β Before we consumed the service in an application, we tested the Credential Store Rest API to retrieve the credentials.
After testing the Rest API, we built a small Node.js application with the SAP Help Documentation its Node.js example. This way we were able to retrieve to credentials from the Credential Store Service and to decrypt the retrieved information to the username and password we required.
In need of a secure and safe service to store credentials? I believe the Credential Store Service might be just what youβre looking for.
Better safe than sorry! π΅οΈββοΈ
Best regards,
Dries Van Vaerenbergh
Hello Dries,
Many thanks for the blog.
I do however get an error in the node-fetch npm moule and hence i am unable to deploy the app.
I am using following versions and when i deploy i get an error that import('node-fetch') is not correct as it is an ESM module
javascript - Error: require() of ES modules is not supported when importing node-fetch - Stack Overflow
May i know whcih version did you mention in the package.json OR can you perhaps try again your exercise with the latest versions of these 2 modules ?
Β
Regards,
Nilesh Puranik
Β
Hi Nilesh,
You need version 2 of node-fetch when working with commonJS modules. Version 3 is only compatible with ES modules. Hope this helps.
BR, Nico
Thank you Nicolai !
Thanks for the great blog.
A question is it possible to use the credential store service instance from the SAP BTP ABAP (steampunk) environment?
For example, we would like to publish an event on SAP event mesh from SAP BTP ABAP and store the credential in the credential store.
Regards,
Abhinav