Skip to content

Create a ServiceAccount, Token and RoleBinding for a (Cluster)-Role (e.g cluster-admin)

Tip

This guide will walk you through setting up kind and Vault and its Kubernetes Secret Engine to create a Service Account, Token and RoleBinding for the predefined cluster-admin ClusterRole

Warning

The cluster-admin role can do anything in every namespace.

Use with caution

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 cluster-admin-creator, which allows to create Service Account, Tokens assigning them the a (Cluster)-Role.

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 clusterrolebindings.

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: ClusterRole
metadata:
  name: cluster-admin-creator
rules:
  - apiGroups: [""]
    resources: ["serviceaccounts"]
    verbs: ["create","update", "delete"]
  - apiGroups: [""]
    resources: ["serviceaccounts/token"]
    verbs: ["create", "update", "delete"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["clusterrolebindings"]
    verbs: ["create", "patch", "delete"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["clusterroles"]
    verbs: ["bind", "escalate"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admin-creator-binding
subjects:
  - kind: ServiceAccount
    name: vault-auth
    namespace: default
roleRef:
  kind: ClusterRole
  name: cluster-admin-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 and RoleBinding:

Important

Note the kubernetes_role_type and kubernetes_role_name

#!/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" \
    kubernetes_role_name="cluster-admin" \
    kubernetes_role_type="ClusterRole" \
    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
# check SA has been created
> KUBECONFIG=kubeconfig.yml kubectl get sa
NAME                                               SECRETS   AGE
v-token-kind-1739829804-zbmswmhaet1qelxccyo97uux   0         25s
# check clusterrrolebinding was created
> KUBECONFIG=kubeconfig.yml kubectl get clusterrolebindings -n default
NAME                                             ROLE                      AGE
v-token-kind-1739829804-zbmswmhaet1qelxccyo97uux ClusterRole/cluster-admin 59s

Teardown

Tear everything down by running:

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