Skip to main content

GCP Minimize User Access To Gcr

More Info:

Scan images being deployed to Amazon EKS for vulnerabilities.

Risk Level

High

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. List who can access Container/Artifact Registry at the project level

    • Run on any machine with gcloud and gsutil configured:
      PROJECT_ID="your-project-id"

      # View project-level IAM
      gcloud projects get-iam-policy "$PROJECT_ID" \
      --format="table(bindings.role, bindings.members)"
    • Review members with broad roles that can affect images, such as:
      • roles/storage.admin, roles/storage.objectAdmin, roles/storage.objectCreator
      • roles/artifactregistry.admin, roles/artifactregistry.repoAdmin, roles/artifactregistry.writer
      • roles/owner, roles/editor
    • Decide which identities truly need publish (write) or pull (read) access for container images.
  2. Inspect IAM on GCR storage bucket(s)

    • For legacy GCR, list buckets and view IAM:
      # List buckets; identify artifacts.<PROJECT_ID>.appspot.com
      gsutil ls

      BUCKET="gs://artifacts.your-project-id.appspot.com"

      # Show IAM policy for the GCR bucket
      gsutil iam get "$BUCKET"
    • Look for members with:
      • roles/storage.admin, roles/storage.objectAdmin, roles/storage.objectCreator
    • Determine which should be reduced to read-only (objectViewer) or removed entirely.
  3. Inspect IAM on Artifact Registry repositories

    • List repositories and their IAM:
      gcloud artifacts repositories list \
      --project="$PROJECT_ID" \
      --format="table(name,format,location)"

      REPO="projects/your-project-id/locations/your-location/repositories/your-repo"

      gcloud artifacts repositories get-iam-policy "$REPO" \
      --project="$PROJECT_ID"
    • Identify users/groups/service accounts with admin or write roles that do not strictly need them.
  4. Right-size access on the GCR bucket (if used)

    • For each identity that only needs to pull images from GCR, grant read and remove excessive roles:
      BUCKET="gs://artifacts.your-project-id.appspot.com"

      # Example: grant read-only (objectViewer)
      gsutil iam ch serviceAccount:sa-name@your-project-id.iam.gserviceaccount.com:objectViewer "$BUCKET"

      # Example: remove overly broad role (e.g., objectAdmin, storage.admin, objectCreator)
      gsutil iam ch -d serviceAccount:sa-name@your-project-id.iam.gserviceaccount.com:roles/storage.objectAdmin "$BUCKET"
    • For project-level roles that grant excessive storage or registry permissions, edit a policy file:
      gcloud projects get-iam-policy "$PROJECT_ID" > /tmp/policy.yaml
      # Edit /tmp/policy.yaml to remove or downgrade roles for identities that do not need them
      gcloud projects set-iam-policy "$PROJECT_ID" /tmp/policy.yaml
  5. Right-size access on Artifact Registry repositories

    • For each principal that only needs to pull images:
      • Prefer roles/artifactregistry.reader on the specific repository.
    • For each that needs to push images but not administer IAM:
      • Prefer roles/artifactregistry.writer on the specific repository.
    • Example to set a minimal policy for one repository (edit as needed):
      REPO="projects/your-project-id/locations/your-location/repositories/your-repo"

      cat > /tmp/repo-policy.yaml << 'EOF'
      bindings:
      - role: roles/artifactregistry.reader
      members:
      - serviceAccount:sa-pull@your-project-id.iam.gserviceaccount.com
      - role: roles/artifactregistry.writer
      members:
      - serviceAccount:sa-push@your-project-id.iam.gserviceaccount.com
      EOF

      gcloud artifacts repositories set-iam-policy "$REPO" /tmp/repo-policy.yaml \
      --project="$PROJECT_ID"
  6. Verify that only least-privilege access remains

    • Re-check project and resource-level IAM to ensure no unnecessary broad roles remain:
      gcloud projects get-iam-policy "$PROJECT_ID" \
      --format="table(bindings.role, bindings.members)"

      gsutil iam get "gs://artifacts.your-project-id.appspot.com"

      gcloud artifacts repositories get-iam-policy "$REPO" \
      --project="$PROJECT_ID"
    • Confirm that:
      • Only identities that must publish images have writer roles on specific repos/buckets.
      • Identities that only pull images have read-only roles.
      • Broad roles (e.g., owner, editor, storage.admin, artifactregistry.admin) are not granted where unnecessary for registry use.
