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

### More Info:

The Kubernetes API stores secrets, which may be service account tokens for the Kubernetes API or credentials used by workloads in the cluster. Access to these secrets should be restricted to the smallest possible group of users to reduce the risk of privilege escalation.

### Risk Level

High

### Address

Security

### Compliance Standards

* APRA CPS 234 (Australia)
* BSI C5 (Germany)
* Brazil LGPD
* CCPA / CPRA (California)
* CIS Critical Security Controls v8
* CIS GKE
* CMMC 2.0
* CSA Cloud Controls Matrix v4
* DPDPA
* Digital Operational Resilience Act (EU)
* Essential 8
* ISO/IEC 27017
* ISO/IEC 27018
* ISO/IEC 27701
* KSA PDPL
* MAS Technology Risk Management (Singapore)
* MITRE ATT\&CK (Cloud)
* NIS2 Directive
* NIST SP 800-171
* NYDFS 23 NYCRR 500
* SWIFT Customer Security Controls Framework
* Sarbanes-Oxley IT General Controls
* UK NCSC Cyber Assessment Framework

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. Identify roles with sensitive 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)"
           ' | sort -u
           ```

        2. Review who is bound to each identified Role
           * Run on: any machine with kubectl access
           ```bash theme={null}
           # Replace <NAMESPACE> and <ROLE_NAME> from step 1
           kubectl get rolebindings -n <NAMESPACE> -o json \
           | jq -r '
             .items[]
             | select(.roleRef.kind == "Role" and .roleRef.name == "<ROLE_NAME>")
             | "RoleBinding: \(.metadata.name) | Subjects: \(.subjects[]? | "\(.kind)/\(.name)")"
           '
           ```
           * Manually decide which users/service accounts/groups truly require get/list/watch on secrets.

        3. Inspect the exact Secret permissions in a Role
           * Run on: any machine with kubectl access
           ```bash theme={null}
           # Replace <NAMESPACE> and <ROLE_NAME>
           kubectl get role -n <NAMESPACE> <ROLE_NAME> -o yaml
           ```
           * Manually determine which rules for `resources: ["secrets"]` and `verbs: ["get","list","watch"]` can be removed or narrowed (e.g., by resourceNames or fewer verbs).

        4. Edit Roles to minimize Secret access
           * Run on: any machine with kubectl access\
             For each Role needing change:
           ```bash theme={null}
           kubectl edit role -n <NAMESPACE> <ROLE_NAME>
           ```
           In the editor:
           * Locate `rules:` entries where `resources` includes `secrets`.
           * Remove unneeded verbs `get`, `list`, `watch` from `verbs:`.
           * If Secret access is not required, delete the entire rule containing `secrets`.
             Save and exit to apply changes.

        5. If necessary, adjust RoleBindings instead of Roles
           * Run on: any machine with kubectl access\
             If some subjects no longer need Secret access but others still do:
           * Create a new, reduced-privilege Role without Secret access:
             ```bash theme={null}
             kubectl get role -n <NAMESPACE> <ROLE_NAME> -o yaml \
               | sed 's/name: <ROLE_NAME>/name: <NEW_ROLE_NAME>/' \
               | yq 'del(.rules[] | select(.resources[] == "secrets"))' \
               | kubectl apply -f -
             ```
           * Rebind subjects that should not see secrets:
             ```bash theme={null}
             kubectl create rolebinding <NEW_RB_NAME> \
               -n <NAMESPACE> \
               --role=<NEW_ROLE_NAME> \
               --user=<USER_OR_GROUP_OR_SA>
             ```
           * Then remove those subjects from the original RoleBinding:
             ```bash theme={null}
             kubectl edit rolebinding -n <NAMESPACE> <ROLEBINDING_NAME>
             ```

        6. Verify minimized Secret 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_SECRETS_ACCESS_WITH_GET_LIST_WATCH_IN_ROLES"
           fi
           ```
      </Accordion>

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

        1. Discover roles that can get/list/watch secrets
           ```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)"
           ' | sort -u
           ```

        2. For each role, review who uses it (RoleBindings)
           ```bash theme={null}
           # Replace <NAMESPACE> and <ROLE_NAME> from previous output
           kubectl get rolebindings -n <NAMESPACE> -o yaml \
           | yq '.items[] | select(.roleRef.kind=="Role" and .roleRef.name=="<ROLE_NAME>")'
           ```

        3. Remove unnecessary get/list/watch verbs from a role (example)\
           Export the current role:

           ```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` and, in any rule that includes `resources: ["secrets"]`, remove `get`, `list`, and `watch` from the `verbs` list if not strictly required. For example, change:

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

           to:

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

           Apply the updated role:

           ```bash theme={null}
           kubectl apply -f /tmp/role-<NAMESPACE>-<ROLE_NAME>.yaml
           ```

        4. If a role’s secret access is not needed at all, remove the rule entirely\
           In the same exported YAML, delete any `rules` entry where `resources` includes `secrets`, then apply:
           ```bash theme={null}
           kubectl apply -f /tmp/role-<NAMESPACE>-<ROLE_NAME>.yaml
           ```

        5. If access is still needed by a smaller group, create a more scoped role and re-bind\
           Example minimal role allowing get on a specific secret only:

           ```yaml theme={null}
           # /tmp/role-secrets-reader-specific.yaml
           apiVersion: rbac.authorization.k8s.io/v1
           kind: Role
           metadata:
             name: secrets-reader-specific
             namespace: <NAMESPACE>
           rules:
           - apiGroups: [""]
             resources: ["secrets"]
             resourceNames: ["<SECRET_NAME>"]
             verbs: ["get"]
           ```

           Apply it:

           ```bash theme={null}
           kubectl apply -f /tmp/role-secrets-reader-specific.yaml
           ```

           Then update or create a RoleBinding to use this scoped role instead of a broader one:

           ```yaml theme={null}
           # /tmp/rb-secrets-reader-specific.yaml
           apiVersion: rbac.authorization.k8s.io/v1
           kind: RoleBinding
           metadata:
             name: secrets-reader-specific-binding
             namespace: <NAMESPACE>
           subjects:
           - kind: User
             name: <USER_NAME>
             apiGroup: rbac.authorization.k8s.io
           roleRef:
             kind: Role
             name: secrets-reader-specific
             apiGroup: rbac.authorization.k8s.io
           ```

           ```bash theme={null}
           kubectl apply -f /tmp/rb-secrets-reader-specific.yaml
           ```

        6. Verification (re-run the audit logic)
           ```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_SECRETS_ACCESS_FOUND_FOR_GET_LIST_WATCH_IN_ROLES"
           fi
           ```
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Minimize access to secrets (CIS GKE 4.1.2)
        #
        # Runs on: any machine with kubectl access and jq installed
        #
        # Behavior:
        # - Enumerates all Roles that grant get/list/watch on "secrets"
        # - Prints them for review
        # - Interactively offers to remove get/list/watch on "secrets" from each matching rule
        # - Applies changes via kubectl patch (idempotent)
        # - Verifies that no Roles still grant get/list/watch on "secrets"
        #
        # NOTE: This is a MANUAL control; this script assists review but
        # final decisions must be made by an administrator who understands
        # which Roles truly need access to secrets (for example, controllers
        # or operators that must read secrets to function).

        set -euo pipefail

        require_cmd() {
          if ! command -v "$1" >/dev/null 2>&1; then
            echo "ERROR: '$1' is required but not installed or not in PATH." >&2
            exit 1
          fi
        }

        require_cmd kubectl
        require_cmd jq

        echo "=== Enumerating Roles with get/list/watch access to secrets ==="

        # Collect Roles with get/list/watch on secrets
        roles_json=$(kubectl get roles --all-namespaces -o json)
        matches=$(echo "$roles_json" | jq -r '
          .items[]
          | select(
              .rules[]?
              | (.resources[]? == "secrets")
              and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch"))
            )
          | "\(.metadata.namespace) \(.metadata.name)"
        ')

        if [ -z "$matches" ]; then
          echo "No Roles currently grant get/list/watch on secrets."
          exit 0
        fi

        echo "The following Roles grant get/list/watch access to secrets:"
        echo "$matches" | awk '{printf "  - Namespace: %-30s Role: %s\n", $1, $2}'

        echo
        echo "For each Role, you must decide if it truly requires get/list/watch on secrets."
        echo "This script can remove those verbs only from rules that target the \"secrets\" resource."
        echo "Other resources and verbs in the same Role are preserved."

        read -r -p "Proceed with interactive cleanup of these Roles? [y/N]: " PROCEED
        PROCEED=${PROCEED:-n}
        if [[ ! "$PROCEED" =~ ^[Yy]$ ]]; then
          echo "Aborting. No changes made."
          exit 0
        fi

        # Function to build a patched Role JSON with minimized secret access
        build_patched_role() {
          local ns="$1"
          local name="$2"

          # Extract the Role, then rewrite rules:
          # - For each rule that includes "secrets":
          #   * remove get/list/watch from verbs for that rule
          #   * if no verbs remain, drop the rule entirely
          # - Other rules untouched
          kubectl get role "$name" -n "$ns" -o json | jq '
            .rules |= (
              map(
                if (.resources // [] | index("secrets")) != null then
                  # rule targeting secrets: strip get/list/watch
                  .verbs |= map(select(. != "get" and . != "list" and . != "watch")) |
                  if (.verbs | length) == 0 then
                    # drop rule with no remaining verbs
                    empty
                  else
                    .
                  end
                else
                  .
                end
              )
            )
          '
        }

        # Process each Role interactively
        echo
        echo "=== Processing Roles ==="
        echo

        while read -r ns name; do
          [ -z "$ns" ] && continue
          echo "Role: $name"
          echo "Namespace: $ns"
          echo

          echo "Current rules for this Role:"
          kubectl get role "$name" -n "$ns" -o json | jq '.rules' | sed 's/^/  /'
          echo

          read -r -p "Remove get/list/watch access on secrets for this Role? [y/N]: " ANSWER
          ANSWER=${ANSWER:-n}
          if [[ ! "$ANSWER" =~ ^[Yy]$ ]]; then
            echo "Skipping $ns/$name"
            echo
            continue
          fi

          echo "Building patched Role without get/list/watch on secrets..."
          patched_json=$(build_patched_role "$ns" "$name")

          echo "Resulting rules will be:"
          echo "$patched_json" | jq '.rules' | sed 's/^/  /'
          echo

          read -r -p "Apply this change to $ns/$name? [y/N]: " APPLY
          APPLY=${APPLY:-n}
          if [[ ! "$APPLY" =~ ^[Yy]$ ]]; then
            echo "Skipping apply for $ns/$name"
            echo
            continue
          fi

          # Apply patch idempotently
          echo "$patched_json" | kubectl apply -n "$ns" -f -
          echo "Updated $ns/$name"
          echo
        done <<< "$matches"

        echo "=== Verification ==="
        echo "Re-running check for Roles with 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 "There are still Roles with get/list/watch on secrets."
          echo "Review them manually with:"
          echo "  kubectl get roles --all-namespaces -o json | jq -r '.items[] | select(.rules[]? | (.resources[]? == \"secrets\") and ((.verbs[]? == \"get\") or (.verbs[]? == \"list\") or (.verbs[]? == \"watch\"))) | \"Namespace: \(.metadata.namespace)  Role: \(.metadata.name)\"'"
          exit 1
        else
          echo "No Roles currently grant get/list/watch on secrets."
        fi

        echo "Done."
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
