Skip to content

Commit 2e7145d

Browse files
authored
feat: SBOM artifacts (#89)
1 parent ae8dad2 commit 2e7145d

File tree

3 files changed

+68
-63
lines changed

3 files changed

+68
-63
lines changed

.github/workflows/pr-open.yml

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,35 +35,8 @@ jobs:
3535
exit 1
3636
fi
3737
38-
# Anything can be retagged if a fallback image exists (e.g. test)
39-
retag:
40-
needs: [build]
41-
name: Retag
42-
runs-on: ubuntu-24.04
43-
steps:
44-
- uses: actions/checkout@v4
45-
- id: retag
46-
uses: ./
47-
with:
48-
package: backend
49-
keep_versions: 10
50-
repository: bcgov/quickstart-openshift
51-
tags: ${{ github.event.number }}-retag
52-
tag_fallback: test
53-
triggers: ('backend/')
54-
55-
- if: steps.retag.outputs.triggered != 'false'
56-
run: |
57-
# Verify outputs
58-
echo "Outputs: ${{ toJSON(steps.retag.outputs) }}"
59-
if [ -z "${{ steps.retag.outputs.digest }}" ] || [ "${{ steps.retag.outputs.triggered }}" != "true" ]; then
60-
echo "Error! Verify outputs."
61-
exit 1
62-
fi
63-
6438
# No attestation, no id-token, no build
6539
no-attestation:
66-
needs: [build]
6740
name: No Attestation
6841
permissions:
6942
attestations: none
@@ -75,12 +48,13 @@ jobs:
7548
- id: no-attestation
7649
uses: ./
7750
with:
78-
package: backend
51+
package: frontend
52+
build_context: frontend
7953
keep_versions: 10
8054
repository: bcgov/quickstart-openshift
8155
tags: ${{ github.event.number }}-no-attestation
8256
tag_fallback: test
83-
triggers: ('backend/')
57+
triggers: ('frontend/')
8458

8559
- if: steps.no-attestation.outputs.triggered != 'false'
8660
run: |
@@ -121,7 +95,7 @@ jobs:
12195
12296
results:
12397
name: PR Results
124-
needs: [advanced, build, no-attestation, retag]
98+
needs: [advanced, build, no-attestation]
12599
if: always()
126100
runs-on: ubuntu-24.04
127101
steps:

README.md

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![MIT License](https://img.shields.io/github/license/bcgov/action-builder-ghcr.svg)](/LICENSE)
55
[![Lifecycle](https://img.shields.io/badge/Lifecycle-Experimental-339999)](https://github.com/bcgov/repomountie/blob/master/doc/lifecycle-badges.md)
66

7-
# Conditional Container Builder with Fallback and Attestations (SBOMs)
7+
# Conditional Container Builder with Fallback, Attestations and SBOMs (Software Bill of Materials)
88

99
This action builds Docker/Podman containers conditionally using a set of directories. If any files were changed matching that, then build a container. If those files were not changed, retag an existing build.
1010

@@ -72,6 +72,10 @@ Only GitHub Container Registry (ghcr.io) is supported so far.
7272
# Defaults to the current one
7373
repository: ${{ github.repository }}
7474

75+
# SBOM generation is enabled by default as a security best practice
76+
# String value, not boolean
77+
sbom: 'true'
78+
7579
# Specify token (GH or PAT), instead of inheriting one from the calling workflow
7680
token: ${{ secrets.GITHUB_TOKEN }}
7781

@@ -152,49 +156,39 @@ builds:
152156

153157
```
154158

155-
# Permissions
159+
# Security Features
160+
161+
This action provides two key security features: Container Attestations and Software Bill of Materials (SBOM) generation.
156162

157-
It is good practice to set explicit permissions for jobs and workflows. These are applied to the GITHUB_TOKEN. Please see the [GitHub documentation](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token) for more information.
163+
## Container Attestations
158164

159-
The following permissions are used by this action:
165+
[Container attestations](https://docs.github.com/en/actions/security-guides/security-hardening-with-openid-connect#about-oidc-and-container-signing) use GitHub's OIDC token to provide cryptographic proof of:
166+
- Where the container was built (GitHub Actions)
167+
- When it was built (timestamp)
168+
- What repository and workflow built it
169+
- What inputs and environment were used
160170

171+
Attestations require the following permissions:
161172
```yaml
162173
permissions:
163174
packages: write # Required for pushing images
164-
id-token: write # Optional: Required for OIDC token generation
165-
attestations: write # Optional: Required for creating attestations
175+
id-token: write # Required for OIDC token generation
176+
attestations: write # Required for creating attestations
166177
```
167178
168-
## Container Attestations
169-
170-
This action supports [container attestations](https://docs.github.com/en/actions/security-guides/security-hardening-with-openid-connect#about-oidc-and-container-signing) using GitHub's OIDC token. Attestations provide cryptographic verification of container images, enhancing supply chain security.
179+
If these permissions are not granted, the action will still build and push images but skip the attestation step.
171180
172-
If the `id-token: write` and `attestations: write` permissions are not granted, the action will still build and push images but will skip the attestation step. This allows the action to work in environments both with and without attestation support.
181+
## Software Bill of Materials (SBOM)
173182
174-
Example workflow with all permissions enabled:
183+
This action automatically generates SBOMs for all container builds using [Syft](https://github.com/anchore/syft). SBOMs provide a detailed inventory that includes:
184+
- All installed packages and their versions
185+
- Dependencies and their relationships
186+
- License information
187+
- Known vulnerabilities
175188
176-
```yaml
177-
name: Build with Attestations
178-
179-
on:
180-
pull_request:
181-
182-
permissions:
183-
attestations: write
184-
id-token: write
185-
packages: write
186-
187-
jobs:
188-
build:
189-
runs-on: ubuntu-24.04
190-
steps:
191-
- uses: actions/checkout@v4
192-
- uses: bcgov/action-builder-ghcr@vX.Y.Z
193-
with:
194-
package: frontend
195-
tag_fallback: test
196-
triggers: ('frontend/')
197-
```
189+
Two SBOM formats are generated and uploaded as workflow artifacts:
190+
- CycloneDX JSON
191+
- SPDX JSON
198192
199193
# Outputs
200194

action.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ inputs:
1515
description: Build context, not required for self-contained package/default directory
1616
build_file:
1717
description: Dockerfile with path, not required for self-contained package/default directory
18+
sbom:
19+
default: 'true'
20+
description: Generate a Software Bill of Materials (SBOM) for the container image. Enabled by default for better security practices.
1821
keep_versions:
1922
description: Number of versions to keep; omit to skip
2023
tag_fallback:
@@ -182,6 +185,40 @@ runs:
182185
cache-to: type=gha,mode=max
183186
build-args: ${{ inputs.build_args }}
184187

188+
- name: Install Syft
189+
if: steps.build.outputs.triggered == 'true' && inputs.sbom == 'true'
190+
shell: bash
191+
run: |
192+
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
193+
194+
- name: Generate SBOM
195+
if: steps.build.outputs.triggered == 'true' && inputs.sbom == 'true'
196+
shell: bash
197+
run: |
198+
# Generate SBOM in both cyclonedx and spdx formats
199+
IMAGE="ghcr.io/${{ steps.vars.outputs.repo_lower }}/${{ steps.vars.outputs.package_lower }}@${{ steps.build_and_push.outputs.digest }}"
200+
201+
# Create SBOMs directory
202+
mkdir -p sboms
203+
204+
# Generate CycloneDX SBOM
205+
syft packages "$IMAGE" -o cyclonedx-json > "sboms/${{ inputs.package }}-cyclonedx.json"
206+
207+
# Generate SPDX SBOM
208+
syft packages "$IMAGE" -o spdx-json > "sboms/${{ inputs.package }}-spdx.json"
209+
210+
# Upload SBOMs as artifacts
211+
echo "sbom_cyclonedx=sboms/${{ inputs.package }}-cyclonedx.json" >> $GITHUB_OUTPUT
212+
echo "sbom_spdx=sboms/${{ inputs.package }}-spdx.json" >> $GITHUB_OUTPUT
213+
214+
- name: Upload SBOMs
215+
uses: actions/upload-artifact@v4
216+
if: steps.build.outputs.triggered == 'true' && inputs.sbom == 'true'
217+
with:
218+
name: sboms-${{ inputs.package }}
219+
path: sboms/
220+
if-no-files-found: error
221+
185222
- name: Check attestation permissions
186223
id: check_permissions
187224
if: steps.build.outputs.triggered == 'true'

0 commit comments

Comments
 (0)