> ## 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):
           ```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 identified in step 1, review what permissions it actually needs by inspecting the workloads that use it (run on any machine with `kubectl` access). Replace `VIOLATING_NAMESPACE` and `VIOLATING_SA` with actual names:
           ```bash theme={null}
           kubectl get pods -A -o json | jq -r '
             .items[]
             | select(.spec.serviceAccountName == "VIOLATING_SA" and .metadata.namespace == "VIOLATING_NAMESPACE")
             | "namespace=\(.metadata.namespace) pod=\(.metadata.name)"
           '
           ```

        3. Define a narrowly-scoped Role or ClusterRole that grants only the minimal verbs/resources required by those workloads (run on any machine with `kubectl` access). Example template to save as `minimal-role.yaml` and edit for correct `namespace`, `apiGroups`, `resources`, and `verbs`:
           ```yaml theme={null}
           apiVersion: rbac.authorization.k8s.io/v1
           kind: Role
           metadata:
             name: minimal-violation-role
             namespace: VIOLATING_NAMESPACE
           rules:
           - apiGroups: [""]
             resources: ["pods"]
             verbs: ["get", "list"]
           ```
           Apply it:
           ```bash theme={null}
           kubectl apply -f minimal-role.yaml
           ```

        4. Bind the violating ServiceAccount to the new narrowly-scoped Role or ClusterRole (run on any machine with `kubectl` access). Example for a namespaced Role:
           ```bash theme={null}
           cat << 'EOF' > minimal-rolebinding.yaml
           apiVersion: rbac.authorization.k8s.io/v1
           kind: RoleBinding
           metadata:
             name: minimal-violation-rolebinding
             namespace: VIOLATING_NAMESPACE
           subjects:
           - kind: ServiceAccount
             name: VIOLATING_SA
             namespace: VIOLATING_NAMESPACE
           roleRef:
             apiGroup: rbac.authorization.k8s.io
             kind: Role
             name: minimal-violation-role
           EOF

           kubectl apply -f minimal-rolebinding.yaml
           ```

        5. After confirming that workloads still function with the new, reduced privileges, remove the ClusterRoleBinding that granted `cluster-admin` to that ServiceAccount (run on any machine with `kubectl` access). Replace `CLUSTERROLEBINDING_NAME` with the name from step 1:
           ```bash theme={null}
           kubectl delete clusterrolebinding CLUSTERROLEBINDING_NAME
           ```

        6. Verification (run on any machine with `kubectl` access): confirm that no ServiceAccount is bound to `cluster-admin` and that the check now reports compliance:
           ```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="Using kubectl">
        On any machine with kubectl access:

        1. Identify violating ClusterRoleBindings

        ```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'
        ```

        Note the `name=` of each violating `ClusterRoleBinding`.

        2. (Optional but recommended) Create a narrowly-scoped Role/ClusterRole and binding\
           Adapt the following to the actual verbs and resources the workload requires.

        Example namespaced Role and RoleBinding for a ServiceAccount `my-sa` in namespace `my-namespace`:

        ```yaml theme={null}
        # save as rbac-my-sa.yaml
        apiVersion: rbac.authorization.k8s.io/v1
        kind: Role
        metadata:
          name: my-sa-role
          namespace: my-namespace
        rules:
          - apiGroups: [""]
            resources: ["pods"]
            verbs: ["get", "list"]

        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: RoleBinding
        metadata:
          name: my-sa-rolebinding
          namespace: my-namespace
        subjects:
          - kind: ServiceAccount
            name: my-sa
            namespace: my-namespace
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: Role
          name: my-sa-role
        ```

        Apply it:

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

        For cluster-scoped access, use `ClusterRole` and `ClusterRoleBinding` instead of `Role`/`RoleBinding`.

        3. Delete ClusterRoleBindings that bind ServiceAccounts to `cluster-admin`

        For each violating binding name (replace `BINDING_NAME` with the actual name):

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

        You can delete multiple at once:

        ```bash theme={null}
        kubectl delete clusterrolebinding BINDING_NAME_1 BINDING_NAME_2
        ```

        4. Verification

        Run the audit command again and confirm it returns only `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
        # Purpose: Remove any ServiceAccount bindings to the cluster-admin ClusterRole
        # Platform: AKS (or any Kubernetes cluster reachable by kubectl)
        # Requirements: kubectl, jq; run on any machine with kubectl access

        set -euo pipefail

        echo "[INFO] Verifying kubectl access..."
        kubectl auth can-i list clusterrolebindings >/dev/null 2>&1 || {
          echo "[ERROR] Current identity cannot list ClusterRoleBindings."
          exit 1
        }

        echo "[INFO] Ensuring jq is available..."
        if ! command -v jq >/dev/null 2>&1; then
          echo "[ERROR] jq is required but not installed."
          exit 1
        fi

        echo "[INFO] Discovering ClusterRoleBindings that bind ServiceAccounts to cluster-admin..."

        # Find all ClusterRoleBindings that:
        #   - reference roleRef.name == "cluster-admin"
        #   - have at least one subject of kind ServiceAccount
        violating_crbs=$(
          kubectl get clusterrolebindings -o json |
          jq -r '
            .items[]
            | select(.roleRef.name == "cluster-admin")
            | select((.subjects // [])[]? | .kind == "ServiceAccount")
            | .metadata.name
          ' | sort -u
        )

        if [[ -z "${violating_crbs}" ]]; then
          echo "[INFO] No ClusterRoleBindings found that bind ServiceAccounts to cluster-admin."
        else
          echo "[INFO] The following ClusterRoleBindings bind ServiceAccounts to cluster-admin and will be deleted:"
          echo "${violating_crbs}" | sed 's/^/  - /'

          # Delete each violating ClusterRoleBinding. This is idempotent: deletions are skipped if already gone.
          while IFS= read -r crb_name; do
            [[ -z "${crb_name}" ]] && continue
            echo "[INFO] Deleting ClusterRoleBinding: ${crb_name}"
            # Use --ignore-not-found to keep script idempotent
            kubectl delete clusterrolebinding "${crb_name}" --ignore-not-found
          done <<< "${violating_crbs}"
        fi

        echo "[INFO] Verification: re-running compliance check..."

        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>
