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

# Prefer Bound Projected ServiceAccount Tokens Over Secret Tokens

### More Info:

Advisory: avoid long-lived ServiceAccount token Secrets; use projected (TokenRequest) tokens with an audience and expiry instead.

### Risk Level

Informational

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify ServiceAccounts using legacy Secret tokens**
           * On any machine with kubectl access:
             ```bash theme={null}
             kubectl get sa --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}' | sort
             ```
             For each ServiceAccount, list referenced Secrets:
             ```bash theme={null}
             kubectl get sa -A -o json | jq -r '.items[] | select(.secrets != null) | "\(.metadata.namespace) \(.metadata.name) -> \(.secrets[].name)"'
             ```
             Note ServiceAccounts that have `.secrets` populated, especially outside `kube-system` and `default` bootstrap usage.

        2. **Identify long‑lived ServiceAccount token Secrets**
           * On any machine with kubectl access, inspect suspected Secrets:
             ```bash theme={null}
             kubectl get secret -A -o json | jq -r '
               .items[] |
               select(.type == "kubernetes.io/service-account-token") |
               "\(.metadata.namespace) \(.metadata.name) SA=\(.metadata.annotations["kubernetes.io/service-account.name"]) created=\(.metadata.creationTimestamp)"
             ' | sort
             ```
           * Flag tokens that are:
             * Used by applications (non‑system namespaces), and
             * Old (e.g., created weeks/months ago), indicating long‑lived credentials.

        3. **Review workloads and external systems that consume these Secrets**
           * On any machine with kubectl access, find Pods mounting or env‑referencing these Secrets:
             ```bash theme={null}
             kubectl get pods -A -o json | jq -r '
               .items[] as $pod |
               ($pod.spec.volumes[]? | select(.secret != null) | "VOL \($pod.metadata.namespace) \($pod.metadata.name) -> \(.secret.secretName)")
               , ($pod.spec.containers[]? as $c |
                   ($c.env[]? | select(.valueFrom.secretKeyRef != null) |
                     "ENV \($pod.metadata.namespace) \($pod.metadata.name) \($c.name) -> \(.valueFrom.secretKeyRef.name)"))
             ' | sort | uniq
             ```
           * Separately review CI/CD pipelines, external services, or scripts that may read these Secrets via `kubectl` or the API. Determine which integrations can be updated to use projected TokenRequest tokens instead of static Secrets.

        4. **Plan and configure use of projected ServiceAccount tokens**
           * For each workload that currently mounts a ServiceAccount token Secret and can be changed, update its Pod spec (Deployment/StatefulSet/Job, etc.) to use a projected ServiceAccount token volume with audience and expiry, for example:
             ```yaml theme={null}
             volumes:
               - name: sa-token
                 projected:
                   sources:
                     - serviceAccountToken:
                         audience: "your-service-audience"
                         expirationSeconds: 3600
                         path: "token"
             ```
           * Apply the updated manifest from any machine with kubectl access:
             ```bash theme={null}
             kubectl apply -f <updated-manifest>.yaml
             ```
           * Ensure the consuming application or external system is updated to read the token from the projected volume path and validate the audience and expiry.

        5. **Decommission unnecessary long‑lived ServiceAccount token Secrets**
           * After verifying that workloads/external systems function correctly with projected tokens, remove their dependency on the old Secrets:
             * Update ServiceAccount manifests to omit explicit `secrets:` entries unless strictly required.
               ```bash theme={null}
               kubectl edit sa <sa-name> -n <namespace>
               ```
               Remove any `secrets:` list entries that correspond to long‑lived token Secrets.
             * Optionally delete unused legacy token Secrets (only after confirming nothing consumes them):
               ```bash theme={null}
               kubectl delete secret <secret-name> -n <namespace>
               ```

        6. **Verify reduced reliance on long‑lived ServiceAccount token Secrets**
           * On any machine with kubectl access, re‑list ServiceAccounts and Secrets:
             ```bash theme={null}
             kubectl get sa -A -o json | jq -r '[
               .items[] |
               select(.secrets != null) |
               "\(.metadata.namespace) \(.metadata.name) -> \(.secrets[].name)"
             ] | sort[]'
             ```
             ```bash theme={null}
             kubectl get secret -A -o json | jq -r '
               .items[] |
               select(.type == "kubernetes.io/service-account-token") |
               "\(.metadata.namespace) \(.metadata.name) SA=\(.metadata.annotations["kubernetes.io/service-account.name"]) created=\(.metadata.creationTimestamp)"
             ' | sort
             ```
           * Confirm that:
             * Only strictly necessary ServiceAccounts still reference token Secrets.
             * Most application workloads have moved to using projected ServiceAccount tokens with bounded audience and expiry.
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 1) List all ServiceAccounts and see if any are auto-mounting tokens
        # Run on: any machine with kubectl access
        kubectl get sa --all-namespaces -o wide
        ```

        Problem indication:

        * `secrets` column populated for many ServiceAccounts (older clusters) suggests token Secrets may exist.
        * Widespread use of default ServiceAccounts (no custom SAs) often means many pods share the same long‑lived token.

        ***

        ```bash theme={null}
        # 2) Inspect one ServiceAccount in detail (replace NAMESPACE and NAME)
        kubectl get sa -n NAMESPACE NAME -o yaml
        ```

        Problem indication:

        * `secrets:` list contains items named like `NAME-token-xxxxx`.
        * `automountServiceAccountToken: true` (or missing and cluster default is true) means all pods using this SA will receive a token volume by default.
        * No explicit design about which workloads should receive a token.

        ***

        ```bash theme={null}
        # 3) Find Secrets that are ServiceAccount tokens (legacy style)
        kubectl get secrets --all-namespaces -o jsonpath='{range .items[?(@.type=="kubernetes.io/service-account-token")]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}'
        ```

        Problem indication:

        * Large numbers of `kubernetes.io/service-account-token` Secrets indicate long‑lived tokens are broadly available.
        * If these Secrets are mounted into pods (see next step), they are likely being used instead of short‑lived projected tokens.

        ***

        ```bash theme={null}
        # 4) Check pods for mounted SA-token Secrets (legacy volume mount style)
        kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{range .spec.volumes[*]}{"  volume: "}{.name}{" type: "}{.projected?"projected":.secret?"secret":"other"}{"\n"}{end}{"---\n"}{end}'
        ```

        Problem indication:

        * Volumes of `type: secret` with a `secretName` matching a `*-token-xxxxx` Secret show pods mounting long‑lived token Secrets.
        * Very few or no `type: projected` volumes configured for tokens suggests the cluster is not using projected ServiceAccount tokens.

        (For a focused check on a single pod:)

        ```bash theme={null}
        kubectl get pod -n NAMESPACE PODNAME -o yaml
        ```

        Look under:

        * `.spec.volumes` for `secret:` entries with names like `*-token-xxxxx`.
        * `.spec.serviceAccountName` and `.spec.automountServiceAccountToken`.

        ***

        ```bash theme={null}
        # 5) Check whether pods are using automounted default tokens vs. explicit projected tokens
        kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{" SA:"}{.spec.serviceAccountName}{" autoToken:"}{.spec.automountServiceAccountToken}{"\n"}{end}'
        ```

        Problem indication:

        * Many pods with `automountServiceAccountToken:true` (or null while the SA/namespace/cluster default is true) but without any explicit projected token volume configuration indicate reliance on legacy auto‑mounted tokens.

        ***

        ```bash theme={null}
        # 6) Identify ServiceAccounts that are widely used by pods (to prioritize review)
        kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.spec.serviceAccountName}{"\n"}{end}' | sort | uniq -c | sort -nr
        ```

        Problem indication:

        * ServiceAccounts with a high pod count are high‑value targets; if they rely on long‑lived token Secrets, the risk is elevated.

        ***

        ```bash theme={null}
        # 7) (Optional) Inspect a projected token volume configuration for comparison
        # This is to understand what "good" can look like; replace NAMESPACE/POD
        kubectl get pod -n NAMESPACE PODNAME -o yaml
        ```

        Positive indication (for reference):

        * Under `.spec.volumes`, a `projected:` volume with a `serviceAccountToken:` source that specifies:
          * `audience: ...`
          * `expirationSeconds: ...`
        * This shows use of bounded, short‑lived tokens rather than legacy Secrets.

        ***

        Human review guidance (what to decide from this data):

        * If you see many `kubernetes.io/service-account-token` Secrets mounted into pods as `secret` volumes, you likely rely on long‑lived tokens and should plan to:
          * Disable automatic token Secret creation (cluster/SA level) going forward.
          * Stop mounting legacy token Secrets into workloads and instead configure projected `serviceAccountToken` volumes with appropriate audiences and short expiries.
        * If most workloads rely only on default auto‑mounted tokens and no projected token volumes are defined, review which workloads truly need a ServiceAccount token and which can have `automountServiceAccountToken: false`.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Report ServiceAccounts that use long-lived Secret-based tokens
        # versus those configured for projected/TokenRequest-style tokens.
        #
        # Run on: any machine with kubectl access and current context set.

        set -euo pipefail

        echo "Collecting ServiceAccount and Secret token usage across the cluster..."
        echo

        # 1) List all ServiceAccounts and whether they are configured to automount any token
        echo "=== ServiceAccounts and automountServiceAccountToken settings ==="
        kubectl get sa --all-namespaces -o json \
          | jq -r '
            .items[]
            | {
                ns: .metadata.namespace,
                name: .metadata.name,
                sa_automount: ( .automountServiceAccountToken // "unset" ),
                pod_default_automount: (
                  # check namespace-level default for pods (if set via namespace annotation)
                  .metadata.namespace as $ns
                  | "" # placeholder, see note below
                ),
                secrets: ( .secrets // [] | map(.name) )
              }
            | "\(.ns)\t\(.name)\t\(.sa_automount)\t\(.secrets | join(","))"
          ' \
          | awk 'BEGIN {print "NAMESPACE\tSERVICEACCOUNT\tSA_AUTOMOUNT_TOKEN\tBOUND_SECRETS"}1'
        echo
        echo "NOTE:"
        echo "- SA_AUTOMOUNT_TOKEN:"
        echo "    true  = pod(s) using this SA will, by default, mount a token volume"
        echo "    false = pod(s) using this SA will NOT auto-mount a token volume"
        echo "    unset = falls back to pod spec or cluster default behavior"
        echo "- BOUND_SECRETS lists Secret objects referenced on the ServiceAccount."
        echo "  These are typically long-lived ServiceAccount token Secrets when of type:"
        echo "    kubernetes.io/service-account-token"
        echo

        # 2) List all Secrets that are ServiceAccount tokens and how many SAs reference them
        echo "=== Secrets of type kubernetes.io/service-account-token ==="
        kubectl get secrets --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(.type == "kubernetes.io/service-account-token")
            | {
                ns: .metadata.namespace,
                name: .metadata.name,
                sa_name: .metadata.annotations["kubernetes.io/service-account.name"],
                sa_uid: .metadata.annotations["kubernetes.io/service-account.uid"]
              }
            | "\(.ns)\t\(.name)\t\(.sa_name)\t\(.sa_uid)"
          ' \
          | awk 'BEGIN {print "NAMESPACE\tSECRET\tBOUND_SERVICEACCOUNT\tSERVICEACCOUNT_UID"}1'
        echo
        echo "INTERPRETATION:"
        echo "- Each row is a long-lived Secret-style token bound to a ServiceAccount."
        echo "- Large numbers of such Secrets for actively used ServiceAccounts"
        echo "  suggest reliance on long-lived tokens instead of short-lived projected tokens."
        echo

        # 3) Identify Pods that are mounting Secret-based SA tokens as volumes
        echo "=== Pods that have a Secret volume of type ServiceAccount token ==="
        kubectl get pods --all-namespaces -o json \
          | jq -r '
            .items[]
            | . as $pod
            | (
                .spec.volumes // []
                | map(
                    select(.secret != null)
                    | {
                        pod_ns: $pod.metadata.namespace,
                        pod_name: $pod.metadata.name,
                        sa_name: ($pod.spec.serviceAccountName // "default"),
                        volume_name: .name,
                        secret_name: .secret.secretName
                      }
                  )
              )
            | .[]
            | "\(.pod_ns)\t\(.pod_name)\t\(.sa_name)\t\(.volume_name)\t\(.secret_name)"
          ' 2>/dev/null \
          | awk 'BEGIN {print "NAMESPACE\tPOD\tSERVICEACCOUNT\tVOLUME_NAME\tSECRET_NAME"}1' \
          || echo "No pods with Secret volumes found (or jq error)."
        echo
        echo "INTERPRETATION:"
        echo "- Pods listed here explicitly mount Secrets as volumes."
        echo "- If SECRET_NAME refers to a ServiceAccount token Secret"
        echo "  (see list above), then that pod is using a long-lived token Secret."
        echo

        # 4) OPTIONAL: Spot ServiceAccounts that appear to rely on Secrets AND allow automount
        echo "=== ServiceAccounts that both have token Secrets and allow automount ==="
        echo "(These are higher-likelihood candidates for review/migration to projected tokens.)"
        kubectl get sa --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(
                # SA is not explicitly disabling automount
                (.automountServiceAccountToken != false)
              )
            | select(
                # Has at least one bound secret
                (.secrets // [] | length) > 0
              )
            | {
                ns: .metadata.namespace,
                name: .metadata.name,
                sa_automount: ( .automountServiceAccountToken // "unset" ),
                secrets: ( .secrets // [] | map(.name) )
              }
            | "\(.ns)\t\(.name)\t\(.sa_automount)\t\(.secrets | join(","))"
          ' \
          | awk 'BEGIN {print "NAMESPACE\tSERVICEACCOUNT\tSA_AUTOMOUNT_TOKEN\tBOUND_SECRETS"}1'
        echo
        echo "INTERPRETATION (high-level):"
        echo "- Rows above are priority review targets."
        echo "- Problematic patterns to focus on:"
        echo "  * ServiceAccounts with SA_AUTOMOUNT_TOKEN=true (or unset) AND many token Secrets."
        echo "  * Workloads (Pods/Deployments/etc.) that mount these Secrets as volumes."
        echo
        echo "WHAT INDICATES A PROBLEM:"
        echo "- Heavy use of Secrets of type kubernetes.io/service-account-token,"
        echo "  especially for application ServiceAccounts (non-system namespaces)."
        echo "- ServiceAccounts used by long-running workloads that:"
        echo "  * Have SA_AUTOMOUNT_TOKEN=true or unset; AND"
        echo "  * Have bound ServiceAccount token Secrets; AND/OR"
        echo "  * Are referenced by Pods mounting those Secrets explicitly."
        echo
        echo "NEXT STEPS (MANUAL):"
        echo "- For flagged ServiceAccounts/workloads, plan to migrate to projected"
        echo "  ServiceAccount tokens (TokenRequest API), with explicit audience and expiry."
        echo "- There is no one-shot automated fix; review each ServiceAccount/workload"
        echo "  to understand how the token is consumed before changing it."
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
