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

Roles that grant get, list, or watch access to secrets expose sensitive data to a wider set of subjects. Restrict such access to only those workloads that require it.

### 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. **List Roles with secret access** (run on any machine with kubectl access)
           ```bash theme={null}
           kubectl get roles --all-namespaces -o json | jq -r '
             .items[]
             | select(.rules[]?
               | (.resources[]? == "secrets")
               and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
             )
             | "\(.metadata.namespace) \(.metadata.name)"'
           ```

        2. **Review which subjects use each Role** (any kubectl machine)\
           For each `<NAMESPACE> <ROLE_NAME>` from step 1, list RoleBindings:
           ```bash theme={null}
           kubectl get rolebindings -n <NAMESPACE> -o json | jq -r '
             .items[]
             | select(.roleRef.kind=="Role" and .roleRef.name=="<ROLE_NAME>")
             | .metadata.name as $rb
             | .subjects[]
             | "\($rb) \(.kind) \(.namespace // "-") \(.name)"'
           ```
           Decide which workloads or users truly require `get`, `list`, or `watch` on `secrets`.

        3. **Inspect the Role rules before changing them** (any kubectl machine)
           ```bash theme={null}
           kubectl get role <ROLE_NAME> -n <NAMESPACE> -o yaml
           ```
           Confirm which rules grant access to `secrets` and whether they are needed (for example, an in‑namespace controller vs. general users).

        4. **Edit the Role to remove unnecessary secret privileges** (any kubectl machine)\
           For each Role where secret access is not strictly required, remove `get`, `list`, and/or `watch` on `secrets`:
           ```bash theme={null}
           kubectl edit role <ROLE_NAME> -n <NAMESPACE>
           ```
           In the editor:
           * Locate any `rules` entry where `resources` includes `secrets`.
           * Remove `get`, `list`, and `watch` from `verbs` (or, if no verbs remain, delete that entire `rules` item).
           * Save and exit to apply.

        5. **(Optional) Split roles for least privilege** (any kubectl machine)\
           If only some subjects need secret access:
           * Clone the original Role, keeping only secret‑related rules:
             ```bash theme={null}
             kubectl get role <ROLE_NAME> -n <NAMESPACE> -o yaml > /tmp/<ROLE_NAME>-with-secrets.yaml
             ```
           * Edit `/tmp/<ROLE_NAME>-with-secrets.yaml`:
             * Change `metadata.name` to something like `<ROLE_NAME>-secrets`.
             * Remove all non‑secret `rules`.
             ```bash theme={null}
             kubectl apply -f /tmp/<ROLE_NAME>-with-secrets.yaml
             ```
           * Update RoleBindings so only the specific workloads/users that truly require secret access bind to `<ROLE_NAME>-secrets`, and other subjects bind to a role without secret verbs:
             ```bash theme={null}
             kubectl edit rolebinding <ROLEBINDING_NAME> -n <NAMESPACE>
             ```

        6. **Verify no unintended Roles retain secrets access** (any kubectl machine)
           ```bash theme={null}
           count=$(kubectl get roles --all-namespaces -o json | jq '
             .items[]
             | select(.rules[]?
               | (.resources[]? == "secrets")
               and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
             )' | wc -l)

           echo "SECRETS_ACCESS_FOUND_COUNT=$count"
           ```
           Review any remaining Roles in this count to ensure their access is justified and documented.
      </Accordion>

      <Accordion title="Using kubectl">
        On any machine with kubectl access:

        1. Identify roles with secret read access

        ```bash theme={null}
        kubectl get roles --all-namespaces -o wide
        kubectl get roles --all-namespaces -o yaml \
          | yq 'select(.rules[]? | (.resources[]? == "secrets") and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))) | {ns: .metadata.namespace, name: .metadata.name, rules: .rules}'
        ```

        2. Back up each affected Role (example for namespace `app-namespace`, role `app-role`)

        ```bash theme={null}
        kubectl get role app-role -n app-namespace -o yaml > app-role-backup.yaml
        ```

        3. Edit the Role to remove `get`, `list`, `watch` on `secrets`\
           Interactive edit (recommended when changes are small):

        ```bash theme={null}
        kubectl edit role app-role -n app-namespace
        ```

        In the editor, find any rule like:

        ```yaml theme={null}
        rules:
        - apiGroups: [""]
          resources:
            - secrets
          verbs:
            - get
            - list
            - watch
            - create
            - update
        ```

        Change it to remove read verbs while keeping only what is required. For example:

        ```yaml theme={null}
        rules:
        - apiGroups: [""]
          resources:
            - secrets
          verbs:
            - create
            - update
        ```

        or, if the Role does not need any secret access, remove the entire rule block that has `resources: ["secrets"]`.

        4. Declarative update via manifest (alternative to step 3)\
           Edit the backed‑up manifest file `app-role-backup.yaml`, adjust the `rules` section as above, then apply:

        ```bash theme={null}
        kubectl apply -f app-role-backup.yaml
        ```

        5. Repeat steps 2–4 for every Role flagged in step 1, ensuring only workloads that truly require secret read access retain `get/list/watch` on `secrets`.

        6. Verification

        ```bash theme={null}
        count=$(kubectl get roles --all-namespaces -o json | jq '
          .items[]
          | select(.rules[]?
            | (.resources[]? == "secrets")
            and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
          )' | wc -l)

        echo "ROLES_WITH_SECRET_READ_VERBS=$count"
        ```
      </Accordion>

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

        # Automation: Minimize access to secrets in Roles cluster-wide
        # Applies to: any machine with kubectl access to the cluster
        # Requirement: kubectl, jq, and a kubeconfig with sufficient RBAC rights

        # This script:
        #   - Finds Roles that grant get/list/watch on "secrets"
        #   - Removes those verbs from the Role rules (only for resource "secrets")
        #   - Leaves other resources/verbs untouched
        #   - Is idempotent (safe to re-run)
        #   - Verifies the result using the benchmark audit logic

        # ---------- configuration ----------
        # Backup directory for original Role definitions
        BACKUP_DIR="./role-secrets-backups-$(date +%Y%m%d%H%M%S)"
        mkdir -p "${BACKUP_DIR}"

        echo "Backing up and patching Roles that grant get/list/watch on secrets..."
        echo "Backups stored in: ${BACKUP_DIR}"

        # ---------- function to patch a single Role ----------
        patch_role() {
          local ns="$1"
          local name="$2"

          echo "Processing Role ${ns}/${name}..."

          # Backup original Role manifest
          kubectl get role "${name}" -n "${ns}" -o yaml > "${BACKUP_DIR}/${ns}__${name}.yaml"

          # Fetch Role as JSON
          role_json="$(kubectl get role "${name}" -n "${ns}" -o json)"

          # Transform rules:
          # - For rules that include "secrets" in resources:
          #     * remove get/list/watch from verbs
          #     * if verbs becomes empty, drop that rule entirely
          # - All other rules are preserved
          patched_json="$(jq '
            .rules |= [
              .[] |
              if (.resources // [] | index("secrets")) != null then
                # This rule touches secrets; clean its verbs
                .verbs |= [ .[] | select(. != "get" and . != "list" and . != "watch") ]
                | if (.verbs | length) == 0 then
                    empty
                  else
                    .
                  end
              else
                .
              end
            ]
          ' <<< "${role_json}")"

          # Apply the patched Role
          # Use kubectl replace to fully update the Role definition
          tmpfile="$(mktemp)"
          printf '%s\n' "${patched_json}" > "${tmpfile}"
          kubectl replace -f "${tmpfile}" >/dev/null
          rm -f "${tmpfile}"

          echo "Patched Role ${ns}/${name}."
        }

        # ---------- discover affected Roles ----------
        echo "Discovering Roles with get/list/watch access to secrets..."

        mapfile -t affected_roles < <(
          kubectl get roles --all-namespaces -o json \
          | jq -r '
              .items[]
              | select(.rules[]?
                  | (.resources[]? == "secrets")
                  and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
                )
              | "\(.metadata.namespace) \(.metadata.name)"
            ' \
          | sort -u
        )

        if [ "${#affected_roles[@]}" -eq 0 ]; then
          echo "No Roles with get/list/watch access to secrets found. Nothing to change."
        else
          echo "Found ${#affected_roles[@]} affected Roles:"
          printf '  %s\n' "${affected_roles[@]}"
          echo

          # ---------- patch each affected Role ----------
          for entry in "${affected_roles[@]}"; do
            ns="$(cut -d' ' -f1 <<< "${entry}")"
            name="$(cut -d' ' -f2- <<< "${entry}")"
            patch_role "${ns}" "${name}"
          done
        fi

        # ---------- verification ----------
        echo
        echo "Verifying that no Roles grant get/list/watch on secrets..."

        count="$(kubectl get roles --all-namespaces -o json | jq '
          .items[]
          | select(.rules[]?
            | (.resources[]? == "secrets")
            and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
          )' | wc -l | tr -d " ")"

        if [ "${count}" -gt 0 ]; then
          echo "SECRETS_ACCESS_FOUND"
          echo "Verification FAILED: ${count} Roles still grant get/list/watch on secrets."
          echo "Review remaining Roles manually; some may be system or intentionally privileged."
          exit 1
        else
          echo "Verification PASSED: no Roles grant get/list/watch on secrets."
        fi
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
