-
Notifications
You must be signed in to change notification settings - Fork 6
214 lines (196 loc) · 8.82 KB
/
Copy pathsbom.yml
File metadata and controls
214 lines (196 loc) · 8.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# 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 }}