Skip to content

Create a ServiceAccount Token for a ServiceAccount with Role & RoleBinding

Tip

This guide will walk you through setting up kind and Vault and its Kubernetes Secret Engine to create a Service Account Token for a pre-existing ServiceAccount with Role & RoleBinding

Prerequisites

You will need the following tools to be installed:

Setup kind

cat <<EOF >>kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  apiServerAddress: "127.0.0.1"
  apiServerPort: 6443
EOF
kind create cluster --config=kind-config.yaml

you should now be able to run kubectl commands:

> kubectl get ns
NAME                 STATUS   AGE
default              Active   64m
kube-node-lease      Active   64m
kube-public          Active   64m
kube-system          Active   64m
local-path-storage   Active   63m

Configure Vault access

The following manifest, creates a ServiceAccount vault-auth and assigns it the role service-account-token-creator, which allows to create Service Account Tokens.

Tip

This Service Account is going to be used by Vault

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault-auth
automountServiceAccountToken: true
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: vault-auth-token
  annotations:
    kubernetes.io/service-account.name: vault-auth
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: service-account-token-creator
rules:
  - apiGroups: [""]
    resources: ["serviceaccounts/token"]
    verbs: ["create", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: service-account-token-creator-binding
subjects:
  - kind: ServiceAccount
    name: vault-auth
roleRef:
  kind: Role
  name: service-account-token-creator
  apiGroup: rbac.authorization.k8s.io
EOF

Create a Service Account for which Vault creates the ServiceAccount Token

This manifest creates a Service Account tmp-sa that is bound to the role-list-pods role that only allows to list pods in the default namespace:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tmp-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: role-list-pods
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: role-abilities
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: role-list-pods
subjects:
- kind: ServiceAccount
  name: tmp-sa
EOF

Configure Vault

Lastly, we will need to start and configure a local Vault Server:

vault server \
    -dev \
    dev-listen-address=0.0.0.0:8200 \
    dev-root-token-id=root

Authenticate to Vault and check with vault status:

export VAULT_ADDR="http://127.0.0.1:8200"
export VAULT_TOKEN="root"
> vault status
Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    1
Threshold       1
Version         1.18.3
Build Date      2024-12-16T14:00:53Z
Storage Type    inmem
Cluster Name    vault-cluster-4cab3957
Cluster ID      597257da-8e8d-6147-c379-e93e3a6013c7
HA Enabled      false

Now, we will configure the Kubernetes Secrets Engine to connect to the local kind Cluster with the vault-auth ServiceAccount and create a role kind, which will create a ServiceAccount Tokens for the tmp-sa ServiceAccount:

#!/usr/bin/env bash
set -ex

K8S_JWT_TOKEN=$(kubectl get secret vault-auth-token -o jsonpath="{.data.token}" | base64 -d)
K8S_CA_CERT=$(kubectl get secret vault-auth-token -o jsonpath="{['data']['ca\.crt']}" | base64 -d)

vault secrets enable kubernetes
vault write -f kubernetes/config \
    kubernetes_host="https://127.0.0.1:6443" \
    kubernetes_ca_cert="$K8S_CA_CERT" \
    service_account_jwt="$K8S_JWT_TOKEN"

vault write kubernetes/roles/kind \
    allowed_kubernetes_namespaces="default" \
    service_account_name="tmp-sa" \
    token_default_ttl="10m"

Putting it together

Write kinds kubeconfig to a file:

kind get kubeconfig > kubeconfig.yml

and update it, to use kubectl-vault-login for authentication:

KUBECONFIG=./kubeconfig.yml kubectl config set-credentials vault \
  --exec-interactive-mode=Never \
  --exec-api-version=client.authentication.k8s.io/v1 \
  --exec-command=kubectl \
  --exec-arg=vault-login \
  --exec-arg=--role=kind
> cat kubeconfig.yml
[...]
users:
- name: vault
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1
      args:
      - vault-login
      - --role=kind
      command: kubectl
      env: null
      interactiveMode: Never
      provideClusterInfo: false
# create a pod to see some results
> kubectl run nginx --image=nginx
# use the updated kubeconfig to list pods in the default namespace
> KUBECONFIG=./kubeconfig.yml kubectl --user=vault get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          73s

You can also use curl to communicate with the Kubernetes API directly:

> curl -sk \
  -H "Authorization: Bearer $(./kubectl-vault-login -r kind | jq -r .status.token)" \
  $(kubectl config view --minify -o 'jsonpath={.clusters[].cluster.server}')/api/v1/namespaces/default/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "resourceVersion": "707"
  },
  "items": []
}

The role role-list-pods allows listing pods for the default namespace, but not for kube-system:

> KUBECONFIG=kubeconfig.yml kubectl --user=vault get pod -n kube-config
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:v-token-kind-1739680669-u5x0uqreffqt8hf2qdydpksf" cannot list resource "pods" in API group "" in the namespace "kube-system"

Teardown

Tear everything down by running:

kind delete cluster
kill -9 $(pgrep -x vault)