Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/base-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ permissions:

env:
PROJECT_ID: casecomp-495718
IMAGE: gcr.io/casecomp-495718/casecomp-node24
IMAGE: us-docker.pkg.dev/casecomp-495718/casecomp-node24/node24

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: google-github-actions/auth@v3
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # 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
- uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3

- uses: actions/setup-go@v5
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.24"

Expand All @@ -34,7 +34,7 @@ jobs:
run: apko build images/node24/apko.yaml ${{ env.IMAGE }}:latest image.tar

- name: Configure Docker auth
run: gcloud auth configure-docker --quiet
run: gcloud auth configure-docker us-docker.pkg.dev --quiet

- name: Load image
run: docker load < image.tar
Expand Down
40 changes: 20 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: CI
on:
push:
branches: [main, dev]
branches: [main]
pull_request:
branches: [main]

Expand All @@ -13,8 +13,8 @@ jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
node-version: 24
- run: npm install
Expand All @@ -24,16 +24,16 @@ jobs:
continue-on-error: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
node-version: 24
- run: npm install
- name: Get Playwright version
id: pw-version
run: echo "version=$(npx playwright --version | awk '{print $2}')" >> $GITHUB_OUTPUT
- name: Cache Playwright browsers
uses: actions/cache@v5
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
id: pw-cache
with:
path: ~/.cache/ms-playwright
Expand All @@ -50,8 +50,8 @@ jobs:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
node-version: 24
- run: npm install
Expand All @@ -61,26 +61,26 @@ jobs:
codeql:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: github/codeql-action/init@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: github/codeql-action/init@7c1e4cf0b20d7c1872b26569c00ba908797a59bf # v4
with:
languages: javascript-typescript
- uses: github/codeql-action/analyze@v4
- uses: github/codeql-action/analyze@7c1e4cf0b20d7c1872b26569c00ba908797a59bf # v4

scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Generate SBOM (Syft)
uses: anchore/sbom-action@v0
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
with:
path: .
format: spdx-json
output-file: sbom.spdx.json

- name: Vulnerability scan (Grype)
uses: anchore/scan-action@v7
uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7
id: grype
with:
sbom: sbom.spdx.json
Expand All @@ -89,15 +89,15 @@ jobs:
add-cpes-if-none: true

- name: Upload SBOM
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: sbom-${{ github.sha }}
path: sbom.spdx.json
retention-days: 90

- name: Upload Grype report
if: always() && steps.grype.outputs.sarif != ''
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: grype-sarif-${{ github.sha }}
path: ${{ steps.grype.outputs.sarif }}
Expand All @@ -106,8 +106,8 @@ jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
node-version: 24
- run: npm install
Expand All @@ -119,9 +119,9 @@ jobs:
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
- uses: gitleaks/gitleaks-action@dcedce43c6f43de0b836d1fe38946645c9c638dc # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
64 changes: 29 additions & 35 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,27 @@ on:
permissions:
contents: read
id-token: write
attestations: write

env:
PROJECT_ID: casecomp-495718
SERVICE: casecomp-api
IMAGE: gcr.io/casecomp-495718/casecomp-api
IMAGE: us-docker.pkg.dev/casecomp-495718/casecomp-api/app

jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.digest.outputs.digest }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: google-github-actions/auth@v3
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # 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
- uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3

