Skip to main content

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

Manual Steps
  1. Identify over-privileged principals at the project level

    • Run on any machine with gcloud access:
      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.
  2. 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.yaml and remove or narrow any bindings that grant:
      • roles/storage.admin
      • roles/storage.objectAdmin
      • roles/storage.objectCreator
      • roles/storage.legacyBucketOwner
      • roles/storage.legacyBucketWriter
      • roles/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
  3. 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.json
      cat /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: to serviceAccount: and the role string as appropriate for each principal you identified.
  4. Restrict Artifact Registry repository IAM (if using AR)

    • List repositories to identify which hold container images:
      gcloud artifacts repositories list \
      --location=us-central1 \
      --project="$PROJECT_ID"
      (Repeat with other locations as needed.)
    • 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.yaml
      cat /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"
  5. 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.repoAdmin on 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.
  6. Re-run the audit to verify reduction of broad roles

    • Run again on any machine with gcloud access:
      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.
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