> ## 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 on Secret objects expose sensitive credentials. Access to Secrets should be minimized across the cluster.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS GKE

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. Identify all Roles with sensitive Secret access
           * Run on: any machine with kubectl access
           ```bash theme={null}
           kubectl get roles --all-namespaces -o json | jq '
             .items[]
             | select(.rules[]?
               | (.resources[]? == "secrets")
               and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
             )
             | {namespace: .metadata.namespace, name: .metadata.name, rules: .rules}' | less
           ```
           * Review each Role to understand which subjects (RoleBindings) depend on it and what applications/functions they support:
           ```bash theme={null}
           kubectl get rolebindings --all-namespaces -o wide | grep '<role-name>'
           ```

        2. Decide which Roles truly need Secret access
           * For each Role found, determine:
             * Does the workload actually need to read Secret contents (e.g., a controller that manages Secrets)?
             * Or is Secret *reference* in Pod specs (which does not require RBAC get/list/watch) sufficient?
           * Mark Roles as:
             * “Keep read to secrets” (strictly required), or
             * “Can be removed or narrowed” (preferred).

        3. Remove unnecessary Secret rules from a Role
           * Run on: any machine with kubectl access
           * Export the Role manifest, edit locally, and remove `secrets` from `resources` and/or remove `get`, `list`, `watch` from `verbs` where not strictly needed:
           ```bash theme={null}
           kubectl get role -n <namespace> <role-name> -o yaml > /tmp/role-<namespace>-<role-name>.yaml
           ```
           * Edit `/tmp/role-<namespace>-<role-name>.yaml`:
             * In `.rules[]`, delete `secrets` from `resources:` or delete the entire rule if only for secrets.
             * If some verbs are truly required, keep only the minimal needed subset.
           * Apply the updated Role:
           ```bash theme={null}
           kubectl apply -f /tmp/role-<namespace>-<role-name>.yaml
           ```

        4. Split roles when only some subjects need Secret access
           * If some workloads bound to the Role require Secret access and others do not:
             * Create a new, narrow Role only for those workloads:
               ```bash theme={null}
               cat <<'EOF' > /tmp/role-secrets-<namespace>-<new-name>.yaml
               apiVersion: rbac.authorization.k8s.io/v1
               kind: Role
               metadata:
                 name: <new-name>
                 namespace: <namespace>
               rules:
               - apiGroups: [""]
                 resources: ["secrets"]
                 verbs: ["get"]  # or minimal required verbs
               EOF

               kubectl apply -f /tmp/role-secrets-<namespace>-<new-name>.yaml
               ```
             * Update RoleBindings so only the specific service accounts/users that truly need Secret access bind to `<new-name>`, and others bind to a Role without Secret permissions:
               ```bash theme={null}
               kubectl edit rolebinding -n <namespace> <rolebinding-name>
               ```
               Adjust `.roleRef.name` and/or split into multiple RoleBindings as needed.

        5. Re-test workloads depending on modified Roles
           * Run on: any machine with kubectl access
           * For each namespace where Roles were changed, check Pods for errors indicating missing Secret access:
           ```bash theme={null}
           kubectl get pods -n <namespace>
           kubectl logs -n <namespace> <pod-name> --tail=100
           ```
           * If a workload fails due to required Secret reads, minimally re-add the specific verbs it needs (e.g., only `get` on specific secrets via `resourceNames` if appropriate).

        6. Verification (no Roles with unnecessary Secret read access)
           * Run on: any machine with kubectl access
           ```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)

           if [ "$count" -gt 0 ]; then
             echo "SECRETS_ACCESS_FOUND"
           else
             echo "NO_EXCESS_SECRETS_ACCESS_FOUND"
           fi
           ```
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 1) Discover Roles with secret read access
        # Run on: any machine with kubectl access

        kubectl get roles --all-namespaces -o json | jq '
          .items[]
          | select(.rules[]?
            | (.resources[]? == "secrets")
            and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
          )
          | {namespace: .metadata.namespace, name: .metadata.name, rules: .rules}
        '

        # 2) For each offending Role, export its manifest for review/edit
        # Replace <namespace> and <role-name> with actual values from the command above.

        kubectl get role <role-name> -n <namespace> -o yaml > /tmp/role-<namespace>-<role-name>.yaml

        # 3) Edit the Role manifest locally to remove or narrow secret access
        # - Remove "secrets" from resources where not strictly required
        # - Or remove "get", "list", "watch" from verbs for rules that reference "secrets"
        #
        # Example BEFORE (snippet in the exported YAML):
        # rules:
        # - apiGroups: [""]
        #   resources: ["pods", "secrets"]
        #   verbs: ["get", "list", "watch"]
        #
        # Example AFTER (if secrets access not needed):
        # rules:
        # - apiGroups: [""]
        #   resources: ["pods"]
        #   verbs: ["get", "list", "watch"]
        #
        # Example AFTER (if write-only access to secrets is enough):
        # rules:
        # - apiGroups: [""]
        #   resources: ["secrets"]
        #   verbs: ["create", "update", "patch"]

        # Edit the file with your editor of choice:
        vi /tmp/role-<namespace>-<role-name>.yaml

        # 4) Apply the updated Role
        kubectl apply -f /tmp/role-<namespace>-<role-name>.yaml
        ```

        Verification:

        ```bash theme={null}
        # Re-run the audit logic to confirm no Roles grant get/list/watch on secrets
        # Run on: any machine with kubectl access

        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)

        if [ "$count" -gt 0 ]; then
          echo "SECRETS_ACCESS_FOUND"
        else
          echo "NO_SECRETS_READ_ACCESS_IN_ROLES"
        fi
        ```
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Minimize Access To Secrets (CIS GKE 4.1.2) - Automation
        #
        # Scope: any machine with kubectl access to the cluster
        #
        # Behavior:
        # - Scans all Roles that grant get/list/watch on "secrets"
        # - For each such Role, interactively asks whether to remove those verbs for "secrets"
        # - Creates a timestamped backup of each modified Role manifest in ./role-backups/
        # - Applies the sanitized Role back to the cluster
        # - Re-runs the audit check at the end to verify
        #
        # NOTE: This is a MANUAL benchmark control. There is no one-size-fits-all fix.
        #       This script helps you review and selectively remove secret access; it
        #       will NOT change anything without your confirmation for each Role.
        #

        set -euo pipefail

        # --- Pre-req checks ---------------------------------------------------------

        if ! command -v kubectl >/dev/null 2>&1; then
          echo "ERROR: kubectl not found in PATH. Install/configure kubectl and re-run." >&2
          exit 1
        fi

        if ! command -v jq >/dev/null 2>&1; then
          echo "ERROR: jq not found in PATH. Install jq and re-run." >&2
          exit 1
        fi

        echo "Using kubectl context:"
        kubectl config current-context || {
          echo "ERROR: No current kubectl context configured." >&2
          exit 1
        }

        # --- Find Roles with get/list/watch on secrets ------------------------------

        echo
        echo "Scanning Roles for get/list/watch access to \"secrets\"..."

        roles_json=$(kubectl get roles --all-namespaces -o json)

        # Count roles matching the condition
        count=$(echo "${roles_json}" | jq '
          .items[]
          | select(.rules[]?
            | (.resources[]? == "secrets")
            and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
          )' | wc -l | tr -d ' ')

        if [ "${count}" -eq 0 ]; then
          echo "No Roles with get/list/watch access to secrets found. Nothing to change."
          exit 0
        fi

        echo "Found ${count} Role(s) with get/list/watch access to secrets."

        # --- Prepare backup directory -----------------------------------------------

        backup_dir="./role-backups"
        mkdir -p "${backup_dir}"

        timestamp=$(date +"%Y%m%d-%H%M%S")

        # --- Iterate over affected Roles -------------------------------------------

        echo
        echo "Reviewing each affected Role. For each Role you can choose whether to"
        echo "remove get/list/watch verbs from rules that apply to the \"secrets\" resource."
        echo

        # Extract namespace and name for each affected Role
        echo "${roles_json}" | jq -r '
          .items[]
          | select(.rules[]?
            | (.resources[]? == "secrets")
            and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
          )
          | [.metadata.namespace, .metadata.name]
          | @tsv
        ' | while IFS=$'\t' read -r ns name; do
          echo "-----------------------------------------------------------------"
          echo "Role: ${name}"
          echo "Namespace: ${ns}"
          echo

          # Show current Role (rules only) for review
          kubectl get role "${name}" -n "${ns}" -o json | jq '{metadata: {name: .metadata.name, namespace: .metadata.namespace}, rules: .rules}' | sed 's/^/  /'

          echo
          read -r -p "Remove get/list/watch on \"secrets\" from this Role? [y/N]: " answer
          answer=${answer:-N}

          case "${answer}" in
            [yY][eE][sS]|[yY])
              echo "Processing Role ${ns}/${name}..."

              # Backup full manifest
              backup_file="${backup_dir}/${ns}__${name}__${timestamp}.yaml"
              kubectl get role "${name}" -n "${ns}" -o yaml > "${backup_file}"
              echo "  Backup saved to ${backup_file}"

              # Generate sanitized Role JSON:
              # - For each rule:
              #   * If "secrets" is among resources:
              #       - remove get/list/watch from verbs
              #       - keep other verbs intact
              #   * Keep rules even if secrets was the only resource; verbs for non-secret
              #     resources are unchanged by this script.
              sanitized_json=$(kubectl get role "${name}" -n "${ns}" -o json | jq '
                . as $role
                | $role
                | .rules |= (
                    map(
                      if (.resources? | index("secrets")) then
                        .verbs |= (map(select(. != "get" and . != "list" and . != "watch")))
                      else
                        .
                      end
                    )
                  )
              ')

              # Apply the sanitized Role
              echo "${sanitized_json}" | kubectl apply -f -
              echo "  Updated Role ${ns}/${name} applied."
              ;;
            *)
              echo "Skipping Role ${ns}/${name} (no changes made)."
              ;;
          esac

          echo
        done

        # --- Verification (re-run audit command) ------------------------------------

        echo "-----------------------------------------------------------------"
        echo "Re-running audit to verify remaining Roles with get/list/watch on secrets..."

        verify_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 [ "${verify_count}" -gt 0 ]; then
          echo "SECRETS_ACCESS_FOUND"
          echo "Remaining Roles with get/list/watch on secrets: ${verify_count}"
          echo
          echo "You should now:"
          echo "  - Review the remaining Roles and decide if they truly need secret access."
          echo "  - Re-run this script to iteratively reduce access where appropriate."
        else
          echo "No Roles with get/list/watch access to secrets remain."
          echo "Verification PASSED."
        fi
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
