Skip to main content

Minimize Access To The Service Account Token Creation

More Info:

Access to create the token sub-resource of ServiceAccounts can be used to obtain persistent unauthorized access to the cluster. It should be limited to trusted administrators only.

Risk Level

High

Address

Security

Compliance Standards

  • CIS AKS

Triage and Remediation

Remediation

Manual Steps
  1. On any machine with kubectl access, list all Roles/ClusterRoles that can create service account tokens (directly or via wildcards):

    kubectl get clusterroles -o json \
    | jq -r '.items[]
    | select(.rules[]?
    | select(
    (.apiGroups[]? == "") and
    (.resources[]? | test("^serviceaccounts(/token)?$")) and
    (.verbs[]? | test("^(create|\\*)$"))
    )
    )
    | .metadata.name' | sort -u

    kubectl get roles --all-namespaces -o json \
    | jq -r '.items[]
    | select(.rules[]?
    | select(
    (.apiGroups[]? == "") and
    (.resources[]? | test("^serviceaccounts(/token)?$")) and
    (.verbs[]? | test("^(create|\\*)$"))
    )
    )
    | [.metadata.namespace, .metadata.name] | @tsv' | sort
  2. For each identified Role/ClusterRole, review the exact rules to confirm whether they include serviceaccounts/token or overly broad patterns:

    # ClusterRole example
    kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml

    # Role example
    kubectl get role <ROLE_NAME> -n <NAMESPACE> -o yaml

    Assess whether create on serviceaccounts/token, serviceaccounts, or * is strictly required for that role’s purpose.

  3. Identify who is actually getting this permission by listing RoleBindings/ClusterRoleBindings that reference each Role/ClusterRole:

    kubectl get rolebindings --all-namespaces -o json \
    | jq -r --arg ROLE "<ROLE_NAME>" '
    .items[]
    | select(.roleRef.kind=="Role" and .roleRef.name==$ROLE)
    | [.metadata.namespace, .metadata.name, .subjects[]?.kind, .subjects[]?.name] | @tsv' | sort

    kubectl get clusterrolebindings -o json \
    | jq -r --arg CR "<CLUSTERROLE_NAME>" '
    .items[]
    | select(.roleRef.kind=="ClusterRole" and .roleRef.name==$CR)
    | [.metadata.name, .subjects[]?.kind, .subjects[]?.namespace, .subjects[]?.name] | @tsv' | sort

    Decide whether each subject (user, group, or service account) is a trusted administrator that should retain this ability.

  4. For non-administrative or unnecessary subjects, remove or adjust bindings so they no longer inherit token creation:

    # Remove an entire binding that only exists to grant this right
    kubectl delete rolebinding <BINDING_NAME> -n <NAMESPACE>
    kubectl delete clusterrolebinding <BINDING_NAME>

    # Or edit to point to a safer Role/ClusterRole without token creation
    kubectl edit rolebinding <BINDING_NAME> -n <NAMESPACE>
    kubectl edit clusterrolebinding <BINDING_NAME>

    When editing, ensure the referenced Role/ClusterRole does not include serviceaccounts/token or wildcard rules granting it.

  5. Where a Role/ClusterRole has broader permissions than needed (e.g., resources: ["serviceaccounts", "serviceaccounts/token", "*"] or verbs: ["create", "*"]), refine it:

    kubectl edit clusterrole <CLUSTERROLE_NAME>
    kubectl edit role <ROLE_NAME> -n <NAMESPACE>

    Remove serviceaccounts/token and any unnecessary wildcards, preserving only the minimal set of verbs and resources truly required.

  6. Verify that only intended administrative roles can still create service account tokens:

    # Re-run evidence collection
    kubectl get clusterroles -o json | jq -r '...same jq as in step 1...'
    kubectl get roles --all-namespaces -o json | jq -r '...same jq as in step 1...'

    Confirm that any remaining roles with create on serviceaccounts/token (or equivalent wildcard) are bound only to trusted administrator identities.