Using kubectl

kubectl cannot be used to change Container Registry or Artifact Registry IAM permissions; those are managed at the GCP project, registry, or underlying storage bucket level via gcloud/gsutil, the GCP console, or IaC. To address this finding, make the changes described in the Manual Steps section using those cloud‑provider tools.

Automation
#!/usr/bin/env bash
#
# Audit GCR/Artifact Registry access relevant to this GKE cluster.
# Runs with: any machine that has:
# - kubectl access to the cluster
# - gcloud, gsutil, and jq installed
#
# REQUIRED INPUTS (set these explicitly before running)
PROJECT_ID="my-gcp-project-id"
CLUSTER_LOCATION="us-central1" # zone or region where the cluster runs
CLUSTER_NAME="my-gke-cluster"

set -euo pipefail

if ! command -v kubectl >/dev/null 2>&1 \
|| ! command -v gcloud >/dev/null 2>&1 \
|| ! command -v gsutil >/dev/null 2>&1 \
|| ! command -v jq >/dev/null 2>&1; then
echo "ERROR: kubectl, gcloud, gsutil, and jq are required on this machine." >&2
exit 1
fi

echo "=== 1. Discover identities used to pull images in this cluster ==="

# 1a. Get all service account emails used by Kubernetes service accounts backing GKE workloads
echo
echo "--- Kubernetes service accounts and their GCP service accounts (GKE Workload Identity) ---"
kubectl get sa --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
sa: .metadata.name,
gsa: (
.metadata.annotations["iam.gke.io/gcp-service-account"]
// .metadata.annotations["iam.gke.io/gcp-serviceaccount"]
// ""
)
}
| select(.gsa != "")
| "\(.ns)/\(.sa) -> \(.gsa)"' \
| sort || echo "No Kubernetes service accounts with Workload Identity annotation found."

# 1b. Get node service accounts (for node image pulls when using node SA)
echo
echo "--- Node pool service accounts (used by kubelet / image pulls) ---"
gcloud container clusters describe "$CLUSTER_NAME" \
--location "$CLUSTER_LOCATION" \
--project "$PROJECT_ID" \
--format='table(nodePools.name, nodeConfig.serviceAccount)'

echo
echo "=== 2. List all IAM members with roles that can write to GCR / Artifact Registry at PROJECT level ==="
# Roles to inspect (expand as needed)
WRITE_ROLES=(
"roles/storage.admin"
"roles/storage.objectAdmin"
"roles/storage.objectCreator"
"roles/artifactregistry.admin"
"roles/artifactregistry.repoAdmin"
"roles/artifactregistry.writer"
)

gcloud projects get-iam-policy "$PROJECT_ID" \
--format=json > /tmp/"$PROJECT_ID"-iam.json

for ROLE in "${WRITE_ROLES[@]}"; do
echo
echo "--- Members with $ROLE on project $PROJECT_ID ---"
jq -r --arg ROLE "$ROLE" '
.bindings[]
| select(.role == $ROLE)
| .members[]
' /tmp/"$PROJECT_ID"-iam.json || true
done

echo
echo "=== 3. Inspect IAM on the GCR/Artifact Registry bucket ==="
BUCKET="gs://artifacts.${PROJECT_ID}.appspot.com"

echo
echo "--- IAM policy for bucket: $BUCKET ---"
gsutil iam get "$BUCKET" | jq '.' || {
echo "WARNING: Could not fetch IAM for $BUCKET (bucket may not exist or account lacks permission)." >&2
}

echo
echo "--- Members with broad Storage roles on the bucket (possible risk) ---"
gsutil iam get "$BUCKET" 2>/dev/null \
| jq -r '
.bindings[]
| select(.role
| IN(
"roles/storage.admin",
"roles/storage.objectAdmin",
"roles/storage.objectCreator"
))
| "ROLE: \(.role)\nMEMBERS:\n - " + (.members | join("\n - ")) + "\n"
' || echo "No broad Storage roles detected on bucket or unable to read bucket IAM."

