Skip to Content
Technical Articles
Author's profile photo Dries Van Vaerenbergh

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:

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 β€œ@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:

  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

Assigned Tags

      4 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Nilesh Puranik
      Nilesh Puranik

      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 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

      Β 

      Author's profile photo Nicolai Schoenteich
      Nicolai Schoenteich

      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

      Author's profile photo Nilesh Puranik
      Nilesh Puranik

      Thank you Nicolai !

      Author's profile photo Abhinav Gupta
      Abhinav Gupta

      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