Using kubectl
# 1) List all ClusterRoles that can create serviceaccount tokens
# Run on: any machine with kubectl access
kubectl get clusterroles -o json \
| jq -r '
.items[]
| select(
.rules[]
| select(
(.apiGroups // [""]) | index("")
)
| select(
(.resources // []) | index("serviceaccounts/token")
)
| select(
(.verbs // []) | index("create")
)
)
| .metadata.name
' | sort -u

Problem indication: Any returned ClusterRole is capable of creating service account tokens. Every such role must be reviewed to ensure it is intended only for trusted administrators.

# 2) Inspect the detailed rules of each suspicious ClusterRole
# Replace <clusterrole-name> with one from the previous command
kubectl get clusterrole <clusterrole-name> -o yaml

What to look for as problematic:

  • resources: includes serviceaccounts/token (or serviceaccounts with * that covers sub-resources)
  • verbs: includes create or *
  • Role name/description does not clearly indicate “admin” or privileged use
  • Rules are overly broad, e.g.:
    • resources: ["*"] or ["serviceaccounts", "serviceaccounts/token"]
    • verbs: ["*"] or includes unrelated high-privilege verbs (impersonate, bind, escalate)
# 3) Find which ClusterRoleBindings/RoleBindings grant these ClusterRoles to subjects
# Run once per suspicious ClusterRole
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind=="ClusterRole" and .roleRef.name=="<clusterrole-name>")
| .metadata.name
' | sort -u
# 4) Inspect each binding to see who actually gets this privilege
kubectl get clusterrolebinding <binding-name> -o yaml

Problem indication:

  • subjects: includes:
    • Broad groups like system:authenticated, system:serviceaccounts, system:serviceaccounts:<namespace>, or system:masters-equivalents where that group is not strictly controlled
    • Generic or many service accounts, instead of a single, well-known admin account
    • External identities (OIDC, AAD, etc.) mapped from large user groups
# 5) Check namespace-scoped Roles that may also allow token creation
kubectl get roles --all-namespaces -o json \
| jq -r '
.items[]
| select(
.rules[]
| select(
(.apiGroups // [""]) | index("")
)
| select(
(.resources // []) | index("serviceaccounts/token")
)
| select(
(.verbs // []) | index("create")
)
)
| "\(.metadata.namespace):\(.metadata.name)"
' | sort -u
# 6) Inspect each suspicious Role and its bindings
kubectl get role -n <namespace> <role-name> -o yaml
kubectl get rolebindings -n <namespace> -o json \
| jq -r '
.items[]
| select(.roleRef.kind=="Role" and .roleRef.name=="<role-name>")
| .metadata.name
' | sort -u

kubectl get rolebinding -n <namespace> <binding-name> -o yaml

Problem indication (namespace Roles/RoleBindings):

  • Any non-admin workload service account given create on serviceaccounts/token
  • Roles bound to many service accounts in an application namespace
  • Roles intended for application use (e.g., app-*-role) that include serviceaccounts/token with create
# 7) Verification after changes (re-run discovery)
kubectl get clusterroles -o json \
| jq -r '
.items[]
| select(
.rules[]
| select(
(.apiGroups // [""]) | index("")
)
| select(
(.resources // []) | index("serviceaccounts/token")
)
| select(
(.verbs // []) | index("create")
)
)
| .metadata.name
' | sort -u

kubectl get roles --all-namespaces -o json \
| jq -r '
.items[]
| select(
.rules[]
| select(
(.apiGroups // [""]) | index("")
)
| select(
(.resources // []) | index("serviceaccounts/token")
)
| select(
(.verbs // []) | index("create")
)
)
| "\(.metadata.namespace):\(.metadata.name)"
' | sort -u

Healthy state: Only tightly controlled, clearly administrative roles (and their bindings) appear in this output. Any role used by regular workloads or broad groups should not have create on serviceaccounts/token.

Automation
#!/usr/bin/env bash
# Run on: any machine with kubectl access and appropriate permissions
# Purpose: Report Roles/ClusterRoles that can create the `token` sub-resource on ServiceAccounts

set -euo pipefail

echo "=== ClusterRoles with access to create serviceaccount 'token' sub-resource ==="
kubectl get clusterroles -o json \
| jq -r '
.items[]
| {
name: .metadata.name,
rules: (
.rules // []
| map(
select(
((.apiGroups // []) | index("")). // core API group
and
((.resources // []) | index("serviceaccounts/token")) and
((.verbs // []) | index("create"))
)
)
)
}
| select(.rules | length > 0)
| "- ClusterRole: \(.name)\n Rules:\n" +
( .rules[]
| " apiGroups: \((.apiGroups // []) | join(\",\"))\n" +
" resources: \((.resources // []) | join(\",\"))\n" +
" verbs: \((.verbs // []) | join(\",\"))\n"
)
'

echo
echo "=== Namespaced Roles with access to create serviceaccount 'token' sub-resource ==="
kubectl get roles --all-namespaces -o json \
| jq -r '
.items[]
| {
name: .metadata.name,
namespace: .metadata.namespace,
rules: (
.rules // []
| map(
select(
((.apiGroups // []) | index("")). // core API group
and
((.resources // []) | index("serviceaccounts/token")) and
((.verbs // []) | index("create"))
)
)
)
}
| select(.rules | length > 0)
| "- Role: \(.name)\n Namespace: \(.namespace)\n Rules:\n" +
( .rules[]
| " apiGroups: \((.apiGroups // []) | join(\",\"))\n" +
" resources: \((.resources // []) | join(\",\"))\n" +
" verbs: \((.verbs // []) | join(\",\"))\n"
)
'

echo
echo "=== Bindings that grant these permissions (by subject) ==="

# Helper: list all ClusterRoleBindings and RoleBindings in a compact form
echo "--- ClusterRoleBindings ---"
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| "- ClusterRoleBinding: \(.metadata.name)\n" +
" ClusterRole: \(.roleRef.kind)/\(.roleRef.name)\n" +
" Subjects:\n" +
( (.subjects // [])
| map(" kind=\(.kind) name=\(.name) namespace=\(.namespace // \"-\")")
| join("\n")
)
'

echo
echo "--- RoleBindings ---"
kubectl get rolebindings --all-namespaces -o json \
| jq -r '
.items[]
| "- RoleBinding: \(.metadata.name)\n" +
" Namespace: \(.metadata.namespace)\n" +
" Role: \(.roleRef.kind)/\(.roleRef.name)\n" +
" Subjects:\n" +
( (.subjects // [])
| map(" kind=\(.kind) name=\(.name) namespace=\(.namespace // \"-\")")
| join("\n")
)
'

cat <<'EOF'

INTERPRETING THE OUTPUT
-----------------------
1) Any ClusterRole or Role listed in the first two sections has the ability to create the
'token' sub-resource on ServiceAccounts. These are sensitive permissions.

Potential problems include:
- Generic or broad names suggesting non-admin use, e.g.:
- "edit", "view", "developer", "ci", "automation", "default", "system:*"
- Roles intended for applications, service accounts, or non-privileged users.

2) Use the bindings sections to see *who* actually receives these permissions:
- For each sensitive Role/ClusterRole identified above, find corresponding
ClusterRoleBindings/RoleBindings where roleRef.name matches that role name.
- Problematic situations include:
- Bindings granting these permissions to:
- service accounts used by applications
- groups representing many users (e.g., "developers", "all-users")
- unauthenticated/authenticated built-in groups
- generic or shared service accounts

3) Remediation is manual by design:
- For any non-admin subject that does not truly need this capability, update or
remove the binding, or refactor the Role/ClusterRole to remove
"serviceaccounts/token" with "create".
EOF