Skip to main content
CleanStart

Knowledge Hub

Building CleanStart Images in Google Cloud Build

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.com

2. 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-sha512

3. 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.yaml

Dockerfile 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 overhead

Parallel builds: Use substitutions to parallelize SAST/SBOM steps Caching: Leverage Docker layer caching with BuildKit Timeout: Set conservative timeout to fail fast on issues.