Source: OpenFaaS Blog

OpenFaaS Blog Introducing built-in authentication for OpenFaaS Functions

A long standing request from OpenFaaS users has been to add built-in authentication for functions. This would allow you to secure your function endpoints without having to write any additional code. Once a function deployed via the OpenFaaS gateway, it will become available on the gateway via the path: /function/NAME and /async-function/NAME. This means that anyone with access to the gateway can invoke the function, and the function’s handler is responsible for any authentication or authorization. In this blog post we’ll show you how to use a pre-release version of IAM for OpenFaaS to create a Policy that restricts access to a function only to authorized users with JSON Web Token (JWT) authentication. You’ll need to have OpenFaaS for Enterprises pre-installed and configured to integrate with your existing Identify Provider (IdP) such as Okta, Keycloak, or Google. We will perform the initial one-time setup process: Create a function to secure, and understand how the watchdog performs the authentication Create a new OAuth/OIDC client for use with OpenFaaS Create a Policy to restrict access to a function Create a Role to bind a Policy to a given OAuth Client or user Then we’ll obtain a token and use it to invoke the function: Obtain an OAuth2 token from your IdP using the client credentials flow (other flows are supported for human users) Perform a token exchange for a Function Token Invoke the function with the Function Token Conceptual diagram showing Function Authentication flow from IdP to function invocation. What if I’m running another version of OpenFaaS? Feel free to reach out to us if you’d like to try out OpenFaaS for Enterprises. Alternatively, you can still write custom code in your function’s handler to validate or authenticate requests using a mechanism like HMAC or an API token mounted via a secret. Create a new function to secure When secured, a Function Token has to be presented via the Authorization header to invoke a function. This is a short-lived JWT token that is obtained through a token exchange process. The token is validated by the OpenFaaS watchdog, and the initial release will only cover the newer of-watchdog, with support for the classic watchdog coming in a future release. We’ll deploy a function from the OpenFaaS store called printer which pretty prints incoming HTTP requests to the logs of the function. provider: name: openfaas printer: skip_build: true image: ghcr.io/openfaas/printer:latest Add the following environment variable: environment: jwt_auth: "true" OpenFaaS injects two environment variables into the function: OPENFAAS_NAME - the name of the function i.e. printer OPENFAAS_NAMESPACE - the namespace of the function i.e. openfaas-fn For each request to the function, the watchdog combines the OPENFAAS_NAME and OPENFAAS_NAMESPACE, then evaluates the value against the permissions encoded in the OpenFaaS Function Token. Create a new OAuth/OIDC Client Setup a new OAuth or OIDC application or client to be used by OpenFaaS in your IdP. Since the token will be obtained by a machine, you’ll need to use the client credentials flow. Sometimes this will mean checking additional settings like “Client credentials”. In Keycloak, check the “Client authentication” and “Service accounts roles” checkboxes on the Create client page. Save the client_secret as ./client-secret.txt Take a note of client_id, for the following steps. Create a JWT Issuer and apply it to your cluster: --- apiVersion: iam.openfaas.com/v1 kind: JwtIssuer metadata: name: keycloak.example.com namespace: openfaas spec: iss: https://keycloak.example.com/realms/openfaas aud: - openfaas tokenExpiry: 1h Create or update a Policy for a function The following policy will allow the env function in the dev namespace to be invoked, and any function in the openfaas-fn namespace. apiVersion: iam.openfaas.com/v1 kind: Policy metadata: name: invoke-policy namespace: openfaas spec: statement: - sid: 1-invoke-policy action: - "Function:Invoke" effect: Allow resource: - "openfaas-fn:*" - "dev:env" Save the file as invoke-policy.yaml and apply it to your cluster with kubectl apply -f invoke-policy.yaml. Create a Role You’ll need to create a Role to map the Policy to a user or group. In this example, we’ll create a role called invoke-role. For a machine account, it’s recommended that you created a dedicated OAuth client application with its own client_id. Then make the Role match on the issuer and on the client_id field. apiVersion: iam.openfaas.com/v1 kind: Role metadata: name: invoke-role namespace: openfaas spec: policy: - invoke-policy condition: StringEqual: jwt:iss: ["https://keycloak.example.com/realms/openfaas"] jwt:client_id: ["openfaas"] Note: if you add client_credentials to an existing OAuth/OIDC application, you will need additional conditions to match on the specific subject, user, email, or group, etc, otherwise anyone with a valid account for the client_id will be able to obtain a token. Obtain an OAuth2 token from your identify provider Form a curl statement: export IDP_TOKEN_URL=https://keycloak.example/realms/openfaas/protocol/openid-connect/token export CLIENT_ID="openfaas" export CLIENT_SECRET="$(cat ./client-secret.txt)" curl -S -L -X POST "${IDP_TOKEN_URL}" \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode "client_id=${CLIENT_ID}" \ --data-urlencode "client_secret=${CLIENT_SECRET}" \ --data-urlencode 'scope=email' \ --data-urlencode 'grant_type=client_credentials' Run the above, to obtain a token. It will look something like this: {"access_token":"REDACTED","expires_in":300,"refresh_expires_in":0,"token_type":"Bearer","not-before-policy":0,"scope":"profile email"} Save the result as token.txt. Perform a token exchange for a Function Token There are two types of token exchange supported in OpenFaaS for Enterprises: Exchange an OAuth2 token for an OpenFaaS API Token Exchange an OAuth2 token for an OpenFaaS Function Token The reason there are separate tokens for different uses, is so that a token with API access isn’t used to invoke function, where it could be used to escalate privileges. Now you have an OAuth2 token from your IdP, we’ll exchange it for a Function Token. Update the IDP_TOKEN_URL to your OpenFaaS gateway URL, with the suffix /oauth/token. Make sure that token.txt file exists from the previous step. If the IdP token has expired, you will need to repeat the previous step. export IDP_TOKEN_URL="https://gateway.example.com/oauth/token" export TOKEN="$(cat token.txt)" curl -S -L -X POST "${IDP_TOKEN_URL}" \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode "subject_token=${TOKEN}" \ --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \ --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \ --data-urlencode 'scope=function' The resulting token will look like this: {"access_token":"REDACTED","expires_in":300,"token_type":"Bearer","scope":"function"} Save the text from the “access_token” field as function-token.txt. It will look something like this: { "iss": "https://openfaas.example.com", "sub": "fed:a9e0e67a-5758-4373-a4ba-23957fa66e6b", "aud": [ "https://openfaas.example.com" ], "exp": 1715892121, "iat": 1715848921, "function": { "permissions": [ "openfaas-fn:*", "dev:env" ] } } As you can see, the union of permissions from the Policy are encoded into the Function Token. If you wish to restrict the token so that it can only be used to invoke a single function, or a subset of functions, you can request a specific audience when you exchange the token. curl -S -L -X POST "${IDP_TOKEN_URL}" \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode "subject_token=${TOKEN}" \ --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \ --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \ --data-urlencode 'scope=function' \ --data-urlencode 'audience=openfaas-fn:env' \ --data-urlencode 'audience=openfaas-fn:figlet' Here’s how the token looks, with the audience also specified. { "iss": "https://openfaas.example.com", "sub": "fed:a9e0e67a-5758-4373-a4ba-23957fa66e6b", "aud": [ "https://openfaas.example.com" ], "exp": 1715892177, "iat": 1715848977, "function": { "permissions": [ "openfaas-fn:*", "dev:env" ], "audience": [ "openfaas-fn:env", "openfaas-fn:figlet" ] } } Invoke the function with the Function Token You now have a token that can be used to invoke a function. You can use it with curl or any HTTP client. First of all, check the function cannot be invoked without a token: curl https://gateway.example.com/function/env Now invoke the function with the token: curl -i https://gateway.example.com/function/env \ -H "Authorization: Bearer $(cat function-token.txt)" You should see a successful response from the function. Conclusion If you already have IAM for OpenFaaS installed and configured for Single-Sign On, then there isn’t a lot of additional work to do to secure your functions with Function Tokens. In most cases, you’ll just set an additional environment variable on your protected functions and create a Policy and Role for any user that needs to invoke them. Q&A Q: Are Function Tokens production-ready? When will I be able to use them in production? A: Function Tokens, once released will be suitable for use in production. They are an extension of the already released IAM for OpenFaaS features and use the same underlying technology for the new type of Function Token. The work is currently pre-release and available for testing, once it’s released you will have access to it via the Helm chart

Read full article »
Est. Annual Revenue
$100K-5.0M
Est. Employees
1-25
Alex Ellis's photo - Founder of OpenFaaS

Founder

Alex Ellis

CEO Approval Rating

90/100