Skip to content

Create a ServiceAccount, a Token, Role and RoleBinding

Tip

This guide will walk you through setting up kind and Vault and its Kubernetes Secret Engine to create a ServiceAccount, Token, Role and 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 role-creator, which allows to create Service Account, Tokens, Roles and RoleBindings.

Tip

This Service Account is going to be used by Vault

Note

**Kubernetes prevents users (including service accounts) from granting RBAC permissions they do not already have themselves. Thats why we have to assign bind and escalate as verbs for roles and rolebindings.

cat <<EOF | kubectl apply -f -
# SA used by vault to create new service account + token
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: role-creator
rules:
  - apiGroups: [""]
    resources: ["serviceaccounts"]
    verbs: ["create"]
  - apiGroups: [""]
    resources: ["serviceaccounts/token"]
    verbs: ["create"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["roles"]
    verbs: ["bind", "create", "update", "delete", "escalate"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings"]
    verbs: ["bind", "create", "update", "delete", "escalate"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: service-account-creator-binding
subjects:
  - kind: ServiceAccount
    name: vault-auth
roleRef:
  kind: Role
  name: role-creator
  apiGroup: rbac.authorization.k8s.io
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 that will create the ServiceAcccount, Token, Role and RoleBinding:

Tip

The role allows to list all pods in the default namespace

Important

Note the generated_role_rules

#!/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" \
    generated_role_rules='{"rules":[{"apiGroups":[""],"resources":["pods"],"verbs":["list"]}]}' \
    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 get pods
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 allows listing pods for the default namespace, but not for kube-system:

> KUBECONFIG=kubeconfig.yml k 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)