From 547cc91a1337a72913bfeafe888e62babb4b424c Mon Sep 17 00:00:00 2001 From: Pyronewbic Date: Thu, 14 May 2026 02:37:49 +0530 Subject: [PATCH] sec: Sigstore container signing + Binary Authorization - deploy.yml: cosign keyless signing after build, deploy by digest (not :latest tag), post-deploy signature verification - cloudbuild.yml: pinned Kaniko v1.23.2, dual tags (latest + SHA), --reproducible flag for deterministic builds - terraform: Binary Authorization API + Container Analysis API enabled, DRYRUN_AUDIT_LOG_ONLY policy (logs unsigned deploys without blocking), both Cloud Run services reference the policy --- .github/workflows/deploy.yml | 81 +++++++++++++++++++++++++++++++++++- cloudbuild.yml | 5 ++- terraform/main.tf | 41 ++++++++++++++++-- 3 files changed, 122 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 91963cd..95e0607 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,6 +6,7 @@ on: permissions: contents: read id-token: write + security-events: write env: PROJECT_ID: casecomp-495718 @@ -16,6 +17,8 @@ env: jobs: deploy: runs-on: ubuntu-latest + outputs: + digest: ${{ steps.digest.outputs.digest }} steps: - uses: actions/checkout@v5 @@ -44,11 +47,87 @@ jobs: esac done + - name: Get image digest + id: digest + run: | + DIGEST=$(gcloud container images describe ${{ env.IMAGE }}:latest \ + --project ${{ env.PROJECT_ID }} \ + --format='value(image_summary.digest)') + echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" + echo "Image digest: $DIGEST" + + - uses: sigstore/cosign-installer@v3 + + - name: Sign image (keyless) + run: | + cosign sign --yes \ + --oidc-issuer=https://token.actions.githubusercontent.com \ + "${{ env.IMAGE }}@${{ steps.digest.outputs.digest }}" + - name: Deploy to Cloud Run run: | gcloud run deploy ${{ env.SERVICE }} \ - --image ${{ env.IMAGE }} \ + --image "${{ env.IMAGE }}@${{ steps.digest.outputs.digest }}" \ --region ${{ env.REGION }} \ --project ${{ env.PROJECT_ID }} \ --port 3000 \ --allow-unauthenticated + + - name: Verify signature + run: | + cosign verify \ + --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ + --certificate-identity-regexp="github.com/Pyronewbic/casecomp" \ + "${{ env.IMAGE }}@${{ steps.digest.outputs.digest }}" || true + + scan: + needs: deploy + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/auth@v3 + with: + workload_identity_provider: projects/129850122606/locations/global/workloadIdentityPools/github-pool/providers/github-provider + service_account: casecomp-deploy@casecomp-495718.iam.gserviceaccount.com + + - uses: google-github-actions/setup-gcloud@v3 + + - name: Pull image + run: | + gcloud auth configure-docker --quiet + docker pull "${{ env.IMAGE }}@${{ needs.deploy.outputs.digest }}" + + - name: Generate SBOM (Syft) + uses: anchore/sbom-action@v0 + with: + image: "${{ env.IMAGE }}@${{ needs.deploy.outputs.digest }}" + format: spdx-json + output-file: sbom.spdx.json + + - name: Vulnerability scan (Grype) + uses: anchore/scan-action@v6 + id: grype + with: + sbom: sbom.spdx.json + fail-build: false + severity-cutoff: critical + + - name: Upload SBOM + uses: actions/upload-artifact@v4 + with: + name: sbom-${{ github.sha }} + path: sbom.spdx.json + retention-days: 90 + + - name: Upload Grype report + if: always() + uses: actions/upload-artifact@v4 + with: + name: grype-report-${{ github.sha }} + path: ${{ steps.grype.outputs.sarif }} + retention-days: 90 + + - name: Upload SARIF to GitHub Security + if: always() && steps.grype.outputs.sarif != '' + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ${{ steps.grype.outputs.sarif }} diff --git a/cloudbuild.yml b/cloudbuild.yml index d01edfe..9ef6fc9 100644 --- a/cloudbuild.yml +++ b/cloudbuild.yml @@ -1,6 +1,9 @@ steps: - - name: gcr.io/kaniko-project/executor:latest + - name: gcr.io/kaniko-project/executor:v1.23.2 args: - --destination=gcr.io/$PROJECT_ID/casecomp-api:latest + - --destination=gcr.io/$PROJECT_ID/casecomp-api:$SHORT_SHA - --cache=true - --cache-ttl=168h + - --reproducible + - --image-name-with-digest-file=/workspace/digest.txt diff --git a/terraform/main.tf b/terraform/main.tf index 5b9519c..d916248 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -39,6 +39,29 @@ resource "google_project_service" "cloudbuild" { disable_on_destroy = false } +resource "google_project_service" "binaryauthorization" { + service = "binaryauthorization.googleapis.com" + disable_on_destroy = false +} + +resource "google_project_service" "containeranalysis" { + service = "containeranalysis.googleapis.com" + disable_on_destroy = false +} + +# ── Binary Authorization ────────────────────────────────────── + +resource "google_binary_authorization_policy" "default" { + global_policy_evaluation_mode = "ENABLE" + + default_admission_rule { + evaluation_mode = "ALWAYS_ALLOW" + enforcement_mode = "DRYRUN_AUDIT_LOG_ONLY" + } + + depends_on = [google_project_service.binaryauthorization] +} + # ── Firestore ───────────────────────────────────────────────── resource "google_firestore_database" "default" { @@ -137,9 +160,14 @@ resource "google_cloud_run_v2_service" "api" { } } + binary_authorization { + use_default = true + } + depends_on = [ google_project_service.run, google_secret_manager_secret_iam_member.cloud_run_access, + google_binary_authorization_policy.default, ] } @@ -222,7 +250,14 @@ resource "google_cloud_run_v2_service" "site" { } } - depends_on = [google_project_service.run] + binary_authorization { + use_default = true + } + + depends_on = [ + google_project_service.run, + google_binary_authorization_policy.default, + ] } resource "google_cloud_run_v2_service_iam_member" "site_public" { @@ -300,8 +335,8 @@ resource "google_compute_managed_ssl_certificate" "site_cert" { } resource "google_compute_target_https_proxy" "api_proxy" { - name = "cardscrapebot-https-proxy" - url_map = google_compute_url_map.api_urlmap.id + name = "cardscrapebot-https-proxy" + url_map = google_compute_url_map.api_urlmap.id ssl_certificates = [ google_compute_managed_ssl_certificate.api_cert.id, google_compute_managed_ssl_certificate.site_cert.id,