Skip to content

Commit cf9a8e0

Browse files
Docs to clarify k8s auth options with short-lived tokens (#13275) (#13720)
* Rework 1.21 content into one heading and add note at top * Add notes about extended k8s token duration * Add example of ClusterRoleBinding for using client JWTs Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
1 parent 36961ba commit cf9a8e0

File tree

2 files changed

+373
-37
lines changed

2 files changed

+373
-37
lines changed

website/content/docs/auth/jwt/oidc_providers.mdx

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,220 @@ vault write auth/oidc/role/your_default_role \
253253
1. Save.
254254
1. Visit Credentials. Select Client ID and Secret and note the generated secret.
255255
256+
## Kubernetes
257+
258+
Kubernetes can function as an OIDC provider such that Vault can validate its
259+
service account tokens using JWT/OIDC auth.
260+
261+
-> **Note:** The JWT auth engine does **not** use Kubernetes' `TokenReview` API
262+
during authentication, and instead uses public key cryptography to verify the
263+
contents of JWTs. This means tokens that have been revoked by Kubernetes will
264+
still be considered valid by Vault until their expiry time. To mitigate this
265+
risk, use short TTLs for service account tokens or use
266+
[Kubernetes auth](/docs/auth/kubernetes) which _does_ use the `TokenReview` API.
267+
268+
### Using service account issuer discovery
269+
270+
When using service account issuer discovery, you only need to provide the JWT
271+
auth mount with an OIDC discovery URL, and sometimes a TLS certificate authority
272+
to trust. This makes it the most straightforward method to configure if your
273+
Kubernetes cluster meets the requirements.
274+
275+
Kubernetes cluster requirements:
276+
277+
* [`ServiceAccountIssuerDiscovery`][k8s-sa-issuer-discovery] feature enabled.
278+
* Present from 1.18, defaults to enabled from 1.20.
279+
* kube-apiserver's `--service-account-issuer` flag is set to a URL that is
280+
reachable from Vault. Public by default for most managed Kubernetes solutions.
281+
* Must use short-lived service account tokens when logging in.
282+
* Tokens mounted into pods default to short-lived from 1.21.
283+
284+
Configuration steps:
285+
286+
1. Ensure OIDC discovery URLs do not require authentication, as detailed
287+
[here][k8s-sa-issuer-discovery]:
288+
289+
```bash
290+
kubectl create clusterrolebinding oidc-reviewer \
291+
--clusterrole=system:service-account-issuer-discovery \
292+
--group=system:unauthenticated
293+
```
294+
295+
1. Find the issuer URL of the cluster.
296+
297+
```bash
298+
kubectl proxy &
299+
ISSUER="$(curl --fail --silent --show-error 127.0.0.1:8001/.well-known/openid-configuration | jq -r '.issuer')"
300+
301+
# Kill the background proxy process when you're done
302+
kill %%
303+
```
304+
305+
1. Enable and configure JWT auth in Vault.
306+
307+
1. If Vault is running in Kubernetes:
308+
309+
```bash
310+
kubectl exec vault-0 -- vault auth enable jwt
311+
kubectl exec vault-0 -- vault write auth/jwt/config \
312+
oidc_discovery_url=https://kubernetes.default.svc.cluster.local \
313+
oidc_discovery_ca_pem=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
314+
```
315+
316+
1. Alternatively, if Vault is _not_ running in Kubernetes:
317+
318+
-> **Note:** When Vault is outside the cluster, the `$ISSUER` endpoint below may
319+
or may not be reachable. If not, you can configure JWT auth using
320+
[`jwt_validation_pubkeys`](#using-jwt-validation-public-keys) instead.
321+
322+
```bash
323+
vault auth enable jwt
324+
vault write auth/jwt/config oidc_discovery_url="${ISSUER}"
325+
```
326+
327+
1. Configure a role and log in as detailed [below](#creating-a-role-and-logging-in).
328+
329+
[k8s-sa-issuer-discovery]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-issuer-discovery
330+
331+
### Using JWT validation public keys
332+
333+
This method can be useful if Kubernetes' API is not reachable from Vault or if
334+
you would like a single JWT auth mount to service multiple Kubernetes clusters
335+
by chaining their public signing keys.
336+
337+
Kubernetes cluster requirements:
338+
339+
* [`ServiceAccountIssuerDiscovery`][k8s-sa-issuer-discovery] feature enabled.
340+
* Present from 1.18, defaults to enabled from 1.20.
341+
* This requirement can be avoided if you can access the Kubernetes master
342+
nodes to read the public signing key directly from disk at
343+
`/etc/kubernetes/pki/sa.pub`. In this case, you can skip the steps to
344+
retrieve and then convert the key as it will already be in PEM format.
345+
* Must use short-lived service account tokens when logging in.
346+
* Tokens mounted into pods default to short-lived from 1.21.
347+
348+
Configuration steps:
349+
350+
1. Fetch the service account signing public key from your cluster's JWKS URI.
351+
352+
```bash
353+
# 1. Find the issuer URL of the cluster.
354+
kubectl proxy &
355+
ISSUER="$(curl --fail --silent --show-error 127.0.0.1:8001/.well-known/openid-configuration | jq -r '.issuer')"
356+
357+
# 2. Query the jwks_uri specified in /.well-known/openid-configuration
358+
# NB: You may need to run this from a pod within the cluster if the $ISSUER
359+
# URL is not available outside the cluster.
360+
curl "$(curl --fail --silent --show-error "${ISSUER}/.well-known/openid-configuration" | jq -r '.jwks_uri')"
361+
362+
# Kill the background proxy process when you're done
363+
kill %%
364+
```
365+
366+
1. Convert the keys from JWK format to PEM. You can use a CLI tool or an online
367+
converter such as [this one][jwk-to-pem].
368+
369+
1. Configure the JWT auth mount with those public keys.
370+
371+
```bash
372+
vault write auth/jwt/config \
373+
jwt_validation_pubkeys="-----BEGIN PUBLIC KEY-----
374+
MIIBIjANBgkqhkiG9...
375+
-----END PUBLIC KEY-----","-----BEGIN PUBLIC KEY-----
376+
MIIBIjANBgkqhkiG9...
377+
-----END PUBLIC KEY-----"
378+
```
379+
380+
1. Configure a role and log in as detailed [below](#creating-a-role-and-logging-in).
381+
382+
[jwk-to-pem]: https://8gwifi.org/jwkconvertfunctions.jsp
383+
384+
### Creating a role and logging in
385+
386+
Once your JWT auth mount is configured, you're ready to configure a role and
387+
log in.
388+
389+
1. Create a role for JWT auth that the `default` service account from the
390+
`default` namespace can use. The audience of tokens defaults to the same as
391+
the issuer, but it is configurable.
392+
393+
```bash
394+
vault write auth/jwt/role/my-role \
395+
role_type="jwt" \
396+
bound_audiences="${ISSUER}" \
397+
user_claim="sub" \
398+
bound_subject="system:serviceaccount:default:default" \
399+
policies="default" \
400+
ttl="1h"
401+
```
402+
403+
1. Pods or other clients with access to a service account JWT can then log in.
404+
405+
```bash
406+
vault write auth/jwt/login \
407+
role=my-role \
408+
jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token
409+
# OR equivalent to:
410+
curl \
411+
--fail \
412+
--request PUT \
413+
--header "X-Vault-Request: true" \
414+
--data '{"jwt":"<JWT-TOKEN-HERE>","role":"my-role"}' \
415+
"${VAULT_ADDR}/v1/auth/jwt/login"
416+
```
417+
418+
### Specifying TTL and audience
419+
420+
If you would like to specify a custom TTL or audience for service account tokens,
421+
the following pod spec illustrates a volume mount that overrides the default
422+
admission injected token. This is especially relevant if you are unable to
423+
disable the [--service-account-extend-token-expiration][k8s-extended-tokens]
424+
flag for `kube-apiserver` and want to use short TTLs.
425+
426+
When using the resulting token, you will need to set `bound_audiences=vault`
427+
when creating roles in Vault's JWT auth mount.
428+
429+
```yaml
430+
apiVersion: v1
431+
kind: Pod
432+
metadata:
433+
name: nginx
434+
spec:
435+
# automountServiceAccountToken is redundant in this example because the
436+
# mountPath overlapping with the default path below will already stop the
437+
# default admission injected token from being created. Use this option if you
438+
# choose a different mount path.
439+
automountServiceAccountToken: false
440+
containers:
441+
- name: nginx
442+
image: nginx
443+
volumeMounts:
444+
- name: custom-token
445+
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
446+
volumes:
447+
- name: custom-token
448+
projected:
449+
defaultMode: 420
450+
sources:
451+
- serviceAccountToken:
452+
path: token
453+
expirationSeconds: 600 # 10 minutes is the minimum TTL
454+
audience: vault
455+
- configMap:
456+
name: kube-root-ca.crt
457+
items:
458+
- key: ca.crt
459+
path: ca.crt
460+
- downwardAPI:
461+
items:
462+
- fieldRef:
463+
apiVersion: v1
464+
fieldPath: metadata.namespace
465+
path: namespace
466+
```
467+
468+
[k8s-extended-tokens]: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/#options
469+
256470
## Okta
257471
258472
1. Make sure an Authorization Server has been created. The "Issuer" field shown on the Setting page

0 commit comments

Comments
 (0)