Skip to main content

kubernetes

Kubernetes

Kubernetes can function as an OIDC provider such that OpenBao can validate its service account tokens using JWT/OIDC auth.

info

Note: The JWT auth engine does not use Kubernetes' TokenReview API during authentication, and instead uses public key cryptography to verify the contents of JWTs. This means tokens that have been revoked by Kubernetes will still be considered valid by OpenBao until their expiry time. To mitigate this risk, use short TTLs for service account tokens or use Kubernetes auth which does use the TokenReview API.

Using service account issuer discovery

When using service account issuer discovery, you only need to provide the JWT auth mount with an OIDC discovery URL, and sometimes a TLS certificate authority to trust. This makes it the most straightforward method to configure if your Kubernetes cluster meets the requirements.

Kubernetes cluster requirements:

  • ServiceAccountIssuerDiscovery feature enabled.
    • Present from 1.18, defaults to enabled from 1.20.
  • kube-apiserver's --service-account-issuer flag is set to a URL that is reachable from OpenBao. Public by default for most managed Kubernetes solutions.
  • Must use short-lived service account tokens when logging in.
    • Tokens mounted into pods default to short-lived from 1.21.

Configuration steps:

  1. Ensure OIDC discovery URLs do not require authentication, as detailed here:

    kubectl create clusterrolebinding oidc-reviewer  \
    --clusterrole=system:service-account-issuer-discovery \
    --group=system:unauthenticated
  2. Find the issuer URL of the cluster.

    ISSUER="$(kubectl get --raw /.well-known/openid-configuration | jq -r '.issuer')"
  3. Enable and configure JWT auth in OpenBao.

  4. If OpenBao is running in Kubernetes:

    kubectl exec openbao-0 -- bao auth enable jwt
    kubectl exec openbao-0 -- bao write auth/jwt/config \
    oidc_discovery_url=https://kubernetes.default.svc.cluster.local \
    oidc_discovery_ca_pem=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  5. Alternatively, if OpenBao is not running in Kubernetes:

info

Note: When OpenBao is outside the cluster, the $ISSUER endpoint below may or may not be reachable. If not, you can configure JWT auth using jwt_validation_pubkeys instead.

bao auth enable jwt
bao write auth/jwt/config oidc_discovery_url="${ISSUER}"
  1. Configure a role and log in as detailed below.

Using JWT validation public keys

This method can be useful if Kubernetes' API is not reachable from OpenBao or if you would like a single JWT auth mount to service multiple Kubernetes clusters by chaining their public signing keys.

Kubernetes cluster requirements:

  • ServiceAccountIssuerDiscovery feature enabled.
    • Present from 1.18, defaults to enabled from 1.20.
    • This requirement can be avoided if you can access the Kubernetes master nodes to read the public signing key directly from disk at /etc/kubernetes/pki/sa.pub. In this case, you can skip the steps to retrieve and then convert the key as it will already be in PEM format.
  • Must use short-lived service account tokens when logging in.
    • Tokens mounted into pods default to short-lived from 1.21.

Configuration steps:

  1. Fetch the service account signing public key from your cluster's JWKS URI.

    # Query the jwks_uri specified in /.well-known/openid-configuration
    kubectl get --raw "$(kubectl get --raw /.well-known/openid-configuration | jq -r '.jwks_uri' | sed -r 's/.*\.[^/]+(.*)/\1/')"
  2. Convert the keys from JWK format to PEM. You can use a CLI tool or an online converter such as this one.

  3. Configure the JWT auth mount with those public keys.

    bao write auth/jwt/config \
    jwt_validation_pubkeys="-----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9...
    -----END PUBLIC KEY-----","-----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9...
    -----END PUBLIC KEY-----"
  4. Configure a role and log in as detailed below.

Creating a role and logging in

Once your JWT auth mount is configured, you're ready to configure a role and log in. The following assumes you use the projected service account token available in all pods by default. See Specifying TTL and audience below if you'd like to control the audience or TTL.

  1. Choose any value from the array of default audiences. In these examples, there is only one audience in the aud array, https://kubernetes.default.svc.cluster.local.

    To find the default audiences, either create a fresh token (requires kubectl v1.24.0+):

    $ kubectl create token default | cut -f2 -d. | base64 --decode
    {"aud":["https://kubernetes.default.svc.cluster.local"], ... "sub":"system:serviceaccount:default:default"}

    Or read a token from a running pod's filesystem:

    $ kubectl exec my-pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -f2 -d. | base64 --decode
    {"aud":["https://kubernetes.default.svc.cluster.local"], ... "sub":"system:serviceaccount:default:default"}
  2. Create a role for JWT auth that the default service account from the default namespace can use.

    bao write auth/jwt/role/my-role \
    role_type="jwt" \
    bound_audiences="<AUDIENCE-FROM-PREVIOUS-STEP>" \
    user_claim="sub" \
    bound_subject="system:serviceaccount:default:default" \
    policies="default" \
    ttl="1h"
  3. Pods or other clients with access to a service account JWT can then log in.

    bao write auth/jwt/login \
    role=my-role \
    jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token
    # OR equivalent to:
    curl \
    --fail \
    --request POST \
    --header "X-Vault-Request: true" \
    --data '{"jwt":"<JWT-TOKEN-HERE>","role":"my-role"}' \
    "${VAULT_ADDR}/v1/auth/jwt/login"

Specifying TTL and audience

If you would like to specify a custom TTL or audience for service account tokens, the following pod spec illustrates a volume mount that overrides the default admission injected token. This is especially relevant if you are unable to disable the --service-account-extend-token-expiration flag for kube-apiserver and want to use short TTLs.

When using the resulting token, you will need to set bound_audiences=openbao when creating roles in OpenBao's JWT auth mount.

apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
# automountServiceAccountToken is redundant in this example because the
# mountPath used overlaps with the default path. The overlap stops the default
# admission injected token from being created. You can use this option to
# ensure only a single token is mounted if you choose a different mount path.
automountServiceAccountToken: false
containers:
- name: nginx
image: nginx
volumeMounts:
- name: custom-token
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
volumes:
- name: custom-token
projected:
defaultMode: 420
sources:
- serviceAccountToken:
path: token
expirationSeconds: 600 # 10 minutes is the minimum TTL
audience: openbao # Must match your JWT role's `bound_audiences`
# The remaining sources are included to mimic the rest of the default
# admission injected volume.
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace