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

# No ServiceAccount Should Be Bound To cluster-admin

### More Info:

Verifies no ServiceAccount is bound to the cluster-admin ClusterRole. Such a binding hands full cluster control to any workload using that account.

### Risk Level

Critical

### 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 ServiceAccounts currently bound to `cluster-admin` (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl get clusterrolebindings -o json | jq -r '
             [ .items[]
               | select(.roleRef.name == "cluster-admin")
               | .metadata as $m
               | ((.subjects // [])[] | select(.kind == "ServiceAccount"))
               | "kind=ClusterRoleBinding name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"
                 + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
                 + " sa=\(.namespace)/\(.name) roleRef=cluster-admin is_compliant=false"
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```

        2. For each violating ServiceAccount, identify what access it truly needs by checking its namespace, name, and associated workloads (run on any machine with kubectl access):
           ```bash theme={null}
           # Example: inspect one violating ServiceAccount
           kubectl get serviceaccount -n NAMESPACE SA_NAME -o yaml

           # See which pods use it
           kubectl get pods -n NAMESPACE -o wide --field-selector spec.serviceAccountName=SA_NAME
           ```

        3. Design least-privilege RBAC for that ServiceAccount by creating a dedicated `Role`/`ClusterRole` and binding (run on any machine with kubectl access). Example template (edit apiGroups, resources, verbs, and scope as required, then apply):
           ```bash theme={null}
           cat << 'EOF' > sa-least-priv-role.yaml
           apiVersion: rbac.authorization.k8s.io/v1
           kind: Role
           metadata:
             name: SA_NAME-minimal
             namespace: NAMESPACE
           rules:
             - apiGroups: [""]
               resources: ["pods"]
               verbs: ["get", "list"]
           ---
           apiVersion: rbac.authorization.k8s.io/v1
           kind: RoleBinding
           metadata:
             name: SA_NAME-minimal-binding
             namespace: NAMESPACE
           subjects:
             - kind: ServiceAccount
               name: SA_NAME
               namespace: NAMESPACE
           roleRef:
             kind: Role
             name: SA_NAME-minimal
             apiGroup: rbac.authorization.k8s.io
           EOF

           kubectl apply -f sa-least-priv-role.yaml
           ```

        4. After confirming the workload still functions with the new least-privilege Role/ClusterRole, delete the `cluster-admin` binding(s) that reference ServiceAccounts (run on any machine with kubectl access). For each violating ClusterRoleBinding `CRB_NAME` from step 1:
           ```bash theme={null}
           kubectl delete clusterrolebinding CRB_NAME
           ```

        5. If a `cluster-admin` ClusterRoleBinding also includes non-ServiceAccount subjects (e.g., users/groups) that must be retained, recreate an equivalent binding without the ServiceAccounts before deleting the original (run on any machine with kubectl access):
           ```bash theme={null}
           # Export the existing binding
           kubectl get clusterrolebinding CRB_NAME -o yaml > crb-original.yaml

           # Edit crb-original.yaml: remove all subjects with kind: ServiceAccount, keep only required User/Group subjects
           # and ensure roleRef.name: cluster-admin is unchanged.

           kubectl apply -f crb-original.yaml
           # Then remove any remaining ServiceAccount-only bindings as in step 4
           ```

        6. Verify no ServiceAccount is bound to `cluster-admin` (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl get clusterrolebindings -o json | jq -r '
             [ .items[]
               | select(.roleRef.name == "cluster-admin")
               | .metadata as $m
               | ((.subjects // [])[] | select(.kind == "ServiceAccount"))
               | "kind=ClusterRoleBinding name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"
                 + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
                 + " sa=\(.namespace)/\(.name) roleRef=cluster-admin is_compliant=false"
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```
           The cluster is compliant when the output is exactly:
           ```text theme={null}
           is_compliant=true
           ```
      </Accordion>

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

        1. List ServiceAccounts bound to `cluster-admin`:

        ```bash theme={null}
        kubectl get clusterrolebindings -o json | jq -r '
          [ .items[]
            | select(.roleRef.name == "cluster-admin")
            | .metadata as $m
            | ((.subjects // [])[] | select(.kind == "ServiceAccount"))
            | "kind=ClusterRoleBinding name=\($m.name) sa=\(.namespace)/\(.name)"
          ][]'
        ```

        2. For each violating ClusterRoleBinding, delete it. Example (replace the name with each one you found):

        ```bash theme={null}
        kubectl delete clusterrolebinding <clusterrolebinding-name>
        ```

        If you need to recreate least-privilege access, first define a narrowed ClusterRole and binding (example pattern, adjust rules to actual needs):

        ```yaml theme={null}
        # save as sa-limited-access.yaml and edit rules/subjects appropriately
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRole
        metadata:
          name: my-workload-limited
        rules:
          - apiGroups: [""]
            resources: ["pods"]
            verbs: ["get", "list"]
        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRoleBinding
        metadata:
          name: my-workload-limited-binding
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: ClusterRole
          name: my-workload-limited
        subjects:
          - kind: ServiceAccount
            name: my-workload-sa
            namespace: my-namespace
        ```

        Apply the least-privilege RBAC:

        ```bash theme={null}
        kubectl apply -f sa-limited-access.yaml
        ```

        Verification (cluster is compliant when this prints `is_compliant=true`):

        ```bash theme={null}
        kubectl get clusterrolebindings -o json | jq -r '
          [ .items[]
            | select(.roleRef.name == "cluster-admin")
            | .metadata as $m
            | ((.subjects // [])[] | select(.kind == "ServiceAccount"))
            | "kind=ClusterRoleBinding name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"
              + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
              + " sa=\(.namespace)/\(.name) roleRef=cluster-admin 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
        #
        # Remediation for: "No ServiceAccount Should Be Bound To cluster-admin"
        # Scope: any machine with kubectl access to the OKE cluster
        #
        # Behavior:
        # - Detects ClusterRoleBindings that bind any ServiceAccount to cluster-admin
        # - For each such binding:
        #     * Prints the ServiceAccounts it affects
        #     * Deletes the ClusterRoleBinding
        # - Safe to re-run: only current violating bindings are deleted
        # - Verifies compliance at the end using the provided audit logic
        #
        # Prerequisites:
        # - kubectl configured to talk to the target OKE cluster
        # - jq installed

        set -euo pipefail

        echo "=== Detecting ClusterRoleBindings that bind ServiceAccounts to cluster-admin ==="

        # Capture all violating ClusterRoleBindings (names only, unique)
        mapfile -t CRBS_TO_DELETE < <(
          kubectl get clusterrolebindings -o json | jq -r '
            [ .items[]
              | select(.roleRef.name == "cluster-admin")
              | select((.subjects // [])[]? | .kind == "ServiceAccount")
              | .metadata.name
            ] | unique[]?'
        )

        if [ "${#CRBS_TO_DELETE[@]}" -eq 0 ]; then
          echo "No ClusterRoleBindings bind ServiceAccounts to cluster-admin."
        else
          echo "Found ${#CRBS_TO_DELETE[@]} violating ClusterRoleBinding(s):"
          printf '  - %s\n' "${CRBS_TO_DELETE[@]}"
          echo

          for crb in "${CRBS_TO_DELETE[@]}"; do
            echo "=== Details for ClusterRoleBinding: ${crb} ==="
            # List only ServiceAccount subjects for visibility before deletion
            kubectl get clusterrolebinding "${crb}" -o json | jq -r '
              .subjects // [] | map(select(.kind == "ServiceAccount"))[]
              | "  SA: \(.namespace)/\(.name)"'
            echo "Deleting ClusterRoleBinding ${crb} ..."
            kubectl delete clusterrolebinding "${crb}"
            echo
          done
        fi

        echo "=== Verification (post-remediation audit) ==="
        kubectl get clusterrolebindings -o json | jq -r '
          [ .items[]
            | select(.roleRef.name == "cluster-admin")
            | .metadata as $m
            | ((.subjects // [])[] | select(.kind == "ServiceAccount"))
            | "kind=ClusterRoleBinding name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"
              + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
              + " sa=\(.namespace)/\(.name) roleRef=cluster-admin is_compliant=false"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