- name: Build and push
run: |
Expand All @@ -48,16 +49,16 @@ jobs:
- name: Get image digest
id: digest
run: |
DIGEST=$(gcloud container images describe ${{ env.IMAGE }}:latest \
DIGEST=$(gcloud artifacts docker images describe ${{ env.IMAGE }}:latest \
--project ${{ env.PROJECT_ID }} \
--format='value(image_summary.digest)')
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
echo "Image digest: $DIGEST"

- name: Configure Docker auth for GCR
run: gcloud auth configure-docker --quiet
- name: Configure Docker auth for Artifact Registry
run: gcloud auth configure-docker us-docker.pkg.dev --quiet

- uses: sigstore/cosign-installer@v3
- uses: sigstore/cosign-installer@f713795cb21599bc4e5c4b58cbad1da852d7eeb9 # v3

- name: Sign image (keyless)
run: |
Expand All @@ -70,10 +71,17 @@ jobs:
cosign verify \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
--certificate-identity-regexp="github.com/Pyronewbic/casecomp" \
"${{ env.IMAGE }}@${{ steps.digest.outputs.digest }}" || true
"${{ env.IMAGE }}@${{ steps.digest.outputs.digest }}"

- name: Create Binary Auth attestation
run: |
gcloud beta container binauthz attestations sign-and-create \
--artifact-url="${{ env.IMAGE }}@${{ steps.digest.outputs.digest }}" \
--attestor="projects/${{ env.PROJECT_ID }}/attestors/deploy-attestor" \
--keyversion="projects/${{ env.PROJECT_ID }}/locations/global/keyRings/binary-auth/cryptoKeys/attestor-key/cryptoKeyVersions/1"

- name: Generate container SBOM (Syft)
uses: anchore/sbom-action@v0
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
with:
image: "${{ env.IMAGE }}@${{ steps.digest.outputs.digest }}"
format: spdx-json
Expand All @@ -88,26 +96,12 @@ jobs:
--type spdxjson \
"${{ env.IMAGE }}@${{ steps.digest.outputs.digest }}"

- name: Attest SLSA provenance
run: |
cat > /tmp/provenance.json << 'PROV'
{
"buildType": "https://slsa.dev/provenance/v1",
"builder": { "id": "https://github.com/Pyronewbic/casecomp/.github/workflows/deploy.yml" },
"invocation": {
"configSource": {
"uri": "git+https://github.com/Pyronewbic/casecomp@refs/heads/main",
"digest": { "sha1": "${{ github.sha }}" },
"entryPoint": ".github/workflows/deploy.yml"
}
}
}
PROV
cosign attest --yes \
--oidc-issuer=https://token.actions.githubusercontent.com \
--predicate /tmp/provenance.json \
--type slsaprovenance \
"${{ env.IMAGE }}@${{ steps.digest.outputs.digest }}"
- name: Attest build provenance
uses: actions/attest-build-provenance@96b4a1ef7235a096b17240c259729fdd70c83d45 # v2
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.digest.outputs.digest }}
push-to-registry: true

deploy:
needs: build
Expand All @@ -117,12 +111,12 @@ jobs:
region: [asia-south1, us-central1]
fail-fast: false
steps:
- uses: google-github-actions/auth@v3
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # 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
- uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3

- name: Deploy to Cloud Run (${{ matrix.region }})
run: |
Expand Down Expand Up @@ -156,9 +150,9 @@ jobs:
needs: deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: ZAP API Scan
uses: zaproxy/action-api-scan@v0.9.0
uses: zaproxy/action-api-scan@a9a916402665623ce9d37a6998d7b48e6b35dd6c # v0.9.0
with:
target: https://api.casecomp.xyz/docs/spec.json
format: openapi
Expand All @@ -167,7 +161,7 @@ jobs:
cmd_options: '-a'
- name: Upload DAST report
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: zap-report-${{ github.sha }}
path: report_html.html
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ jobs:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: google-github-actions/auth@v3
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # 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: hashicorp/setup-terraform@v4
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4
with:
terraform_version: ${{ env.TF_VERSION }}

Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
working-directory: ${{ env.TF_DIR }}

- name: Post plan to PR
uses: actions/github-script@v7
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const fs = require('fs');
Expand Down Expand Up @@ -85,14 +85,14 @@ jobs:
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: google-github-actions/auth@v3
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # 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: hashicorp/setup-terraform@v4
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4
with:
terraform_version: ${{ env.TF_VERSION }}

