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

### More Info:

Broad get, list and watch access to secret objects allows credentials to be exfiltrated. Restrict these permissions to only the workloads and users that require them.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS OKE

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **List all ClusterRoles with secret access**
           * Run on: any machine with `kubectl` access
           * Command:
             ```sh theme={null}
             kubectl get clusterroles -o json \
               | jq -r '.items[]
                 | select(.rules[]? 
                   | (.resources[]? == "secrets") 
                   and ([.verbs[]?] | inside(["get","list","watch"])))
                 | .metadata.name' | sort -u
             ```
           * This identifies ClusterRoles that can `get`, `list`, or `watch` secrets.

        2. **Inspect each flagged ClusterRole’s secret permissions in detail**
           * Run on: any machine with `kubectl` access
           * For each ClusterRole name from step 1:
             ```sh theme={null}
             kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml \
               | sed -n '/rules:/,$p'
             ```
           * Review which `secrets` resources and verbs are granted, and whether any `*` verbs or `*` resources are present.

        3. **Map ClusterRoles to subjects via ClusterRoleBindings**
           * Run on: any machine with `kubectl` access
           * For each ClusterRole from step 1:
             ```sh theme={null}
             kubectl get clusterrolebindings -o json \
               | jq -r --arg cr "<CLUSTERROLE_NAME>" '
                 .items[]
                 | select(.roleRef.kind == "ClusterRole" and .roleRef.name == $cr)
                 | .metadata.name as $rb
                 | .subjects[]? 
                 | "\($rb)\t\(.kind)\t\(.name)\t\(.namespace // "-")"'
             ```
           * Use this to determine which users, groups, or service accounts receive secret access and in which namespaces they operate.

        4. **Decide least-privilege requirements per subject**
           * For each subject from step 3, answer:
             * Does it truly need `get`/`list`/`watch` on `secrets`?
             * If yes, is cluster-wide access required, or only specific namespaces?
             * Can access be narrowed to specific secret names (via `resourceNames`) or limited to `get` only instead of `list`/`watch`?
           * Document which permissions are justified and which should be removed or scope-reduced.

        5. **Adjust ClusterRoles and bindings to enforce least privilege**
           * Run on: any machine with `kubectl` access
           * To remove or narrow secret access from a ClusterRole:
             ```sh theme={null}
             kubectl edit clusterrole <CLUSTERROLE_NAME>
             ```
             * In the editor, either:
               * Remove the entire rule that includes `secrets`, or
               * Remove `secrets` from `.rules[].resources`, or
               * Remove unnecessary verbs (`list`, `watch`, or `*`) from `.rules[].verbs`, or
               * Add `.resourceNames` to restrict to specific secret names if appropriate.
           * If cluster-scoped access is not needed, create namespace-scoped `Role`s and `RoleBinding`s instead and then remove the corresponding ClusterRoleBinding(s).

        6. **Re-verify effective secret access after changes**
           * Run on: any machine with `kubectl` access
           * Re-run the detection query:
             ```sh theme={null}
             kubectl get clusterroles -o json \
               | jq -r '.items[]
                 | select(.rules[]? 
                   | (.resources[]? == "secrets") 
                   and ([.verbs[]?] | inside(["get","list","watch"])))
                 | .metadata.name' | sort -u
             ```
           * Confirm that only the ClusterRoles you explicitly decided to keep with secret access remain, and that their definitions match your least-privilege decisions from step 4.
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 1. List all ClusterRoles that can access secrets
        # Run on: any machine with kubectl access
        kubectl get clusterroles -o json | jq -r '
          .items[]
          | select(
              .rules[]
              | select(
                  (.resources[]? == "secrets")
                  and ((.verbs[]? | IN("get","list","watch")) == true)
                )
            )
          | .metadata.name
        ' | sort -u
        ```

        Output meaning:

        * Any ClusterRole name printed here has at least one rule granting `get`, `list`, or `watch` on the `secrets` resource.
        * Each such role must be manually reviewed to decide if that level of access is truly required.

        ***

        ```bash theme={null}
        # 2. Inspect the full definition of each such ClusterRole
        # Replace <clusterrole-name> with a name from the previous output
        kubectl get clusterrole <clusterrole-name> -o yaml
        ```

        What to look for:

        * Problematic examples:
          * `resources: ["*"]` combined with `verbs: ["*"]` or including `get`, `list`, `watch`.
          * `resources: ["secrets"]` with `verbs` including any of `get`, `list`, `watch` where the role is used by broad subjects (e.g., all service accounts in a namespace).
        * Safer patterns:
          * Roles scoped to specific non-secret resources only.
          * Roles that only use non-read verbs for secrets (e.g., rare cases of `create` or `update` for a tightly scoped automation, though these also warrant review).

        ***

        ```bash theme={null}
        # 3. Find which subjects are bound to each ClusterRole
        # Run for each ClusterRole of interest from step 1
        kubectl get clusterrolebindings -o json | jq -r '
          .items[]
          | select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<clusterrole-name>")
          | .metadata.name + " " +
            (.subjects // [] | map(.kind + ":" + .namespace + ":" + .name) | join(","))
        '
        ```

        Output meaning:

        * Prints: `<clusterrolebinding-name> <subject-kind:namespace:name,...>`
        * Problematic indicators:
          * Bindings to `system:authenticated`, `system:serviceaccounts`, or `system:serviceaccounts:<namespace>` when the role has secret read access.
          * Bindings to many generic service accounts that don’t clearly need secret read access.

        ***

        ```bash theme={null}
        # 4. Also review namespaced Roles that can read secrets
        kubectl get roles --all-namespaces -o json | jq -r '
          .items[]
          | select(
              .rules[]
              | select(
                  (.resources[]? == "secrets")
                  and ((.verbs[]? | IN("get","list","watch")) == true)
                )
            )
          | .metadata.namespace + "/" + .metadata.name
        ' | sort
        ```

        Then inspect each Role:

        ```bash theme={null}
        kubectl get role -n <namespace> <role-name> -o yaml
        ```

        What to look for:

        * Roles with secret read verbs in namespaces where many workloads run, especially paired with broad RoleBindings.

        ***

        ```bash theme={null}
        # 5. See which service accounts and users get these Roles
        # For a specific Role in a namespace:
        kubectl get rolebindings -n <namespace> -o json | jq -r '
          .items[]
          | select(.roleRef.kind == "Role" and .roleRef.name == "<role-name>")
          | .metadata.name + " " +
            (.subjects // [] | map(.kind + ":" + .namespace + ":" + .name) | join(","))
        '
        ```

        Problem indicators:

        * RoleBindings that attach secret-reading Roles to:
          * Default service accounts (e.g., `ServiceAccount:<namespace>:default`).
          * Large groups or all service accounts in a namespace.

        ***

        ```bash theme={null}
        # 6. (Optional) Quick summary: who can read secrets cluster-wide
        kubectl auth can-i \
          --list \
          --all-namespaces \
          2>/dev/null | grep -E '\bsecrets\b' | grep -E '\b(get|list|watch)\b'
        ```

        Output meaning:

        * Shows effective permissions; lines mentioning `secrets` with `get`, `list`, or `watch` indicate that the associated user/group/service account can read secrets.
        * Use this as a cross-check to focus your manual review.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Report broad access to Secrets across the cluster
        # Run on: any machine with kubectl access and current context set

        set -euo pipefail

        echo "=== ClusterRoles and Roles with get/list/watch on secrets ==="
        echo

        # 1) Show all ClusterRoles that can get/list/watch secrets
        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | (.resources // []) | index("secrets")
                and (
                  (.verbs // []) | index("get")
                  or (.verbs // []) | index("list")
                  or (.verbs // []) | index("watch")
                )
              )
            | .metadata.name
          ' | sort -u | while read -r cr; do
              echo "ClusterRole: ${cr}"
              kubectl get clusterrole "${cr}" -o json \
                | jq '
                  {
                    name: .metadata.name,
                    rules: (
                      .rules
                      | map(
                          select(
                            (.resources // []) | index("secrets")
                            and (
                              (.verbs // []) | index("get")
                              or (.verbs // []) | index("list")
                              or (.verbs // []) | index("watch")
                            )
                          )
                        )
                    )
                  }'
              echo
            done

        echo "=== Roles with get/list/watch on secrets (all namespaces) ==="
        echo

        kubectl get roles --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | (.resources // []) | index("secrets")
                and (
                  (.verbs // []) | index("get")
                  or (.verbs // []) | index("list")
                  or (.verbs // []) | index("watch")
                )
              )
            | "\(.metadata.namespace) \(.metadata.name)"
          ' | sort -u | while read -r ns role; do
              echo "Role: ${role} (namespace: ${ns})"
              kubectl get role "${role}" -n "${ns}" -o json \
                | jq '
                  {
                    namespace: .metadata.namespace,
                    name: .metadata.name,
                    rules: (
                      .rules
                      | map(
                          select(
                            (.resources // []) | index("secrets")
                            and (
                              (.verbs // []) | index("get")
                              or (.verbs // []) | index("list")
                              or (.verbs // []) | index("watch")
                            )
                          )
                        )
                    )
                  }'
              echo
            done

        echo "=== ClusterRoleBindings referencing those ClusterRoles ==="
        echo

        # Get list once to avoid recomputation
        problem_clusterroles=$(
          kubectl get clusterroles -o json \
            | jq -r '
              .items[]
              | select(
                  .rules[]
                  | (.resources // []) | index("secrets")
                  and (
                    (.verbs // []) | index("get")
                    or (.verbs // []) | index("list")
                    or (.verbs // []) | index("watch")
                  )
                )
              | .metadata.name
            ' | sort -u
        )

        if [ -n "${problem_clusterroles}" ]; then
          kubectl get clusterrolebindings -o json \
            | jq -r --argjson crs "$(printf '%s\n' ${problem_clusterroles} | jq -R . | jq -s .)" '
                .items[]
                | select(.roleRef.kind == "ClusterRole")
                | select(.roleRef.name as $n | $crs | index($n))
                | .metadata.name
              ' | sort -u | while read -r crb; do
                  echo "ClusterRoleBinding: ${crb}"
                  kubectl get clusterrolebinding "${crb}" -o json \
                    | jq '
                      {
                        name: .metadata.name,
                        roleRef: .roleRef,
                        subjects: (.subjects // [])
                      }'
                  echo
                done
        else
          echo "No ClusterRoles with get/list/watch on secrets found."
        fi

        echo "=== RoleBindings referencing those Roles (by namespace) ==="
        echo

        # Build a temp file of roles-with-secret-access to speed up lookups
        tmp_roles=$(mktemp)
        kubectl get roles --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | (.resources // []) | index("secrets")
                and (
                  (.verbs // []) | index("get")
                  or (.verbs // []) | index("list")
                  or (.verbs // []) | index("watch")
                )
              )
            | "\(.metadata.namespace) \(.metadata.name)"
          ' | sort -u > "${tmp_roles}"

        kubectl get rolebindings --all-namespaces -o json \
          | jq -r '
              .items[]
              | "\(.metadata.namespace) \(.metadata.name) \(.roleRef.kind) \(.roleRef.name)"
            ' \
          | while read -r ns rbkind kind name; do
              if [ "${kind}" = "Role" ]; then
                if grep -q "^${ns} ${name}$" "${tmp_roles}"; then
                  echo "RoleBinding: ${rbkind} (namespace: ${ns})"
                  kubectl get rolebinding "${rbkind}" -n "${ns}" -o json \
                    | jq '
                      {
                        namespace: .metadata.namespace,
                        name: .metadata.name,
                        roleRef: .roleRef,
                        subjects: (.subjects // [])
                      }'
                  echo
                fi
              fi
            done

        rm -f "${tmp_roles}"

        echo "=== INTERPRETATION ==="
        cat <<'EOF'

        Output that indicates potential problems:

        1. ClusterRoles or Roles whose rules include:
           - resources: ["secrets"] (or contains "secrets")
           - verbs includes any of: "get", "list", "watch"
           AND they are:
           - Generic or broad-scope roles (e.g., used cluster-wide, default/admin/view/edit-like roles you created, or names suggesting infrastructure-wide use).
           - Intended for many users/service accounts rather than a single tightly-scoped workload.

        2. ClusterRoleBindings or RoleBindings that attach such roles to:
           - system:authenticated, system:serviceaccounts, or other large groups.
           - Namespaces or service accounts not explicitly needing Secret read access.

        Use this report to:
        - Confirm which subjects (users, groups, service accounts) truly need get/list/watch on secrets.
        - Plan manual tightening: remove or narrow rules/verbs/resources, or split roles so that only specific workloads keep Secret access.

        This script does NOT apply any changes; it only surfaces where broad Secret read permissions exist.
        EOF
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
