> ## 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 lets subjects read sensitive credentials. Restrict this access to the minimum required.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS Kubernetes

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify which Roles/ClusterRoles grant broad Secret access** (run on any machine with kubectl access)
           ```bash theme={null}
           kubectl get clusterroles -o yaml | grep -nA5 "kind: ClusterRole" | grep -nB5 "secrets"
           kubectl get roles --all-namespaces -o yaml | grep -nA5 "kind: Role" | grep -nB5 "secrets"
           ```
           Or more explicitly:
           ```bash theme={null}
           kubectl get clusterroles -o yaml | awk '/kind: ClusterRole/{cr=$0} /resources:/{r=$0} /verbs:/{v=$0} /secrets/{print cr ORS r ORS v ORS "----"}'
           kubectl get roles --all-namespaces -o yaml | awk '/kind: Role/{ro=$0} /resources:/{r=$0} /verbs:/{v=$0} /secrets/{print ro ORS r ORS v ORS "----"}'
           ```

        2. **Review which subjects actually use those roles** (run on any machine with kubectl access)\
           For each Role/ClusterRole identified, list RoleBindings/ClusterRoleBindings:
           ```bash theme={null}
           kubectl get rolebindings --all-namespaces -o wide | grep "<ROLE_NAME>"
           kubectl get clusterrolebindings -o wide | grep "<CLUSTERROLE_NAME>"
           ```
           Replace `<ROLE_NAME>` / `<CLUSTERROLE_NAME>` with each name found in step 1 and decide, based on application requirements, which subjects truly need get/list/watch on secrets.

        3. **Edit ClusterRoles to remove unnecessary get/list/watch on secrets** (run on any machine with kubectl access)\
           For each ClusterRole that is too broad:
           ```bash theme={null}
           kubectl edit clusterrole <CLUSTERROLE_NAME>
           ```
           In the editor, within `rules` that include `resources: ["secrets"]`, remove `get`, `list`, and/or `watch` from `verbs` where they are not strictly required. If no verb on `secrets` is needed, remove the entire rule item that references `secrets`.

        4. **Edit namespace-scoped Roles to remove unnecessary get/list/watch on secrets** (run on any machine with kubectl access)\
           For each Role that is too broad:
           ```bash theme={null}
           kubectl edit role <ROLE_NAME> -n <NAMESPACE>
           ```
           As in step 3, adjust or remove the `verbs` on `resources: ["secrets"]` so that only subjects that truly require secret access retain it.

        5. **If needed, create least-privilege roles and re-bind subjects** (run on any machine with kubectl access)\
           Where applications still require limited Secret access, create narrowly scoped Roles/ClusterRoles (for example, restricted to specific namespaces or resources) and bind only the necessary service accounts/users:
           ```bash theme={null}
           cat <<'EOF' | kubectl apply -f -
           apiVersion: rbac.authorization.k8s.io/v1
           kind: Role
           metadata:
             name: app-needs-secret-read
             namespace: default
           rules:
           - apiGroups: [""]
             resources: ["secrets"]
             resourceNames: ["app-config-secret"]
             verbs: ["get"]
           ---
           apiVersion: rbac.authorization.k8s.io/v1
           kind: RoleBinding
           metadata:
             name: app-needs-secret-read-binding
             namespace: default
           subjects:
           - kind: ServiceAccount
             name: app-sa
             namespace: default
           roleRef:
             kind: Role
             name: app-needs-secret-read
             apiGroup: rbac.authorization.k8s.io
           EOF
           ```

        6. **Verify that broad secret access is minimized** (run on any machine with kubectl access)
           ```bash theme={null}
           echo "canGetListWatchSecretsAsSystemAuthenticated: $(kubectl auth can-i get,list,watch secrets --all-namespaces --as=system:authenticated)"
           ```
           Confirm this returns `no` or otherwise reflects only the explicitly required, minimized access you decided on in steps 2–5.
      </Accordion>

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

        1. Identify Roles and ClusterRoles that grant secret read access
           ```bash theme={null}
           kubectl get clusterroles -o yaml | grep -nE 'kind: ClusterRole|^- apiGroups:|^- secrets$|^- get$|^- list$|^- watch$' 
           kubectl get roles --all-namespaces -o yaml | grep -nE 'kind: Role|^- apiGroups:|^- secrets$|^- get$|^- list$|^- watch$'
           ```

        2. For each Role/ClusterRole, review usage (which subjects are bound)
           ```bash theme={null}
           # Cluster-wide bindings
           kubectl get clusterrolebindings -o yaml | grep -nE 'kind: ClusterRoleBinding|roleRef:|^  name:|kind: (User|Group|ServiceAccount)'

           # Namespaced bindings
           kubectl get rolebindings --all-namespaces -o yaml | grep -nE 'kind: RoleBinding|roleRef:|^  name:|kind: (User|Group|ServiceAccount)'
           ```

        3. For roles that should NOT have broad secrets read access, edit them to remove `get`, `list`, `watch` on `secrets`. Example commands (repeat per offending role):

           * ClusterRole:
             ```bash theme={null}
             kubectl edit clusterrole <clusterrole-name>
             ```
             In the editor, locate any rule like:
             ```yaml theme={null}
             - apiGroups: [""]
               resources:
                 - secrets
               verbs:
                 - get
                 - list
                 - watch
                 # possibly others
             ```
             and either:
             * remove `secrets` from `resources`, or
             * remove `get`, `list`, `watch` from `verbs`, keeping only what is required.

           * Namespaced Role:
             ```bash theme={null}
             kubectl edit role <role-name> -n <namespace>
             ```
             Apply the same edit pattern as above.

           If you manage RBAC declaratively, instead edit the corresponding YAML in version control. Example patch of a ClusterRole manifest before applying:

           **before:**

           ```yaml theme={null}
           kind: ClusterRole
           apiVersion: rbac.authorization.k8s.io/v1
           metadata:
             name: example-broad-secret-access
           rules:
             - apiGroups: [""]
               resources: ["secrets"]
               verbs: ["get", "list", "watch"]
           ```

           **after (no secret read access):**

           ```yaml theme={null}
           kind: ClusterRole
           apiVersion: rbac.authorization.k8s.io/v1
           metadata:
             name: example-broad-secret-access
           rules:
             - apiGroups: [""]
               resources: []        # or remove this rule entirely if now empty
               verbs: []
           ```

           Apply the manifest:

           ```bash theme={null}
           kubectl apply -f <path-to-updated-clusterrole>.yaml
           ```

        4. For roles that must read specific secrets, scope access narrowly instead of removing it entirely, for example using `resourceNames`:

           ```yaml theme={null}
           kind: Role
           apiVersion: rbac.authorization.k8s.io/v1
           metadata:
             name: limited-secret-reader
             namespace: app-namespace
           rules:
             - apiGroups: [""]
               resources: ["secrets"]
               resourceNames:
                 - db-credentials
               verbs: ["get"]
           ```

           Apply:

           ```bash theme={null}
           kubectl apply -f limited-secret-reader.yaml
           ```

        5. Re-check which roles still allow all authenticated users to read secrets

           ```bash theme={null}
           echo "canGetListWatchSecretsAsSystemAuthenticated: $(kubectl auth can-i get,list,watch secrets --all-namespaces --as=system:authenticated)"
           ```

           The goal is that this returns `canGetListWatchSecretsAsSystemAuthenticated: no` unless you have a conscious, documented exception.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Restrict broad get/list/watch access to Secret objects.
        # Scope: Kubernetes API objects, runs from any machine with kubectl access.
        #
        # Strategy:
        # - Enumerate Roles and ClusterRoles that grant get/list/watch on secrets.
        # - For each, remove those verbs from the "secrets" resource only.
        # - Skip system and well-known controller roles by default (can be overridden).
        # - Re-run safe: uses server-side apply with filtered rules.
        #
        # REQUIREMENTS:
        # - kubectl configured with cluster-admin privileges.
        # - jq installed.

        set -euo pipefail

        # ------------- CONFIGURABLE FILTERS ----------------

        # Namespaces to skip (space-separated). System namespaces typically need broad access.
        SKIP_NAMESPACES=("kube-system" "kube-public" "kube-node-lease")

        # ClusterRoles to skip (space-separated) even if they grant access.
        SKIP_CLUSTERROLES=(
          "cluster-admin"
          "system:controller:attachdetach-controller"
          "system:controller:cronjob-controller"
          "system:controller:deployment-controller"
          "system:controller:endpoint-controller"
          "system:controller:generic-garbage-collector"
          "system:controller:replicaset-controller"
          "system:controller:statefulset-controller"
          "system:controller:namespace-controller"
          "system:controller:job-controller"
          "system:controller:persistent-volume-binder"
          "system:controller:service-account-controller"
          "system:controller:ttl-after-finished-controller"
        )

        # Roles to skip per-namespace, format: "namespace/name"
        SKIP_ROLES=()

        # ------------- HELPER FUNCTIONS ----------------

        contains() {
          local needle="$1"; shift
          for x in "$@"; do
            [[ "$x" == "$needle" ]] && return 0
          done
          return 1
        }

        skip_namespace() {
          local ns="$1"
          contains "$ns" "${SKIP_NAMESPACES[@]}"
        }

        skip_clusterrole() {
          local name="$1"
          contains "$name" "${SKIP_CLUSTERROLES[@]}"
        }

        skip_role() {
          local ns="$1" name="$2"
          local fq="${ns}/${name}"
          contains "$fq" "${SKIP_ROLES[@]}"
        }

        patch_role_like() {
          # $1: kind (Role|ClusterRole)
          # $2: namespace (empty for ClusterRole)
          # $3: name
          local kind="$1" ns="$2" name="$3"

          echo "Processing ${kind} ${ns:+$ns/}$name"

          local get_cmd=(kubectl get "$kind" "$name" -o json)
          if [[ "$kind" == "Role" ]]; then
            get_cmd+=( -n "$ns" )
          fi

          local json
          if ! json="$("${get_cmd[@]}")"; then
            echo "  WARN: Failed to fetch ${kind} ${ns:+$ns/}$name, skipping." >&2
            return
          fi

          # Build a new rules array with 'secrets' rules stripped of get/list/watch, but keep other resources/verbs.
          local patched
          patched="$(jq '
            .rules |= map(
              if (.resources // [] | index("secrets") | not) then
                .
              else
                # Split rules which mention secrets into:
                #  - secrets-only: verbs without get/list/watch (if any remain)
                #  - non-secrets: same verbs for remaining resources
                (
                  (.resources // []) as $res
                  | (.verbs // []) as $verbs
                  | ($res - ["secrets"]) as $otherRes
                  | ($verbs - ["get","list","watch"]) as $safeVerbs
                  | if ($otherRes | length) == 0 and ($safeVerbs | length) == 0 then
                      # Rule becomes empty / useless: drop by emitting null (filtered later)
                      null
                    elif ($otherRes | length) == 0 then
                      # Only secrets in resources, but with restricted verbs
                      .resources = ["secrets"] | .verbs = $safeVerbs
                    elif ($safeVerbs | length) == 0 then
                      # Only non-secret resources remain, original verbs
                      .resources = $otherRes
                    else
                      # Need two rules: one for secrets with safe verbs, one for others unchanged verbs
                      [
                        (. | .resources = ["secrets"] | .verbs = $safeVerbs),
                        (. | .resources = $otherRes)
                      ]
                    end
                )
              end
            )
            # Flatten arrays and remove nulls
            | .rules = (.rules | map(if type=="array" then .[] else . end) | map(select(. != null)))
          ' <<< "$json")"

          # If no change, skip apply.
          if diff -q <(echo "$json" | jq '.rules') <(echo "$patched" | jq '.rules') >/dev/null 2>&1; then
            echo "  No change needed."
            return
          fi

          echo "  Updating ${kind} ${ns:+$ns/}$name"
          if [[ "$kind" == "Role" ]]; then
            echo "$patched" | kubectl apply -f -
          else
            echo "$patched" | kubectl apply -f -
          fi
        }

        # ------------- MAIN LOGIC ----------------

        echo "=== Restricting broad access to Secret objects ==="

        # 1) Process Roles
        echo "Scanning Roles for get/list/watch on secrets..."
        mapfile -t roles < <(
          kubectl get roles --all-namespaces -o json \
          | jq -r '
              .items[]
              | select(
                  (.rules // [])
                  | map(
                      (.resources // []) | index("secrets")
                      and
                      (.verbs // []) | (index("get") or index("list") or index("watch"))
                    )
                  | any
                )
              | .metadata.namespace + " " + .metadata.name
            '
        )

        for line in "${roles[@]}"; do
          ns="${line%% *}"
          name="${line#* }"

          if skip_namespace "$ns"; then
            echo "Skipping Role $ns/$name (namespace filtered)"
            continue
          fi
          if skip_role "$ns" "$name"; then
            echo "Skipping Role $ns/$name (role filtered)"
            continue
          fi

          patch_role_like "Role" "$ns" "$name"
        done

        # 2) Process ClusterRoles
        echo "Scanning ClusterRoles for get/list/watch on secrets..."
        mapfile -t croles < <(
          kubectl get clusterroles -o json \
          | jq -r '
              .items[]
              | select(
                  (.rules // [])
                  | map(
                      (.resources // []) | index("secrets")
                      and
                      (.verbs // []) | (index("get") or index("list") or index("watch"))
                    )
                  | any
                )
              | .metadata.name
            '
        )

        for name in "${croles[@]}"; do
          if skip_clusterrole "$name"; then
            echo "Skipping ClusterRole $name (clusterrole filtered)"
            continue
          fi

          patch_role_like "ClusterRole" "" "$name"
        done

        # 3) VERIFICATION
        echo "=== Verification ==="
        echo "canGetListWatchSecretsAsSystemAuthenticated: $(kubectl auth can-i get,list,watch secrets --all-namespaces --as=system:authenticated)"

        echo "Script complete."
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
