> ## 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. **List all ServiceAccount token Secrets**
           * Run on: any machine with kubectl access
           * Command:
             ```sh theme={null}
             kubectl get secrets -A --field-selector type=kubernetes.io/service-account-token -o wide
             ```
           * Review which namespaces and ServiceAccounts still have token Secrets and note any in application namespaces (not just `kube-system`).

        2. **Identify who uses each token Secret and how**
           * For a specific Secret, inspect it (do not paste token contents into tickets/logs):
             ```sh theme={null}
             kubectl describe secret -n <namespace> <secret-name>
             ```
           * Search for references to that Secret name in:
             * Pod specs:
               ```sh theme={null}
               kubectl get pods -n <namespace> -o yaml | grep -C3 "<secret-name>" || true
               ```
             * Deployments/StatefulSets/DaemonSets/Jobs/CronJobs:
               ```sh theme={null}
               kubectl get deploy,sts,ds,job,cronjob -n <namespace> -o yaml | grep -C3 "<secret-name>" || true
               ```
           * Also search your app/IaC repos for the Secret name or `service-account-token` usage.

        3. **Decide whether the workload can switch to projected ServiceAccount tokens**\
           For each workload that uses a ServiceAccount token Secret:
           * Confirm if it really needs to call the Kubernetes API from inside the pod. If not, plan to remove the token mount entirely.
           * If it does:
             * Check if the code or sidecar can read the token from:
               * The default projected token mount (`/var/run/secrets/kubernetes.io/serviceaccount/token`), or
               * An explicit projected ServiceAccount token volume with `audience` and `expirationSeconds`.
             * If the current configuration is hard‑wiring a Secret volume/volumeMount, plan to migrate it to a projected ServiceAccount token volume.

        4. **Reconfigure manifests to stop using long‑lived token Secrets**
           * For each affected ServiceAccount:
             * If the only reason it exists is to auto‑provision a legacy token Secret, ensure there are no pods mounting that Secret, then plan to delete the Secret.
           * For each affected workload manifest (e.g., Deployment):
             * Remove explicit `secret` volume entries and `volumeMounts` that point at `kubernetes.io/service-account-token` Secrets.
             * Ensure `spec.serviceAccountName` is set to the intended ServiceAccount.
             * If the app cannot use the default mount, define a projected token volume similar to:
               ```yaml theme={null}
               volumes:
                 - name: sa-token
                   projected:
                     sources:
                       - serviceAccountToken:
                           path: token
                           audience: "<audience-for-this-app>"
                           expirationSeconds: 3600
               volumeMounts:
                 - name: sa-token
                   mountPath: /var/run/myapp
                   readOnly: true
               ```
           * Apply changes:
             ```sh theme={null}
             kubectl apply -f <updated-manifest>.yaml
             ```

        5. **Remove unused ServiceAccount token Secrets after migration**
           * For each previously identified Secret, confirm no pod is mounting it:
             ```sh theme={null}
             kubectl get pods -n <namespace> -o yaml | grep -C3 "<secret-name>" || true
             ```
           * When you are sure it is unused, delete it:
             ```sh theme={null}
             kubectl delete secret -n <namespace> <secret-name>
             ```
           * In AKS, new long‑lived ServiceAccount token Secrets should not be auto‑created by recent Kubernetes versions; if you see new ones appear, investigate controllers or tools that may be creating them manually.

        6. **Verify that only projected tokens are in use**
           * Re-run the review command and confirm that no application namespaces rely on `kubernetes.io/service-account-token` Secrets:
             ```sh theme={null}
             kubectl get secrets -A --field-selector type=kubernetes.io/service-account-token -o wide
             ```
           * Spot‑check updated pods to ensure they are using projected tokens (either the default mount or your projected volume) and that no `secret`‑type volumes for ServiceAccount tokens remain.
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 1. List all long-lived ServiceAccount token Secrets
        # Run on: any machine with kubectl access
        kubectl get secrets -A --field-selector type=kubernetes.io/service-account-token \
          -o wide
        ```

        **What to look for (potential problems):**

        * Many Secrets of type `kubernetes.io/service-account-token` in namespaces that host apps using modern SDKs or that already use projected tokens.
        * Namespaces where workloads are short‑lived or highly sensitive but still have multiple legacy token Secrets.
        * ServiceAccounts used by critical apps that also have a corresponding token Secret and no sign of projected token use in the Pods.

        ***

        ```bash theme={null}
        # 2. For a specific Secret, inspect annotations and age
        # Replace NAMESPACE and SECRET_NAME with values from the previous output
        kubectl get secret SECRET_NAME -n NAMESPACE -o yaml
        ```

        **What to look for:**

        * `type: kubernetes.io/service-account-token`
        * Very old `metadata.creationTimestamp` (indicates a long‑lived token).
        * Annotation `kubernetes.io/service-account.name` pointing to a ServiceAccount still in use.
        * No automated process rotating or deleting these Secrets.

        ***

        ```bash theme={null}
        # 3. Identify whether active Pods mount these Secrets directly
        kubectl get pods -A -o jsonpath='{range .items[*]}{@.metadata.namespace}{" "}{@.metadata.name}{"\n"}{range @.spec.volumes[*]}{"  "}{@.name}{" => "}{@.secret.secretName}{"\n"}{end}{"\n"}{end}' 2>/dev/null | grep -v " => <no value>"
        ```

        **What to look for:**

        * Pods that reference Secrets of type `kubernetes.io/service-account-token` as volumes.
        * These indicate applications may be using long‑lived token Secrets rather than projected tokens.

        ***

        ```bash theme={null}
        # 4. For a specific Pod, confirm whether it uses projected ServiceAccount tokens
        kubectl get pod POD_NAME -n NAMESPACE -o yaml
        ```

        **What to look for (good vs. problematic):**

        * **Good (preferred):**
          * Volumes of type `projected` with a `serviceAccountToken` source:
            ```yaml theme={null}
            volumes:
            - name: sa-token
              projected:
                sources:
                - serviceAccountToken:
                    path: token
                    audience: <some-audience>
                    expirationSeconds: <short duration>
            ```
        * **Problematic (legacy):**
          * Volumes of type `secret` that reference a ServiceAccount token Secret:
            ```yaml theme={null}
            volumes:
            - name: sa-token
              secret:
                secretName: <serviceaccount-name>-token-xxxxx
            ```
          * Containers reading the token from `/var/run/secrets/kubernetes.io/serviceaccount/token` directly with no projected token volume.

        ***

        ```bash theme={null}
        # 5. Inspect the ServiceAccount to see how tokens are being used
        kubectl get serviceaccount SA_NAME -n NAMESPACE -o yaml
        ```

        **What to look for:**

        * `secrets:` list containing entries like `<serviceaccount-name>-token-xxxxx` (indicates classic token Secret exists).
        * Whether Pods using this ServiceAccount (from step 4) mount that Secret directly instead of using a projected `serviceAccountToken` volume.

        ***

        ```bash theme={null}
        # 6. (AKS-specific context) Check for legacy ServiceAccount token use across a namespace
        # Helpful to focus review on application namespaces
        kubectl get secrets -n NAMESPACE --field-selector type=kubernetes.io/service-account-token \
          -o jsonpath='{range .items[*]}{@.metadata.name}{" "}{@.metadata.creationTimestamp}{"\n"}{end}'
        ```

        **What to look for:**

        * Very old tokens in application namespaces still attached to active ServiceAccounts.
        * Patterns where every ServiceAccount automatically gets and keeps a token Secret, while Pods are not using projected volumes. This suggests refactoring to TokenRequest/projected tokens is needed.

        These kubectl commands only surface current usage; a human must decide per application whether and how to migrate from long‑lived ServiceAccount token Secrets to projected tokens with audience and expiry.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Report legacy ServiceAccount token Secrets and pods using projected tokens in an AKS cluster.
        # Run on: any machine with kubectl access and current context set to the AKS cluster.

        set -euo pipefail

        echo "=== 1) Legacy ServiceAccount token Secrets (type=kubernetes.io/service-account-token) ==="
        echo
        kubectl get secrets -A \
          --field-selector type=kubernetes.io/service-account-token \
          -o json | jq -r '
            .items[]? |
            [
              .metadata.namespace,
              .metadata.name,
              .metadata.annotations."kubernetes.io/service-account.name",
              .metadata.annotations."kubernetes.io/service-account.uid",
              (.metadata.creationTimestamp // "unknown")
            ] | @tsv
          ' | column -t || echo "No legacy ServiceAccount token Secrets found."

        cat <<'EOF'

        Interpretation:
        - Any row above represents a long-lived ServiceAccount token Secret.
        - These are what you should be avoiding in favor of projected (TokenRequest) tokens.
        - Pay special attention to:
          * Very old creationTimestamp values (indicates long-lived tokens).
          * ServiceAccounts that should not need cluster access, or have broad RBAC.

        EOF

        echo "=== 2) Pods using projected ServiceAccount tokens (serviceAccountToken projection) ==="
        echo
        kubectl get pods -A -o json | jq -r '
          .items[]
          | . as $pod
          | [
              $pod.metadata.namespace,
              $pod.metadata.name,
              (
                [$pod.spec.volumes[]? 
                  | select(.projected?.sources[]? .serviceAccountToken != null)
                  | .name
                ] | unique | join(",")
              )
            ]
          | select(.[2] != "")
          | @tsv
        ' | column -t || echo "No pods with projected ServiceAccountToken volumes found."

        cat <<'EOF'

        Interpretation:
        - Rows above show pods that are already using projected ServiceAccount tokens.
        - Namespaces/pods NOT listed here but that use ServiceAccounts likely still rely on:
          * The legacy auto-mounted Secret token volume, or
          * Out-of-cluster storage of long-lived ServiceAccount tokens.

        EOF

        echo "=== 3) ServiceAccounts with legacy token auto-mount still enabled (serviceAccountToken secrets present) ==="
        echo
        kubectl get serviceaccounts -A -o json | jq -r '
          .items[]
          | . as $sa
          | [
              $sa.metadata.namespace,
              $sa.metadata.name,
              (
                [$sa.secrets[]?.name] | join(",")
              )
            ]
          | select(.[2] != "")
          | @tsv
        ' | column -t || echo "No ServiceAccounts with attached Secrets recorded."

        cat <<'EOF'

        Interpretation:
        - Any ServiceAccount listed above has Secrets attached; many of these may be
          kubernetes.io/service-account-token Secrets.
        - Cross-check the secret names here against the legacy token list in section (1).
        - ServiceAccounts with attached token Secrets are candidates for migration to
          projected TokenRequest-based tokens with audience and expiry.

        EOF

        echo "=== Review guidance ==="
        cat <<'EOF'
        A configuration is potentially problematic when:
        - A ServiceAccount has one or more kubernetes.io/service-account-token Secrets, AND
        - Workloads using that ServiceAccount are not using projected serviceAccountToken volumes
          (i.e., they are absent from section (2)).

        Use this report to:
        - Identify which ServiceAccounts and namespaces still depend on legacy token Secrets.
        - Prioritize those for migration to projected tokens with explicit audiences and expiries.
        Note: This script only surfaces current state; migration to projected tokens must be
        planned and applied manually per application and ServiceAccount.

        EOF
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
