> ## 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 ClusterRoleBindings that grant `pods/exec` to broad subjects
           * Run on: any machine with kubectl access
           ```bash theme={null}
           # Identify bindings flagged by the audit (copy relevant lines or rerun the jq if needed)
           kubectl get clusterrolebindings -o yaml | grep -n "system:authenticated\|system:unauthenticated\|system:anonymous\|system:serviceaccounts" -n
           ```

        2. Inspect each implicated ClusterRole and its bindings
           * Run on: any machine with kubectl access\
             Replace `<clusterrole-name>` with the name from the audit output.
           ```bash theme={null}
           # See which permissions include pods/exec
           kubectl get clusterrole <clusterrole-name> -o yaml

           # See which ClusterRoleBindings use this ClusterRole
           kubectl get clusterrolebindings \
             -o jsonpath='{range .items[?(@.roleRef.kind=="ClusterRole" && @.roleRef.name=="<clusterrole-name>")]}{@.metadata.name}{"\n"}{end}'
           ```

        3. Remove broad subjects from each non-essential binding and replace with named users/groups
           * Run on: any machine with kubectl access\
             For each implicated ClusterRoleBinding `<binding-name>`:
           ```bash theme={null}
           # Edit the binding to remove broad subjects and add specific subjects
           kubectl edit clusterrolebinding <binding-name>
           ```
           In the editor:
           * Under `subjects:`, **remove** any entries with:
             * `name: system:authenticated`
             * `name: system:unauthenticated`
             * `name: system:anonymous`
             * `kind: Group` and `name: system:serviceaccounts` (or similar broad SA groups)
           * Optionally **add** explicit human operators or specific groups, for example:
             ```yaml theme={null}
             subjects:
               - kind: User
                 name: alice@example.com
                 apiGroup: rbac.authorization.k8s.io
               - kind: Group
                 name: ops-team
                 apiGroup: rbac.authorization.k8s.io
             ```

        4. If needed, split shared ClusterRoles to avoid giving `pods/exec` broadly
           * Run on: any machine with kubectl access\
             When a ClusterRole is used by both broad and restricted bindings and only some need `pods/exec`:
           ```bash theme={null}
           # Export the existing ClusterRole
           kubectl get clusterrole <clusterrole-name> -o yaml > /tmp/<clusterrole-name>.yaml

           # Create a copy for operators-only that still has pods/exec
           cp /tmp/<clusterrole-name>.yaml /tmp/<clusterrole-name>-operators.yaml
           sed -i 's/name: <clusterrole-name>/name: <clusterrole-name>-operators/' /tmp/<clusterrole-name>-operators.yaml

           # In the ORIGINAL file, remove pods/exec (and any '*' that implies it) from rules:
           #   - Edit /tmp/<clusterrole-name>.yaml manually to delete "pods/exec" from resources
           #     and avoid "*" covering it.
           # Apply both roles:
           kubectl apply -f /tmp/<clusterrole-name>.yaml
           kubectl apply -f /tmp/<clusterrole-name>-operators.yaml
           ```

        5. Point restricted operators at the new operators-only ClusterRole
           * Run on: any machine with kubectl access\
             For bindings that should retain `pods/exec`, update `roleRef.name`:
           ```bash theme={null}
           kubectl edit clusterrolebinding <binding-name>
           ```
           In the editor, change:
           ```yaml theme={null}
           roleRef:
             kind: ClusterRole
             name: <clusterrole-name>
           ```
           to:
           ```yaml theme={null}
           roleRef:
             kind: ClusterRole
             name: <clusterrole-name>-operators
           ```

        6. Verification: confirm no broad subjects have `create` on `pods/exec`
           * Run on: any machine with kubectl access
           ```bash 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)))
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```
           Ensure the output is `is_compliant=true`.
      </Accordion>

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

        1. Identify the offending bindings and their ClusterRoles (from the audit output)

        Use the audit output to note:

        * The binding kind/name/namespace
        * The roleRef (ClusterRole name)
        * The broad subject (e.g. `system:authenticated`, `system:serviceaccounts`)

        2. Inspect the ClusterRole rules granting `pods/exec`

        ```bash theme={null}
        kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
        ```

        Look for rules like:

        ```yaml theme={null}
        rules:
        - apiGroups: [""]
          resources: ["pods/exec"]        # or ["*"]
          verbs: ["create"]               # or ["*"]
        ```

        3. Restrict `pods/exec` to a small set of named human operators

        a) Create a dedicated ClusterRole that only grants `pods/exec`:

        ```bash theme={null}
        cat << 'EOF' > exec-into-pods-clusterrole.yaml
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRole
        metadata:
          name: exec-into-pods
        rules:
        - apiGroups: [""]
          resources: ["pods/exec"]
          verbs: ["create"]
        EOF

        kubectl apply -f exec-into-pods-clusterrole.yaml
        ```

        b) Bind that role only to specific named users or a single dedicated SA (example with two Azure AD users):

        ```bash theme={null}
        cat << 'EOF' > exec-into-pods-clusterrolebinding.yaml
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRoleBinding
        metadata:
          name: exec-into-pods-operators
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: ClusterRole
          name: exec-into-pods
        subjects:
        - kind: User
          name: "alice@contoso.com"
        - kind: User
          name: "bob@contoso.com"
        EOF

        kubectl apply -f exec-into-pods-clusterrolebinding.yaml
        ```

        Adjust `subjects` to your actual Azure AD user principals or a single service account as appropriate.

        4. Remove broad subjects from existing offending bindings

        For each binding reported by the audit as non‑compliant, edit it to remove broad subjects such as `system:authenticated`, `system:unauthenticated`, `system:anonymous`, `system:serviceaccounts`, and (if present) any `Group` that represents all users.

        ClusterRoleBinding (cluster‑scoped):

        ```bash theme={null}
        kubectl edit clusterrolebinding <BINDING_NAME>
        ```

        RoleBinding (namespaced):

        ```bash theme={null}
        kubectl edit rolebinding <BINDING_NAME> -n <NAMESPACE>
        ```

        In the opened YAML, under `subjects:`, delete entries like:

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

        If the binding is only used to grant `pods/exec` broadly and you have replaced it with the new, restricted binding, you can optionally delete the old binding entirely:

        ```bash theme={null}
        kubectl delete clusterrolebinding <BINDING_NAME>
        # or
        kubectl delete rolebinding <BINDING_NAME> -n <NAMESPACE>
        ```

        5. (Optional) Narrow the original ClusterRole if it is too broad

        If the original ClusterRole grants `pods/exec` alongside other permissions and is bound to many subjects, you can remove `pods/exec` from it and rely on the new `exec-into-pods` ClusterRole for exec access:

        ```bash theme={null}
        kubectl edit clusterrole <CLUSTERROLE_NAME>
        ```

        Under `rules:`, either:

        * Remove `"pods/exec"` from `resources`, or
        * Split rules so that non‑exec permissions stay in this ClusterRole, and exec is granted only by the new dedicated ClusterRole.

        6. Verification

        Run the same logic used by the audit to confirm there are no bindings granting `create` on `pods/exec` to broad subjects:

        ```bash 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
        #
        # Restrict broad subjects from ClusterRoles that grant create on pods/exec
        # Platform: Azure AKS
        # Runs on: any machine with kubectl access and jq installed
        #
        # Behavior:
        # - Detects ClusterRoleBindings that:
        #     * bind a ClusterRole/Role that allows create (or *) on pods/exec (or *)
        #     * and have at least one subject in:
        #         system:authenticated, system:unauthenticated,
        #         system:anonymous, system:serviceaccounts
        # - For each such binding:
        #     * removes the broad subjects from the binding
        #     * leaves any named users, groups, or individual service accounts intact
        # - Safe to re-run: when no broad subjects remain, no changes are made
        # - Verification: re-runs the benchmark jq audit at the end

        set -euo pipefail

        # -------- Preconditions --------
        if ! command -v kubectl >/dev/null 2>&1; then
          echo "kubectl not found in PATH. Install kubectl and ensure KUBECONFIG is set." >&2
          exit 1
        fi

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

        echo "Fetching current Roles/ClusterRoles and Bindings..."
        TMPDIR="$(mktemp -d)"
        trap 'rm -rf "'"$TMPDIR"'"' EXIT

        ROLES_JSON="${TMPDIR}/roles.json"
        BINDINGS_JSON="${TMPDIR}/bindings.json"

        kubectl get roles,clusterroles --all-namespaces -o json > "${ROLES_JSON}"
        kubectl get rolebindings,clusterrolebindings --all-namespaces -o json > "${BINDINGS_JSON}"

        # -------- Identify exec-capable roles (pods/exec create or *) --------
        echo "Identifying Roles/ClusterRoles that can create pods/exec..."
        EXEC_ROLES_JSON="${TMPDIR}/exec_roles.json"

        jq '
          [ .items[]
            | select(any(.rules[]?;
                (any(.resources[]?; . == "pods/exec" or . == "*"))
                and (any(.verbs[]?; . == "create" or . == "*"))))
            | { kind: .kind, ns: (.metadata.namespace // ""), name: .metadata.name }
          ]
        ' "${ROLES_JSON}" > "${EXEC_ROLES_JSON}"

        if [[ "$(jq 'length' "${EXEC_ROLES_JSON}")" -eq 0 ]]; then
          echo "No Roles or ClusterRoles grant create on pods/exec. Nothing to do."
          exit 0
        fi

        # -------- Process RoleBindings and ClusterRoleBindings --------
        echo "Scanning RoleBindings and ClusterRoleBindings for broad subjects..."

        BROAD_SUBJECTS='["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"]'

        # Function to patch a single binding (RoleBinding or ClusterRoleBinding)
        patch_binding() {
          local ns kind name
          ns="$1"     # can be empty for ClusterRoleBinding
          kind="$2"   # RoleBinding or ClusterRoleBinding
          name="$3"

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

          # Get current YAML
          if [[ "${kind}" == "ClusterRoleBinding" ]]; then
            kubectl get clusterrolebinding "${name}" -o yaml > "${TMPDIR}/binding.yaml"
          else
            kubectl -n "${ns}" get rolebinding "${name}" -o yaml > "${TMPDIR}/binding.yaml"
          fi

          # Use yq if available; otherwise fall back to kubectl+jsonpatch
          if command -v yq >/dev/null 2>&1; then
            # Remove broad subjects from subjects list, if any
            yq eval '
              .subjects |= (
                . // [] |
                map(select(
                  (.kind == "Group" and .name == "system:authenticated" )   | not and
                  (.kind == "Group" and .name == "system:unauthenticated" ) | not and
                  (.kind == "User"  and .name == "system:anonymous" )       | not and
                  (.kind == "Group" and .name == "system:serviceaccounts" ) | not
                ))
              )
            ' "${TMPDIR}/binding.yaml" > "${TMPDIR}/binding_patched.yaml"

            # Apply only if changed
            if ! diff -q "${TMPDIR}/binding.yaml" "${TMPDIR}/binding_patched.yaml" >/dev/null 2>&1; then
              echo "  Removing broad subjects from ${kind}/${name}..."
              kubectl apply -f "${TMPDIR}/binding_patched.yaml"
            else
              echo "  No broad subjects present; no change."
            fi
          else
            # yq not available: do JSON patch via kubectl
            echo "  yq not found; using jsonpatch via kubectl."

            # Build JSON representation of subjects without broad subjects
            local subj_json filtered_json
            if [[ "${kind}" == "ClusterRoleBinding" ]]; then
              subj_json="$(kubectl get clusterrolebinding "${name}" -o json | jq '.subjects // []')"
            else
              subj_json="$(kubectl -n "${ns}" get rolebinding "${name}" -o json | jq '.subjects // []')"
            fi

            filtered_json="$(jq --argjson broad "${BROAD_SUBJECTS}" '
              [ .[]
                | select(
                    (.kind == "Group" and (.name | IN($broad[]))) | not and
                    (.kind == "User"  and (.name == "system:anonymous")) | not
                  )
              ]
            ' <<< "${subj_json}")"

            if [[ "${subj_json}" == "${filtered_json}" ]]; then
              echo "  No broad subjects present; no change."
              return
            fi

            # Construct and apply JSON patch
            local patch
            patch="$(jq -n --argjson subs "${filtered_json}" '[{"op":"replace","path":"/subjects","value":$subs}]')"

            echo "  Removing broad subjects from ${kind}/${name} via jsonpatch..."
            if [[ "${kind}" == "ClusterRoleBinding" ]]; then
              kubectl patch clusterrolebinding "${name}" --type=json -p "${patch}"
            else
              kubectl -n "${ns}" patch rolebinding "${name}" --type=json -p "${patch}"
            fi
          fi
        }

        export TMPDIR BROAD_SUBJECTS
        export -f patch_binding

        # Build list of offending bindings and invoke patch_binding for each
        jq -rs --argjson broad "${BROAD_SUBJECTS}" '
          .[0] as $roles | .[1] |
          [ $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 // ""))))
            | { kind: $kind, ns: ($m.namespace // ""), name: .metadata.name,
                subjects: (.subjects // []) }
            | select(any(.subjects[];
                (.kind == "Group" and .name as $n | $broad | index($n))
                or (.kind == "User" and .name == "system:anonymous")))
          ]
        ' "${ROLES_JSON}" "${BINDINGS_JSON}" > "${TMPDIR}/offending_bindings.json"

        COUNT="$(jq 'length' "${TMPDIR}/offending_bindings.json")"
        if [[ "${COUNT}" -eq 0 ]]; then
          echo "No RoleBindings or ClusterRoleBindings with broad subjects and pods/exec create found. Nothing to change."
        else
          echo "Found ${COUNT} offending bindings. Updating..."
          # Iterate in bash to keep compatibility
          for i in $(seq 0 $((COUNT - 1))); do
            ns="$(jq -r ".[$i].ns" "${TMPDIR}/offending_bindings.json")"
            kind="$(jq -r ".[$i].kind" "${TMPDIR}/offending_bindings.json")"
            name="$(jq -r ".[$i].name" "${TMPDIR}/offending_bindings.json")"
            patch_binding "${ns}" "${kind}" "${name}"
          done
        fi

        # -------- Verification (re-run audit) --------
        echo
        echo "Re-running audit to verify that no broad subjects can exec pods..."
        {
          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>
