> ## 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 ClusterRoleBindings that bind `cluster-admin` to any ServiceAccount (run on any machine with kubectl access):
           ```sh 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, determine the minimal permissions it actually needs (run on any machine with kubectl access):
           * Inspect its namespace and associated workloads:
             ```sh theme={null}
             kubectl get sa -A
             kubectl get pods -A -o wide --field-selector spec.serviceAccountName=<service-account-name>
             ```
           * Review application requirements (deployment manifests, documentation, and logs for authorization errors).

        3. Create a narrowly scoped Role or ClusterRole that grants only the required verbs and resources (run on any machine with kubectl access, then apply to the cluster):
           * Example namespaced Role (adjust namespace, resources, verbs):
             ```sh theme={null}
             cat << 'EOF' > sa-limited-role.yaml
             apiVersion: rbac.authorization.k8s.io/v1
             kind: Role
             metadata:
               name: <workload-sa-role>
               namespace: <sa-namespace>
             rules:
               - apiGroups: [""]
                 resources: ["pods"]
                 verbs: ["get", "list"]
             EOF

             kubectl apply -f sa-limited-role.yaml
             ```
           * Example RoleBinding to attach it to the ServiceAccount:
             ```sh theme={null}
             cat << 'EOF' > sa-limited-rolebinding.yaml
             apiVersion: rbac.authorization.k8s.io/v1
             kind: RoleBinding
             metadata:
               name: <workload-sa-rolebinding>
               namespace: <sa-namespace>
             subjects:
               - kind: ServiceAccount
                 name: <service-account-name>
                 namespace: <sa-namespace>
             roleRef:
               apiGroup: rbac.authorization.k8s.io
               kind: Role
               name: <workload-sa-role>
             EOF

             kubectl apply -f sa-limited-rolebinding.yaml
             ```

        4. After confirming the workload functions correctly with the new, least-privilege Role/RoleBinding (monitor pod logs and behavior), identify the offending ClusterRoleBinding names (run on any machine with kubectl access):
           ```sh theme={null}
           kubectl get clusterrolebindings -o json | jq -r '
             .items[]
             | select(.roleRef.name == "cluster-admin")
             | select((.subjects // [])[]? | select(.kind == "ServiceAccount"))
             | .metadata.name
           ' | sort -u
           ```

        5. Delete each ClusterRoleBinding that binds any ServiceAccount to `cluster-admin` (run on any machine with kubectl access):
           ```sh theme={null}
           kubectl delete clusterrolebinding <clusterrolebinding-name-1>
           kubectl delete clusterrolebinding <clusterrolebinding-name-2>
           # repeat for all names from step 4
           ```

        6. Verify no ServiceAccount is bound to `cluster-admin` (run on any machine with kubectl access):
           ```sh 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 output must be exactly:
           ```text theme={null}
           is_compliant=true
           ```
      </Accordion>

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

        1. Identify violating ClusterRoleBindings and affected ServiceAccounts

        ```bash theme={null}
        kubectl get clusterrolebindings -o wide
        kubectl get clusterrolebindings -o yaml | grep -A5 "name: cluster-admin"
        ```

        2. For each ClusterRoleBinding that binds a ServiceAccount to `cluster-admin`, design and apply a narrowly-scoped replacement (example only; adjust rules to what the workload actually needs):

        ```bash theme={null}
        cat << 'EOF' > sa-limited-role.yaml
        apiVersion: rbac.authorization.k8s.io/v1
        kind: Role
        metadata:
          name: example-sa-limited
          namespace: default
        rules:
          - apiGroups: [""]
            resources: ["pods"]
            verbs: ["get", "list"]
        EOF

        kubectl apply -f sa-limited-role.yaml
        ```

        Bind the ServiceAccount to the new Role (replace NAMESPACE and SA\_NAME appropriately):

        ```bash theme={null}
        cat << 'EOF' > sa-limited-rolebinding.yaml
        apiVersion: rbac.authorization.k8s.io/v1
        kind: RoleBinding
        metadata:
          name: example-sa-limited-binding
          namespace: default
        subjects:
          - kind: ServiceAccount
            name: SA_NAME
            namespace: NAMESPACE
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: Role
          name: example-sa-limited
        EOF

        kubectl apply -f sa-limited-rolebinding.yaml
        ```

        3. Delete the insecure ClusterRoleBinding(s)

        Replace `BINDING_NAME` with each offending ClusterRoleBinding name:

        ```bash theme={null}
        kubectl delete clusterrolebinding BINDING_NAME
        ```

        4. Verification

        Run the benchmark audit command exactly:

        ```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
        #
        # Automation: Remove ServiceAccount bindings to cluster-admin
        #
        # Runs on: any machine with kubectl access and jq installed
        #
        # Behavior:
        # - Identifies all ClusterRoleBindings that:
        #     * reference roleRef.name == "cluster-admin"
        #     * and have at least one ServiceAccount subject
        # - Prints what it will delete
        # - Deletes only those ClusterRoleBindings
        # - Safe to re-run: deleting an already-removed binding is a no-op
        # - Verifies compliance using the benchmark audit command

        set -euo pipefail

        echo "Checking for ClusterRoleBindings that bind ServiceAccounts to cluster-admin..."

        # Capture current violating ClusterRoleBindings (names only, de-duplicated)
        violating_crbs=$(kubectl get clusterrolebindings -o json | jq -r '
          [ .items[]
            | select(.roleRef.name == "cluster-admin")
            | select(((.subjects // [])[] | select(.kind == "ServiceAccount")) | length > 0)
            | .metadata.name
          ] | unique[]?')

        if [[ -z "${violating_crbs}" ]]; then
          echo "No ClusterRoleBindings with ServiceAccounts bound to cluster-admin found."
        else
          echo "The following ClusterRoleBindings bind ServiceAccounts to cluster-admin and will be deleted:"
          printf '  %s\n' ${violating_crbs}

          # Delete each violating ClusterRoleBinding
          for crb in ${violating_crbs}; do
            echo "Deleting ClusterRoleBinding: ${crb}"
            kubectl delete clusterrolebinding "${crb}"
          done
        fi

        echo
        echo "Verification (should print 'is_compliant=true' if no violations remain):"
        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>
