-
Notifications
You must be signed in to change notification settings - Fork 38
353 lines (308 loc) · 18.1 KB
/
Copy pathci-main-pull-request-stub.yml
File metadata and controls
353 lines (308 loc) · 18.1 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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# stub to call common GitHub Action (GA) as part of Continuous Integration (CI) Pull Request process checks for main branch
# inputs are described in the chef/common-github-actions/<GA.yml> with same name as this stub
#
# secrets are inherited from the calling workflow, typically SONAR_TOKEN, SONAR_HOST_URL, GH_TOKEN, AKEYLESS_JWT_ID, POLARIS_SERVER_URL and POLARIS_ACCESS_TOKEN
name: CI Pull Request on Main Branch
on:
pull_request:
branches: [ main, release/** ]
push:
branches: [ main, release/** ]
workflow_dispatch:
permissions:
contents: read
env:
STUB_VERSION: "1.0.5"
jobs:
echo_version:
name: 'Echo stub version'
runs-on: ubuntu-latest
steps:
- name: echo version of stub and inputs
run: |
echo "CI main pull request stub version $STUB_VERSION"
call-ci-main-pr-check-pipeline:
uses: chef/common-github-actions/.github/workflows/ci-main-pull-request.yml@main
secrets: inherit
permissions:
id-token: write
contents: read
with:
visibility: ${{ github.event.repository.visibility }} # private, public, or internal
# go-private-modules: GOPRIVATE for Go private modules, default is 'github.com/progress-platform-services/*
# if version specified, it takes precedence; can be a semver like 1.0.2-xyz or a tag like "latest"
# version is sourced from the repo VERSION file to keep BlackDuck/SBOM steps aligned
detect-version-source-type: 'file' # options include "none" (do not detect), "file", "github-tag" or "github-release"
detect-version-source-parameter: 'VERSION' # filename to read the version from
language: 'rust' # Go, Ruby, Rust, JavaScript, TypeScript, Python, Java, C#, PHP, other - used for build and SonarQube language setting
# complexity-checks
perform-complexity-checks: true
# scc-output-filename: 'scc-output.txt'
perform-language-linting: false # Perform language-specific linting and pre-compilation checks
# trufflehog secret scanning
perform-trufflehog-scan: true
# trivy dependency and container scanning
perform-trivy-scan: true
# BlackDuck SAST (Polaris) and SCA scans (requires a build or download to do SAST)
# requires these secrets: POLARIS_SERVER_URL, POLARIS_ACCESS_TOKEN
perform-blackduck-polaris: true
polaris-application-name: "Chef-Habitat" # one of these: Chef-Agents, Chef-Automate, Chef-Chef360, Chef-Habitat, Chef-Infrastructure-Server, Chef-Shared-Services, Chef-Other, Chef-Non-Product
polaris-project-name: ${{ github.event.repository.name }}
polaris-blackduck-executable: 'path/to/blackduck/binary'
polaris-executable-detect-path: 'path/to/detect'
# perform application build and unit testing, will use custom repository properties when implemented for chef-primary-application, chef-build-profile, and chef-build-language
build: false
# ga-build-profile: $chef-ga-build-profile
# language: $chef-ga-build-language # this will be removed from stub as autodetected in central GA
unit-tests: false
# perform SonarQube scan, with or wihout unit test coverage data
# requires secrets SONAR_TOKEN and SONAR_HOST_URL (progress.sonar.com)
perform-sonarqube-scan: true
# perform-sonar-build: true
# build-profile: 'default'
# report-unit-test-coverage: true
perform-docker-scan: false # scan Dockerfile and built images with Docker Scout or Trivy; see repo custom properties matching "container"
# report to central developer dashboard
report-to-atlassian-dashboard: false
quality-product-name: 'Chef-Habitat' # product name for quality reporting, like Chef360, Courier, Inspec
# quality-product-name: ${{ github.event.repository.name }} # like 'Chef-360' - the product name for quality reporting, like Chef360, Courier, Inspec
# quality-sonar-app-name: 'YourSonarAppName'
# quality-testing-type: 'Integration' like Unit, Integration, e2e, api, Performance, Security
# quality-service-name: 'YourServiceOrRepoName'
# quality-junit-report: 'path/to/junit/report''
# perform native and Habitat packaging, publish to package repositories
package-binaries: false # Package binaries (e.g., RPM, DEB, MSI, dpkg + signing + SHA)
habitat-build: false # Create Habitat packages
publish-packages: false # Publish packages (e.g., container from Dockerfile to ECR, go-releaser binary to releases page, omnibus to artifactory, gems, choco, homebrew, other app stores)
# generate and export Software Bill of Materials (SBOM) in various formats
generate-sbom: true
export-github-sbom: true # SPDX JSON artifact on job instance
perform-blackduck-sca-scan: true # combined with generate sbom & generate github-sbom, also needs version above
blackduck-project-group-name: 'Chef-Habitat' # typically one of (Chef), Chef-Agents, Chef-Automate, Chef-Chef360, Chef-Habitat, Chef-Infrastructure-Server, Chef-Shared-Services, Chef-Non-Product'
blackduck-project-name: ${{ github.event.repository.name }} # BlackDuck project name, typically the repository name
generate-blackduck-sbom: false # obsolete, use perform-blackduck-sca-scan instead
generate-msft-sbom: false
license_scout: false # Run license scout for license compliance (uses .license_scout.yml)
# udf1: 'default' # user defined flag 1
# udf2: 'default' # user defined flag 2
# udf3: 'default' # user defined flag 3
# ---------------------------------------------------------------------------
# Combined Rust + npm + Habitat-package SBOM generation, BlackDuck import
#
# Motivation: BlackDuck Detect's `cargo tree` scan (above) only covers
# components/builder-api, the only Rust project in this repo — all other
# deployable components (builder-api-proxy, builder-memcached,
# builder-minio, builder-datastore) wrap third-party binaries packaged as
# plain Habitat packages, which are invisible to any Cargo-based tooling.
# components/builder-web is an npm/Angular frontend whose dependencies are
# likewise invisible to Cargo-based tooling.
#
# This job generates a CycloneDX SBOM for builder-api via cargo-cyclonedx
# (single target — everything here ships x86_64-linux only), a second
# CycloneDX SBOM for builder-web's npm dependencies via cyclonedx-npm, plus
# a third CycloneDX fragment built by querying the public Builder API for
# the dependency trees (tdeps) of this repo's top-level Habitat packages in
# the on-prem-base channel, filtered to core-origin packages. All three are
# merged into one SBOM and imported into the same BlackDuck project/version
# the SCA scan above already created.
# ---------------------------------------------------------------------------
generate-habitat-sbom:
name: 'Generate Habitat Package + Cargo + npm SBOM and Import to BlackDuck'
runs-on: ubuntu-latest
env:
# Match the project name used by call-ci-main-pr-check-pipeline above
BD_PROJECT_NAME: ${{ github.event.repository.name }}
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set BD_VERSION_NAME from VERSION file
run: echo "BD_VERSION_NAME=$(cat VERSION)" >> "$GITHUB_ENV"
# Read the pinned toolchain from rust-toolchain at the repo root so we
# don't have to hardcode the version here.
- name: Read Rust toolchain version
id: rust-version
run: |
echo "toolchain=$(grep ^channel rust-toolchain | cut -d'"' -f2)" >> "$GITHUB_OUTPUT"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
id: toolchain
with:
toolchain: ${{ steps.rust-version.outputs.toolchain }}
- run: rustup override set ${{ steps.toolchain.outputs.name }}
- name: Install cargo-cyclonedx
uses: ClementTsang/cargo-action@v0.0.7
with:
command: install
args: cargo-cyclonedx --locked
# Download the CycloneDX CLI used to merge the cargo SBOM with the
# Habitat package dependency fragment.
- name: Install CycloneDX CLI
run: |
curl -sSL https://github.com/CycloneDX/cyclonedx-cli/releases/latest/download/cyclonedx-linux-x64 \
-o /usr/local/bin/cyclonedx-cli
chmod +x /usr/local/bin/cyclonedx-cli
# Generate the Rust dependency SBOM for the whole workspace (only
# components/builder-api and its internal library crates are Rust;
# everything ships x86_64-linux only, so a single target is enough).
- name: Generate Cargo SBOM — Linux x86_64
run: cargo cyclonedx --all --format json --target x86_64-unknown-linux-gnu --target-in-filename
# components/builder-web is the only npm project in this repo.
- name: Read Node.js version
id: node-version
run: |
echo "version=$(jq -r '.engines.node' components/builder-web/package.json | grep -oE '[0-9]+' | head -1)" >> "$GITHUB_OUTPUT"
- name: Install Node.js toolchain
uses: actions/setup-node@v4
with:
node-version: ${{ steps.node-version.outputs.version }}
- name: Install builder-web npm dependencies
working-directory: components/builder-web
run: npm ci
# Generate the npm dependency SBOM for builder-web using the official
# CycloneDX npm generator. Written to the repo root so the merge step
# below (which globs for "*.cdx.json") picks it up automatically.
- name: Generate npm SBOM for builder-web
working-directory: components/builder-web
run: npx --yes @cyclonedx/cyclonedx-npm --output-format json --output-file ../../builder-web-npm.cdx.json
# Query the public Builder API for the core-origin transitive
# dependencies of this repo's top-level, deployable Habitat packages
# (habitat/builder-api-proxy, habitat/builder-api,
# habitat/builder-memcached, habitat/builder-minio,
# habitat/builder-datastore) in the on-prem-base channel.
- name: Generate Habitat package dependency SBOM fragment
run: bash support/sbom/generate-habitat-pkg-deps.sh > habitat-pkg-deps.cdx.json
# Merge the per-crate cargo SBOMs, the builder-web npm SBOM, and the
# Habitat package dependency fragment into one deduplicated CycloneDX
# document. cargo cyclonedx writes one *.cdx.json per workspace member
# into each member's directory, and builder-web-npm.cdx.json was
# written to the repo root above. We use find to collect them all,
# excluding the target/ build directory.
- name: Merge SBOMs
run: |
SBOM_FILES=$(find . -name "*.cdx.json" -not -path "*/target/*" -not -name "habitat-pkg-deps.cdx.json" | sort | tr '\n' ' ')
echo "Merging files: $SBOM_FILES habitat-pkg-deps.cdx.json"
cyclonedx-cli merge \
--input-files $SBOM_FILES habitat-pkg-deps.cdx.json \
--output-file sbom-all.json \
--output-format json \
--output-version v1_4
# Inject a version-based serialNumber and the current version so BlackDuck
# treats each release upload as a distinct scan (prevents "already mapped" errors).
# Also strip first-party crates from this workspace so only third-party
# dependencies and the Habitat package fragment remain:
# artifactory-client, builder_core, github-api-client, oauth-client,
# token-generator, and anything starting with "habitat" (except the
# "Habitat core_" prefixed entries from the Habitat package fragment).
jq --arg serial "urn:uuid:builder-${BD_VERSION_NAME}" --arg ver "$BD_VERSION_NAME" \
'.serialNumber = $serial
| .metadata.component.version = $ver
| .components = [.components[] | select(
(.name | ascii_downcase | startswith("habitat core_")) or
(
(.name | ascii_downcase | startswith("habitat") | not) and
([.name] | inside(["artifactory-client","builder_core","github-api-client","oauth-client","token-generator"]) | not)
)
)]' \
sbom-all.json > sbom-versioned.json
mv sbom-versioned.json sbom-all.json
echo "Components after filtering: $(jq '.components|length' sbom-all.json)"
# Always upload the merged SBOM as an artifact for auditing/review
- name: Upload merged SBOM artifact
uses: actions/upload-artifact@v7
with:
name: cyclonedx-sbom-habitat
path: sbom-all.json
retention-days: 90
# Import the merged SBOM into the same BlackDuck project/version that
# the SCA scan above already created, so the Habitat package deps and
# any Rust deps missed by Detect's cargo tree scan appear in the same BOM.
#
# Required secrets (already present in repo/org):
# BLACKDUCK_SBOM_URL — e.g. https://your-instance.blackducksoftware.com
# BLACKDUCK_SCA_TOKEN — a BlackDuck personal access token with BOM write rights
- name: Import SBOM into BlackDuck
if: github.event_name != 'pull_request'
env:
BLACKDUCK_URL: ${{ secrets.BLACKDUCK_SBOM_URL }}
BLACKDUCK_API_TOKEN: ${{ secrets.BLACKDUCK_SCA_TOKEN }}
run: |
set -euo pipefail
# Authenticate and obtain a short-lived bearer token
BEARER=$(curl -sSf -X POST \
"${BLACKDUCK_URL}/api/tokens/authenticate" \
-H "Authorization: token ${BLACKDUCK_API_TOKEN}" \
-H "Accept: application/vnd.blackducksoftware.user-4+json" \
| jq -r '.bearerToken')
if [ -z "${BEARER}" ] || [ "${BEARER}" = "null" ]; then
echo "ERROR: Failed to obtain bearer token."
exit 1
fi
# URL-encode BD_PROJECT_NAME and BD_VERSION_NAME for use in query strings.
# BD_VERSION_NAME comes from the VERSION file and may contain '+' (semver
# build metadata) or other reserved characters.
BD_PROJECT_ENCODED=$(printf '%s' "${BD_PROJECT_NAME}" | jq -sRr @uri)
BD_VERSION_ENCODED=$(printf '%s' "${BD_VERSION_NAME}" | jq -sRr @uri)
# Look up the project by exact name (BD search is substring, so we filter client-side)
PROJECT_BODY=$(curl -sSf \
"${BLACKDUCK_URL}/api/projects?q=name:${BD_PROJECT_ENCODED}&limit=10" \
--oauth2-bearer "${BEARER}" \
-H "Accept: application/vnd.blackducksoftware.project-detail-4+json")
PROJECT_HREF=$(echo "${PROJECT_BODY}" | jq -r \
--arg name "${BD_PROJECT_NAME}" \
'.items[] | select(.name == $name) | ._meta.href' | head -1)
if [ -z "${PROJECT_HREF}" ] || [ "${PROJECT_HREF}" = "null" ]; then
echo "ERROR: BlackDuck project '${BD_PROJECT_NAME}' not found."
echo "Available matches: $(echo "${PROJECT_BODY}" | jq -r '.items[].name')"
exit 1
fi
# Look up the project version by exact name; create it if it doesn't exist yet
VERSION_BODY=$(curl -sSf \
"${PROJECT_HREF}/versions?q=versionName:${BD_VERSION_ENCODED}&limit=10" \
--oauth2-bearer "${BEARER}" \
-H "Accept: application/vnd.blackducksoftware.project-detail-5+json")
VERSION_HREF=$(echo "${VERSION_BODY}" | jq -r \
--arg ver "${BD_VERSION_NAME}" \
'.items[] | select(.versionName == $ver) | ._meta.href' | head -1)
if [ -z "${VERSION_HREF}" ] || [ "${VERSION_HREF}" = "null" ]; then
echo "Version '${BD_VERSION_NAME}' not found — creating it..."
VERSION_HREF=$(curl -sS -o /tmp/bd_create_resp.json -w "%{header_json}" -X POST \
"${PROJECT_HREF}/versions" \
--oauth2-bearer "${BEARER}" \
-H "Content-Type: application/vnd.blackducksoftware.project-detail-4+json" \
-H "Accept: application/vnd.blackducksoftware.project-detail-5+json" \
-d "{\"versionName\":\"${BD_VERSION_NAME}\",\"phase\":\"DEVELOPMENT\",\"distribution\":\"EXTERNAL\"}" \
| jq -r '.location[0] // empty')
# Prefer Location header; fall back to re-fetching the version href
if [ -z "${VERSION_HREF}" ]; then
VERSION_HREF=$(curl -sSf \
"${PROJECT_HREF}/versions?q=versionName:${BD_VERSION_ENCODED}&limit=10" \
--oauth2-bearer "${BEARER}" \
-H "Accept: application/vnd.blackducksoftware.project-detail-5+json" \
| jq -r --arg ver "${BD_VERSION_NAME}" \
'.items[] | select(.versionName == $ver) | ._meta.href' | head -1)
fi
if [ -z "${VERSION_HREF}" ] || [ "${VERSION_HREF}" = "null" ]; then
echo "ERROR: Failed to create or locate BlackDuck version '${BD_VERSION_NAME}'."
echo "Create response: $(cat /tmp/bd_create_resp.json)"
exit 1
fi
echo "Created version: ${VERSION_HREF}"
fi
echo "Uploading SBOM ($(jq '.components|length' sbom-all.json) components) to project '${BD_PROJECT_NAME}' version '${BD_VERSION_NAME}'"
# POST /api/scan/data as multipart/form-data per BD API spec.
# projectName, versionName, and autocreate are form parts, not query params.
# Content type for the file part must be application/vnd.cyclonedx (no +json).
HTTP_STATUS=$(curl -sS -o /tmp/bd_upload_resp.json -w "%{http_code}" -X POST \
"${BLACKDUCK_URL}/api/scan/data" \
--oauth2-bearer "${BEARER}" \
-F "file=@sbom-all.json;type=application/vnd.cyclonedx" \
-F "projectName=${BD_PROJECT_NAME}" \
-F "versionName=${BD_VERSION_NAME}" \
-F "autocreate=true")
echo "BlackDuck SBOM upload HTTP status: ${HTTP_STATUS}"
if [[ "${HTTP_STATUS}" != "2"* ]]; then
echo "ERROR: SBOM upload failed with HTTP ${HTTP_STATUS}"
echo "Response: $(cat /tmp/bd_upload_resp.json)"
exit 1
fi
echo "SBOM upload complete. Components will appear in BlackDuck after async processing."