echo
echo "=== 4. Correlate pulling identities with high-privilege roles ==="

echo
echo "--- High-privilege members that are also used by this cluster (Workload Identity or nodes) ---"

# Collect cluster-related identities (Workload Identity GSAs + node SAs)
CLUSTER_IDENTITIES_FILE=/tmp/"$PROJECT_ID"-cluster-identities.txt
: > "$CLUSTER_IDENTITIES_FILE"

kubectl get sa --all-namespaces -o json \
| jq -r '
.items[]
| .metadata.annotations["iam.gke.io/gcp-service-account"]
// .metadata.annotations["iam.gke.io/gcp-serviceaccount"]
// empty
' | sort -u >> "$CLUSTER_IDENTITIES_FILE" || true

gcloud container clusters describe "$CLUSTER_NAME" \
--location "$CLUSTER_LOCATION" \
--project "$PROJECT_ID" \
--format='value(nodePools[].nodeConfig.serviceAccount)' \
| tr ';' '\n' | sort -u >> "$CLUSTER_IDENTITIES_FILE" || true

sort -u -o "$CLUSTER_IDENTITIES_FILE" "$CLUSTER_IDENTITIES_FILE"

# Now intersect with high-privilege members from project and bucket IAM
HIGH_PRIV_FILE=/tmp/"$PROJECT_ID"-high-priv-members.txt
: > "$HIGH_PRIV_FILE"

jq -r '
.bindings[]
| select(.role
| IN(
"roles/storage.admin",
"roles/storage.objectAdmin",
"roles/storage.objectCreator",
"roles/artifactregistry.admin",
"roles/artifactregistry.repoAdmin",
"roles/artifactregistry.writer"
))
| .members[]
' /tmp/"$PROJECT_ID"-iam.json >> "$HIGH_PRIV_FILE" || true

gsutil iam get "$BUCKET" 2>/dev/null \
| jq -r '
.bindings[]
| select(.role
| IN(
"roles/storage.admin",
"roles/storage.objectAdmin",
"roles/storage.objectCreator"
))
| .members[]
' >> "$HIGH_PRIV_FILE" || true

sort -u -o "$HIGH_PRIV_FILE" "$HIGH_PRIV_FILE"

echo "Cluster identities with high-privilege GCR/Artifact Registry roles:"
grep -Ff "$CLUSTER_IDENTITIES_FILE" "$HIGH_PRIV_FILE" || echo "None found."

echo
echo "=== 5. Interpretation guidance ==="
cat <<'EOF'
Potential PROBLEMS indicated by the output above:

1) Any of the following in the bucket IAM (gsutil iam get):
- role: roles/storage.admin
- role: roles/storage.objectAdmin
- role: roles/storage.objectCreator
especially when the members are:
- allUsers or allAuthenticatedUsers (anonymous/broad access)
- user:* emails that do not need to push images
- serviceAccount:* that are not CI/CD or dedicated image-publisher accounts

2) At the project level (gcloud projects get-iam-policy):
- Bindings for Storage or Artifact Registry write/admin roles granted to:
- broad groups (e.g. group:devs@example.com) that don't all need image push rights
- generic service accounts shared by many workloads
- individual users instead of central CI/CD identities

3) In the correlation section:
- Any service account that your cluster uses (Workload Identity GSA or node SA)
also appearing as a member with:
- roles/storage.admin
- roles/storage.objectAdmin
- roles/storage.objectCreator
- roles/artifactregistry.admin
- roles/artifactregistry.repoAdmin
- roles/artifactregistry.writer
should be reviewed. Many workloads only need read (objectViewer / artifactregistry.reader).

This script does NOT automatically fix anything.
Use the benchmark remediation to:
- Replace broad write/admin roles with least-privilege roles (e.g. objectViewer/reader),
- Then gsutil iam ch -d ... or update project IAM to remove excess roles.
EOF

Additional Reading: