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

# pods/exec Should Not Be Granted To Broad Subjects

### More Info:

Advisory: review Roles/ClusterRoles that grant create on pods/exec. Exec into a running pod bypasses image immutability and admission controls.

### Risk Level

High

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **List all Roles/ClusterRoles that grant `create` on `pods/exec`**\
           Run on: any machine with kubectl access
           ```sh theme={null}
           kubectl get roles,clusterroles --all-namespaces -o json \
           | jq '
             [ .items[]
               | select(any(.rules[]?;
                   (any(.resources[]?; . == "pods/exec" or . == "*"))
                   and (any(.verbs[]?; . == "create" or . == "*"))))
               | {kind, namespace: (.metadata.namespace // ""), name: .metadata.name}
             ]'
           ```

        2. **Identify “broad” bindings to those Roles/ClusterRoles**\
           Run on: any machine with kubectl access
           ```sh theme={null}
           { kubectl get roles,clusterroles --all-namespaces -o json
             kubectl get rolebindings,clusterrolebindings --all-namespaces -o json
           } | jq -rs '
             .[0] as $roles | .[1] |
             def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];
             [ $roles.items[]
               | select(any(.rules[]?;
                   (any(.resources[]?; . == "pods/exec" or . == "*"))
                   and (any(.verbs[]?; . == "create" or . == "*"))))
               | { kind: .kind, ns: (.metadata.namespace // ""), name: .metadata.name } ] as $execRoles
             | [ .items[]
               | .kind as $kind | .metadata as $m | .roleRef as $ref
               | select(any($execRoles[];
                   .name == $ref.name and .kind == $ref.kind
                   and (.ns == "" or .ns == ($m.namespace // ""))))
               | ((.subjects // [])[] | select(.name as $n | broad | index($n)))
               | { bindingKind: $kind,
                   namespace: ($m.namespace // ""),
                   bindingName: $m.name,
                   roleRefKind: $ref.kind,
                   roleRefName: $ref.name,
                   subjectKind: .kind,
                   subjectName: .name }
             ]'
           ```

        3. **Review and decide which broad bindings must be removed or replaced**\
           Run on: any machine with kubectl access
           * For each listed binding, determine which *specific* human users or service accounts truly need `pods/exec` and document that set.
           * Plan to delete the broad binding and create new bindings only to those named identities.

        4. **Remove a non‑justified broad binding to `pods/exec`**\
           Run on: any machine with kubectl access
           * For a ClusterRoleBinding:
             ```sh theme={null}
             kubectl delete clusterrolebinding <BINDING_NAME>
             ```
           * For a RoleBinding (namespace‑scoped):
             ```sh theme={null}
             kubectl delete rolebinding <BINDING_NAME> -n <NAMESPACE>
             ```

        5. **Create least‑privilege bindings for named subjects that actually need exec**\
           Run on: any machine with kubectl access
           * Example: bind an existing ClusterRole that includes `pods/exec` only to a specific user:
             ```sh theme={null}
             kubectl create clusterrolebinding allow-exec-to-alice \
               --clusterrole=<EXEC_CLUSTERROLE_NAME> \
               --user=alice@example.com
             ```
           * Example: bind to a single service account in a namespace:
             ```sh theme={null}
             kubectl create rolebinding allow-exec-to-deployer \
               --role=<EXEC_ROLE_NAME> \
               --serviceaccount=app-namespace:deployer-sa \
               -n app-namespace
             ```

        6. **Verify that no broad subjects are granted `create` on `pods/exec`**\
           Run on: any machine with kubectl access
           ```sh theme={null}
           { kubectl get roles,clusterroles --all-namespaces -o json
             kubectl get rolebindings,clusterrolebindings --all-namespaces -o json
           } | jq -rs '
             .[0] as $roles | .[1] |
             def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];
             [ $roles.items[]
               | select(any(.rules[]?;
                   (any(.resources[]?; . == "pods/exec" or . == "*"))
                   and (any(.verbs[]?; . == "create" or . == "*"))))
               | { kind: .kind, ns: (.metadata.namespace // ""), name: .metadata.name } ] as $execRoles
             | [ .items[]
               | .kind as $kind | .metadata as $m | .roleRef as $ref
               | select(any($execRoles[];
                   .name == $ref.name and .kind == $ref.kind
                   and (.ns == "" or .ns == ($m.namespace // ""))))
               | ((.subjects // [])[] | select(.name as $n | broad | index($n)))
               | "kind=\($kind)"
                 + (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
                 + " name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"
                 + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
                 + " subject=\(.name) roleRef=\($ref.kind)/\($ref.name) grants=pods/exec is_compliant=false"
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```
           Confirm the output is `is_compliant=true`.
      </Accordion>

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

        1. Identify the offending bindings and roles

        ```sh theme={null}
        kubectl get rolebindings,clusterrolebindings --all-namespaces -o wide
        kubectl get roles,clusterroles --all-namespaces -o yaml | grep -A10 "pods/exec"
        ```

        2. Inspect a specific flagged binding and its role

        Replace the names from the audit output accordingly:

        ```sh theme={null}
        # Example: inspect a ClusterRoleBinding
        kubectl get clusterrolebinding exec-access-binding -o yaml

        # Example: inspect the referenced ClusterRole
        kubectl get clusterrole exec-access-role -o yaml
        ```

        3. Remove broad subjects from the binding

        Edit the binding so that subjects like `system:authenticated`, `system:unauthenticated`, `system:anonymous`, and `system:serviceaccounts` are removed and replaced with a small set of named human users or dedicated groups.

        ```sh theme={null}
        # ClusterRoleBinding
        kubectl edit clusterrolebinding exec-access-binding

        # RoleBinding (namespace-scoped)
        kubectl edit rolebinding exec-access-binding -n <namespace>
        ```

        In the editor, adjust `subjects` from something like:

        ```yaml theme={null}
        subjects:
        - kind: Group
          name: system:authenticated
          apiGroup: rbac.authorization.k8s.io
        ```

        to something like:

        ```yaml theme={null}
        subjects:
        - kind: User
          name: alice@example.com
          apiGroup: rbac.authorization.k8s.io
        - kind: User
          name: bob@example.com
          apiGroup: rbac.authorization.k8s.io
        ```

        Make sure no remaining subject has `name` equal to any of:
        `system:authenticated`, `system:unauthenticated`, `system:anonymous`, `system:serviceaccounts`.

        4. If needed, remove `pods/exec` from a shared ClusterRole

        If a ClusterRole is broadly bound and you cannot safely constrain the subjects, move `pods/exec` into a dedicated, tightly bound role.

        a) Remove `pods/exec` from the shared ClusterRole:

        ```sh theme={null}
        kubectl edit clusterrole shared-role-with-exec
        ```

        In the editor, delete or narrow any rule that includes `pods/exec` or `resources: ["*"]` with `verbs: ["create"]` (or `"*"`), unless it is strictly needed for that broad audience.

        b) Create a dedicated ClusterRole and bind only specific operators:

        ```sh theme={null}
        cat <<'EOF' | kubectl apply -f -
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRole
        metadata:
          name: pods-exec-operators
        rules:
        - apiGroups: [""]
          resources: ["pods/exec"]
          verbs: ["create"]
        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRoleBinding
        metadata:
          name: pods-exec-operators-binding
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: ClusterRole
          name: pods-exec-operators
        subjects:
        - kind: User
          apiGroup: rbac.authorization.k8s.io
          name: alice@example.com
        - kind: User
          apiGroup: rbac.authorization.k8s.io
          name: bob@example.com
        EOF
        ```

        5. Verification

        Run the same style of check to confirm no broad subjects are granted `create` on `pods/exec`:

        ```sh theme={null}
        { kubectl get roles,clusterroles --all-namespaces -o json \
          kubectl get rolebindings,clusterrolebindings --all-namespaces -o json; } | jq -rs '
          .[0] as $roles | .[1] |
          def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];
          [ $roles.items[]
            | select(any(.rules[]?;
                (any(.resources[]?; . == "pods/exec" or . == "*"))
                and (any(.verbs[]?; . == "create" or . == "*"))))
            | { kind: .kind, ns: (.metadata.namespace // ""), name: .metadata.name } ] as $execRoles
          | [ .items[]
            | .kind as $kind | .metadata as $m | .roleRef as $ref
            | select(any($execRoles[];
                .name == $ref.name and .kind == $ref.kind
                and (.ns == "" or .ns == ($m.namespace // ""))))
            | ((.subjects // [])[] | select(.name as $n | broad | index($n)))
            | "kind=\($kind)"
              + (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
              + " name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"
              + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
              + " subject=\(.name) roleRef=\($ref.kind)/\($ref.name) grants=pods/exec is_compliant=false"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        ```
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Fix CBP C2.3: tighten ClusterRoleBindings/RoleBindings that grant create on pods/exec
        # Scope: run from any machine with kubectl access and current-context set to target GKE cluster.

        set -euo pipefail

        # --- Configurable allowlist of subjects that ARE allowed to have pods/exec ---
        # Format: kind:name (kind is one of: User, Group, ServiceAccount)
        # Example entries:
        #   User:alice@example.com
        #   Group:devops
        #   ServiceAccount:exec-operator
        ALLOWED_SUBJECTS=(
          # Add your approved human operators or specific SAs here
          # Example:
          # "User:alice@example.com"
        )

        allowed_subject() {
          local kind="$1" name="$2" s
          for s in "${ALLOWED_SUBJECTS[@]}"; do
            if [[ "$s" == "${kind}:${name}" ]]; then
              return 0
            fi
          done
          return 1
        }

        require_cmd() {
          command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }
        }

        require_cmd kubectl
        require_cmd jq

        echo "Discovering Roles/ClusterRoles that grant create on pods/exec ..."

        # Collect roles/clusterroles and bindings once
        roles_json="$(kubectl get roles,clusterroles --all-namespaces -o json)"
        bindings_json="$(kubectl get rolebindings,clusterrolebindings --all-namespaces -o json)"

        # Compute execRoles (namespaced and cluster-wide)
        exec_roles_json="$(
          jq -r '
            [ .items[]
              | select(any(.rules[]?;
                  (any(.resources[]?; . == "pods/exec" or . == "*"))
                  and (any(.verbs[]?; . == "create" or . == "*"))))
              | { kind: .kind, ns: (.metadata.namespace // ""), name: .metadata.name }
            ]' <<<"$roles_json"
        )"

        if [[ "$(jq 'length' <<<"$exec_roles_json")" -eq 0 ]]; then
          echo "No Roles/ClusterRoles grant create on pods/exec; nothing to change."
        else
          echo "Found $(jq 'length' <<<"$exec_roles_json") Roles/ClusterRoles granting pods/exec."
        fi

        # Extract violating bindings: those that bind execRoles to "broad" subjects
        violating_bindings_json="$(
          jq -r --argjson execRoles "$exec_roles_json" '
            def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];
            [ .items[]
              | .kind as $kind | .metadata as $m | .roleRef as $ref
              | select(any($execRoles[];
                  .name == $ref.name and .kind == $ref.kind
                  and (.ns == "" or .ns == ($m.namespace // ""))))
              | . as $b
              | ((.subjects // [])[]
                 | select(.name as $n | broad | index($n))
                 | { bindingKind: $kind,
                     bindingNS: ($m.namespace // ""),
                     bindingName: $m.name,
                     subjectKind: .kind,
                     subjectName: .name })
            ]' <<<"$bindings_json"
        )"

        if [[ "$(jq 'length' <<<"$violating_bindings_json")" -eq 0 ]]; then
          echo "No bindings grant pods/exec to broad subjects; cluster is already compliant."
        else
          echo "Processing $(jq 'length' <<<"$violating_bindings_json") violating subject bindings..."
        fi

        # Group by binding (kind/ns/name) and patch them
        mapfile -t bindings < <(jq -r '
          map(.bindingKind + "|" + .bindingNS + "|" + .bindingName) | unique[]' \
          <<<"$violating_bindings_json")

        for b in "${bindings[@]}"; do
          IFS='|' read -r bKind bNS bName <<<"$b"
          ns_arg=()
          [[ -n "$bNS" ]] && ns_arg=( -n "$bNS" )

          echo "Inspecting $bKind $([[ -n "$bNS" ]] && echo "$bNS/")$bName ..."

          # Get full binding
          b_json="$(kubectl get "$bKind" "${ns_arg[@]}" "$bName" -o json)"

          # Build new subjects array:
          # - drop broad subjects
          # - keep any non-broad subjects that are in ALLOWED_SUBJECTS
          new_subjects="$(
            jq --argjson vb "$violating_bindings_json" '
              def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];
              . as $orig
              | .subjects // [] 
              | map(
                  if (.name as $n | broad | index($n)) then
                    empty      # drop all broad subjects
                  else
                    # only keep explicitly allowed non-broad subjects (optional gate)
                    (if (.kind and .name) then
                       .kind + ":" + .name
                     else
                       ""
                     end) as $id
                    | if $id == "" then empty
                      else $id
                      end
                  end
                )
              ' <<<"$b_json"
          )"

          # Reconstruct subjects list from allowed IDs
          if [[ -z "$new_subjects" ]]; then
            # Could happen if there were only broad subjects and no allowed ones
            subject_array="[]"
          else
            subject_array="$(
              jq -n --argjson ids "$new_subjects" '
                [ $ids[]
                  | capture("(?<kind>[^:]+):(?<name>.+)")
                  | {kind: .kind, name: .name}
                ]'
            )"
          fi

          # Additionally, preserve any existing specific subjects that were not broad,
          # regardless of ALLOWED_SUBJECTS, to avoid over-revocation.
          preserved_specific="$(
            jq '
              def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];
              (.subjects // [])
              | map(select(.name as $n | (broad | index($n)) | not))
            ' <<<"$b_json"
          )"

          # Merge: preserved_specific ∪ subject_array, keyed by kind+name (idempotent)
          merged_subjects="$(
            jq -n --argjson a "$preserved_specific" --argjson b "$subject_array" '
              ( ($a + $b)
                | (map({ key: (.kind + ":" + .name), value: . }) | from_entries) )
              | [.[]]
            '
          )"

          # If merged_subjects is empty, we effectively remove all subjects from the binding,
          # which strips access but keeps the object; this is safe and idempotent.
          tmp_patch="$(mktemp)"
          cat >"$tmp_patch" <<EOF
        {"subjects": $merged_subjects}
        EOF

          echo "Patching $bKind $([[ -n "$bNS" ]] && echo "$bNS/")$bName to remove broad subjects..."
          kubectl patch "$bKind" "${ns_arg[@]}" "$bName" --type=merge -p "$(cat "$tmp_patch")" >/dev/null

          rm -f "$tmp_patch"
        done

        echo "Re-running compliance check..."

        { kubectl get roles,clusterroles --all-namespaces -o json
          kubectl get rolebindings,clusterrolebindings --all-namespaces -o json
        } | jq -rs '
          .[0] as $roles | .[1] |
          def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];
          [ $roles.items[]
            | select(any(.rules[]?;
                (any(.resources[]?; . == "pods/exec" or . == "*"))
                and (any(.verbs[]?; . == "create" or . == "*"))))
            | { kind: .kind, ns: (.metadata.namespace // ""), name: .metadata.name } ] as $execRoles
          | [ .items[]
            | .kind as $kind | .metadata as $m | .roleRef as $ref
            | select(any($execRoles[];
                .name == $ref.name and .kind == $ref.kind
                and (.ns == "" or .ns == ($m.namespace // ""))))
            | ((.subjects // [])[] | select(.name as $n | broad | index($n)))
            | "kind=\($kind)"
              + (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
              + " name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"
              + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
              + " subject=\(.name) roleRef=\($ref.kind)/\($ref.name) grants=pods/exec is_compliant=false"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