Expand Down
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ Strict palette — no deviations:
- GCP: Cloud Run in asia-south1 + us-central1, Firestore (asia-south1), HTTPS LB (global, geo-routes), Cloud CDN, Secret Manager, Cloud Scheduler
- Terraform: GCS state, 8 files by resource type, CI plan/apply, `for_each` over regions
- **CI (ci.yml):** single workflow. Jobs: unit, api (demo-based, continue-on-error), smoke (non-blocking), codeql, scan (SBOM+Grype), audit (npm+lockfile-lint), secrets (gitleaks). Required: unit + codeql.
- **Deploy:** Kaniko v1.23.2 --reproducible → cosign sign → SBOM attest (Syft SPDX from container) → SLSA provenance attest → deploy by digest → both regions → health check → OWASP ZAP DAST
- **Custom Wolfi base image:** gcr.io/casecomp-495718/casecomp-node24. Built with apko. 9 smoke tests. 0 CVEs.
- **Supply chain:** SBOM + SLSA attestations on image digest, Dependabot, lockfile-lint, Socket.dev, pre-commit hook (blocks .env, secrets, large files)
- **Binary Authorization:** ENFORCED on both Cloud Run services
- **Deploy:** Kaniko v1.23.2 --reproducible → cosign sign → Binary Auth attestation (KMS) → SBOM attest (Syft SPDX from container) → build provenance (actions/attest-build-provenance) → deploy by digest → both regions → health check → OWASP ZAP DAST
- **Custom Wolfi base image:** us-docker.pkg.dev/casecomp-495718/casecomp-node24/node24. Built with apko. 9 smoke tests. 0 CVEs.
- **Supply chain:** SBOM + SLSA attestations on image digest, SHA-pinned GitHub Actions, Dependabot, lockfile-lint, Socket.dev, pre-commit hook (blocks .env, secrets, large files)
- **Binary Authorization:** ENFORCED on both Cloud Run services, KMS-backed attestor, deploy pipeline creates attestations
- **Secret workflow:** Add to secrets.tf → CI creates → `gcloud secrets versions add` for value. Never `gcloud secrets create`.
- Secrets: EBAY_CLIENT_ID/SECRET, ANTHROPIC_API_KEY, TOGETHER_API_KEY, PSA_AUTH_TOKEN, CASECOMP_API_KEY, CASECOMP_SANDBOX_KEY, RESEND_API_KEY, CASECOMP_JWT_SECRET, GOOGLE_OAUTH_CLIENT_ID, CASECOMP_ADMIN_SUB

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ RUN npm install --production

COPY . .

FROM gcr.io/casecomp-495718/casecomp-node24:latest
FROM us-docker.pkg.dev/casecomp-495718/casecomp-node24/node24:latest

WORKDIR /app
COPY --from=build /app /app
Expand Down
4 changes: 2 additions & 2 deletions cloudbuild.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
steps:
- 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
- --destination=us-docker.pkg.dev/$PROJECT_ID/casecomp-api/app:latest
- --destination=us-docker.pkg.dev/$PROJECT_ID/casecomp-api/app:$SHORT_SHA
- --cache=true
- --cache-ttl=168h
- --reproducible
Expand Down
2 changes: 1 addition & 1 deletion docs/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Both `casecomp-api` and `casecomp-site` run in asia-south1 (Mumbai) and us-centr
| Cloud Scheduler | asia-south1 | Hits LB domain, auto-routes |
| HTTPS LB | Global | Backend services have NEGs in both regions |
| Artifact Registry (frontend) | us (multi-region) | `us-docker.pkg.dev`, accessible from both regions |
| GCR (API) | us (multi-region) | `gcr.io`, accessible globally |
| Artifact Registry (API) | us (multi-region) | `us-docker.pkg.dev`, accessible globally |

Deploy workflow: build once → cosign sign → SBOM attest → SLSA attest → deploy to both regions via GitHub Actions matrix (parallel, fail-fast: false) → health check → ZAP DAST.

Expand Down
Loading
Loading