Skip to main content

Minimize Cluster Access To Read-Only For Gcr

More Info:โ€‹

Scan images being deployed to Amazon EKS for vulnerabilities.

Risk Levelโ€‹

Medium

Addressโ€‹

Security

Compliance Standardsโ€‹

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS Critical Security Controls v8
  • CIS GKE
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • Essential 8
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • UK NCSC Cyber Assessment Framework

Triage and Remediationโ€‹

Remediationโ€‹

Manual Steps
  1. Identify which identities your cluster nodes use to pull images

    • On any machine with gcloud access, list GKE node service accounts (Workload Identity or node SA) you expect to use for image pulls:
      gcloud container clusters describe <cluster-name> \
      --region <region> \
      --project <project-id> \
      --format="yaml(nodeConfig.serviceAccount,workloadIdentityConfig)"
    • Note each service account email that should have read-only access to Artifact Registry and/or GCR.
  2. Review projectโ€‘level roles that allow image push or overwrite

    • On any machine with gcloud access, check for broad storage roles in the project (these may implicitly allow write access to GCR buckets):
      gcloud projects get-iam-policy <project-id> \
      --flatten="bindings[].members" \
      --format='table(bindings.members,bindings.role)' \
      --filter="bindings.role:roles/storage.admin OR bindings.role:roles/storage.objectAdmin OR bindings.role:roles/storage.objectCreator OR bindings.role:roles/storage.legacyBucketOwner OR bindings.role:roles/storage.legacyBucketWriter OR bindings.role:roles/storage.legacyObjectOwner"
    • For any member that matches a node or workload service account, decide whether it truly needs projectโ€‘wide write access. If not, plan to remove or narrow that role via your normal IAM/IaC process.
  3. For Artifact Registry: ensure only artifactregistry.reader on needed repositories

    • On any machine with gcloud access, for each Artifact Registry repo used by the cluster:
      gcloud artifacts repositories get-iam-policy <repository> \
      --location=<repository-location>
    • For each node/workload service account, confirm it has at most:
      • roles/artifactregistry.reader on that repo.
    • If you find stronger roles (for example roles/artifactregistry.writer or roles/editor) that are not strictly required, plan to remove them and instead grant only:
      gcloud artifacts repositories add-iam-policy-binding <repository> \
      --location=<repository-location> \
      --member="serviceAccount:<service-account-email>" \
      --role="roles/artifactregistry.reader"
  4. For GCR: inspect bucketโ€‘level permissions for write capability

    • On any machine with gsutil access, check IAM on the GCR bucket:
      gsutil iam get gs://artifacts.<project-id>.appspot.com
    • Look for any of these roles on node/workload service accounts:
      • roles/storage.admin (or roles/storage.objectAdmin, roles/storage.objectCreator)
      • Legacy roles: roles/storage.legacyBucketOwner, roles/storage.legacyBucketWriter, roles/storage.legacyObjectOwner
    • If the account needs only to pull images, plan to:
      • Ensure it has objectViewer:
        gsutil iam ch serviceAccount:<service-account-email>:objectViewer \
        gs://artifacts.<project-id>.appspot.com
      • Remove writeโ€‘capable roles that are not required:
        gsutil iam ch -d serviceAccount:<service-account-email>:<role> \
        gs://artifacts.<project-id>.appspot.com
  5. If write access is inherited from projectโ€‘level IAM, update policy carefully

    • If in step 2 you found that node/workload service accounts are granted storageโ€‘adminโ€‘type roles at the project level and they do not need them, export the policy for review:
      gcloud projects get-iam-policy <project-id> --format=yaml > /tmp/<project-id>-policy.yaml
    • Edit /tmp/<project-id>-policy.yaml to remove or narrow only those bindings that are not required for other workloads, following your changeโ€‘management process.
    • Reโ€‘apply when you are confident of the impact:
      gcloud projects set-iam-policy <project-id> /tmp/<project-id>-policy.yaml
  6. Reโ€‘verify that only readโ€‘only access remains

    • On any machine with gcloud access, reโ€‘run:
      gcloud projects get-iam-policy <project-id> \
      --flatten="bindings[].members" \
      --format='table(bindings.members,bindings.role)' \
      --filter="bindings.role:roles/storage.admin OR bindings.role:roles/storage.objectAdmin OR bindings.role:roles/storage.objectCreator OR bindings.role:roles/storage.legacyBucketOwner OR bindings.role:roles/storage.legacyBucketWriter OR bindings.role:roles/storage.legacyObjectOwner"
    • Confirm that no node or workload service accounts used by the GKE cluster appear with these roles, and that for Artifact Registry/GCR they only have readโ€‘only roles needed to pull images.
