feat(governance): add FlowSignal provider seam and TLS hardening #206
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright 2026 Google LLC | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # https://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| name: SBOM Generation | |
| # POAM-006 / NIST SP 800-53 CM-8 / ISO 42001 §A.8.3 | |
| # Generates CycloneDX SBOMs for all CAGE container images after each build. | |
| # Uploads SBOM artifacts to object storage and fails the build if CRITICAL | |
| # CVEs are found (except allowlisted items in .trivyignore). | |
| # | |
| # Storage backend — technology-agnostic via the S3-compatible API: | |
| # AWS S3 → set SBOM_S3_BUCKET + SBOM_S3_ACCESS_KEY + SBOM_S3_SECRET_KEY | |
| # leave SBOM_S3_ENDPOINT unset (defaults to AWS) | |
| # GCS → set SBOM_S3_BUCKET + SBOM_S3_ACCESS_KEY + SBOM_S3_SECRET_KEY | |
| # (use a GCS HMAC key pair — Cloud Storage > Settings > Interoperability) | |
| # set SBOM_S3_ENDPOINT=https://storage.googleapis.com | |
| # MinIO/R2 → set all four secrets with your endpoint URL | |
| # | |
| # If none of the SBOM_S3_* secrets are configured the upload step is skipped; | |
| # the SBOM is still generated and uploaded as a GitHub Actions artifact. | |
| on: | |
| push: | |
| branches: [main, "feat/**"] | |
| paths: | |
| - "src/**" | |
| - "Dockerfile*" | |
| - "pyproject.toml" | |
| - "uv.lock" | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - "src/**" | |
| - "Dockerfile*" | |
| - "pyproject.toml" | |
| - "uv.lock" | |
| workflow_dispatch: | |
| env: | |
| REGISTRY: gcr.io | |
| # Fallback to a placeholder when GCP_PROJECT_ID is not configured (e.g. PRs | |
| # from forks or CI contexts without GCP secrets). Without this, the | |
| # interpolated value is an empty string, producing "gcr.io//<image>:<tag>" | |
| # — an invalid Docker reference ("invalid reference format") that fails | |
| # `docker build` before the image can even be built for SBOM/CVE scanning. | |
| PROJECT_ID: ${{ secrets.GCP_PROJECT_ID || 'cage-ci-placeholder' }} | |
| permissions: | |
| contents: read | |
| # id-token: write is NOT required — no Workload Identity Federation used here. | |
| # SBOM upload uses the S3-compatible API with HMAC/access-key credentials. | |
| jobs: | |
| sbom-scan: | |
| name: Generate SBOM and Scan for CVEs | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| service: | |
| - name: gateway | |
| dockerfile: Dockerfile | |
| image_suffix: gateway | |
| - name: compliance-bridge | |
| dockerfile: src/compliance_bridge/Dockerfile | |
| image_suffix: compliance-bridge | |
| fail-fast: false | |
| # Expose S3-compatible storage secrets as env vars so step-level `if:` | |
| # conditions can test whether they are configured. | |
| # secrets context is not available in `if:` expressions — env vars are. | |
| # | |
| # Backend selection: | |
| # SBOM_S3_ENDPOINT unset → AWS S3 (aws cli default) | |
| # SBOM_S3_ENDPOINT set → custom S3-compatible endpoint | |
| # e.g. https://storage.googleapis.com (GCS) | |
| # https://<account>.r2.cloudflarestorage.com | |
| # http://minio:9000 | |
| env: | |
| SBOM_S3_BUCKET: ${{ secrets.SBOM_S3_BUCKET }} | |
| SBOM_S3_ACCESS_KEY: ${{ secrets.SBOM_S3_ACCESS_KEY }} | |
| SBOM_S3_SECRET_KEY: ${{ secrets.SBOM_S3_SECRET_KEY }} | |
| SBOM_S3_ENDPOINT: ${{ secrets.SBOM_S3_ENDPOINT }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install Trivy | |
| run: | | |
| sudo apt-get install -y wget apt-transport-https gnupg lsb-release | |
| wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key \ | |
| | sudo apt-key add - | |
| echo deb https://aquasecurity.github.io/trivy-repo/deb \ | |
| $(lsb_release -sc) main \ | |
| | sudo tee -a /etc/apt/sources.list.d/trivy.list | |
| sudo apt-get update && sudo apt-get install -y trivy | |
| - name: Set image tag | |
| id: tag | |
| run: | | |
| echo "IMAGE_TAG=${{ github.sha }}" >> $GITHUB_OUTPUT | |
| echo "IMAGE_REF=${{ env.REGISTRY }}/${PROJECT_ID}/${{ matrix.service.image_suffix }}:${{ github.sha }}" >> $GITHUB_OUTPUT | |
| - name: Build container image | |
| run: | | |
| docker build \ | |
| -f ${{ matrix.service.dockerfile }} \ | |
| -t ${STEPS_TAG_OUTPUTS_IMAGE_REF} \ | |
| . | |
| env: | |
| STEPS_TAG_OUTPUTS_IMAGE_REF: ${{ steps.tag.outputs.IMAGE_REF }} | |
| # POAM-006: Generate CycloneDX SBOM for this container image | |
| - name: Generate CycloneDX SBOM | |
| run: | | |
| trivy image \ | |
| --format cyclonedx \ | |
| --output sbom-${{ matrix.service.name }}.cdx.json \ | |
| ${STEPS_TAG_OUTPUTS_IMAGE_REF} | |
| echo "✅ SBOM generated: sbom-${{ matrix.service.name }}.cdx.json" | |
| env: | |
| STEPS_TAG_OUTPUTS_IMAGE_REF: ${{ steps.tag.outputs.IMAGE_REF }} | |
| # POAM-006: Fail if CRITICAL CVEs are found (except .trivyignore allowlist) | |
| - name: Scan for CRITICAL CVEs | |
| run: | | |
| trivy image \ | |
| --format table \ | |
| --severity CRITICAL,HIGH \ | |
| --ignorefile .trivyignore \ | |
| --exit-code 1 \ | |
| ${STEPS_TAG_OUTPUTS_IMAGE_REF} | |
| continue-on-error: false # Hard fail on CRITICAL CVEs | |
| env: | |
| STEPS_TAG_OUTPUTS_IMAGE_REF: ${{ steps.tag.outputs.IMAGE_REF }} | |
| - name: Upload SBOM as GitHub Artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: sbom-${{ matrix.service.name }}-${{ github.sha }} | |
| path: sbom-${{ matrix.service.name }}.cdx.json | |
| retention-days: 90 | |
| # Upload SBOM to object storage via the S3-compatible API. | |
| # Works with AWS S3, GCS (HMAC), Cloudflare R2, MinIO, and any | |
| # S3-compatible backend — no cloud-provider-specific auth action needed. | |
| # | |
| # GCS HMAC setup (one-time): | |
| # gcloud storage hmac create <service-account-email> \ | |
| # --project=<project-id> | |
| # → copy Access ID → SBOM_S3_ACCESS_KEY secret | |
| # → copy Secret → SBOM_S3_SECRET_KEY secret | |
| # Set SBOM_S3_ENDPOINT=https://storage.googleapis.com | |
| # Set SBOM_S3_BUCKET=<your-gcs-bucket-name> | |
| - name: Upload SBOM to object storage (S3-compatible) | |
| if: env.SBOM_S3_BUCKET != '' && env.SBOM_S3_ACCESS_KEY != '' | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.SBOM_S3_ACCESS_KEY }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.SBOM_S3_SECRET_KEY }} | |
| # AWS_DEFAULT_REGION is required by the AWS CLI even for non-AWS | |
| # endpoints; 'auto' is accepted by most S3-compatible backends. | |
| AWS_DEFAULT_REGION: ${{ secrets.SBOM_S3_REGION || 'auto' }} | |
| run: | | |
| DEST="s3://${SBOM_S3_BUCKET}/sboms/${{ github.sha }}/sbom-${{ matrix.service.name }}.cdx.json" | |
| # Build endpoint flag — omit for native AWS S3 | |
| if [ -n "${SBOM_S3_ENDPOINT}" ]; then | |
| ENDPOINT_FLAG="--endpoint-url ${SBOM_S3_ENDPOINT}" | |
| else | |
| ENDPOINT_FLAG="" | |
| fi | |
| aws s3 cp \ | |
| sbom-${{ matrix.service.name }}.cdx.json \ | |
| "$DEST" \ | |
| $ENDPOINT_FLAG \ | |
| --no-progress | |
| echo "✅ SBOM uploaded to $DEST" | |
| if [ -n "${SBOM_S3_ENDPOINT}" ]; then | |
| echo " Endpoint: ${SBOM_S3_ENDPOINT}" | |
| fi | |
| - name: Summary | |
| run: | | |
| echo "## SBOM Generation Summary — ${{ matrix.service.name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|---|---|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Image | \`${STEPS_TAG_OUTPUTS_IMAGE_REF}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| SBOM Format | CycloneDX |" >> $GITHUB_STEP_SUMMARY | |
| echo "| POAM Reference | POAM-006 (CM-8 / ISO 42001 §A.8.3) |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Artifact | \`sbom-${{ matrix.service.name }}.cdx.json\` |" >> $GITHUB_STEP_SUMMARY | |
| if [ -n "${SBOM_S3_BUCKET}" ] && [ -n "${SBOM_S3_ACCESS_KEY}" ]; then | |
| ENDPOINT="${SBOM_S3_ENDPOINT}" | |
| BACKEND="${ENDPOINT:-AWS S3}" | |
| echo "| Storage backend | \`${BACKEND}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Storage path | \`s3://${SBOM_S3_BUCKET}/sboms/${{ github.sha }}/\` |" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "| Storage backend | GitHub Artifact only (SBOM_S3_BUCKET not configured) |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| env: | |
| STEPS_TAG_OUTPUTS_IMAGE_REF: ${{ steps.tag.outputs.IMAGE_REF }} |