Skip to Content
Technical Articles

Writing Function-as-a-Service [5]: Top Secret

With other words:

Secrets and Config Maps
in
SAP Cloud Platform Serverless Runtime
What’s that?

This blog is part of a series of tutorials explaining how to write serverless functions using the Functions-as-a-Service offering in SAP Cloud Platform Serverless Runtime

Quicklinks:
Quick Guide
Sample Code

So what about those Secrets and Config Maps?w
Short answer:
To me, they look like simple property files

Example:
We want to write a function which needs to call a different REST endpoint
We don’t want to hardcode the URL and credentials in the code
So we’re looking for a way to store such info outside the code, somehow in some property files
In the server-and-file-system-less Function world, there’s a special mechanism to do so.

In this blog, we’re going through a simple example.
We’re going to define a Secret and access it from within the code

Note:
This blog shows the usage of Extension Center, but everything can be done in your local dev environment as well

Prerequisites

You should already be familiar with the Function-as-a-Service part of SAP Cloud Platform Extension Center, serverless runtime
Otherwise, see series of tutorials

Create Project

We can create a new Extension (Functions project) or reuse an existing project

In my example, using “Template with function”:
Extension name: topsecret
Runtime: nodejs10
Function name : secretfunction
HTTP trigger name : showsecret

After project creation, we want to define secret and config map.
Currently, there’s no UI support, so we have to create files and folders and edit the faas.json manually

Define secret

First of all, we have to understand that a secret is a logical artifact, handled by the FaaS runtime
A secret points to a folder, where a file is located (can be multiple)
That file contains the secret information

To me, a secret is like a secret treasure map. It doesn’t contain the treasure itself, only the path
The hidden treasure can be found there and it contains …. tasty cat food

So let’s create files and folders first

Note:
As usual, by choosing silly names, I’ve tried to make clear that you’re free to choose the file and folder names
There’s only one exception:
The “data” folder must have name as “data”

Create Folder
To create a folder, select the Code folder, then press the create-folder symbol
Enter folder name as data
Then click on the new data folder and create a subfolder called donotopenthisfolder
Click on folder donotopenthisfolder and press the file symbol
Enter file name as hiddensecret.json

Enter secret values
Now open the file  hiddensecret.json
Paste the following content:

{
  "Max": {
    "username": "max@maxmail.com",
    "password": "max123"
  },
  "Joe": {
    "username": "joe@joemail.com",
    "password": "joe123"
  }
}

That’s not it
I mean, we’ve only created a file with secret info.
That’s not THE secret yet
Although the file is located in a folder which nobody would dare to open

Note:
Make sure to press “Save And Deploy” from time to time

Define Config Map

A config map is similar like a secret, but not so top secret.
We use config maps to store any configuration info which we don’t want to hardcode in the javascript module
Again, a config map is a file, located in a subfolder of the “data” folder

Create folder
Select the folder data and create a subfolder with name cfg
Select the folder cfg and create a file with name addressconfig.json

Enter configuration values
Paste the following content which contains an example for possible REST endpoint URLs

{
  "dev": {
    "user": "Max",
    "url": "http://maxservice/endpoint"
  },
  "prod": {
    "user": "Joe",
    "url": "http://joeservice/endpoint"
  }
}

Create another folder
Select the folder cfg and create a file with name addresstext.de

Enter configuration value
This file contains an example for plain text in German language:

Guten Morgen

Note:
Finally, our FaaS project should look like this:

Now really define Secret and Config Map

Up to now we’ve only created files.
Now we need to declare them in the manifest file, such that the FaaS runtime can take care of them
So now we really DEFINE the secret and config maps

We open faas.json
Since we’ve used the template to create the extension project, the file initially would look like this:

 

We can see the elements which sound promising: “secrets” and “configs”
And we can see that the secrets are defined and where they are referenced:
A function has to declare which secret and config it wants to use

So now we can go ahead and enter the declarations and usages

As mentioned, a secret is a logical artifact which is defined as an entry in faas.json
To make it less logical and more physical, it has the source property which points to a directory.
It is the directory which contains the files with configuration info

"secrets": {
  "mysecret": {
    "source": "./data/donotopenthisfolder"
  }
},

And the secret has a name of our choice, such that it can be referenced.

BTW, same procedure with config

Next step:
If we want to access secret info from the implementation of our function, we need to explicitly reference it.
To reference a secret, we just write the name in an array (can reference multiple secrets)

