Skip to main content

Manage Kubernetes RBAC Users With Google Groups For GKE

More Info:

Bind Kubernetes RBAC roles to Google Groups so cluster access is managed centrally through group membership. This simplifies and secures access administration versus per-user bindings.

Risk Level

Medium

Address

Security

Compliance Standards

  • CIS GKE

Triage and Remediation

Remediation

Manual Steps
  1. Review current cluster Google Groups for GKE settings

    • On any machine with gcloud configured, run:
      gcloud container clusters describe CLUSTER_NAME \
      --location CONTROL_PLANE_LOCATION \
      --project PROJECT_ID \
      --format json | jq '{Enabled: .authenticatorGroupsConfig.enabled, "Security Group": .authenticatorGroupsConfig.securityGroup}'
    • Decide whether Enabled is true and Security Group is set to the intended parent group (for example gke-security-groups@YOUR_DOMAIN).
  2. If not enabled, decide on the security group and enable it (control-plane impact)

    • Identify or create, in Google Workspace / Cloud Identity, a parent group (e.g. gke-security-groups@YOUR_DOMAIN) and document its purpose and membership policy (who can be added, who can administer it).
    • On any machine with gcloud, update the existing cluster:
      gcloud container clusters update CLUSTER_NAME \
      --location CONTROL_PLANE_LOCATION \
      --project PROJECT_ID \
      --security-group="gke-security-groups@YOUR_DOMAIN"
    • Be aware: updating this may change how users authenticate/authorize; plan and execute during a maintenance window if needed.
  3. Inventory existing RBAC bindings that use direct users vs groups

    • On any machine with kubectl access:
      # ClusterRoleBindings using user subjects
      kubectl get clusterrolebindings -o json | jq -r '
      .items[]
      | select(.subjects != null)
      | select([.subjects[].kind] | index("User"))
      | .metadata.name as $n
      | "CRB: \($n)\n" + (.subjects[] | select(.kind=="User") | " User: \(.name)") + "\n"
      '

      # RoleBindings using user subjects across all namespaces
      kubectl get rolebindings --all-namespaces -o json | jq -r '
      .items[]
      | select(.subjects != null)
      | select([.subjects[].kind] | index("User"))
      | .metadata.namespace as $ns
      | .metadata.name as $n
      | "RB: \($ns)/\($n)\n" + (.subjects[] | select(.kind=="User") | " User: \(.name)") + "\n"
      '
    • Use this output to identify principals that should instead be granted access via Google Groups.
  4. Design target Google Groups-to-RBAC mapping

    • For each set of permissions you want to manage centrally (e.g. “cluster-admins”, “namespace-owners”, “read-only”), define a Google Group email that will be used as the RBAC subject (e.g. gke-cluster-admins@YOUR_DOMAIN).
    • Map:
      • Google Group → Kubernetes (Cluster)Role(s) → Scope (cluster or namespace).
    • Ensure membership of these groups is managed through your standard identity governance process.
  5. Implement or update RBAC bindings to use Google Groups

    • On any machine with kubectl access, for each intended mapping, create or update bindings referencing the Google Group (subject kind: Group, name: GROUP_EMAIL). Example (adjust names/namespaces as needed):
      cat <<'EOF' | kubectl apply -f -
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRoleBinding
      metadata:
      name: cluster-admins-gke-group
      subjects:
      - kind: Group
      apiGroup: rbac.authorization.k8s.io
      name: gke-cluster-admins@YOUR_DOMAIN
      roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: cluster-admin
      EOF
    • Gradually phase out direct User subjects by removing or editing the existing bindings once you’ve confirmed equivalent group-based access is in place.
  6. Verify configuration and access behavior

    • Re-run the cluster describe command on any machine with gcloud to ensure Google Groups for GKE is enabled and pointing at the correct security group:
      gcloud container clusters describe CLUSTER_NAME \
      --location CONTROL_PLANE_LOCATION \
      --project PROJECT_ID \
      --format json | jq '{Enabled: .authenticatorGroupsConfig.enabled, "Security Group": .authenticatorGroupsConfig.securityGroup}'
    • On any machine with kubectl access, confirm that RBAC bindings now reference Group subjects (and not User) for intended access paths:
      kubectl get clusterrolebindings,rolebindings --all-namespaces -o json | jq -r '
      .items[]
      | select(.subjects != null)
      | .kind + "/" + .metadata.name + " (" + (.metadata.namespace // "cluster-scope") + ")\n" +
      ( .subjects[]
      | " " + .kind + ": " + .name
      )
      '
    • Optionally, have a test user who is only a member of the appropriate Google Group log in and confirm they receive the expected level of access and no more.
Using kubectl

kubectl cannot configure Google Groups for GKE or the cluster’s --security-group setting; those are managed in the GKE control plane via gcloud, console, or IaC. To remediate this finding, follow the guidance in the Manual Steps section, using the cloud provider tools to enable Google Groups for GKE and then create the appropriate RBAC bindings.

Automation
#!/usr/bin/env bash
#
# Audit Google Groups for GKE usage across all GKE clusters in one or more projects.
# Requirements:
# - gcloud, jq
# - You are authenticated and have sufficient IAM permissions.
#
# Usage:
# ./audit-gke-google-groups.sh PROJECT_ID [PROJECT_ID_2 ...]
#

set -euo pipefail

if ! command -v gcloud >/dev/null 2>&1; then
echo "ERROR: gcloud not found in PATH" >&2
exit 1
fi

if ! command -v jq >/dev/null 2>&1; then
echo "ERROR: jq not found in PATH" >&2
exit 1
fi

if [ "$#" -lt 1 ]; then
echo "Usage: $0 PROJECT_ID [PROJECT_ID_2 ...]" >&2
exit 1
fi

echo "Collecting GKE Google Groups for GKE configuration and RBAC bindings..."
echo

for PROJECT in "$@"; do
echo "======================================================================"
echo "Project: ${PROJECT}"
echo "======================================================================"

MAP_OUTPUT=$(gcloud projects get-iam-policy "${PROJECT}" \
--format='json(bindings)' 2>/dev/null | jq -r '
.bindings[]
| select(.role=="roles/container.admin" or .role=="roles/container.clusterAdmin")
| " IAM: " + .role + " -> " + (.members | join(", "))
' || true)

echo "Project-level IAM holders of container.admin / container.clusterAdmin:"
if [ -z "${MAP_OUTPUT}" ]; then
echo " (none found)"
else
echo "${MAP_OUTPUT}"
fi
echo

CLUSTERS=$(gcloud container clusters list \
--project="${PROJECT}" \
--format='value(name,location)' 2>/dev/null || true)

if [ -z "${CLUSTERS}" ]; then
echo "No GKE clusters found in project ${PROJECT}"
echo
continue
fi

while read -r NAME LOCATION; do
[ -z "${NAME:-}" ] && continue

echo "----------------------------------------------------------------------"
echo "Cluster: ${NAME}"
echo "Location: ${LOCATION}"
echo "----------------------------------------------------------------------"

DESC_JSON=$(gcloud container clusters describe "${NAME}" \
--project "${PROJECT}" \
--location "${LOCATION}" \
--format=json 2>/dev/null || echo "{}")

ENABLED=$(echo "${DESC_JSON}" | jq -r '.authenticatorGroupsConfig.enabled // false')
SEC_GROUP=$(echo "${DESC_JSON}" | jq -r '.authenticatorGroupsConfig.securityGroup // ""')

echo "Google Groups for GKE:"
echo " Enabled: ${ENABLED}"
echo " Security Group: ${SEC_GROUP}"

if [ "${ENABLED}" != "true" ] || [ -z "${SEC_GROUP}" ] || [ "${SEC_GROUP}" = "null" ]; then
echo " STATUS: NOT COMPLIANT with CISGKE 5.8.2 (Google Groups for GKE not fully configured)"
else
echo " STATUS: Google Groups for GKE configured at cluster level"
fi
echo

echo "RBAC ClusterRoleBindings referencing Google Groups (by email-like subjects):"
# This uses kubectl and requires cluster access.
# Any machine with kubectl access and current context pointing to this cluster.
if kubectl config use-context "gke_${PROJECT}_${LOCATION}_${NAME}" >/dev/null 2>&1; then
kubectl get clusterrolebindings.rbac.authorization.k8s.io -o json 2>/dev/null | jq -r '
.items[]
| . as $crb
| $crb.subjects // []
| map(select(.kind=="Group" and (.name | test("@"))))
| select(length > 0)
| $crb.metadata.name as $name
| .[]
| " " + $name + " -> Group: " + .name
' || echo " (none found or error retrieving ClusterRoleBindings)"
echo

echo "RBAC RoleBindings referencing Google Groups (by email-like subjects):"
kubectl get rolebindings.rbac.authorization.k8s.io --all-namespaces -o json 2>/dev/null | jq -r '
.items[]
| . as $rb
| $rb.subjects // []
| map(select(.kind=="Group" and (.name | test("@"))))
| select(length > 0)
| ($rb.metadata.namespace + "/" + $rb.metadata.name) as $name
| .[]
| " " + $name + " -> Group: " + .name
' || echo " (none found or error retrieving RoleBindings)"
else
echo " NOTE: kubectl context gke_${PROJECT}_${LOCATION}_${NAME} not found or unusable."
echo " Skipping RBAC binding inspection for this cluster."
fi

echo
done <<< "${CLUSTERS}"

echo
done

cat <<'EOF'
INTERPRETING RESULTS:

1) Cluster-level Google Groups for GKE configuration:
- Compliant / desired:
Enabled: true
Security Group: gke-security-groups@<your-domain>
and the script prints:
STATUS: Google Groups for GKE configured at cluster level
- Potential problem (requires review):
Enabled: false
or Security Group is empty or "null"
and the script prints:
STATUS: NOT COMPLIANT with CISGKE 5.8.2 ...
This indicates:
* The cluster is not using Google Groups for GKE, OR
* The security group is not set, which does not meet the benchmark guidance.

2) RBAC role bindings:
- Desired pattern:
ClusterRoleBindings and RoleBindings reference Google Groups
(subjects of kind: Group, name looks like an email, e.g. team-admins@domain).
- Problem indicators:
* Few or no Group subjects using email-style names, but many User subjects
(per-user bindings), meaning access is managed per-user instead of via groups.
* Group names that are not your intended Google Groups for GKE (e.g. legacy groups).

This script only reports configuration for review. Enabling Google Groups for GKE
and restructuring RBAC bindings to use groups must be done manually following
the provider documentation and your access-control design.
EOF