cloud security

Implementing GCP Binary Authorization

Implement GCP Binary Authorization to enforce deploy-time security controls that ensure only trusted, attested container images are deployed to Google Kubernetes Engine and Cloud Run.

attestationbinary-authorizationcloud-runcontainer-securitygcpgkesoftware-integritysupply-chain
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Binary Authorization is a Google Cloud deploy-time security control that ensures only trusted container images are deployed on GKE or Cloud Run. It works through a policy-based model where images must have cryptographic attestations confirming they passed predefined requirements such as vulnerability scans, code reviews, or build pipeline verification. Continuous validation (CV) monitors running pods against policies and logs violations.

When to Use

  • When deploying or configuring implementing gcp binary authorization capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • GCP project with Binary Authorization API enabled
  • GKE cluster or Cloud Run service
  • Container Analysis API enabled
  • KMS keys for attestation signing
  • Cloud Build or external CI/CD pipeline

Enable Binary Authorization

# Enable required APIs
gcloud services enable binaryauthorization.googleapis.com
gcloud services enable containeranalysis.googleapis.com
gcloud services enable container.googleapis.com
 
# Enable Binary Authorization on GKE cluster
gcloud container clusters update CLUSTER_NAME \
  --enable-binauthz \
  --zone us-central1-a

Create Attestor

Create a KMS key for signing

# Create keyring
gcloud kms keyrings create binauthz-keyring \
  --location global
 
# Create signing key
gcloud kms keys create attestor-key \
  --keyring binauthz-keyring \
  --location global \
  --algorithm ec-sign-p256-sha256 \
  --purpose asymmetric-signing

Create Container Analysis note

cat > /tmp/note.json << 'EOF'
{
  "attestation": {
    "hint": {
      "humanReadableName": "Production Build Attestor"
    }
  }
}
EOF
 
curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://containeranalysis.googleapis.com/v1/projects/PROJECT_ID/notes/?noteId=prod-build-note" \
  -d @/tmp/note.json

Create the attestor

gcloud container binauthz attestors create prod-build-attestor \
  --attestation-authority-note=prod-build-note \
  --attestation-authority-note-project=PROJECT_ID
 
# Add KMS key to attestor
gcloud container binauthz attestors public-keys add \
  --attestor=prod-build-attestor \
  --keyversion-project=PROJECT_ID \
  --keyversion-location=global \
  --keyversion-keyring=binauthz-keyring \
  --keyversion-key=attestor-key \
  --keyversion=1

Configure Policy

Default deny-all policy

# binauthz-policy.yaml
admissionWhitelistPatterns:
  - namePattern: "gcr.io/google_containers/*"
  - namePattern: "gcr.io/google-containers/*"
  - namePattern: "k8s.gcr.io/**"
  - namePattern: "gke.gcr.io/**"
  - namePattern: "gcr.io/stackdriver-agents/*"
defaultAdmissionRule:
  evaluationMode: REQUIRE_ATTESTATION
  enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
  requireAttestationsBy:
    - projects/PROJECT_ID/attestors/prod-build-attestor
globalPolicyEvaluationMode: ENABLE
gcloud container binauthz policy import binauthz-policy.yaml

Per-cluster rules

admissionWhitelistPatterns:
  - namePattern: "gcr.io/google_containers/*"
clusterAdmissionRules:
  us-central1-a.production-cluster:
    evaluationMode: REQUIRE_ATTESTATION
    enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
    requireAttestationsBy:
      - projects/PROJECT_ID/attestors/prod-build-attestor
  us-central1-a.staging-cluster:
    evaluationMode: ALWAYS_ALLOW
    enforcementMode: DRYRUN_AUDIT_LOG_ONLY
defaultAdmissionRule:
  evaluationMode: ALWAYS_DENY
  enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG

Create Attestations

Attest an image after successful build

# Get image digest
IMAGE_DIGEST=$(gcloud container images describe \
  gcr.io/PROJECT_ID/my-app:latest \
  --format='get(image_summary.digest)')
 
# Create attestation
gcloud container binauthz attestations sign-and-create \
  --artifact-url="gcr.io/PROJECT_ID/my-app@${IMAGE_DIGEST}" \
  --attestor="prod-build-attestor" \
  --attestor-project="PROJECT_ID" \
  --keyversion-project="PROJECT_ID" \
  --keyversion-location="global" \
  --keyversion-keyring="binauthz-keyring" \
  --keyversion-key="attestor-key" \
  --keyversion="1"

Cloud Build integration

