Skip to content

Commit 8f3e247

Browse files
committed
Function Authentication preview
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
1 parent 9a0955f commit 8f3e247

2 files changed

Lines changed: 292 additions & 0 deletions

File tree

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
---
2+
title: Introducing built-in Function Authentication for OpenFaaS
3+
description:
4+
date: 2024-05-14
5+
categories:
6+
- kubernetes
7+
- faas
8+
- functions
9+
- authentication
10+
- authorization
11+
dark_background: true
12+
# image: "/images/2024-05-01-chargeback/background.png"
13+
author_staff_member: alex
14+
hide_header_image: true
15+
---
16+
17+
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.
18+
19+
In this blog post we'll show you how to use an updated 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.
20+
21+
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.
22+
23+
We will perform the initial one-time setup process:
24+
25+
* Create a function to secure, and understand how the watchdog performs the authentication
26+
* Create a new OAuth/OIDC client for use with OpenFaaS
27+
* Create a Policy to restrict access to a function
28+
* Create a Role to bind a Policy to a given OAuth Client or user
29+
30+
Then we'll obtain a token and use it to invoke the function:
31+
32+
* Obtain an OAuth2 token from your IdP using the client credentials flow (other flows are supported for human users)
33+
* Perform a token exchange for a Function Token
34+
* Invoke the function with the Function Token
35+
36+
37+
![Conceptual diagram showing Function Authentication flow](/images/2024-05-function-auth/conceptual.png)
38+
> Conceptual diagram showing Function Authentication flow from IdP to function invocation.
39+
40+
*What if I'm running another version of OpenFaaS?*
41+
42+
Feel free to [reach out to us](https://openfaas.com/pricing) if you'd like to try out OpenFaaS for Enterprises.
43+
44+
Alternatively, you can still [write custom code in your function's handler](https://docs.openfaas.com/reference/authentication/) to validate or authenticate requests using a mechanism like HMAC or an API token mounted via a secret.
45+
46+
## Create a new function to secure
47+
48+
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.
49+
50+
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.
51+
52+
We'll deploy a function from the OpenFaaS store called `printer` which pretty prints incoming HTTP requests to the logs of the function.
53+
54+
```yaml
55+
provider:
56+
name: openfaas
57+
58+
printer:
59+
skip_build: true
60+
image: ghcr.io/openfaas/printer:latest
61+
```
62+
63+
Add the following environment variable:
64+
65+
```yaml
66+
environment:
67+
jwt_auth: "true"
68+
```
69+
70+
OpenFaaS injects two environment variables into the function:
71+
72+
* `OPENFAAS_NAME` - the name of the function i.e. `printer`
73+
* `OPENFAAS_NAMESPACE` - the namespace of the function i.e. `openfaas-fn`
74+
75+
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.
76+
77+
## Create a new OAuth/OIDC Client
78+
79+
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.
80+
81+
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.
82+
83+
Save the client_secret as `./client-secret.txt`
84+
85+
Take a note of client_id, for the following steps.
86+
87+
Create a JWT Issuer and apply it to your cluster:
88+
89+
```yaml
90+
---
91+
apiVersion: iam.openfaas.com/v1
92+
kind: JwtIssuer
93+
metadata:
94+
name: keycloak.example.com
95+
namespace: openfaas
96+
spec:
97+
iss: https://keycloak.example.com/realms/openfaas
98+
aud:
99+
- openfaas
100+
tokenExpiry: 1h
101+
```
102+
103+
## Create or update a Policy for a function
104+
105+
The following policy will allow the `env` function in the `dev` namespace to be invoked, and any function in the `openfaas-fn` namespace.
106+
107+
```yaml
108+
apiVersion: iam.openfaas.com/v1
109+
kind: Policy
110+
metadata:
111+
name: invoke-policy
112+
namespace: openfaas
113+
spec:
114+
statement:
115+
- sid: 1-invoke-policy
116+
action:
117+
- "Function:Invoke"
118+
effect: Allow
119+
resource:
120+
- "openfaas-fn:*"
121+
- "dev:env"
122+
```
123+
124+
Save the file as invoke-policy.yaml and apply it to your cluster with `kubectl apply -f invoke-policy.yaml`.
125+
126+
## Create a Role
127+
128+
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`.
129+
130+
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.
131+
132+
```yaml
133+
apiVersion: iam.openfaas.com/v1
134+
kind: Role
135+
metadata:
136+
name: invoke-role
137+
namespace: openfaas
138+
spec:
139+
policy:
140+
- invoke-policy
141+
condition:
142+
StringEqual:
143+
jwt:iss: ["https://keycloak.example.com/realms/openfaas"]
144+
jwt:client_id: ["openfaas"]
145+
```
146+
147+
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.
148+
149+
## Obtain an OAuth2 token from your identify provider
150+
151+
Form a curl statement:
152+
153+
```bash
154+
export IDP_TOKEN_URL=https://keycloak.example/realms/openfaas/protocol/openid-connect/token
155+
export CLIENT_ID="openfaas"
156+
export CLIENT_SECRET="$(cat ./client-secret.txt)"
157+
158+
curl -S -L -X POST "${IDP_TOKEN_URL}" \
159+
--header 'Content-Type: application/x-www-form-urlencoded' \
160+
--data-urlencode "client_id=${CLIENT_ID}" \
161+
--data-urlencode "client_secret=${CLIENT_SECRET}" \
162+
--data-urlencode 'scope=email' \
163+
--data-urlencode 'grant_type=client_credentials'
164+
```
165+
166+
Run the above, to obtain a token.
167+
168+
It will look something like this:
169+
170+
```json
171+
{"access_token":"REDACTED","expires_in":300,"refresh_expires_in":0,"token_type":"Bearer","not-before-policy":0,"scope":"profile email"}
172+
```
173+
174+
Save the result as token.txt.
175+
176+
## Perform a token exchange for an Function Token
177+
178+
There are two types of token exchange supported in OpenFaaS for Enterprises:
179+
180+
1. Exchange an OAuth2 token for an OpenFaaS API Token
181+
2. Exchange an OAuth2 token for an OpenFaaS Function Token
182+
183+
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.
184+
185+
Now you have an OAuth2 token from your IdP, we'll exchange it for a Function Token.
186+
187+
Update the `IDP_TOKEN_URL` to your OpenFaaS gateway URL, with the suffix `/oauth/token`.
188+
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.
189+
190+
```bash
191+
export IDP_TOKEN_URL="https://gateway.example.com/oauth/token"
192+
export TOKEN="$(cat token.txt)"
193+
194+
curl -S -L -X POST "${IDP_TOKEN_URL}" \
195+
--header 'Content-Type: application/x-www-form-urlencoded' \
196+
--data-urlencode "subject_token=${TOKEN}" \
197+
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \
198+
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
199+
--data-urlencode 'scope=function'
200+
```
201+
202+
The resulting token will look like this:
203+
204+
```json
205+
{"access_token":"REDACTED","expires_in":300,"token_type":"Bearer","scope":"function"}
206+
```
207+
208+
Save the text from the "access_token" field as function-token.txt.
209+
210+
## Invoke the function with the Function Token
211+
212+
You now have a token that can be used to invoke a function. You can use it with curl or any HTTP client.
213+
214+
First of all, check the function cannot be invoked without a token:
215+
216+
```bash
217+
curl https://gateway.example.com/function/env
218+
```
219+
220+
Now invoke the function with the token:
221+
222+
```bash
223+
curl -i https://gateway.example.com/function/env \
224+
-H "Authorization: Bearer $(cat function-token.txt)"
225+
```
226+
227+
You should see a successful response from the function.
228+
229+
## Conclusion
230+
231+
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.
232+
233+
### Q&A
234+
235+
Q: I use another version of OpenFaaS i.e. faasd, what can I do to authenticate functions?
236+
237+
A: OpenFaaS functions serve HTTP, therefore [you can use any standard authentication mechanism](https://docs.openfaas.com/reference/authentication/) such as Basic Auth, HMAC, API tokens, or OAuth2. We do not recommend using an API gateway or reverse proxy to implement authentication, as functions can be invoked directly at the Pod level, or via the OpenFaaS gateway's internal address, bypassing the proxy.
238+
239+
Q: Can I bypass authentication by invoking a Function's Pod directly by its ClusterIP?
240+
241+
A: Function Authentication is implemented in the OpenFaaS watchdog, which means that you cannot bypass it by invoking the function directly. The watchdog will always intercept the request and enforce the policy.
242+
243+
Q: How long do Function Tokens last?
244+
245+
A: This is configurable at the JWT Issuer level using the `tokenExpiry` field of the `JwtIssuer`.
246+
247+
Q: Can a function access the API if I send it a normal OpenFaaS API Access Token?
248+
249+
A: You can exchange your OpenFaaS API Access Token for a Function Invoke token, but you cannot use the API Access Token to invoke a function directly.
250+
251+
Q: Can I invoke authenticated functions from another microservice or backend application?
252+
253+
A: Yes, you can use the OAuth2 client credentials flow to obtain a token for a machine account, and then exchange it for a Function Token. If your application runs within Kubernetes, then you can use the Kubernetes service account to obtain a token. See also: [How to authenticate to the OpenFaaS API using Kubernetes JWT tokens](https://www.openfaas.com/blog/kubernetes-tokens-openfaas-api/)
254+
255+
Q: Can a function invoke another function which has Function Authentication enabled?
256+
257+
A: In order to invoke an Function with Authentication enabled, you will need to obtain a token from an IdP, then exchange it for a Function Token. This means that a function can invoke another function, but only if it has the necessary permissions.
258+
259+
Q: Can the queue-worker invoke functions using Function Tokens?
260+
261+
A: The queue-worker can invoke functions using Function Tokens, so long as you pass in the token as a header.
262+
263+
Q: Does the queue-worker's 'X-Callback-Url' work with Function Authentication?
264+
265+
A: The queue-worker can send the result of an invocation to another function by passing in a header of `X-Callback-Url`. This will continue to work for functions without authentication, but is out of scope for the initial version when Function Authentication is used.
266+
267+
Q: Are the cron-connector, kafka-connector, and other connectors supported?
268+
269+
A: These connectors will require additional work to support Function Tokens, and will be supported in a future release. Let us know if you need this feature.
270+
271+
Q: Can I invoke functions via the OpenFaaS Dashboard with Function Tokens?
272+
273+
A: A future version of the OpenFaaS Dashboard will be able to exchange your OpenFaaS Access Token for a Function Token, and then use it to invoke functions. For the initial release, you'll need to use curl or another HTTP client.
274+
275+
Q: Can the CLI invoke functions with Function Tokens?
276+
277+
A: The initial release includes changes to the OpenFaaS CLI to obtain Function Tokens on your behalf to invoke functions via `faas-cli invoke`.
278+
279+
Q: If I deploy a web page as a function, can I use Function Tokens through a web-browser?
280+
281+
A: If you need to secure a web page hosted on OpenFaaS, to be accessed by a human user, then you should use an OAuth2 middleware or Basic Authentication.
282+
283+
Q: Can I use Function Tokens with the classic watchdog?
284+
285+
A: The initial release will only support the of-watchdog, with support for the classic watchdog coming in a future release.
286+
287+
How do I try it out?
288+
289+
This tutorial is based upon a pre-release version of OpenFaaS for Enterprises, and the final implementation may differ. The of-watchdog will be supported first, and you will need to update your templates to use the latest available version to enable the feature.
290+
291+
Please reach out to us if you'd like a demo, or to try it out in your own environment.
292+
344 KB
Loading

0 commit comments

Comments
 (0)