@@ -42,6 +42,7 @@ project simply generates provenance as a separate step in an existing workflow.
4242- [ Provenance for matrix strategy builds] ( #provenance-for-matrix-strategy-builds )
4343 - [ A single provenance attestation for all artifacts] ( #a-single-provenance-attestation-for-all-artifacts )
4444 - [ A different attestation for each iteration] ( #a-different-attestation-for-each-iteration )
45+ - [ Provenance for artifacts built across multiple operating systems] ( #provenance-for-artifacts-built-across-multiple-operating-systems )
4546- [ Known Issues] ( #known-issues )
4647 - [ Skip output 'hashes' since it may contain secret] ( #skip-output-hashes-since-it-may-contain-secret )
4748 - [ 'internal error' when using ` upload-assets ` ] ( #internal-error-when-using-upload-assets )
@@ -1408,6 +1409,175 @@ jobs:
14081409 upload-assets: true # Optional: Upload to a new release
14091410` ` `
14101411
1412+ # # Provenance for artifacts built across multiple operating systems
1413+
1414+ If a single release job produces artifacts for multiple operating systems (using
1415+ matrix strategy), there are a few more caveats to consider :
1416+
1417+ 1. First, it is ideal to make Windows behave the same as Linux and MacOS by
1418+ making the runner use `bash` as the shell to execute commands in :
1419+
1420+ ` ` ` yaml
1421+ defaults:
1422+ run:
1423+ shell: bash
1424+ ` ` `
1425+
1426+ 2. Optionally, you might also want to make the workflow use LF as line
1427+ terminator even on Windows :
1428+
1429+ ` ` ` yaml
1430+ - run: git config --global core.autocrlf input
1431+ # Alternatively, also force line endings for every file
1432+ # - run: |
1433+ # git config --global core.eol lf
1434+ # git config --global core.autocrlf input
1435+ ` ` `
1436+
1437+ The other complexity arises from the fact that the utilities used to compute the
1438+ digest (`sha256sum`) and the base 64 encoding (`base64`) have different
1439+ behaviors across the operating systems :
1440+
1441+ - On MacOS, the utlity to compute the digest is called `shasum` and the
1442+ algorithm is passed via the `-a 256` algorithm
1443+ - On Windows, we need to tell `sha256sum` to treat all files as text by using
1444+ the `-t` switch, otherwise the output will contain an extra `*` in front of
1445+ the filename. This will later be wrongly interpretted as a glob pattern, so we
1446+ should avoid it.
1447+ - On MacOS, `base64` does not have a `-w0` option, the line wrapping is
1448+ implicit.
1449+
1450+ One way to merge all these differences is to use the bash `||` operator :
1451+
1452+ ` ` ` yaml
1453+ - id: hash
1454+ run: |
1455+ set -euo pipefail
1456+ (sha256sum -t release_artifact_${{ runner.os }} || shasum -a 256 release_artifact_${{ runner.os }}) > checksum
1457+ echo "hash-${{ matrix.os }}=$(base64 -w0 checksum || base64 checksum)" >> "${GITHUB_OUTPUT}"
1458+ ` ` `
1459+
1460+ Thus, to generate a single provenance for artifacts built on all 3 operating
1461+ systems, you would use the following example :
1462+
1463+ ` ` ` yaml
1464+ defaults:
1465+ run:
1466+ shell: bash
1467+
1468+ jobs:
1469+ build:
1470+ runs-on: ${{ matrix.os }}
1471+ strategy:
1472+ fail-fast: false # Don't cancel other jobs if one fails
1473+ matrix:
1474+ os: [ubuntu-latest, macos-latest, windows-latest]
1475+ outputs:
1476+ hash-ubuntu-latest: ${{ steps.hash.outputs.hash-ubuntu-latest }}
1477+ hash-macos-latest: ${{ steps.hash.outputs.hash-macos-latest }}
1478+ hash-windows-latest: ${{ steps.hash.outputs.hash-windows-latest }}
1479+ steps:
1480+ - run: git config --global core.autocrlf input
1481+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
1482+ with:
1483+ persist-credentials: false
1484+
1485+ # Do the build to create release_artifact_${{ runner.os }}
1486+ - run: ...
1487+
1488+ - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
1489+ with:
1490+ path: release_artifact_${{ runner.os }}
1491+ name: release_artifact_${{ runner.os }}
1492+ if-no-files-found: error
1493+ - id: hash
1494+ run: |
1495+ set -euo pipefail
1496+ (sha256sum -t release_artifact_${{ runner.os }} || shasum -a 256 release_artifact_${{ runner.os }}) > checksum
1497+ echo "hash-${{ matrix.os }}=$(base64 -w0 checksum || base64 checksum)" >> "${GITHUB_OUTPUT}"
1498+
1499+ provenance:
1500+ needs: [build]
1501+ strategy:
1502+ fail-fast: false # Don't cancel other jobs if one fails
1503+ matrix:
1504+ os: [ubuntu-latest, macos-latest, windows-latest]
1505+ permissions:
1506+ actions: read
1507+ id-token: write
1508+ contents: write
1509+ uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected] 1510+ with:
1511+ base64-subjects: "${{ needs.build.outputs[format('hash-{0}', matrix.os)] }}"
1512+ upload-assets: true # NOTE: This does nothing unless 'upload-tag-name' parameter is also set to an existing tag
1513+ ` ` `
1514+
1515+ Alternatively, to generate 3 different provenance statements, one per each
1516+ artifact, you would use the following example :
1517+
1518+ ` ` ` yaml
1519+ defaults:
1520+ run:
1521+ shell: bash
1522+
1523+ jobs:
1524+ build:
1525+ runs-on: ${{ matrix.os }}
1526+ strategy:
1527+ fail-fast: false # Don't cancel other jobs if one fails
1528+ matrix:
1529+ os: [ubuntu-latest, macos-latest, windows-latest]
1530+ outputs:
1531+ hash-ubuntu-latest: ${{ steps.hash.outputs.hash-ubuntu-latest }}
1532+ hash-macos-latest: ${{ steps.hash.outputs.hash-macos-latest }}
1533+ hash-windows-latest: ${{ steps.hash.outputs.hash-windows-latest }}
1534+ steps:
1535+ - run: git config --global core.autocrlf input
1536+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
1537+ with:
1538+ persist-credentials: false
1539+
1540+ # Do the build to create release_artifact_${{ runner.os }}
1541+ - run: ...
1542+
1543+ - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
1544+ with:
1545+ path: release_artifact_${{ runner.os }}
1546+ name: release_artifact_${{ runner.os }}
1547+ if-no-files-found: error
1548+ - id: hash
1549+ run: |
1550+ set -euo pipefail
1551+ (sha256sum -t release_artifact_${{ runner.os }} || shasum -a 256 release_artifact_${{ runner.os }}) > checksum
1552+ echo "hash-${{ matrix.os }}=$(base64 -w0 checksum || base64 checksum)" >> "${GITHUB_OUTPUT}"
1553+
1554+ combine_hashes:
1555+ needs: [build]
1556+ runs-on: ubuntu-latest
1557+ outputs:
1558+ hashes: ${{ steps.combine_hashes.outputs.hashes }}
1559+ env:
1560+ HASHES: ${{ toJSON(needs.build.outputs) }}
1561+ steps:
1562+ - id: combine_hashes
1563+ run: |
1564+ set -euo pipefail
1565+ echo "${HASHES}" | jq -r '.[] | @base64d' | sed "/^$/d" > hashes
1566+ echo "hashes=$(base64 -w0 hashes)" >> "${GITHUB_OUTPUT}"
1567+
1568+ provenance:
1569+ needs: [combine_hashes]
1570+ permissions:
1571+ actions: read
1572+ id-token: write
1573+ contents: write
1574+ uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected] 1575+ with:
1576+ base64-subjects: "${{ needs.combine_hashes.outputs.hashes }}"
1577+ upload-assets: true # NOTE: This does nothing unless 'upload-tag-name' parameter is also set to an existing tag
1578+
1579+ ` ` `
1580+
14111581# # Known Issues
14121582
14131583# ## Skip output 'hashes' since it may contain secret
0 commit comments