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: 
vvdries
Contributor
7,061
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:

Here you have the possibility to edit the credentials if needed.


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 “@Sisn/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:

  1. yourBinding/CredentialStoreConfig

  2. YourCredentialStoreNameSpace

  3. passwordOrKey

  4. 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
5 Comments
nileshpuranik
Explorer
0 Kudos

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

    "node-fetch": "^3.2.0",
    "node-jose": "^0.3.0"

javascript - Error: require() of ES modules is not supported when importing node-fetch - Stack Overf...

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

 

nicoschoenteich
Developer Advocate
Developer Advocate
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
Abhinav1
Explorer
0 Kudos
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
nileshpuranik
Explorer
0 Kudos
Thank you Nicolai !
rouzbeh
Explorer
0 Kudos
Hi Abhinav,

we also have a similar use case. Did you find a solution to this problem meanwhile?

Best regards
Rouzbeh
Labels in this area