# cloudbuild.yaml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/$PROJECT_ID/my-app:$SHORT_SHA', '.']
 
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/$PROJECT_ID/my-app:$SHORT_SHA']
 
  # Vulnerability scanning
  - name: 'gcr.io/cloud-builders/gcloud'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        gcloud artifacts docker images scan \
          gcr.io/$PROJECT_ID/my-app:$SHORT_SHA \
          --format='value(response.scan)'
 
  # Create attestation after successful scan
  - name: 'gcr.io/cloud-builders/gcloud'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        IMAGE_DIGEST=$(gcloud container images describe \
          gcr.io/$PROJECT_ID/my-app:$SHORT_SHA \
          --format='get(image_summary.digest)')
        gcloud container binauthz attestations sign-and-create \
          --artifact-url="gcr.io/$PROJECT_ID/my-app@$${IMAGE_DIGEST}" \
          --attestor="prod-build-attestor" \
          --attestor-project="$PROJECT_ID" \
          --keyversion-project="$PROJECT_ID" \
          --keyversion-location="global" \
          --keyversion-keyring="binauthz-keyring" \
          --keyversion-key="attestor-key" \
          --keyversion="1"

Continuous Validation

# Enable CV on a GKE cluster
gcloud container clusters update CLUSTER_NAME \
  --enable-binauthz-monitoring \
  --zone us-central1-a

Monitor CV violations in Cloud Logging

resource.type="k8s_cluster"
logName="projects/PROJECT_ID/logs/binaryauthorization.googleapis.com%2Fcontinuous_validation"

Verification and Testing

Test deployment of unattested image

# This should be blocked
kubectl run test-unapproved \
  --image=docker.io/library/nginx:latest
 
# Verify the pod was denied
kubectl get events --field-selector reason=FailedCreate

Verify attestation exists

gcloud container binauthz attestations list \
  --attestor=prod-build-attestor \
  --attestor-project=PROJECT_ID

Break-Glass Override

For emergency deployments bypassing Binary Authorization:

apiVersion: v1
kind: Pod
metadata:
  name: emergency-pod
  labels:
    image-policy.k8s.io/break-glass: "true"
  annotations:
    alpha.image-policy.k8s.io/break-glass: "Emergency deployment - ticket INC-12345"
spec:
  containers:
    - name: emergency
      image: gcr.io/PROJECT_ID/emergency-fix:latest

References

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.8 KB

API Reference: Implementing GCP Binary Authorization

gcloud CLI Commands

# Enable APIs
gcloud services enable binaryauthorization.googleapis.com containeranalysis.googleapis.com
 
# Enable on GKE cluster
gcloud container clusters update CLUSTER --enable-binauthz --zone ZONE
 
# Export policy
gcloud container binauthz policy export --project PROJECT_ID
 
# Import policy
gcloud container binauthz policy import policy.yaml
 
# Create attestor
gcloud container binauthz attestors create ATTESTOR_NAME \
  --attestation-authority-note=NOTE_ID \
  --attestation-authority-note-project=PROJECT_ID
 
# Create attestation
gcloud container binauthz attestations sign-and-create \
  --artifact-url="gcr.io/PROJECT/IMAGE@DIGEST" \
  --attestor="ATTESTOR" --attestor-project="PROJECT" \
  --keyversion-project=PROJECT --keyversion-location=global \
  --keyversion-keyring=KEYRING --keyversion-key=KEY --keyversion=1

Policy Structure

Field Values Description
evaluationMode ALWAYS_ALLOW, ALWAYS_DENY, REQUIRE_ATTESTATION How images are evaluated
enforcementMode ENFORCED_BLOCK_AND_AUDIT_LOG, DRYRUN_AUDIT_LOG_ONLY Block or audit-only
globalPolicyEvaluationMode ENABLE, DISABLE Google-maintained system policy

Break-Glass Annotation

metadata:
  annotations:
    alpha.image-policy.k8s.io/break-glass: "Emergency - INC-12345"

Cloud Logging Filter (CV Violations)

resource.type="k8s_cluster"
logName="projects/PROJECT/logs/binaryauthorization.googleapis.com%2Fcontinuous_validation"

References

standards.md0.5 KB

Standards - GCP Binary Authorization

SLSA Framework Levels

  • SLSA 1: Documentation of build process
  • SLSA 2: Tamper resistance of build service
  • SLSA 3: Extra resistance to threats
  • SLSA 4: Highest levels of confidence and trust

NIST 800-53

  • SA-10: Developer Configuration Management
  • SA-12: Supply Chain Protection
  • SI-7: Software, Firmware, and Information Integrity

