> ## 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, inspect what it is used by so you can design a narrower Role/ClusterRole (run on any machine with kubectl access, replace `NAMESPACE` and `SA_NAME` from step 1 output):
           ```bash theme={null}
           kubectl get pods -A -o json | jq -r '
             .items[]
             | select(.spec.serviceAccountName == "SA_NAME" and .metadata.namespace == "NAMESPACE")
             | "ns=\(.metadata.namespace) pod=\(.metadata.name)"
           '
           ```

        3. Create a least-privilege Role or ClusterRole with exactly the permissions the workload needs (example skeleton; edit rules before applying, run on any machine with kubectl access):
           ```bash theme={null}
           cat << 'EOF' > sa-least-priv-role.yaml
           apiVersion: rbac.authorization.k8s.io/v1
           kind: Role
           metadata:
             name: sa-least-priv-role
             namespace: NAMESPACE
           rules:
             # TODO: replace with only required resources/verbs
             - apiGroups: [""]
               resources: ["pods"]
               verbs: ["get", "list"]
           ---
           apiVersion: rbac.authorization.k8s.io/v1
           kind: RoleBinding
           metadata:
             name: sa-least-priv-binding
             namespace: NAMESPACE
           subjects:
             - kind: ServiceAccount
               name: SA_NAME
               namespace: NAMESPACE
           roleRef:
             apiGroup: rbac.authorization.k8s.io
             kind: Role
             name: sa-least-priv-role
           EOF

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

        4. If the ServiceAccount truly requires cluster‑scope privileges (rare), create a narrowly scoped ClusterRole instead (edit rules before applying, run on any machine with kubectl access):
           ```bash theme={null}
           cat << 'EOF' > sa-least-priv-clusterrole.yaml
           apiVersion: rbac.authorization.k8s.io/v1
           kind: ClusterRole
           metadata:
             name: sa-least-priv-clusterrole
           rules:
             # TODO: replace with only required resources/verbs at cluster scope
             - apiGroups: [""]
               resources: ["nodes"]
               verbs: ["get", "list"]
           ---
           apiVersion: rbac.authorization.k8s.io/v1
           kind: ClusterRoleBinding
           metadata:
             name: sa-least-priv-clusterrolebinding
           subjects:
             - kind: ServiceAccount
               name: SA_NAME
               namespace: NAMESPACE
           roleRef:
             apiGroup: rbac.authorization.k8s.io
             kind: ClusterRole
             name: sa-least-priv-clusterrole
           EOF

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

        5. After confirming workloads function with the new least‑privilege bindings, delete each offending `cluster-admin` ClusterRoleBinding (run on any machine with kubectl access, substitute the `name` from step 1 output):
           ```bash theme={null}
           kubectl delete clusterrolebinding BINDING_NAME
           ```

        6. Verify no ServiceAccount remains 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'
           ```
           A compliant cluster prints:
           ```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`, delete it (replace `BINDING_NAME` with the name from step 1):

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

        If you need to recreate a narrower-scope binding for the same ServiceAccount, define a dedicated `ClusterRole` or `Role` and binding, for example:

        ```yaml theme={null}
        # save as rbac-limited.yaml and adjust rules/subjects as needed
        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: RoleBinding
        metadata:
          name: my-workload-limited-binding
          namespace: my-namespace
        subjects:
          - kind: ServiceAccount
            name: my-serviceaccount
            namespace: my-namespace
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: ClusterRole
          name: my-workload-limited
        ```

        Apply the narrowed permissions:

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

        3. Verification (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'
        ```
      </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 EKS cluster
        #
        # Requirements:
        #   - kubectl installed and configured (KUBECONFIG or in-cluster)
        #   - jq installed
        #
        # Behavior:
        #   - Identifies all ClusterRoleBindings that:
        #       * reference roleRef.name == "cluster-admin"
        #       * have at least one subject of kind == "ServiceAccount"
        #   - Deletes those ClusterRoleBindings
        #   - Safe to re-run: no-op if there are no such bindings
        #   - Prints a post-remediation verification using the benchmark’s audit query

        set -euo pipefail

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

        # Get names of offending ClusterRoleBindings
        mapfile -t CRBS_TO_DELETE < <(
          kubectl get clusterrolebindings -o json | jq -r '
            .items[]
            | select(.roleRef.name == "cluster-admin")
            | select(((.subjects // [])[] | select(.kind == "ServiceAccount")) | length) as $len
              | .metadata.name
          ' 2>/dev/null | sort -u
        )

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

          echo
          echo "=== Deleting offending ClusterRoleBindings ==="
          for crb in "${CRBS_TO_DELETE[@]}"; do
            if kubectl get clusterrolebinding "${crb}" >/dev/null 2>&1; then
              echo "Deleting clusterrolebinding/${crb} ..."
              kubectl delete clusterrolebinding "${crb}"
            else
              echo "clusterrolebinding/${crb} already absent; skipping."
            fi
          done
        fi

        echo
        echo "=== Verification (benchmark audit command) ==="
        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>
