> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Minimize Access To The Service Account Token Creation

### More Info:

The ability to create the token sub-resource of service accounts lets subjects mint credentials for other identities. Restrict token creation access to trusted administrators only.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS EKS

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify all roles with `serviceaccounts/token` create permission**\
           Run on any machine with kubectl access:
           ```bash theme={null}
           kubectl get clusterroles,roles -A -o json \
           | jq -r '
             .items[]
             | select(.rules != null)
             | select(
                 any(.rules[];
                   (.resources // []) | index("serviceaccounts/token")
                   and ((.verbs // []) | index("create"))
                 )
               )
             | .kind + "/" + .metadata.name + " (ns: " + (.metadata.namespace // "cluster-scope") + ")"
           '
           ```
           Save the output as your initial inventory of roles to review.

        2. **List all bindings that grant those roles**\
           Using the role names from step 1, run (replace `ROLE_NAME` and `ROLE_KIND`):
           ```bash theme={null}
           kubectl get clusterrolebindings -o json \
           | jq -r '
             .items[]
             | select(.roleRef.kind=="ROLE_KIND" and .roleRef.name=="ROLE_NAME")
             | "ClusterRoleBinding/" + .metadata.name + " -> " + (.subjects // [] | map(.kind + ":" + (.namespace // "") + ":" + .name) | join(","))
           '
           ```
           ```bash theme={null}
           kubectl get rolebindings -A -o json \
           | jq -r '
             .items[]
             | select(.roleRef.kind=="ROLE_KIND" and .roleRef.name=="ROLE_NAME")
             | "RoleBinding/" + .metadata.namespace + "/" + .metadata.name + " -> " + (.subjects // [] | map(.kind + ":" + (.namespace // "") + ":" + .name) | join(","))
           '
           ```
           This shows which users, groups, and service accounts can create tokens.

        3. **Decide which subjects truly need token-creation capability**\
           For each subject from step 2, determine:
           * Is this a trusted admin identity (e.g., platform SRE group)?
           * Is token minting required for its function (e.g., CI system creating short-lived tokens)?
           * Could it instead use normal service account mounting or pre-created secrets?\
             Mark subjects as “required”, “over-privileged”, or “unknown; investigate owner”.

        4. **Restrict or remove access for over-privileged subjects**\
           For any binding you decide should **not** have this right:
           * If the role is used only for token creation, delete the binding:
             ```bash theme={null}
             kubectl delete clusterrolebinding BINDING_NAME
             ```
             ```bash theme={null}
             kubectl delete rolebinding -n NAMESPACE BINDING_NAME
             ```
           * If the role has other needed permissions, clone and edit it to remove the `serviceaccounts/token` `create` rule, then re-bind:
             ```bash theme={null}
             kubectl get clusterrole ORIGINAL_ROLE -o yaml > /tmp/original-role.yaml
             # Edit /tmp/original-role.yaml: remove any rule containing resources: ["serviceaccounts/token"] and verbs: ["create"...]
             kubectl apply -f /tmp/original-role.yaml
             ```
           Ensure only trusted admin identities remain bound to roles that still include this permission.

        5. **Optionally create a dedicated, tightly scoped admin role for token creation**\
           Run if you need explicit admin-only token creation capability:
           ```bash theme={null}
           cat << 'EOF' | kubectl apply -f -
           apiVersion: rbac.authorization.k8s.io/v1
           kind: ClusterRole
           metadata:
             name: admin-serviceaccount-token-creator
           rules:
           - apiGroups: [""]
             resources: ["serviceaccounts/token"]
             verbs: ["create"]
           EOF
           ```
           Bind this only to a small, trusted admin group:
           ```bash theme={null}
           cat << 'EOF' | kubectl apply -f -
           apiVersion: rbac.authorization.k8s.io/v1
           kind: ClusterRoleBinding
           metadata:
             name: admin-serviceaccount-token-creator-binding
           roleRef:
             apiGroup: rbac.authorization.k8s.io
             kind: ClusterRole
             name: admin-serviceaccount-token-creator
           subjects:
           - kind: Group
             name: YOUR-ADMIN-GROUP-NAME
           EOF
           ```

        6. **Re-verify effective access**\
           Re-run the discovery from step 1 and 2 to confirm only intended admin identities retain `create` on `serviceaccounts/token`:
           ```bash theme={null}
           kubectl get clusterroles,roles -A -o json | jq -r '...same jq as in step 1...'
           ```
           ```bash theme={null}
           kubectl get clusterrolebindings,rolebindings -A -o yaml | grep -A5 -E 'serviceaccounts/token|ROLE_NAME_YOU_EXPECT'
           ```
           Confirm no non-admin or unintended subjects appear in these results.
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 1. List all roles/clusterroles that can create service account tokens
        # Run on: any machine with kubectl access

        kubectl get clusterrole,role -A -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | select(
                    (.resources[]? == "serviceaccounts/token")
                    and (.verbs[]? | IN("create";"*"))
                )
              )
            | [.kind, .metadata.name, (.metadata.namespace // "-")]
            | @tsv
          ' | column -t
        ```

        **How to read this:**

        * Output columns: `KIND  NAME  NAMESPACE`
        * Any `ClusterRole` or `Role` listed here allows creating service account tokens.
        * `ClusterRole` entries are cluster-wide; impact is larger than namespace-scoped `Role`.
        * A problem exists if:
          * The role name is clearly meant for applications or CI/CD (not admin/ops), or
          * You see very generic roles (e.g. `edit`, `admin`, `*`) granting this, or
          * There are many such roles without a clear administrative purpose.

        ***

        ```bash theme={null}
        # 2. Inspect the detailed rules for each suspicious role/clusterrole
        # Replace <KIND> and <NAME> from the previous command
        # Run on: any machine with kubectl access

        kubectl get <KIND> <NAME> -o yaml
        ```

        Focus on:

        * `resources: ["serviceaccounts/token"]`
        * `verbs` containing `create` or `*`

        A problem exists if non-admin or broadly used roles (bound to many subjects) contain this rule.

        ***

        ```bash theme={null}
        # 3. Find what is bound to each role/clusterrole with token create
        # Run on: any machine with kubectl access

        # (a) For clusterroles
        for cr in $(kubectl get clusterrole -o json \
          | jq -r '.items[]
            | select(
                .rules[]
                | select(
                    (.resources[]? == "serviceaccounts/token")
                    and (.verbs[]? | IN("create";"*"))
                )
              )
            | .metadata.name'); do

          echo "=== ClusterRole: $cr ==="
          kubectl get clusterrolebinding -o json \
            | jq -r --arg CR "$cr" '
              .items[]
              | select(.roleRef.kind=="ClusterRole" and .roleRef.name==$CR)
              | .metadata.name as $rb
              | .subjects[]? 
              | [$rb, .kind, .name, (.namespace // "-")]
              | @tsv
            ' | column -t || echo "  (no bindings)"
          echo
        done

        # (b) For namespaced roles
        for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
          for r in $(kubectl get role -n "$ns" -o json \
            | jq -r '.items[]
              | select(
                  .rules[]
                  | select(
                      (.resources[]? == "serviceaccounts/token")
                      and (.verbs[]? | IN("create";"*"))
                  )
                )
              | .metadata.name'); do

            echo "=== Role: $r (namespace: $ns) ==="
            kubectl get rolebinding -n "$ns" -o json \
              | jq -r --arg R "$r" '
                .items[]
                | select(.roleRef.kind=="Role" and .roleRef.name==$R)
                | .metadata.name as $rb
                | .subjects[]?
                | [$rb, .kind, .name, (.namespace // "-")]
                | @tsv
              ' | column -t || echo "  (no bindings)"
            echo
          done
        done
        ```

        **How to read this:**

        * Columns: `ROLEBINDING-NAME  SUBJECT-KIND  SUBJECT-NAME  SUBJECT-NAMESPACE`
        * Problematic patterns:
          * `User` or `Group` subjects representing general developer groups, CI groups, or broad SSO groups.
          * `ServiceAccount` subjects for application workloads, not admin/ops.
          * Many subjects bound to the same token-creating role (widespread privilege).

        ***

        ```bash theme={null}
        # 4. Quick high-level summary by subject for review
        # Run on: any machine with kubectl access

        kubectl get clusterrolebinding,rolebinding -A -o json \
          | jq -r '
            .items[]
            | .metadata.namespace as $ns
            | .metadata.name as $rb
            | .roleRef.kind as $rk
            | .roleRef.name as $rn
            | .subjects[]? as $s
            | [$ns, $rb, $rk, $rn, $s.kind, $s.name, ($s.namespace // "-")]
            | @tsv
          ' | column -t
        ```

        Cross-check:

        * For each binding where the referenced role/clusterrole was found in step 1, ask:
          * Is this subject a trusted administrator identity?
          * Does this subject truly need to mint tokens for other service accounts?

        If the answer is “no” or unclear, that binding is likely a problem and should be considered for restriction or removal during manual remediation.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        set -euo pipefail

        # This script must run on any machine with kubectl configured for the target cluster.
        # It requires:
        #   - access to cluster-wide RBAC (cluster-admin or equivalent)
        #   - kubectl in PATH

        echo "=== Detecting RBAC permissions to create serviceaccount token subresources ==="
        echo

        # Helper: pretty header
        header() {
          printf '\n===== %s =====\n' "$1"
        }

        # 1) List ALL ClusterRoles and Roles that can create serviceaccount tokens
        header "1) Roles/ClusterRoles that can create serviceaccounts/token"

        # We look for:
        #   - apiGroups: [""]
        #   - resources: ["serviceaccounts/token"]  (preferred new-style)
        #   - OR resources: ["serviceaccounts"] with resourceNames including "token"
        #   - verbs including "create"
        #
        # This prints a line per matching rule: KIND;NAMESPACE;NAME;VERBS;RESOURCES;RESOURCENAMES

        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | . as $cr
            | .rules[]
            | select(
                (
                  # explicit subresource
                  (.apiGroups // []) | index("") != null and
                  (.resources // []) | index("serviceaccounts/token") != null and
                  (.verbs // []) | index("create") != null
                ) or (
                  # serviceaccounts with specific resourceNames potentially including token
                  (.apiGroups // []) | index("") != null and
                  (.resources // []) | index("serviceaccounts") != null and
                  (.verbs // []) | index("create") != null and
                  (.resourceNames // []) != null and
                  ((.resourceNames[] | ascii_downcase) | contains("token"))
                )
              )
            | "ClusterRole;;\($cr.metadata.name);\(.verbs|join(","));\(.resources|join(","));\((.resourceNames // [])|join(","))"
          ' \
          | sort || true

        kubectl get roles --all-namespaces -o json \
          | jq -r '
            .items[]
            | . as $r
            | .rules[]
            | select(
                (
                  (.apiGroups // []) | index("") != null and
                  (.resources // []) | index("serviceaccounts/token") != null and
                  (.verbs // []) | index("create") != null
                ) or (
                  (.apiGroups // []) | index("") != null and
                  (.resources // []) | index("serviceaccounts") != null and
                  (.verbs // []) | index("create") != null and
                  (.resourceNames // []) != null and
                  ((.resourceNames[] | ascii_downcase) | contains("token"))
                )
              )
            | "Role;\($r.metadata.namespace);\($r.metadata.name);\(.verbs|join(","));\(.resources|join(","));\((.resourceNames // [])|join(","))"
          ' \
          | sort || true

        echo
        echo "Columns: KIND;NAMESPACE;ROLE_NAME;VERBS;RESOURCES;RESOURCENAMES"
        echo "Any non-empty output above represents RBAC roles that can create service account tokens."
        echo

        # 2) For each such role, show which subjects actually receive that permission via bindings
        header "2) Bindings (who actually gets those permissions)"

        # Build a temp list of all roles with token-create permission
        TMP_ROLES="$(mktemp)"
        TMP_BINDS="$(mktemp)"
        trap 'rm -f "$TMP_ROLES" "$TMP_BINDS"' EXIT

        {
          kubectl get clusterroles -o json \
            | jq -r '
              .items[]
              | . as $cr
              | select(
                  any(.rules[]?;
                    (
                      (.apiGroups // []) | index("") != null and
                      (.resources // []) | index("serviceaccounts/token") != null and
                      (.verbs // []) | index("create") != null
                    ) or (
                      (.apiGroups // []) | index("") != null and
                      (.resources // []) | index("serviceaccounts") != null and
                      (.verbs // []) | index("create") != null and
                      (.resourceNames // []) != null and
                      ((.resourceNames[] | ascii_downcase) | contains("token"))
                    )
                  )
                )
              | "ClusterRole;;\($cr.metadata.name)"
            '

          kubectl get roles --all-namespaces -o json \
            | jq -r '
              .items[]
              | . as $r
              | select(
                  any(.rules[]?;
                    (
                      (.apiGroups // []) | index("") != null and
                      (.resources // []) | index("serviceaccounts/token") != null and
                      (.verbs // []) | index("create") != null
                    ) or (
                      (.apiGroups // []) | index("") != null and
                      (.resources // []) | index("serviceaccounts") != null and
                      (.verbs // []) | index("create") != null and
                      (.resourceNames // []) != null and
                      ((.resourceNames[] | ascii_downcase) | contains("token"))
                    )
                  )
                )
              | "Role;\($r.metadata.namespace);\($r.metadata.name)"
            '
        } | sort > "$TMP_ROLES"

        # Collect all RoleBindings and ClusterRoleBindings
        kubectl get rolebindings --all-namespaces -o json > "$TMP_BINDS.role"
        kubectl get clusterrolebindings -o json > "$TMP_BINDS.clusterrole"

        echo "KIND;BINDING_NAMESPACE;BINDING_NAME;ROLE_KIND;ROLE_NAMESPACE;ROLE_NAME;SUBJECT_KIND;SUBJECT_NAMESPACE;SUBJECT_NAME"

        # ClusterRoleBindings
        jq -r --argfile roles "$TMP_ROLES" '
          def has_role($kind; $ns; $name):
            ($roles[] | select(.[0]==$kind and .[1]==$ns and .[2]==$name)) | length > 0;
          .items[]
          | . as $b
          | .roleRef as $r
          | select(
              has_role($r.kind; ""; $r.name)
            )
          | (.subjects // [{kind:"User",name:"<no-subjects>"}])[]
          | [
              "ClusterRoleBinding",
              "",                       # binding namespace
              $b.metadata.name,
              $r.kind,
              "",                       # role namespace
              $r.name,
              .kind,
              (.namespace // ""),
              .name
            ] | @csv
        ' "$TMP_BINDS.clusterrole" | sed 's/^"\|"$//g;s/","/;/g'

        # RoleBindings
        jq -r --argfile roles "$TMP_ROLES" '
          def has_role($kind; $ns; $name):
            ($roles[] | select(.[0]==$kind and .[1]==$ns and .[2]==$name)) | length > 0;
          .items[]
          | . as $b
          | .roleRef as $r
          | select(
              has_role($r.kind; $b.metadata.namespace; $r.name)
            )
          | (.subjects // [{kind:"User",name:"<no-subjects>"}])[]
          | [
              "RoleBinding",
              $b.metadata.namespace,
              $b.metadata.name,
              $r.kind,
              $b.metadata.namespace,
              $r.name,
              .kind,
              (.namespace // ""),
              .name
            ] | @csv
        ' "$TMP_BINDS.role" | sed 's/^"\|"$//g;s/","/;/g'
        echo

        cat <<'EOF'
        How to interpret the binding output:

        - Each line is:
          KIND;BINDING_NAMESPACE;BINDING_NAME;ROLE_KIND;ROLE_NAMESPACE;ROLE_NAME;SUBJECT_KIND;SUBJECT_NAMESPACE;SUBJECT_NAME

        - Every line represents a subject that can create service account tokens.

        What indicates a potential problem:

        1) Any subject that is not a tightly controlled administrator identity, for example:
           - SUBJECT_KIND=User or Group representing broad or external users.
           - SUBJECT_KIND=ServiceAccount used by application workloads.
           - Wildcard or broad groups (e.g., system:authenticated, system:serviceaccounts).

        2) Any ClusterRoleBinding that grants this power cluster-wide to non-admin subjects.

        3) Roles/ClusterRoles where:
           - VERBS contains "*" or includes "create" alongside very broad permissions.
           - RESOURCES includes "*" or many unrelated resources.

        Next steps (manual review required):

        - For any suspect line, decide whether that subject truly needs to mint service account tokens.
        - If not required, update or delete the corresponding Role/ClusterRole or Binding to remove create on serviceaccounts/token.
        - Re-run this script after changes to confirm only trusted administrator identities remain.
        EOF
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