CIS GKE Benchmark

  • 6.10.4: Ensure Binary Authorization is enabled for GKE clusters
workflows.md0.6 KB

Workflows - GCP Binary Authorization

Attestation Pipeline

1. Developer pushes code
2. Cloud Build triggers container build
3. Vulnerability scan runs on built image
4. If scan passes → Create cryptographic attestation
5. Push attested image to registry
6. GKE validates attestation at deploy time
7. Continuous validation monitors running pods

Break-Glass Procedure

1. Emergency identified → Create incident ticket
2. Apply break-glass annotation to pod spec
3. Deploy with override documented
4. Alert security team of break-glass usage
5. Post-incident: Review and attest emergency image
6. Remove break-glass annotation

Scripts 2

agent.py7.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for auditing and managing GCP Binary Authorization policies."""

import json
import argparse
import subprocess
from datetime import datetime


def run_gcloud(args_list):
    """Run a gcloud command and return parsed JSON output."""
    cmd = ["gcloud"] + args_list + ["--format=json"]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
    if result.returncode != 0:
        return {"error": result.stderr.strip()}
    try:
        return json.loads(result.stdout) if result.stdout.strip() else {}
    except json.JSONDecodeError:
        return {"raw": result.stdout.strip()}


def get_binauthz_policy(project):
    """Retrieve the Binary Authorization policy for a project."""
    return run_gcloud(["container", "binauthz", "policy", "export",
                       "--project", project])


def list_attestors(project):
    """List all attestors in the project."""
    return run_gcloud(["container", "binauthz", "attestors", "list",
                       "--project", project])


def list_attestations(project, attestor):
    """List attestations for a given attestor."""
    return run_gcloud(["container", "binauthz", "attestations", "list",
                       "--attestor", attestor, "--attestor-project", project])


def verify_image_attested(project, attestor, image_url):
    """Check if a container image has a valid attestation."""
    result = run_gcloud(["container", "binauthz", "attestations", "list",
                         "--attestor", attestor, "--attestor-project", project,
                         "--artifact-url", image_url])
    if isinstance(result, list) and len(result) > 0:
        return {"image": image_url, "attested": True, "attestation_count": len(result)}
    return {"image": image_url, "attested": False, "attestation_count": 0}


def audit_policy(policy):
    """Audit a Binary Authorization policy for security issues."""
    findings = []
    if isinstance(policy, dict) and "error" in policy:
        return [{"issue": "Cannot retrieve policy", "severity": "CRITICAL",
                 "detail": policy["error"]}]

    default_rule = policy.get("defaultAdmissionRule", {})
    eval_mode = default_rule.get("evaluationMode", "")
    enforce_mode = default_rule.get("enforcementMode", "")

    if eval_mode == "ALWAYS_ALLOW":
        findings.append({"issue": "Default rule allows all images",
                         "severity": "CRITICAL",
                         "recommendation": "Set evaluationMode to REQUIRE_ATTESTATION"})

    if enforce_mode == "DRYRUN_AUDIT_LOG_ONLY":
        findings.append({"issue": "Default rule is dry-run only",
                         "severity": "HIGH",
                         "recommendation": "Set enforcementMode to ENFORCED_BLOCK_AND_AUDIT_LOG"})

    attestors = default_rule.get("requireAttestationsBy", [])
    if eval_mode == "REQUIRE_ATTESTATION" and not attestors:
        findings.append({"issue": "Attestation required but no attestors configured",
                         "severity": "CRITICAL"})

    global_eval = policy.get("globalPolicyEvaluationMode", "")
    if global_eval != "ENABLE":
        findings.append({"issue": "Global policy evaluation not enabled",
                         "severity": "MEDIUM",
                         "recommendation": "Set globalPolicyEvaluationMode to ENABLE"})

    whitelist = policy.get("admissionWhitelistPatterns", [])
    for pattern in whitelist:
        name = pattern.get("namePattern", "")
        if name.endswith("/**") or name.endswith("/*"):
            if not any(safe in name for safe in ["gcr.io/google", "k8s.gcr.io", "gke.gcr.io"]):
                findings.append({"issue": f"Broad whitelist pattern: {name}",
                                 "severity": "HIGH",
                                 "recommendation": "Restrict whitelist to specific images"})

    cluster_rules = policy.get("clusterAdmissionRules", {})
    for cluster, rule in cluster_rules.items():
        if rule.get("evaluationMode") == "ALWAYS_ALLOW":
            findings.append({"issue": f"Cluster {cluster} allows all images",
                             "severity": "HIGH"})

    if not findings:
        findings.append({"issue": "No issues found", "severity": "INFO"})

    return findings


def generate_policy(project, attestors, allowed_patterns=None):
    """Generate a Binary Authorization policy YAML."""
    whitelist = allowed_patterns or [
        "gcr.io/google_containers/*", "gcr.io/google-containers/*",
        "k8s.gcr.io/**", "gke.gcr.io/**", "gcr.io/stackdriver-agents/*",
    ]
    policy = {
        "admissionWhitelistPatterns": [{"namePattern": p} for p in whitelist],
        "defaultAdmissionRule": {
            "evaluationMode": "REQUIRE_ATTESTATION",
            "enforcementMode": "ENFORCED_BLOCK_AND_AUDIT_LOG",
            "requireAttestationsBy": [
                f"projects/{project}/attestors/{a}" for a in attestors
            ],
        },
        "globalPolicyEvaluationMode": "ENABLE",
    }
    return policy


def check_cv_status(project, cluster, zone):
    """Check continuous validation status on a GKE cluster."""
    result = run_gcloud(["container", "clusters", "describe", cluster,
                         "--zone", zone, "--project", project])
    if isinstance(result, dict):
        binauthz = result.get("binaryAuthorization", {})
        return {
            "cluster": cluster,
            "enabled": binauthz.get("enabled", False),
            "evaluation_mode": binauthz.get("evaluationMode", ""),
        }
    return {"cluster": cluster, "error": "Cannot retrieve cluster info"}


def main():
    parser = argparse.ArgumentParser(description="GCP Binary Authorization Agent")
    parser.add_argument("--project", required=True, help="GCP project ID")
    parser.add_argument("--action", choices=["audit", "list-attestors", "verify",
                                              "generate", "cv-status"],
                        default="audit")
    parser.add_argument("--attestor", help="Attestor name")
    parser.add_argument("--image", help="Container image URL for verification")
    parser.add_argument("--cluster", help="GKE cluster name")
    parser.add_argument("--zone", default="us-central1-a")
    parser.add_argument("--output", default="binauthz_report.json")
    args = parser.parse_args()

    report = {"generated_at": datetime.utcnow().isoformat(), "project": args.project,
              "results": {}}

    if args.action == "audit":
        policy = get_binauthz_policy(args.project)
        findings = audit_policy(policy)
        report["results"]["policy"] = policy
        report["results"]["findings"] = findings
        for f in findings:
            print(f"[{f['severity']}] {f['issue']}")

    elif args.action == "list-attestors":
        attestors = list_attestors(args.project)
        report["results"]["attestors"] = attestors
        print(f"[+] Found {len(attestors) if isinstance(attestors, list) else 0} attestors")

    elif args.action == "verify" and args.attestor and args.image:
        result = verify_image_attested(args.project, args.attestor, args.image)
        report["results"]["verification"] = result
        status = "ATTESTED" if result["attested"] else "NOT ATTESTED"
        print(f"[+] {args.image}: {status}")

    elif args.action == "generate":
        attestors = [args.attestor] if args.attestor else ["prod-build-attestor"]
        policy = generate_policy(args.project, attestors)
        report["results"]["generated_policy"] = policy
        print("[+] Policy generated")

    elif args.action == "cv-status" and args.cluster:
        status = check_cv_status(args.project, args.cluster, args.zone)
        report["results"]["cv_status"] = status
        print(f"[+] CV enabled: {status.get('enabled', False)}")

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
process.py6.0 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
GCP Binary Authorization Management Script

Automates attestor management, policy configuration, and attestation
creation for container image supply chain security.
"""

