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