"functions": {
   "secretfunction": {
      "secrets": ["mysecret"],

Finally, the full faas.json looks as follows

{
	"project": "topsecret",
	"version": "0.0.1",
	"runtime": "nodejs10",
	"library": "./lib",
	"secrets": {
        "mysecret": {
          "source": "./data/donotopenthisfolder"
        }
	},
	"configs": {
        "myconfiguration": {
          "source": "./data/cfg"
        }
	},
	"functions": {
		"secretfunction": {
			"module": "index.js",
			"handler": "handler",
			"secrets": ["mysecret"],
			"configs": ["myconfiguration"]
		}
	},
	"triggers": {
		"showsecret": {
			"type": "HTTP",
			"function": "secretfunction"
		}
	}
}

Note: saveanddeploy

View the values

Now let’s explore what we’ve just defined
In Extension Center, click on Form View, then “Secrets” tab and finally on the icon to view the details
The content of the secret is visualized in a nice way:

The dialog is well aware that the value of a secret is secret
Anyways, I still believe it looks like cat food in a treasure chest…

Let’s also view the details of the config:

We can see that we have 2 config maps and that one has json content and the second one has string content (german string, BTW)

Note the terminology:
The file name is the key, the file content is the value
In fact, it is a (config) map with key and value
We will need that later

Access secret and config in function code

We didn’t create the secret only for playing hide and seek
We want to access it in the impementation and extract the value

How to do this?

It is done with support of FaaS runtime, which offers a convenient API for it
When the runtime invokes our handler function, it passes an object to us which contains all required info: the context object

How to use it?

The convenience API offers access to the value of the secret, based on the name of the secret

context.getSecretValue...

Furthermore, we can choose if we want the response as plain String or as JSON

context.getSecretValueJSON(…)

Next, we have to pass the information about which secret we want to use
Since there can be multiple secrets defined in faas.json and referenced by any function, we have to pass the secret name as first param

Remember that the secret basically is a folder, such that we also have to pass the file name as second param:

context.getSecretValueJSON('mysecret', 'hiddensecret.json')

As a response we get a JSON object (depending on the API-method we’ve chosen)
Example for node10 syntax:

const credentials = await context.getSecretValueJSON('mysecret', 'hiddensecret.json');

The API overview can be found in the SAP Help Portal
Example for the syntax

getSecretValueJSON(name, key)

 

Explanation:
As already explained, the
“name” is the secret name (pointing to a folder)
“key” is the file name

Now we can open our function in the Code editor and replace the generated content with the following snippet:

module.exports = { 
	handler: async function (event, context) { 
        const credentials = await context.getSecretValueJSON('mysecret', 'hiddensecret.json');
        const config = await context.getConfigValueJSON('myconfiguration', 'addressconfig.json')
        const greeting = await context.getConfigValueString('myconfiguration', 'addresstext.de');
    
        const url = config.dev.url;
        const user = credentials.Max.username
        const pwd = credentials.Max.password
        
        console.log(`Calling URL: '${url}' with user '${user}' and password '${pwd}' and greeting '${greeting}'`)
        return "Top Secret";
	} 
 }

The example shows the usage of API for secret and configs and it shows how the different content is accessed: JSON and String
The example assumes that we would use the info for sending a request to a REST endpoint…
But we can skip that and just write the info to the log

Note: saveanddeploy

Run

After paste and save and deploy, we can invoke the function with the generated HTTP trigger URL
Our browser response contains just a silly text.
Now we regret that we’ve written the info text to the log instead to the HTTTP response
So we have to continue cligging digging for the treasure chest
Finally we find it:

What we see:
The secret information was extracted from the secret and config maps
Good job done nicely by the FaaS runtime
Maybe we think that it wasn’t a good idea to write the sensitive info to the log – but who cares what is done in a simple example?
We can also see that the runtime provides some info about the used environment, which is the nodejs runtime and the existing secrets and configs
This is another convenient service for us

Summary

We’ve learned that secrets and config maps are elements in the faas.json which point to simple property files
And we’ve learned how to access the content of such files from the implementation code

Quick Guide

Create files and folders to store the desired config data
Concrete: create “data” folder and another subfolder and store the files there

Define the secret in the faas.json
“secrets”: {
“mysecret”: {
“source”: “./data/donotopenthisfolder”
}
},

Reference it from function in faas.json
“secrets”: [“mysecret”],

In code:
Use helper methods to access the secret, and pass secret name and file name:
context.getSecretValueJSON(‘mysecret’, ‘filename.json’)

Appendix: All Sample Project Files

For your convenience, see here the project structure:

faas.json

{
	"project": "topsecret",
	"version": "0.0.1",
	"runtime": "nodejs10",
	"library": "./lib",
	"secrets": {
        "mysecret": {
          "source": "./data/donotopenthisfolder"
        }
	},
	"configs": {
        "myconfiguration": {
          "source": "./data/cfg"
        }
	},
	"functions": {
		"secretfunction": {
			"module": "index.js",
			"handler": "handler",
			"secrets": ["mysecret"],
			"configs": ["myconfiguration"]
		}
	},
	"triggers": {
		"showsecret": {
			"type": "HTTP",
			"function": "secretfunction"
		}
	}
}

package.json

{}

index.js

module.exports = { 
	handler: async function (event, context) { 
        const credentials = await context.getSecretValueJSON('mysecret', 'hiddensecret.json');
        const config = await context.getConfigValueJSON('myconfiguration', 'addressconfig.json')
        const greeting = await context.getConfigValueString('myconfiguration', 'addresstext.de');
    
        const url = config.dev.url;
        const user = credentials.Max.username
        const pwd = credentials.Max.password
        
        console.log(`Calling URL: '${url}' with user '${user}' and password '${pwd}' and greeting '${greeting}'`)
        return "Top Secret";
	} 
 }

 

Appendix: The Hidden Secret

Be the first to leave a comment
You must be Logged on to comment or reply to a post.