import json
import subprocess
import sys


def run_gcloud(args):
    """Execute gcloud command and return parsed output."""
    cmd = ["gcloud"] + args + ["--format=json"]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        return None, result.stderr
    try:
        return json.loads(result.stdout) if result.stdout.strip() else {}, None
    except json.JSONDecodeError:
        return result.stdout, None


def check_binauthz_status(project_id):
    """Check Binary Authorization configuration status."""
    print(f"[*] Checking Binary Authorization status for project: {project_id}")

    policy, err = run_gcloud([
        "container", "binauthz", "policy", "export",
        f"--project={project_id}"
    ])

    if err:
        print(f"[!] Error fetching policy: {err}")
        return None

    print(f"[+] Current policy retrieved")
    return policy


def list_attestors(project_id):
    """List all configured attestors."""
    attestors, err = run_gcloud([
        "container", "binauthz", "attestors", "list",
        f"--project={project_id}"
    ])

    if err:
        print(f"[!] Error listing attestors: {err}")
        return []

    if isinstance(attestors, list):
        print(f"[+] Found {len(attestors)} attestors:")
        for a in attestors:
            print(f"  - {a.get('name', 'unknown')}")
    return attestors or []


def verify_attestation(project_id, image_url, attestor):
    """Verify that an image has a valid attestation."""
    print(f"[*] Verifying attestation for: {image_url}")

    attestations, err = run_gcloud([
        "container", "binauthz", "attestations", "list",
        f"--attestor={attestor}",
        f"--attestor-project={project_id}",
        f"--artifact-url={image_url}"
    ])

    if err:
        print(f"[!] Error: {err}")
        return False

    if isinstance(attestations, list) and len(attestations) > 0:
        print(f"[+] Image has {len(attestations)} attestation(s)")
        return True
    else:
        print(f"[!] No attestations found for image")
        return False


