Minimize User Access To Container Image Repositories
More Info:
Restrict IAM permissions so only required users and service accounts have write access to container image repositories. Broad storage-admin roles allow tampering with image content.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS GKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify over-privileged principals at the project level
- Run on any machine with
gcloudaccess:PROJECT_ID="my-project-id"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 each returned
member, document which applications, CI pipelines, or teams depend on that access and whether they truly need write or admin rights to container images.
- Run on any machine with
-
Review and adjust project-level IAM for GCR/AR access
- If a principal does not require broad storage admin or legacy write roles, update the project IAM policy file instead of editing individual bindings ad hoc.
- Export the current policy:
gcloud projects get-iam-policy "$PROJECT_ID" > /tmp/${PROJECT_ID}-iam-policy.yaml
- Edit
/tmp/${PROJECT_ID}-iam-policy.yamland remove or narrow any bindings that grant:roles/storage.adminroles/storage.objectAdminroles/storage.objectCreatorroles/storage.legacyBucketOwnerroles/storage.legacyBucketWriterroles/storage.legacyObjectOwner
where they are not strictly required for image publishing/management.
- Re-apply the policy:
gcloud projects set-iam-policy "$PROJECT_ID" /tmp/${PROJECT_ID}-iam-policy.yaml
-
Tighten GCR bucket-level permissions (if using GCR)
- Determine the GCR bucket name, typically:
artifacts.${PROJECT_ID}.appspot.com. - Show current IAM on the bucket:
BUCKET="gs://artifacts.${PROJECT_ID}.appspot.com"gsutil iam get "$BUCKET" > /tmp/${PROJECT_ID}-gcr-bucket-iam.jsoncat /tmp/${PROJECT_ID}-gcr-bucket-iam.json
- For any principal that only needs read access, first grant a read-only role if not already present, for example:
gsutil iam ch user:someone@example.com:objectViewer "$BUCKET"
- Then remove over-privileged roles from the bucket:
gsutil iam ch -d user:someone@example.com:roles/storage.admin "$BUCKET"gsutil iam ch -d user:someone@example.com:roles/storage.objectAdmin "$BUCKET"gsutil iam ch -d user:someone@example.com:roles/storage.objectCreator "$BUCKET"
- Adjust
user:toserviceAccount:and the role string as appropriate for each principal you identified.
- Determine the GCR bucket name, typically:
-
Restrict Artifact Registry repository IAM (if using AR)
- List repositories to identify which hold container images:
(Repeat with other locations as needed.)gcloud artifacts repositories list \--location=us-central1 \--project="$PROJECT_ID"
- For each repository that stores container images, get its current IAM policy:
REPO_NAME="my-repo"LOCATION="us-central1"gcloud artifacts repositories get-iam-policy "$REPO_NAME" \--location="$LOCATION" > /tmp/${REPO_NAME}-iam-policy.yamlcat /tmp/${REPO_NAME}-iam-policy.yaml
- Review bindings and ensure only the minimal required users/service accounts have write or admin roles (for example, CI/CD or image promotion jobs). Remove wide groups or general-purpose accounts that do not need image write access.
- After editing
/tmp/${REPO_NAME}-iam-policy.yaml, apply it:gcloud artifacts repositories set-iam-policy "$REPO_NAME" \/tmp/${REPO_NAME}-iam-policy.yaml \--location="$LOCATION"
- List repositories to identify which hold container images:
-
Confirm remaining write access is justified and scoped
- For each principal that still has write or admin access (project-level, bucket-level, or AR repo-level), document:
- What pipeline or workload uses it (e.g., image build, promotion, security scanning).
- Why write access cannot be further reduced (e.g., needs only
artifactregistry.repoAdminon a single repo instead of project-level storage admin).
- Where possible, replace direct user accounts with dedicated service accounts scoped to a single project or repository and used only from CI/CD or automation.
- For each principal that still has write or admin access (project-level, bucket-level, or AR repo-level), document:
-
Re-run the audit to verify reduction of broad roles
- Run again on any machine with
gcloudaccess: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 general-purpose users, broad groups, or unrelated service accounts retain these roles.
- Any remaining use of these roles is explicitly reviewed and accepted as a justified exception.
- Run again on any machine with
Using kubectl
kubectl cannot be used to change IAM permissions on Google Cloud container image repositories; these settings are managed at the cloud project / Artifact Registry / GCR level via gcloud, gsutil, or IaC. Perform the remediation in the cloud provider configuration instead, following the guidance in the Manual Steps section.
Automation
#!/usr/bin/env bash
#
# Report broad write-level access to GCR / Artifact Registry used by this GKE cluster.
# Runs read‑only checks against:
# - Project‑level IAM
# - GCR buckets (if used)
# - Artifact Registry repositories (if used)
#
# RUN ON: any machine with gcloud, kubectl and gsutil configured and access to the project.
set -euo pipefail
PROJECT_ID="${1:-}"
REGION_FILTER="${2:-}" # optional: e.g. "us-" to limit AR scan
if [[ -z "${PROJECT_ID}" ]]; then
echo "Usage: $0 <gcp-project-id> [artifact-registry-region-filter]" >&2
exit 1
fi
echo "=== Context ==="
echo "Project: ${PROJECT_ID}"
echo "Kubernetes context: $(kubectl config current-context 2>/dev/null || echo 'N/A')"
echo
###############################################################################
# 1. Identify image registries actually used by this cluster
###############################################################################
echo "=== Step 1: Images used by workloads in the cluster ==="
echo "[INFO] Collecting images from all namespaces via kubectl..."
# Requires access to the cluster API
IMAGES=$(kubectl get pods -A -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{range .spec.initContainers[*]}{.image}{"\n"}{end}{end}' \
| sort -u)
if [[ -z "${IMAGES}" ]]; then
echo "[WARN] No images found via kubectl; cluster may be empty or access restricted."
else
echo "[INFO] Unique images in use:"
echo "${IMAGES}"
fi
echo
GCR_USED=false
AR_USED=false
if echo "${IMAGES}" | grep -qE '(^|/)gcr\.io/|\.gcr\.io/'; then
GCR_USED=true
fi
if echo "${IMAGES}" | grep -qE '(^|/)([a-z0-9-]+)-docker\.pkg\.dev/'; then
AR_USED=true
fi
echo "GCR used by cluster: ${GCR_USED}"
echo "Artifact Registry used by cluster: ${AR_USED}"
echo
###############################################################################
# 2. Project-level IAM: look for broad storage roles
###############################################################################
echo "=== Step 2: Project-level IAM with broad Storage roles ==="
echo "[INFO] Checking for roles that can modify GCR/AR content at project scope..."
gcloud projects get-iam-policy "${PROJECT_ID}" \
--flatten="bindings[].members" \
--format='table(bindings.members,bindings.role)' \
--filter="bindings.role:(roles/storage.admin roles/storage.objectAdmin roles/storage.objectCreator roles/storage.legacyBucketOwner roles/storage.legacyBucketWriter roles/storage.legacyObjectOwner)"
cat <<'EOF'
[INTERPRETATION]
- Any row here indicates a principal with broad write/admin capability on Cloud Storage.
- If you use GCR (which is backed by a GCS bucket), these roles can enable registry tampering.
- Pay special attention to:
- user:* or serviceAccount:* that do not need write access to container images
- groups with wide membership
- roles granted to allUsers / allAuthenticatedUsers (critical issue)
EOF
echo
###############################################################################
# 3. GCR buckets: bucket-level IAM (if GCR is used)
###############################################################################
if [[ "${GCR_USED}" == "true" ]]; then
echo "=== Step 3: GCR bucket-level IAM (since GCR is in use) ==="
# Typical multi-region GCR bucket name:
GCR_BUCKET="artifacts.${PROJECT_ID}.appspot.com"
echo "[INFO] Checking IAM policy for GCR bucket: gs://${GCR_BUCKET}"
if gsutil ls "gs://${GCR_BUCKET}" >/dev/null 2>&1; then
gsutil iam get "gs://${GCR_BUCKET}" | jq -r '
.bindings[]
| select(.role
| IN(
"roles/storage.admin",
"roles/storage.objectAdmin",
"roles/storage.objectCreator",
"roles/storage.legacyBucketOwner",
"roles/storage.legacyBucketWriter",
"roles/storage.legacyObjectOwner"
))
| .role as $r
| .members[]
| @tsv "\($r)\t\(.)"
' 2>/dev/null || echo "[WARN] Failed to parse IAM policy with jq; raw policy follows:" && gsutil iam get "gs://${GCR_BUCKET}"
cat <<'EOF'
[INTERPRETATION]
- Any principal listed with the roles above has write/admin capability on image blobs.
- Problematic cases:
- Principals that do not directly manage images but have storage.admin or objectAdmin
- allUsers / allAuthenticatedUsers with any of these roles (very high risk)
- Shared groups used for non-CI purposes
EOF
else
echo "[INFO] GCR bucket gs://${GCR_BUCKET} not found; GCR may not be configured in this form."
fi
echo
else
echo "=== Step 3: Skipping GCR bucket check (no GCR images detected in workloads) ==="
echo
fi
###############################################################################
# 4. Artifact Registry repositories: IAM (if AR is used)
###############################################################################
if [[ "${AR_USED}" == "true" ]]; then
echo "=== Step 4: Artifact Registry repository IAM (since Artifact Registry is in use) ==="
echo "[INFO] Listing Artifact Registry repositories in project ${PROJECT_ID}..."
if [[ -n "${REGION_FILTER}" ]]; then
REPOS=$(gcloud artifacts repositories list \
--project "${PROJECT_ID}" \
--format='value(name)' \
--filter="location:${REGION_FILTER}" || true)
else
REPOS=$(gcloud artifacts repositories list \
--project "${PROJECT_ID}" \
--format='value(name)' || true)
fi
if [[ -z "${REPOS}" ]]; then
echo "[INFO] No Artifact Registry repositories found."
else
while read -r REPO; do
[[ -z "${REPO}" ]] && continue
echo "--- Repository: ${REPO} ---"
# NOTE: AR IAM roles of concern typically include:
# roles/artifactregistry.admin
# roles/artifactregistry.repoAdmin
# roles/artifactregistry.writer
gcloud artifacts repositories get-iam-policy "${REPO}" \
--project "${PROJECT_ID}" \
--format='table(bindings.members,bindings.role)' \
| grep -E 'roles/artifactregistry\.admin|roles/artifactregistry\.repoAdmin|roles/artifactregistry\.writer' \
|| echo "[INFO] No broad AR admin/writer roles found on this repo."
cat <<'EOF'
[INTERPRETATION PER REPOSITORY]
- Focus on members with:
- roles/artifactregistry.admin
- roles/artifactregistry.repoAdmin
- roles/artifactregistry.writer
- Problematic cases:
- Human users with admin/writer but not directly responsible for publishing images
- Wide groups (e.g., all developers) that can overwrite or push images
- Service accounts unrelated to CI/CD or image promotion
EOF
done <<< "${REPOS}"
fi
echo
else
echo "=== Step 4: Skipping Artifact Registry IAM check (no AR images detected in workloads) ==="
echo
fi
###############################################################################
# 5. Summary guidance
###############################################################################
cat <<'EOF'
=== How to interpret this report ===
A configuration is problematic for CIS GKE 5.1.2 when:
1. Project-level IAM
- Any principal (user, group, or service account) has one of:
roles/storage.admin
roles/storage.objectAdmin
roles/storage.objectCreator
roles/storage.legacyBucketOwner
roles/storage.legacyBucketWriter
roles/storage.legacyObjectOwner
- …and does not have a strict, justifiable need to write or administer
container images (GCR/AR).
2. GCR bucket IAM
- The GCR bucket IAM shows the roles above granted to:
- Unnecessary human users
- Broad groups (e.g. "developers", "engineering")
- allUsers or allAuthenticatedUsers
- These indicate excessive write access to image content.
3. Artifact Registry IAM
- Repository policies grant:
roles/artifactregistry.admin
roles/artifactregistry.repoAdmin
roles/artifactregistry.writer
to principals that are not:
- CI/CD service accounts that push images
- Dedicated release automation or image promotion systems
Use this report as input to a manual review:
- For each principal with broad access, decide:
- Do they truly need to push/modify images?
- Can their access be reduced to read-only or removed?
- Then adjust IAM using:
- gsutil iam ch / gcloud projects set-iam-policy for GCR
- gcloud artifacts repositories set-iam-policy for Artifact Registry
This script does NOT make any changes; it only surfaces potential issues for review.
EOF