Using kubectl

kubectl cannot change Container Registry or Artifact Registry IAM permissions; this finding must be fixed in Google Cloud IAM / registry configuration using gcloud, gsutil, or IaC. See the Manual Steps section for how to review and adjust project, bucket, and repository permissions to enforce readโ€‘only access.

Automation
#!/usr/bin/env bash
#
# Audit script for CIS GKE 5.1.3:
# "Minimize cluster access to read-only for Container Image repositories"
#
# REQUIREMENTS:
# - Run on any machine with:
# - gcloud, gsutil, jq, and kubectl installed
# - Access to the target GCP project and GKE clusters
# - You must set or pass the GCP project ID.
#
# USAGE:
# ./audit_cis_gke_5_1_3.sh <GCP_PROJECT_ID>
#
# OUTPUT:
# - Lists GCP IAM bindings where principals have write/admin-level Storage roles
# that could allow pushing/overwriting images in GCR/AR.
# - You must manually assess which of these principals are used by the GKE
# cluster nodes or workloads and whether they should be reduced to read-only.

set -euo pipefail

PROJECT_ID="${1:-}"

if [[ -z "${PROJECT_ID}" ]]; then
echo "Usage: $0 <GCP_PROJECT_ID>" >&2
exit 1
fi

echo "=== CIS GKE 5.1.3 audit for project: ${PROJECT_ID} ==="
echo

###############################################################################
# 1. Identify Kubernetes-related service accounts in this project
###############################################################################
echo ">> Discovering Kubernetes-related Google service accounts (GSA)..."
echo

# Common patterns:
# - GKE default node service accounts (if manually created) often include "gke"
# - Workload Identity GSA names vary; we just list all in the project.
gcloud iam service-accounts list \
--project="${PROJECT_ID}" \
--format="table(email,displayName)" \
| sed 's/^/ /'
echo

###############################################################################
# 2. Project-level IAM: find principals with Storage write/admin privileges
###############################################################################
echo ">> Project-level IAM: principals with Storage write/admin-type roles"
echo " (These may inherit access to GCR buckets or AR repositories)"
echo

gcloud projects get-iam-policy "${PROJECT_ID}" \
--flatten="bindings[].members" \
--format='table(bindings.members,bindings.role)' \
--filter="bindings.role:roles/storage.admin OR bindings.role:roles/storage.objectAdmin OR bindings.role:roles/storage.objectCreator OR bindings.role:roles/storage.legacyBucketOwner OR bindings.role:roles/storage.legacyBucketWriter OR bindings.role:roles/storage.legacyObjectOwner" \
| sed 's/^/ /'

echo
cat <<'EOF'
Interpretation:
- Any member here that is a service account used by:
* GKE node pools (node service accounts), or
* Workload Identity for pods
has more than read-only access to GCR/AR via project-level roles.
- PROBLEM: If a Kubernetes-related service account appears with any of these
roles, it violates the intent of CIS GKE 5.1.3. It should typically be
restricted to objectViewer / artifactregistry.reader only.

EOF

###############################################################################
# 3. GCR: bucket-level IAM for artifacts.<project_id>.appspot.com
###############################################################################
GCR_BUCKET="gs://artifacts.${PROJECT_ID}.appspot.com"

echo ">> GCR bucket-level IAM for ${GCR_BUCKET}"
echo

if gsutil ls "${GCR_BUCKET}" >/dev/null 2>&1; then
gsutil iam get "${GCR_BUCKET}" | jq '.bindings[] | {role: .role, members: .members}' \
| sed 's/^/ /'
else
echo " Bucket ${GCR_BUCKET} does not exist or is not accessible."
fi

cat <<'EOF'

