Skip to Content
Technical Articles

Writing Function-as-a-Service [12]: Adding Security

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

Introduction

Functions provide support for easily protecting an endpoint
In this tutorial we learn how to add security to a function

Overview

Basically, to protect a function, we need an instance of XSUAA and configure it in faas.json

  1. Create and test simple function
  2. Create instance of XSUAA
  3. Register XSUAA in FaaS
  4. Configure protection for Function
  5. Call Function ;-(
  6. Call Function 😉

Prerequisites

Basic understanding of OAuth, see easy intro and description for using REST client

Also, you should have an up-to-date version of xfsrt-cli

1. Create and test simple function

As usual, to focus only on the new learning topic, we create a very simple function.
See Appendix for all files

Let’s first define a normal free simple function, without security, just to make sure that it responds

{
    "project": "protected",
    "version": "0.0.1",
    "runtime": "nodejs10",
    "library": "./lib",
    "functions": {
        "protected-function": {
            "module": "functionImpl.js"
        }
    },
    "triggers": {
        "call": {
            "type": "HTTP",
            "function": "protected-function"
        }
    }
}          

And the very simple implementation of the function

module.exports = async function () {
    return '...miaow...!';
} 

Let’s deploy and test the function

xfsrt-cli faas project deploy

To test the function, we invoke the HTTP trigger

https://abc123….functions.xfs.cloud.sap/call/

Result: we see the response, as coded in our function
BUT: this meaningful output must be protected, such that only logged-in users can see it

2. Create instance of XSUAA

In Cloud Foundry we can use XSUAA, the extended user account and authentication server
As such, we need an instance of XSUAA, to enforce the OAuth flow
To create the instance for this simple tutorial, we use very basic configuration parameters

{
  "xsappname" : "xsappforfaas",
  "tenant-mode" : "dedicated"
}

Let’s store this configuration in a file called xs-security_faas.json
Then we can create an instance of XSUAA and use this json file
cf cs xsuaa application xsuaa_faas -c xs-security_faas.json

Since we want to use this XSUAA instance from FaaS, we need to create a Service Key
cf csk xsuaa_faas xsuaa_faas_servicekey

3. Register XSUAA in FaaS

You know how to register it. Here’s the command:
xfsrt-cli faas service register

Then choose the newly created service instance from the list
Afterwards, make sure to take a note of the GUID of the newly registered service instance
E.g.: a1b2c3d4-a1b2-c3d4-c3d4-a1b2c3d4e5f6

Also, take a note of the service key name

4. Configure protection for Function

FaaS currently only supports one type of authentication: OAuth 2.0

First, we have to declare the usage of the registered xsuaa service instance

	"services": {
	    "xsuaa-srv": {
			"type": "xsuaa",
			"instance": "a1b2c3d4-a1b2-c3d4-c3d4-a1b2c3d4e5f6",
			"key": "xsuaa_faas_servicekey"			
		}
	}

If you didn’t take a note of GUID and service key, you can view it with
xfsrt-cli faas service list

Afterwards, declare the usage of the usage in the function definition:

        "protected-function": {
			"module": "functionImpl.js",
			"services": ["xsuaa-srv"]
        }

And now the very interesting configuration:
Now we declare that our function should be protected with OAuth.
To be more precise: the security is configured on a HTTTP trigger
To do so, we add an “auth” section to our HTTP trigger:

        "call": {
            "type": "HTTP",
            "function": "protected-function",
			"auth": {
				"type": "xsuaa",
				"service": "xsuaa-srv"
			}			
        },

The “auth” attribute, enforces authentication, when the function is called via this HTTP trigger
To define authentication, we need to specify just little bit of additional info:

The type:
This is a hard-coded value.
As of now, only “xsuaa” is supported

The service:
Here we have to enter the name of the service reference. The alias for the service registered in FaaS
This is the XSUAA instance which is responsible for validation of the incoming OAuth request (the JWT token)

That’s already all
It is just as easy as expected
See Appendix for full faas.json

5. Call Function ;-(

Now we can test if it is really protected
We deploy the function and call the endpoint again
https://abc123….functions.xfs.cloud.sap/call/

The result is: unauthorized with status code 403

And sad emoji ;-(

6. Call Function 😉

On the other hand, we can be happy, because this has proven how easy it is to secure our function endpoint
Now we want to call the function with success response and status 200 and happy emoji

To do so, we can use postman REST client and the support for OAuth 2.0
Please refer to my detailed blog about the procedure

Short description:

As preparation, we need to view the service key of our xsuaa service instance

cf service-key xsuaa_faas xsuaa_faas_servicekey

From there we need 3 attributes which we enter below
In REST client, we enter our target URL:
https://abc123….functions.xfs.cloud.sap/call/

As Authorization, we choose OAuth 2.0

Then click the button “Get new Access Token” and configure:

Grant Type:
Client Credentials (note: Password Credentials is fine as well, you need to enter your cloud user credentials)
Access Token URL:
we use the attribute “url” from the service key and append /oauth/token
E.g.
https://mysubaccount.authentication.sap.hana.ondemand.com/oauth/token

Client ID:
we use the attribute clientid from the service key
E.g.
sb-xsappforfaas!t12345

Client Secret:
we use the attribute clientsecret from the service key
E.g.
a0b1c2d3e4e4F5G6H7I8J9A00AA00

Client Authentication:
Basic Auth header

Then press “Request Token”
Then press “Use Token”
Then press “Send”
As a result, we get the response which makes us happy

 

That’s it.
We’ve learned how to protect our function
We only need to add an “auth” attribute to an HTTP trigger definition
And need to wire it to an instance of XSUAA

Optional: additional HTTP trigger

For testing purpose, we can always add an additional trigger to our faas.json
Like that, we would have a protected endpoint for real usage, and a temporary free endpoint for testing and troubleshooting

Let’s try:
We add a free trigger

        "callfree": {
            "type": "HTTP",
            "function": "protected-function"
        }

We deploy and try the “free” trigger in browser:
https://abc123….functions.xfs.cloud.sap/callfree/

We can see the nice result, although the other endpoint still requires the OAuth flow

Troubleshooting

But what if the request fails, although a valid JWT token is being sent?
At least, we strongly believe that a valid JWT token is being sent
In such case, we can use the free endpoint and print some debug information
I mean, we send the JWT token to the free endpoint, although it is not required.
This gives us the chance to view the content of the token
It is the only possibility, because the JWT token is rejected by the FaaS runtime, our function code is not invoked in such case
To print some debug info, we can add the following lines to our function impl

event.auth.credentials

event.auth is always present, in case that no JWT is being sent, it contains empty values.
If JWT is present, we can access the value in the field credentials.
There’s also the type property, which currently is either empty or “Bearer”

The property credentials contains the raw JWT token, the cryptic string
We can manually decode it, but it is easier to use the handy function provided by FaaS:

event.decodeJsonWebToken()

As such, our troubleshooting code looks like this:

module.exports = async function (event, context) {
	console.log(`=> Decoded value of JWT token (if available): ${JSON.stringify(event.decodeJsonWebToken())}`) 
	return '...miaow...!';
} 

 

However, the interesting part is in the payload property:

event.decodeJsonWebToken().payload

Now we can deploy and send an OAuth request (containing JWT token) with REST client to the unprotected endpoint
https://abc123….functions.xfs.cloud.sap/callfree/

In the log we can find useful information, which helps us to analyze failing requests

Summary

We’ve learned that the FaaS runtime has nice support for easily protecting a function endpoint with OAuth
The protection is defined on an HTTP trigger, the function itself remains untouched
As such, it is always possible to define additional HTTP triggers with no protection
To protect an endpoint, an instance of XSUAA is used, which needs to be created beforehand and needs to be registered in FaaS
The FaaS runtime uses XSUAA for token validation, there’s no automatic check of available scopes

Quick Guide

To enable OAuth protection, we only need to add the following snippet to an HTTP trigger definition

			"auth": {
				"type": "xsuaa",
				"service": "xsuaa-srv"
			}			

Prerequisite: created instance of xsuaa service and registered in faas

Links

Cloud Foundry
Documentation: https://docs.cloudfoundry.org
UAA concepts 
And UAA  architecture

Appendix: All sample project files

These are the files required for the function
They are located in the function project folder, with “lib” subfolder

xs-security_faas.json

{
  "xsappname" : "xsappforfaas",
  "tenant-mode" : "dedicated"
}

faas.json

{
    "project": "protected",
    "version": "0.0.1",
    "runtime": "nodejs10",
    "library": "./lib",
    "functions": {
        "protected-function": {
			"module": "functionImpl.js",
			"services": ["xsuaa-srv"]
        }
    },
    "triggers": {
        "call": {
            "type": "HTTP",
            "function": "protected-function",
			"auth": {
				"type": "xsuaa",
				"service": "xsuaa-srv"
			}			
        },
        "callfree": {
            "type": "HTTP",
            "function": "protected-function"
        }
	},
	"services": {
	    "xsuaa-srv": {
			"type": "xsuaa",
			"instance": "32a8385b-e57e-41a3-88ea-281535689595",
			"key": "xsuaa_faas_servicekey"			
		}
	}
}          

package.json

{}

functionImpl.js

module.exports = async function (event, context) {
	return '...miaow...!';
} 
2 Comments
You must be Logged on to comment or reply to a post.
  • Hi Carlos Roggan ,

    thank you verry much for sharing this knowledge with us! Beside the FaaS technology is still following the red-line, the cats do this as well.

    I just wanna give other ones a ‘heads-up’!

    I was going to try and implement this on one of my use cases and didn’t got it working.

    After all the time spend, it’s an easy solution: Update your xfsrt-cli!!! Deploying with an earlier version (like mine from may 2020) is working, but the function will keep unsecure. So please do yourself a favor and update the tool before 😀 – With the newest version it was working immediately.

    Thank you again, Carlos. Looking forward for binding other services as well (destination service or logging service, ha ? 😉 )