Skip to content

Commit fca5907

Browse files
authored
BUILD-9447 Generation attestation for build actions (#136)
* BUILD-9447 Add provenance attestation to all build actions - Add generate-provenance and provenance-on-pr input parameters to all 5 build actions - Add provenance-subject-path override parameter for custom artifact paths - Implement artifact capture for all build types: * Gradle: Search build/libs, distributions, publications directories * Maven: Search target directories * Poetry: Search dist directory * NPM: Copy .tgz to .attestation-artifacts before jf npm publish deletes it * Yarn: Copy .tgz to .attestation-artifacts before jf npm publish deletes it - Add attestation step using actions/[email protected] - Only generate attestations on default branch (master) or when provenance-on-pr=true - Update example workflows (sonar-dummy-*, sonar-go-enterprise) to use new parameters - Add attestations write permission to workflow examples
1 parent d30726d commit fca5907

File tree

16 files changed

+648
-21
lines changed

16 files changed

+648
-21
lines changed

README.md

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,8 +890,6 @@ jobs:
890890
- Support for different branch types (default, maintenance, PR, dogfood, long-lived feature)
891891
- Comprehensive build logging and error handling
892892

893-
---
894-
895893
## `promote`
896894

897895
This action promotes a build in JFrog Artifactory and updates the GitHub status check accordingly.
@@ -1154,6 +1152,68 @@ After running this action, the following environment variables are available:
11541152
- **Smart Cache Management**: Caches smctl installation directory and jsign .deb package for faster subsequent runs
11551153
- **Automatic Setup**: Handles all DigiCert authentication and environment configuration
11561154

1155+
## Provenance Attestation
1156+
1157+
The build actions in this repository can automatically generate SLSA build provenance
1158+
attestations for produced artifacts when the build is considered deployable. This feature is
1159+
powered by [`actions/attest-build-provenance`](https://github.com/actions/attest-build-provenance).
1160+
1161+
Attestations identify the artifact(s) that serve as the subject of the attestation. The `build-*` actions
1162+
attempt to discover these subjects automatically using conventional build output locations and
1163+
common file types for each ecosystem. Automatic discovery runs only when deployment is enabled.
1164+
The attestation step runs when `provenance` parameter is enabled and artifact paths are available (either via
1165+
`provenance-artifact-paths` or from the build output); otherwise, it is skipped.
1166+
1167+
### Ecosystem assumptions (automatic discovery)
1168+
1169+
- Gradle
1170+
- Locations: `**/build/libs/**`, `**/build/distributions/**`, `**/build/reports/**` (for SBOM JSONs)
1171+
- File types: `*.jar`, `*.war`, `*.ear`, `*.zip`, `*.tar.gz`, `*.tar`, `*.json`
1172+
- Exclusions: `*-sources.jar`, `*-javadoc.jar`, `*-tests.jar`
1173+
1174+
- Maven
1175+
- Location: `**/<project.build.directory>/**` (queried via Maven); falls back to `target/`
1176+
- File types: `*.jar`, `*.war`, `*.ear`, `*.zip`, `*.tar.gz`, `*.tar`, `*.pom`, `*.json`
1177+
- Exclusions: `*-sources.jar`, `*-javadoc.jar`, `*-tests.jar`
1178+
- Skip rule: if `maven.deploy.skip=true` is effective, attestation is skipped for that module
1179+
1180+
- Poetry (Python)
1181+
- Location: `dist/`
1182+
- File types: `*.whl`, `*.tar.gz`, `*.json`
1183+
1184+
- NPM
1185+
- Location: `.attestation-artifacts/`
1186+
- File types: `*.tgz`
1187+
1188+
- Yarn
1189+
- Location: `.attestation-artifacts/`
1190+
- File types: `*.tgz`
1191+
1192+
These assumptions are based on widely-used industry conventions and on how artifacts are currently
1193+
published to our Artifactory. They should cover most repositories, but they are not exhaustive. If
1194+
needed, you can customize the paths via the `provenance-artifact-paths` input.
1195+
1196+
### Manually specify subjects when needed
1197+
1198+
For complete accuracy, we recommend explicitly specifying the artifacts to attest using the
1199+
`provenance-artifact-paths` input. Repository owners know best what their build produces, so
1200+
providing explicit paths might be sometimes preferable. `provenance-artifact-paths` is passed to
1201+
`actions/attest-build-provenance` as the `subject-path` input. It may be a glob pattern or a
1202+
newline-separated list of paths (total subject count cannot exceed 1024). See upstream docs for
1203+
details and more examples: [`actions/attest-build-provenance`](https://github.com/actions/attest-build-provenance).
1204+
1205+
Example with a build action (same idea applies to other actions):
1206+
1207+
```yaml
1208+
- uses: SonarSource/ci-github-actions/build-maven@v1
1209+
with:
1210+
provenance-artifact-paths: |
1211+
target/*.jar
1212+
target/*bom.json
1213+
```
1214+
1215+
---
1216+
11571217
## Release
11581218

11591219
1. Create a new GitHub release on <https://github.com/SonarSource/ci-github-actions/releases>

build-gradle/action.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ inputs:
5858
disable-caching:
5959
description: Whether to disable Gradle caching entirely
6060
default: 'false'
61+
provenance:
62+
description: Whether to generate provenance attestation for built artifacts
63+
default: 'false'
64+
provenance-pull-request:
65+
description: Whether to generate provenance attestation on pull requests (similar to deploy-pull-request)
66+
default: 'false'
67+
provenance-artifact-paths:
68+
description: >-
69+
Relative paths of the artifacts for which to generate a provenance attestation (glob pattern).
70+
Default is collected from '*/build/libs/*', '*/build/distributions/*', and '*/build/reports/*'
71+
default: ''
6172

6273
outputs:
6374
project-version:
@@ -69,6 +80,9 @@ outputs:
6980
deployed:
7081
description: Whether artifacts were deployed
7182
value: ${{ steps.build.outputs.deployed }}
83+
artifact-paths:
84+
description: Newline-separated list of artifact paths for provenance attestation
85+
value: ${{ steps.build.outputs.artifact-paths }}
7286

7387
runs:
7488
using: composite
@@ -230,6 +244,15 @@ runs:
230244
name: problems-report-${{ github.job }}${{ strategy.job-index }}
231245
path: build/reports/problems/problems-report.html
232246
if-no-files-found: ignore
247+
- name: Generate provenance attestation
248+
if: >-
249+
${{ inputs.provenance == 'true' && steps.build.outputs.deployed == 'true' &&
250+
(inputs.provenance-artifact-paths != '' || steps.build.outputs.artifact-paths != '') }}
251+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
252+
with:
253+
subject-path: >-
254+
${{ inputs.provenance-artifact-paths != '' && inputs.provenance-artifact-paths || steps.build.outputs.artifact-paths }}
255+
show-summary: true
233256

234257
- name: Generate workflow summary
235258
if: always()

build-gradle/build.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ gradle_build_and_analyze() {
227227
"$GRADLE_CMD" "${gradle_args[@]}"
228228
if should_deploy; then
229229
echo "deployed=true" >> "$GITHUB_OUTPUT"
230+
export_built_artifacts
230231
fi
231232
}
232233

@@ -254,6 +255,47 @@ gradle_build() {
254255
fi
255256
}
256257

258+
export_built_artifacts() {
259+
if ! should_deploy; then
260+
return 0
261+
fi
262+
263+
echo "::group::Capturing built artifacts for attestation"
264+
265+
# Find all built artifacts, excluding sources/javadoc/tests JARs
266+
local artifacts find_bin
267+
find_bin="/bin/find"
268+
if [[ ! -x "$find_bin" ]]; then
269+
find_bin="/usr/bin/find"
270+
fi
271+
artifacts=$("$find_bin" . \( -path '*/build/libs/*' -o -path '*/build/distributions/*' -o -path '*/build/reports/*' \) \
272+
\( -name '*.jar' -o -name '*.war' -o -name '*.ear' -o -name '*.zip' -o -name '*.tar.gz' -o -name '*.tar' -o -name '*.json' \) \
273+
! -name '*-sources.jar' ! -name '*-javadoc.jar' ! -name '*-tests.jar' \
274+
-type f 2>/dev/null)
275+
276+
# Sort and deduplicate (avoid Windows sort.exe)
277+
if [[ -n "$artifacts" ]]; then
278+
artifacts=$(echo "$artifacts" | /usr/bin/sort -u)
279+
fi
280+
281+
if [[ -z "$artifacts" ]]; then
282+
echo "::warning title=No artifacts found::No artifacts found for attestation in build output directories"
283+
echo "::endgroup::"
284+
return 0
285+
fi
286+
287+
echo "Found artifacts for attestation:"
288+
echo "$artifacts"
289+
290+
{
291+
echo "artifact-paths<<EOF"
292+
echo "$artifacts"
293+
echo "EOF"
294+
} >> "$GITHUB_OUTPUT"
295+
296+
echo "::endgroup::"
297+
}
298+
257299
main() {
258300
check_tool java -version
259301
set_gradle_cmd

build-maven/action.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,27 @@ inputs:
5858
disable-caching:
5959
description: Whether to disable Maven caching entirely
6060
default: 'false'
61+
provenance:
62+
description: Whether to generate provenance attestation for built artifacts
63+
default: 'false'
64+
provenance-pull-request:
65+
description: Whether to generate provenance attestation on pull requests (similar to deploy-pull-request)
66+
default: 'false'
67+
provenance-artifact-paths:
68+
description: >-
69+
Relative paths of the artifacts for which to generate a provenance attestation (glob pattern).
70+
Default is collected from '*/target/*' (or custom build directory from pom.xml)
71+
default: ''
6172
outputs:
6273
BUILD_NUMBER:
6374
description: The build number, incremented or reused if already cached
6475
value: ${{ steps.config.outputs.BUILD_NUMBER }}
6576
deployed:
6677
description: Whether artifacts were deployed
6778
value: ${{ steps.build.outputs.deployed }}
79+
artifact-paths:
80+
description: Newline-separated list of artifact paths for provenance attestation
81+
value: ${{ steps.build.outputs.artifact-paths }}
6882

6983
runs:
7084
using: composite
@@ -161,6 +175,16 @@ runs:
161175
rm -rf "$MAVEN_CONFIG/repository/com/sonarsource/"
162176
/usr/bin/find "$MAVEN_CONFIG/repository" -name resolver-status.properties -delete
163177
178+
- name: Generate provenance attestation
179+
if: >-
180+
${{ inputs.provenance == 'true' && steps.build.outputs.deployed == 'true' &&
181+
(inputs.provenance-artifact-paths != '' || steps.build.outputs.artifact-paths != '') }}
182+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
183+
with:
184+
subject-path: >-
185+
${{ inputs.provenance-artifact-paths != '' && inputs.provenance-artifact-paths || steps.build.outputs.artifact-paths }}
186+
show-summary: true
187+
164188
- name: Generate workflow summary
165189
if: always()
166190
shell: bash

build-maven/build.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ build_maven() {
174174

175175
if should_deploy; then
176176
echo "deployed=true" >> "$GITHUB_OUTPUT"
177+
export_built_artifacts
177178
fi
178179

179180
# Execute SonarQube analysis if enabled
@@ -189,6 +190,52 @@ build_maven() {
189190
fi
190191
}
191192

193+
export_built_artifacts() {
194+
local deployed
195+
deployed=$(grep "deployed=" "$GITHUB_OUTPUT" 2>/dev/null | cut -d= -f2)
196+
[[ "$deployed" != "true" ]] && return 0
197+
198+
echo "::group::Capturing built artifacts for attestation"
199+
200+
# Query Maven for build directory name, fallback to 'target'
201+
local build_dir
202+
build_dir=$(mvn help:evaluate -Dexpression=project.build.directory -q -DforceStdout 2>/dev/null | xargs basename 2>/dev/null || echo "target")
203+
echo "Scanning for artifacts in: */${build_dir}/*"
204+
205+
# Find all built artifacts (excluding sources, javadoc, tests)
206+
local artifacts find_bin
207+
find_bin="/bin/find"
208+
if [[ ! -x "$find_bin" ]]; then
209+
find_bin="/usr/bin/find"
210+
fi
211+
artifacts=$("$find_bin" . -path "*/${build_dir}/*" \
212+
\( -name '*.jar' -o -name '*.war' -o -name '*.ear' -o -name '*.zip' -o -name '*.tar.gz' -o -name '*.tar' -o -name '*.pom' -o -name '*.asc' -o -name '*.json' \) \
213+
! -name '*-sources.jar' ! -name '*-javadoc.jar' ! -name '*-tests.jar' \
214+
-type f 2>/dev/null)
215+
216+
# Sort and deduplicate (avoid Windows sort.exe)
217+
if [[ -n "$artifacts" ]]; then
218+
artifacts=$(echo "$artifacts" | /usr/bin/sort -u)
219+
fi
220+
221+
if [[ -z "$artifacts" ]]; then
222+
echo "::warning title=No artifacts found::No artifacts found for attestation in build output directories"
223+
echo "::endgroup::"
224+
return 0
225+
fi
226+
227+
echo "Found artifacts for attestation:"
228+
echo "$artifacts"
229+
230+
{
231+
echo "artifact-paths<<EOF"
232+
echo "$artifacts"
233+
echo "EOF"
234+
} >> "$GITHUB_OUTPUT"
235+
236+
echo "::endgroup::"
237+
}
238+
192239
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
193240
build_maven "$@"
194241
fi

build-npm/action.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ inputs:
4242
build-name:
4343
description: Name of the build to publish. Defaults to the repository name.
4444
default: ''
45+
provenance:
46+
description: Whether to generate provenance attestation for built artifacts
47+
default: 'false'
48+
provenance-pull-request:
49+
description: Whether to generate provenance attestation on pull requests (similar to deploy-pull-request)
50+
default: 'false'
51+
provenance-artifact-paths:
52+
description: >-
53+
Relative paths of the artifacts for which to generate a provenance attestation (glob pattern).
54+
Default is collected from '.attestation-artifacts/' directory
55+
default: ''
4556

4657
outputs:
4758
BUILD_NUMBER:
@@ -53,6 +64,12 @@ outputs:
5364
project-version:
5465
description: The project version with build number (after replacement). Also set as environment variable PROJECT_VERSION.
5566
value: ${{ steps.config.outputs.project-version }}
67+
deployed:
68+
description: Whether artifacts were deployed
69+
value: ${{ steps.build.outputs.deployed }}
70+
artifact-paths:
71+
description: Newline-separated list of artifact paths for provenance attestation
72+
value: ${{ steps.build.outputs.artifact-paths }}
5673

5774
runs:
5875
using: composite
@@ -153,6 +170,16 @@ runs:
153170
path: ~/.npm/_logs/
154171
if-no-files-found: ignore
155172

173+
- name: Generate provenance attestation
174+
if: >-
175+
${{ inputs.provenance == 'true' && steps.build.outputs.deployed == 'true' &&
176+
(inputs.provenance-artifact-paths != '' || steps.build.outputs.artifact-paths != '') }}
177+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
178+
with:
179+
subject-path: >-
180+
${{ inputs.provenance-artifact-paths != '' && inputs.provenance-artifact-paths || steps.build.outputs.artifact-paths }}
181+
show-summary: true
182+
156183
- name: Generate workflow summary
157184
if: always()
158185
shell: bash

0 commit comments

Comments
 (0)