> ## 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 ServiceAccounts 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, review what it actually needs to do (on any machine with `kubectl` access):
           * Inspect workloads using the ServiceAccount:
             ```bash theme={null}
             kubectl get pods --all-namespaces -o json | jq -r '
               .items[]
               | select(.spec.serviceAccountName == "SERVICEACCOUNT_NAME")
               | "ns=\(.metadata.namespace) pod=\(.metadata.name)"
             '
             ```
           * Replace `SERVICEACCOUNT_NAME` with the name from step 1, and note the namespaces and pods.

        3. Design and create a minimally-privileged Role/ClusterRole that grants only the necessary permissions (on any machine with `kubectl` access). Example template (edit apiGroups, resources, verbs, and scope before applying):
           ```bash theme={null}
           cat << 'EOF' > sa-limited-role.yaml
           apiVersion: rbac.authorization.k8s.io/v1
           kind: ClusterRole
           metadata:
             name: sa-limited-role
           rules:
             - apiGroups: [""]
               resources: ["pods"]
               verbs: ["get", "list"]
           EOF

           kubectl apply -f sa-limited-role.yaml
           ```
           If access is only needed within one namespace, change `kind: ClusterRole` to `kind: Role` and add `metadata.namespace: TARGET_NAMESPACE`.

        4. Bind the ServiceAccount to the new limited Role/ClusterRole (on any machine with `kubectl` access). Adjust names and namespace as needed:
           ```bash theme={null}
           kubectl create clusterrolebinding sa-limited-binding \
             --clusterrole=sa-limited-role \
             --serviceaccount=SERVICEACCOUNT_NAMESPACE:SERVICEACCOUNT_NAME
           ```
           For namespace-scoped permissions instead, use:
           ```bash theme={null}
           kubectl create rolebinding sa-limited-binding \
             --role=sa-limited-role \
             --serviceaccount=SERVICEACCOUNT_NAMESPACE:SERVICEACCOUNT_NAME \
             --namespace=SERVICEACCOUNT_NAMESPACE
           ```

        5. Remove the insecure `cluster-admin` binding once you have confirmed workloads still function as expected (on any machine with `kubectl` access). For each violating ClusterRoleBinding name from step 1:
           ```bash theme={null}
           kubectl delete clusterrolebinding CLUSTERROLEBINDING_NAME
           ```

        6. Verify no ServiceAccount is bound to `cluster-admin` anymore (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'
           ```
           Compliance is achieved when the output is exactly:
           ```text theme={null}
           is_compliant=true
           ```
      </Accordion>

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

        1. Identify 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) 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 ClusterRoleBinding, inspect it to understand what it’s used for:

        ```bash theme={null}
        kubectl get clusterrolebinding <clusterrolebinding-name> -o yaml
        ```

        3. (Optional but recommended) Create a narrowly-scoped Role/ClusterRole and binding for the workload, based on its actual needs. Example pattern (replace placeholders with real values):

        Namespace-scoped permissions:

        ```yaml theme={null}
        apiVersion: rbac.authorization.k8s.io/v1
        kind: Role
        metadata:
          name: <minimal-role-name>
          namespace: <sa-namespace>
        rules:
        - apiGroups: [""]
          resources: ["pods"]
          verbs: ["get", "list"]
        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: RoleBinding
        metadata:
          name: <minimal-rolebinding-name>
          namespace: <sa-namespace>
        subjects:
        - kind: ServiceAccount
          name: <sa-name>
          namespace: <sa-namespace>
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: Role
          name: <minimal-role-name>
        ```

        Cluster-scoped permissions (only if truly required):

        ```yaml theme={null}
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRole
        metadata:
          name: <minimal-clusterrole-name>
        rules:
        - apiGroups: [""]
          resources: ["nodes"]
          verbs: ["get", "list"]
        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRoleBinding
        metadata:
          name: <minimal-clusterrolebinding-name>
        subjects:
        - kind: ServiceAccount
          name: <sa-name>
          namespace: <sa-namespace>
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: ClusterRole
          name: <minimal-clusterrole-name>
        ```

        Apply your minimal RBAC:

        ```bash theme={null}
        kubectl apply -f minimal-rbac.yaml
        ```

        4. Delete the ClusterRoleBinding that grants `cluster-admin` to a ServiceAccount:

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

        Repeat for every offending binding reported by the audit.

        5. Verification (cluster is compliant when only `is_compliant=true` is printed):

        ```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
        #
        # Remove any ServiceAccount bindings to the cluster-admin ClusterRole.
        # Scope: run on any machine with kubectl and jq configured for the target GKE cluster.
        set -euo pipefail

        # Fail fast if required tools are missing
        command -v kubectl >/dev/null 2>&1 || { echo "kubectl not found in PATH"; exit 1; }
        command -v jq >/dev/null 2>&1 || { echo "jq not found in PATH"; exit 1; }

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

        # Find offending ClusterRoleBindings (those with roleRef=cluster-admin and at least one ServiceAccount subject)
        mapfile -t CRBS_WITH_SA < <(
          kubectl get clusterrolebindings -o json \
          | jq -r '
              .items[]
              | select(.roleRef.name == "cluster-admin")
              | select((.subjects // [])[]? | .kind == "ServiceAccount")
              | .metadata.name
            ' \
          | sort -u
        )

        if [ "${#CRBS_WITH_SA[@]}" -eq 0 ]; then
          echo "No ClusterRoleBindings bind ServiceAccounts to cluster-admin. Nothing to remediate."
        else
          echo "The following ClusterRoleBindings bind ServiceAccounts to cluster-admin and will be deleted:"
          for crb in "${CRBS_WITH_SA[@]}"; do
            echo "  - ${crb}"
          done

          # Delete the offending ClusterRoleBindings (idempotent: delete is a no-op if already gone)
          for crb in "${CRBS_WITH_SA[@]}"; do
            echo "Deleting ClusterRoleBinding ${crb}..."
            # Use --ignore-not-found to allow safe re-runs
            kubectl delete clusterrolebinding "${crb}" --ignore-not-found
          done
        fi

        echo "Verifying compliance..."

        # Re-run the audit logic to confirm there are no ServiceAccounts bound to cluster-admin
        AUDIT_OUTPUT="$(
        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'
        )"

        echo "${AUDIT_OUTPUT}"

        if grep -q '^is_compliant=true$' <<< "${AUDIT_OUTPUT}"; then
          echo "Remediation successful: no ServiceAccounts are bound to cluster-admin."
          exit 0
        else
          echo "Remediation incomplete: some ServiceAccount bindings to cluster-admin remain."
          exit 1
        fi
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