Interpretation:
- Look for roles that allow write/admin capabilities such as:
roles/storage.admin
roles/storage.objectAdmin
roles/storage.objectCreator
roles/storage.legacyBucketOwner
roles/storage.legacyBucketWriter
roles/storage.legacyObjectOwner
- PROBLEM: Any Kubernetes-related service account (e.g. GKE node SA or
Workload Identity GSA) appearing with any of the above roles at this
bucket is overly privileged and can push/overwrite images.

EOF

###############################################################################
# 4. Artifact Registry: per-repository IAM
###############################################################################
echo ">> Artifact Registry repositories and IAM bindings with elevated roles"
echo

# List all AR repositories
REPOS_JSON="$(gcloud artifacts repositories list \
--project="${PROJECT_ID}" \
--location=- \
--format=json || echo '[]')"

if [[ "${REPOS_JSON}" == "[]" ]]; then
echo " No Artifact Registry repositories found in project ${PROJECT_ID}."
else
echo "${REPOS_JSON}" | jq -r '.[] | "\(.name) \(.format) \(.location)"' | while read -r REPO_NAME REPO_FORMAT REPO_LOCATION; do
echo " Repository: ${REPO_NAME} (format=${REPO_FORMAT}, location=${REPO_LOCATION})"
# Extract repository ID from the full resource name projects/PROJECT/locations/LOC/repositories/REPO
REPO_ID="$(basename "${REPO_NAME}")"

# Get IAM policy for the specific repository
gcloud artifacts repositories get-iam-policy "${REPO_ID}" \
--project="${PROJECT_ID}" \
--location="${REPO_LOCATION}" \
--format=json \
| jq '.bindings[] | select(.role | test("artifactregistry.admin|artifactregistry.writer|storage.admin|storage.objectAdmin|storage.objectCreator|storage.legacyBucketOwner|storage.legacyBucketWriter|storage.legacyObjectOwner"))' \
| sed 's/^/ /' || true

echo
done
fi

cat <<'EOF'
Interpretation:
- Focus on roles that exceed read-only, such as:
roles/artifactregistry.admin
roles/artifactregistry.writer
roles/storage.admin
roles/storage.objectAdmin
roles/storage.objectCreator
roles/storage.legacyBucketOwner
roles/storage.legacyBucketWriter
roles/storage.legacyObjectOwner
- PROBLEM: If any Kubernetes-related service account (node SA or workload
GSA) has one of these roles on an Artifact Registry repository, it can
push/overwrite images, violating the control's intent.

EOF

###############################################################################
# 5. Map Kubernetes identities to GCP service accounts (for review)
###############################################################################
echo ">> (Optional) Mapping K8s โ†’ GCP identities for Workload Identity (for review)"
echo " Run from a machine with kubectl configured for this cluster."
echo

# This section does NOT change anything; it only helps correlate principals.
if kubectl get ns >/dev/null 2>&1; then
echo " Kubernetes service accounts annotated with GSA (Workload Identity):"
kubectl get serviceaccounts --all-namespaces -o json \
| jq -r '
.items[]
| {
namespace: .metadata.namespace,
name: .metadata.name,
gsa: (.metadata.annotations["iam.gke.io/gcp-service-account"] // "")
}
| select(.gsa != "")
| "\(.namespace)/\(.name) -> \(.gsa)"
' \
| sed 's/^/ /'
else
echo " kubectl not configured or cluster not reachable; skipping K8s mapping."
fi

cat <<'EOF'

Interpretation:
- For each "namespace/serviceaccount -> GSA" mapping above:
* Check if that GSA appears in the project-level, GCR bucket-level,
or Artifact Registry IAM outputs with any write/admin-style role.
- PROBLEM: A mapped GSA that also appears with elevated Storage or
Artifact Registry roles is a candidate for restriction to read-only
roles (e.g., objectViewer, artifactregistry.reader).

FINAL REVIEW:
- A finding is considered problematic when:
* A node service account or any GSA used by your workloads
has more than read-only permissions on:
- GCR bucket: artifacts.<project_id>.appspot.com, or
- Any Artifact Registry repository.
- Use this script's output to build a list of:
K8s SA โ†’ GSA โ†’ Roles on GCR/AR
and then decide which roles should be reduced to:
- roles/storage.objectViewer (for GCR)
- roles/artifactregistry.reader (for AR)
EOF

Additional Reading:โ€‹