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
Remediation
Manual Steps
-
List who can access Container/Artifact Registry at the project level
- Run on any machine with
gcloudandgsutilconfigured:PROJECT_ID="your-project-id"# View project-level IAMgcloud 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.objectCreatorroles/artifactregistry.admin,roles/artifactregistry.repoAdmin,roles/artifactregistry.writerroles/owner,roles/editor
- Decide which identities truly need publish (write) or pull (read) access for container images.
- Run on any machine with
-
Inspect IAM on GCR storage bucket(s)
- For legacy GCR, list buckets and view IAM:
# List buckets; identify artifacts.<PROJECT_ID>.appspot.comgsutil lsBUCKET="gs://artifacts.your-project-id.appspot.com"# Show IAM policy for the GCR bucketgsutil 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.
- For legacy GCR, list buckets and view IAM:
-
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.
- List repositories and their IAM:
-
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 themgcloud projects set-iam-policy "$PROJECT_ID" /tmp/policy.yaml
- For each identity that only needs to pull images from GCR, grant read and remove excessive roles:
-
Right-size access on Artifact Registry repositories
- For each principal that only needs to pull images:
- Prefer
roles/artifactregistry.readeron the specific repository.
- Prefer
- For each that needs to push images but not administer IAM:
- Prefer
roles/artifactregistry.writeron the specific repository.
- Prefer
- 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.readermembers:- serviceAccount:sa-pull@your-project-id.iam.gserviceaccount.com- role: roles/artifactregistry.writermembers:- serviceAccount:sa-push@your-project-id.iam.gserviceaccount.comEOFgcloud artifacts repositories set-iam-policy "$REPO" /tmp/repo-policy.yaml \--project="$PROJECT_ID"
- For each principal that only needs to pull images:
-
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.
- Re-check project and resource-level IAM to ensure no unnecessary broad roles remain:
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