def create_attestation(project_id, image_url, attestor, key_info):
    """Create an attestation for a container image."""
    print(f"[*] Creating attestation for: {image_url}")

    _, err = run_gcloud([
        "container", "binauthz", "attestations", "sign-and-create",
        f"--artifact-url={image_url}",
        f"--attestor={attestor}",
        f"--attestor-project={project_id}",
        f"--keyversion-project={key_info['project']}",
        f"--keyversion-location={key_info['location']}",
        f"--keyversion-keyring={key_info['keyring']}",
        f"--keyversion-key={key_info['key']}",
        f"--keyversion={key_info['version']}"
    ])

    if err:
        print(f"[!] Error creating attestation: {err}")
        return False

    print(f"[+] Attestation created successfully")
    return True


def audit_policy_compliance(project_id):
    """Audit Binary Authorization policy for security compliance."""
    policy, err = run_gcloud([
        "container", "binauthz", "policy", "export",
        f"--project={project_id}"
    ])

    if not policy:
        print("[!] Could not retrieve policy")
        return

    findings = []

    if isinstance(policy, str):
        import yaml
        policy = yaml.safe_load(policy)

    default_rule = policy.get("defaultAdmissionRule", {})
    if default_rule.get("evaluationMode") == "ALWAYS_ALLOW":
        findings.append({
            "severity": "HIGH",
            "finding": "Default admission rule allows all images",
            "recommendation": "Set to REQUIRE_ATTESTATION or ALWAYS_DENY"
        })

    if default_rule.get("enforcementMode") == "DRYRUN_AUDIT_LOG_ONLY":
        findings.append({
            "severity": "MEDIUM",
            "finding": "Default rule is in dry-run mode (not enforcing)",
            "recommendation": "Switch to ENFORCED_BLOCK_AND_AUDIT_LOG"
        })

    if not policy.get("globalPolicyEvaluationMode") == "ENABLE":
        findings.append({
            "severity": "LOW",
            "finding": "Global policy evaluation not explicitly enabled",
            "recommendation": "Enable globalPolicyEvaluationMode"
        })

    whitelist = policy.get("admissionWhitelistPatterns", [])
    for pattern in whitelist:
        name = pattern.get("namePattern", "")
        if name == "*" or name == "**":
            findings.append({
                "severity": "CRITICAL",
                "finding": f"Wildcard whitelist pattern: {name}",
                "recommendation": "Remove overly broad whitelist patterns"
            })

    print(f"\n[*] Policy Compliance Audit Results:")
    if findings:
        for f in findings:
            print(f"  [{f['severity']}] {f['finding']}")
            print(f"    Recommendation: {f['recommendation']}")
    else:
        print("  [OK] No issues found")

    return findings


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="GCP Binary Authorization Management")
    parser.add_argument("--project", required=True, help="GCP Project ID")
    parser.add_argument("--status", action="store_true", help="Check policy status")
    parser.add_argument("--list-attestors", action="store_true", help="List attestors")
    parser.add_argument("--verify", type=str, help="Verify attestation for image URL")
    parser.add_argument("--attestor", type=str, help="Attestor name")
    parser.add_argument("--audit", action="store_true", help="Audit policy compliance")
    args = parser.parse_args()

    if args.status:
        check_binauthz_status(args.project)
    if args.list_attestors:
        list_attestors(args.project)
    if args.verify and args.attestor:
        verify_attestation(args.project, args.verify, args.attestor)
    if args.audit:
        audit_policy_compliance(args.project)

Assets 1

template.mdtext/markdown · 0.7 KB
Keep exploring