Google Cloud Build pipeline with Cloud KMS signing, Binary Authorization policy enforcement, and automated Artifact Registry scanning. The pipeline integrates natively with Google Cloud's security ecosystem.
Prerequisites
1. Enable Required APIs
PROJECT_ID="your-project-id"gcloud config set project $PROJECT_ID gcloud services enable \ cloudbuild.googleapis.com \ artifactregistry.googleapis.com \ cloudkms.googleapis.com \ containeranalysis.googleapis.com \ binaryauthorization.googleapis.com \ cloudresourcemanager.googleapis.com2. Create Cloud KMS Key Ring and Key
# Create key ringgcloud kms keyrings create cosign-keys \ --location=us-central1 # Create signing keygcloud kms keys create github-actions \ --location=us-central1 \ --keyring=cosign-keys \ --purpose=asymmetric-sign \ --default-algorithm=rsa-sign-pkcs1-4096-sha5123. Create Artifact Registry Repository
# Create dev repositorygcloud artifacts repositories create dev \ --repository-format=docker \ --location=us-central1 \ --description="Development images" # Create prod repositorygcloud artifacts repositories create prod \ --repository-format=docker \ --location=us-central1 \ --description="Production images"4. Set up Cloud Build Service Account
# Grant Cloud Build service account permissionsPROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')CB_SA="${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" # Grant Artifact Registry write accessgcloud projects add-iam-policy-binding $PROJECT_ID \ --member="serviceAccount:${CB_SA}" \ --role="roles/artifactregistry.writer" # Grant KMS signing permissiongcloud kms keys add-iam-policy-binding github-actions \ --location=us-central1 \ --keyring=cosign-keys \ --member="serviceAccount:${CB_SA}" \ --role="roles/cloudkms.signerVerifier" # Grant Cloud Build job permission to use the keygcloud kms keys add-iam-policy-binding github-actions \ --location=us-central1 \ --keyring=cosign-keys \ --member="serviceAccount:${CB_SA}" \ --role="roles/iam.serviceAccountUser"5. Configure Binary Authorization (Optional but Recommended)
# Enable Binary Authorization APIgcloud services enable binaryauthorization.googleapis.com # Create attestor for Cloud Buildgcloud beta container binauthz attestors create cloud-build-attestor \ --location=us-central1 \ --public-keys-file=/dev/stdin \ --attestation-authority-note=cloud-build-note \ --attestation-authority-note-project=$PROJECT_ID <<< \ "$(gcloud kms keys versions list \ --key=github-actions \ --location=us-central1 \ --keyring=cosign-keys \ --format='get(name)' | \ xargs -I {} gcloud kms keys versions describe {} \ --location=us-central1 \ --keyring=cosign-keys \ --key=github-actions \ --format='value(publicKey.pemCertificate)')"Complete Cloud Build Configuration
Create cloudbuild.yaml:
steps: # ===== STAGE 1: ANALYZE ===== - id: 'analyze-sast' name: 'gcr.io/cloud-builders/gke-deploy' entrypoint: 'bash' args: - '-c' - | # Use Cloud Code Analysis API or local SAST tool echo "Running SAST analysis on source code..." # Example: trivy fs . echo "SAST complete" - id: 'analyze-sbom' name: 'ghcr.io/cyclonedx/cyclonedx-linux' args: - '--output' - 'sbom.json' - '.' - id: 'generate-spdx-sbom' name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: - '-c' - | cat > sbom.spdx <<'EOF' SPDXVersion: SPDX-3.0 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT DocumentName: $PROJECT_ID-$BRANCH_NAME Creator: Tool: Cloud-Build Created: $(date -u +'%Y-%m-%dT%H:%M:%SZ') EOF cat sbom.json >> sbom.spdx # ===== STAGE 2: BUILD ===== - id: 'build-image' name: 'gcr.io/cloud-builders/docker' args: - 'build' - '--build-arg' - 'BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')' - '--build-arg' - 'VCS_REF=$COMMIT_SHA' - '--build-arg' - 'VERSION=$SHORT_SHA' - '-t' - '${REGISTRY}/${_REPOSITORY}/dev/${_IMAGE_NAME}:${SHORT_SHA}' - '-t' - '${REGISTRY}/${_REPOSITORY}/dev/${_IMAGE_NAME}:latest' - '.' env: - 'REGISTRY=us-central1-docker.pkg.dev' - '_REPOSITORY=dev' - id: 'test-image' name: 'gcr.io/cloud-builders/docker' args: - 'run' - '--rm' - 'us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA}' - '/test.sh' - id: 'generate-image-sbom' name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: - '-c' - | docker run --rm \ us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA} \ clnstrt-cli generate-sbom \ --image us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA} \ --output /tmp/image-sbom.spdx cp /tmp/image-sbom.spdx image-sbom.spdx # ===== STAGE 3: VERIFY ===== - id: 'verify-fips' name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: - '-c' - | echo "Verifying FIPS compliance..." if grep -iE "md5|sha1|des|rc4" Dockerfile; then echo "ERROR: Non-FIPS algorithms found" exit 1 fi echo "✅ FIPS verification passed" - id: 'scan-image' name: 'gcr.io/cloud-builders/container-scanning' args: - 'us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA}' - id: 'verify-sbom' name: 'alpine:latest' entrypoint: 'sh' args: - '-c' - | echo "Verifying SBOM..." if [[ ! -f "image-sbom.spdx" ]]; then echo "ERROR: SBOM not found" exit 1 fi echo "✅ SBOM verified" # ===== STAGE 4: SIGN & ATTEST ===== - id: 'sign-image-kms' name: 'gcr.io/cloud-builders/gke-deploy' entrypoint: 'bash' env: - 'CLOUDSDK_COMPUTE_REGION=us-central1' args: - '-c' - | # Sign image digest with Cloud KMS IMAGE_URI="us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA}" IMAGE_DIGEST=$(gcloud container images describe $IMAGE_URI --format='value(image_summary.digest)') echo "Signing image digest: $IMAGE_DIGEST" # Use Cosign with KMS key cosign sign --key \ kms://projects/${PROJECT_ID}/locations/us-central1/keyRings/cosign-keys/cryptoKeys/github-actions \ $IMAGE_URI@$IMAGE_DIGEST echo "✅ Image signed with KMS key" - id: 'create-provenance' name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: - '-c' - | cat > provenance.json <<EOF { "predicateType": "https://slsa.dev/provenance/v0.2", "predicate": { "builder": { "id": "https://cloudbuild.googleapis.com/projects/${PROJECT_ID}/builds/${BUILD_ID}" }, "sourceRepository": "${REPO_NAME}", "ref": "${BRANCH_NAME}", "commit": "${COMMIT_SHA}", "buildStartedOn": "${BUILD_START_TIME}", "buildFinishedOn": "$(date -u +'%Y-%m-%dT%H:%M:%SZ')", "completeness": { "arguments": true, "environment": true, "materials": true }, "reproducible": true } } EOF cat provenance.json - id: 'attest-provenance' name: 'gcr.io/cloud-builders/gke-deploy' entrypoint: 'bash' args: - '-c' - | IMAGE_URI="us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA}" cosign attest --predicate provenance.json \ --key kms://projects/${PROJECT_ID}/locations/us-central1/keyRings/cosign-keys/cryptoKeys/github-actions \ $IMAGE_URI echo "✅ SLSA provenance attestation created" - id: 'attest-sbom' name: 'gcr.io/cloud-builders/gke-deploy' entrypoint: 'bash' args: - '-c' - | IMAGE_URI="us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA}" cosign attest --predicate image-sbom.spdx \ --type spdx \ --key kms://projects/${PROJECT_ID}/locations/us-central1/keyRings/cosign-keys/cryptoKeys/github-actions \ $IMAGE_URI echo "✅ SBOM attestation created" # ===== STAGE 5: PUSH TO REGISTRY ===== - id: 'push-to-registry' name: 'gcr.io/cloud-builders/docker' args: - 'push' - 'us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA}' - id: 'push-latest-tag' name: 'gcr.io/cloud-builders/docker' args: - 'push' - 'us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:latest' # ===== STAGE 6: BINARY AUTHORIZATION (if enabled) ===== - id: 'create-attestation' name: 'gcr.io/cloud-builders/gke-deploy' entrypoint: 'bash' args: - '-c' - | IMAGE_URI="us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA}" IMAGE_DIGEST=$(gcloud container images describe $IMAGE_URI --format='value(image_summary.digest)') # Create Binary Authorization attestation gcloud beta container binauthz attestations create \ --attestation-authority-note=cloud-build-note \ --attestation-authority-note-project=${PROJECT_ID} \ --artifact-url=$IMAGE_URI@$IMAGE_DIGEST \ --validate \ --pgp-public-key-id=cloud-build-attestor echo "✅ Binary Authorization attestation created" # ===== PROD PROMOTION (on main branch) ===== - id: 'promote-prod' name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: - '-c' - | if [[ "$BRANCH_NAME" != "main" ]]; then echo "Skipping prod promotion (branch: $BRANCH_NAME)" exit 0 fi IMAGE_DEV="us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA}" IMAGE_PROD="us-central1-docker.pkg.dev/${PROJECT_ID}/prod/${_IMAGE_NAME}:${SHORT_SHA}" echo "Promoting image to production..." docker pull $IMAGE_DEV docker tag $IMAGE_DEV $IMAGE_PROD docker push $IMAGE_PROD echo "✅ Image promoted to production" # Timeout for entire buildtimeout: '3600s'logsBucket: 'gs://${PROJECT_ID}-cloudbuild-logs' # Images to pushimages: - 'us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:${SHORT_SHA}' - 'us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${_IMAGE_NAME}:latest' # Substitution variablessubstitutions: _IMAGE_NAME: 'myapp' _REPOSITORY: 'dev' options: logging: CLOUD_LOGGING_ONLY machineType: 'N1_HIGHCPU_8' # Build trigger configuration# Go to Cloud Build → Triggers# - Name: main-branch-build# - Repository: [your repository]# - Branch: ^main$# - Cloud Build configuration location: cloudbuild.yamlDockerfile for CleanStart
FROM cleanstart-gcc:14-builder AS builderLABEL stage=builderLABEL maintainer="security@example.com" RUN apk add --no-cache \ git \ make \ autoconf \ automake \ libtool \ pkgconfig COPY . /srcWORKDIR /src # Hermetic buildRUN ./configure \ --prefix=/usr/local \ --disable-doc \ --disable-man \ && make -j$(nproc) \ && make check \ && make install # Generate SBOMRUN clnstrt-cli generate-sbom \ --output /build-artifacts/sbom.spdx FROM cleanstart-gcc:14-prod # Non-root userRUN groupadd -r appuser && useradd -r -g appuser appuser # Build provenance labelsARG BUILD_DATEARG VCS_REFARG VERSION LABEL \ org.opencontainers.image.created="$BUILD_DATE" \ org.opencontainers.image.revision="$VCS_REF" \ org.opencontainers.image.version="$VERSION" \ org.opencontainers.image.title="myapp" \ org.opencontainers.image.description="Production-ready app" \ org.opencontainers.image.vendor="Example Corp" COPY --from=builder /usr/local /usr/localCOPY --from=builder /build-artifacts /attestations # Minimal surface areaRUN rm -rf /tmp/* /var/cache/* /var/log/* USER appuser HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD /usr/local/bin/healthcheck || exit 1 ENTRYPOINT ["/usr/local/bin/myapp"]Deployment Verification Script
Create verify-deployment.sh:
#!/bin/bashset -e PROJECT_ID="your-project-id"IMAGE_NAME="myapp"REGION="us-central1" echo "Verifying Cloud Build deployment..." # Get latest buildBUILD_ID=$(gcloud builds list \ --limit=1 \ --format='value(id)') echo "Latest build: $BUILD_ID" # Get build detailsgcloud builds log $BUILD_ID --stream=false # Verify image exists in registryIMAGE_URI="us-central1-docker.pkg.dev/${PROJECT_ID}/dev/${IMAGE_NAME}:latest" echo "Verifying image: $IMAGE_URI"gcloud container images describe $IMAGE_URI # Get image digestIMAGE_DIGEST=$(gcloud container images describe $IMAGE_URI --format='value(image_summary.digest)') echo "Image digest: $IMAGE_DIGEST" # Verify signatureecho "Verifying Cosign signature..."cosign verify --key \ kms://projects/${PROJECT_ID}/locations/us-central1/keyRings/cosign-keys/cryptoKeys/github-actions \ ${IMAGE_URI}@${IMAGE_DIGEST} echo "✅ All verifications passed"Setting up Build Triggers
# Create trigger from GitHub/Cloud Source Repositoriesgcloud builds triggers create github \ --name="main-branch-secure-build" \ --repo-name="your-repo" \ --repo-owner="your-org" \ --branch-pattern="^main$" \ --build-config="cloudbuild.yaml" \ --service-account="projects/${PROJECT_ID}/serviceAccounts/${CB_SA}" \ --substitutions="_IMAGE_NAME=myapp,_REPOSITORY=dev"Monitoring and Compliance
View build logs and attestations:
# View recent buildsgcloud builds list --limit=10 # View build loggcloud builds log BUILD_ID # List attestations for imagegcloud container binauthz attestations list \ --attestation-authority-note=cloud-build-note \ --attestation-authority-note-project=${PROJECT_ID} # Verify attestationgcloud beta container binauthz attestations describe \ --artifact-url=us-central1-docker.pkg.dev/PROJECT_ID/dev/myapp:TAG \ --attestation-authority-note=cloud-build-note \ --attestation-authority-note-project=${PROJECT_ID}Cost Optimization
options: machineType: 'N1_HIGHCPU_8' # Faster builds = lower cost logging: CLOUD_LOGGING_ONLY # Avoid Cloud Build logs bucket overheadParallel builds: Use substitutions to parallelize SAST/SBOM steps Caching: Leverage Docker layer caching with BuildKit Timeout: Set conservative timeout to fail fast on issues.
