๐ SBOM Signing Action
A comprehensive GitHub Action for signing Software Bill of Materials (SBOM) files using CycloneDX CLI with secure key management, flexible installation methods, and automatic verification.
โจ Features
- ๐ RSA Digital Signatures - Sign SBOM files with industry-standard RSA encryption
- ๐ณ Flexible Installation - Install the CycloneDX CLI via Docker (any OS with Docker) or Homebrew (macOS/Linux), or skip installation if it's already available
- ๐ Secure Key Handling - Temporary key files with restrictive permissions
- โ Signature Verification - Built-in signature validation after signing
- ๐งน Automatic Cleanup - Secure cleanup of temporary files
- ๐ Comprehensive Validation - Input validation with helpful error messages
๐ Basic Usage
Sign an SBOM with default settings:
- name: "๐ Sign SBOM"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./sbom.xml"
signing-key: ${{ secrets.SIGNING_KEY }}
- name: "๐ Sign with custom output"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./artifacts/sbom.xml"
signing-key: ${{ secrets.PRIVATE_SIGNING_KEY }}
output-path: "./signed-sbom.xml"
- name: "๐ Sign with specific CLI version"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./sbom.xml"
signing-key: ${{ secrets.SIGNING_KEY }}
cyclonedx-cli-version: "0.27.2"
๐ง Advanced Usage
Full configuration with all available options:
- name: "๐ Advanced SBOM signing"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./artifacts/sbom.xml"
signing-key: ${{ secrets.DEPENDENCY_TRACKER_SIGNING_KEY }}
installation-method: "docker"
cyclonedx-cli-version: "0.29.1"
output-path: "./artifacts/signed-sbom.xml"
signing-key-file-path: "./temp/signing.pem"
show-summary: "true"
๐ Permissions Required
This action requires standard repository permissions:
permissions:
contents: read # Required to checkout repository code
๐๏ธ CI/CD Example
Complete workflow for SBOM generation, signing, and publishing:
name: "SBOM Security Pipeline"
on:
push:
branches: ["main"]
tags: ["v*"]
pull_request:
branches: ["main"]
permissions:
contents: read
jobs:
secure-sbom:
runs-on: ubuntu-latest
steps:
- name: "๐ฅ Checkout repository"
uses: actions/checkout@v6
# ...
- name: "๐ Generate SBOM"
id: generate-sbom
uses: laerdal/github_actions/dotnet-cyclonedx@main
with:
path: "./src/MyProject.csproj"
output: "./artifacts"
output-format: "xml"
- name: "๐ Sign SBOM"
id: sign-sbom
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: ${{ steps.generate-sbom.outputs.sbom-file }}
signing-key: ${{ secrets.SBOM_SIGNING_KEY }}
output-path: "./signed-sbom.xml"
cyclonedx-cli-version: "0.29.1"
show-summary: "true"
- name: "๐ค Upload signed SBOM"
uses: laerdal/github_actions/sbom-publish@main
with:
sbom-file-path: ${{ steps.sign-sbom.outputs.signed-sbom-path }}
project-name: "MyProject"
project-version: ${{ github.ref_name }}
dependency-tracker-url: ${{ vars.DEPENDENCY_TRACKER_URL }}
dependency-tracker-api-key: ${{ secrets.DEPENDENCY_TRACKER_API_KEY }}
- name: "๐ Archive signed SBOM"
uses: actions/upload-artifact@v7
with:
name: "signed-sbom-${{ github.sha }}"
path: ${{ steps.sign-sbom.outputs.signed-sbom-path }}
retention-days: 90
๐ Inputs
| Input | Description | Required | Default | Example |
|---|---|---|---|---|
sbom-file-path |
Path to the SBOM file to sign | โ Yes | - | ./sbom.xml, ./artifacts/sbom.xml |
signing-key |
Private signing key content (PEM format) | โ Yes | - | ${{ secrets.SIGNING_KEY }} |
installation-method |
Installation method for the CycloneDX CLI | โ No | docker |
docker, brew, skip |
cyclonedx-cli-version |
CycloneDX CLI tool version (used with the docker method) |
โ No | 0.29.1 |
0.29.1, 0.27.2 |
output-path |
Path for signed SBOM output | โ No | Same as input | ./signed-sbom.xml |
signing-key-file-path |
Path for the temporary signing key file | โ No | ./signing-key.pem |
./temp/signing.pem |
show-summary |
Display action summary | โ No | false |
true, false |
๐ค Outputs
| Output | Description | Type | Example |
|---|---|---|---|
signed-sbom-path |
Path to the signed SBOM file | string |
./signed-sbom.xml |
signature-algorithm |
Signature algorithm used | string |
RS256 |
signing-timestamp |
ISO timestamp when SBOM was signed | string |
2024-10-04T12:30:45Z |
cli-version |
CycloneDX CLI version that performed the signing | string |
0.29.1 |
file-size |
Size of signed SBOM file in bytes | string |
15728 |
๐ Related Actions
| Action | Purpose | Repository |
|---|---|---|
| ๐ dotnet-cyclonedx | Generate SBOM files | laerdal/github_actions/dotnet-cyclonedx |
| ๐ค sbom-publish | Publish signed SBOMs | laerdal/github_actions/sbom-publish |
| ๐ gh-sbom | GitHub SBOM operations | laerdal/github_actions/gh-sbom |
| ๐ dotnet | .NET build operations | laerdal/github_actions/dotnet |
๐ก Examples
Basic SBOM Signing
- name: "Sign SBOM with default settings"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./sbom.xml"
signing-key: ${{ secrets.PRIVATE_SIGNING_KEY }}
Custom CLI Version
- name: "Sign with specific CLI version"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./build/sbom.xml"
signing-key: ${{ secrets.SIGNING_KEY }}
cyclonedx-cli-version: "0.26.0"
output-path: "./artifacts/signed-sbom.xml"
Pre-installed CLI
- name: "Install CycloneDX CLI"
run: |
curl -L -o cyclonedx https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.29.1/cyclonedx-linux-x64
chmod +x cyclonedx
sudo mv cyclonedx /usr/local/bin/
- name: "Sign SBOM with pre-installed CLI"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./sbom.xml"
signing-key: ${{ secrets.SIGNING_KEY }}
installation-method: "skip"
Chain with SBOM Generation
- name: "Generate SBOM"
id: generate
uses: laerdal/github_actions/dotnet-cyclonedx@main
with:
path: "./MyProject.csproj"
output-format: "xml"
- name: "Sign Generated SBOM"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: ${{ steps.generate.outputs.sbom-file }}
signing-key: ${{ secrets.SIGNING_KEY }}
show-summary: "true"
Multi-Key Signing Strategy
- name: "Sign with primary key"
id: primary-sign
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./sbom.xml"
signing-key: ${{ secrets.PRIMARY_SIGNING_KEY }}
output-path: "./sbom-signed-primary.xml"
- name: "Sign with backup key"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: ${{ steps.primary-sign.outputs.signed-sbom-path }}
signing-key: ${{ secrets.BACKUP_SIGNING_KEY }}
output-path: "./sbom-dual-signed.xml"
๐ง Installation Methods
The action supports three ways of getting the CycloneDX CLI onto the runner, selected via installation-method:
| Method | Description | Requirements |
|---|---|---|
docker |
(default) Pulls cyclonedx/cyclonedx-cli:<cyclonedx-cli-version> and runs signing inside the container |
Docker available on the runner |
brew |
Installs the CLI via Homebrew (cyclonedx/cyclonedx/cyclonedx-cli) |
Homebrew available on the runner (macOS/Linux) |
skip |
Assumes cyclonedx is already installed and available on PATH |
CLI pre-installed by an earlier step |
โ ๏ธ Docker mount constraint: the
dockermethod mounts the current working directory ($PWD) into the container as/workspace. Always passsbom-file-path/output-pathas paths relative to the working directory (e.g../sbom.xml); absolute paths outside the working directory are not visible inside the container and will cause signing to fail.
๐ Security Features
The action implements multiple security layers:
- Restrictive File Permissions: Temporary key files created with 600 permissions
- Automatic Cleanup: Secure deletion of temporary files even on failure
- Input Validation: Comprehensive validation of required parameters
- Signature Verification: Post-signing validation of signature integrity
- Secure Key Handling: The key is written only to a temporary, restricted-permission file that is securely removed immediately after signing, and is masked from workflow logs
๐ Key Format Requirements
The signing key must be in PEM format:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----
Key Generation
Generate RSA private keys for SBOM signing:
# Generate 2048-bit RSA private key
openssl genrsa -out private-key.pem 2048
# Extract public key
openssl rsa -in private-key.pem -pubout -out public-key.pem
# Convert to PKCS#8 format (if needed)
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt \
-in private-key.pem -out private-key-pkcs8.pem
Key Storage Best Practices
- GitHub Secrets: Store keys in GitHub repository secrets
- Base64 Encoding: Encode keys for secret storage if needed
- Key Rotation: Regularly rotate signing keys
- Access Control: Limit access to signing keys
- Backup Strategy: Maintain secure backups of keys
๐ Troubleshooting
Common Issues
CycloneDX CLI Not Found
Problem: Docker or Homebrew is not available on the runner, so installation-method: docker
(the default) or installation-method: brew failed to install the CycloneDX CLI
Solution: Switch installation method, or pre-install the CLI and use installation-method: skip:
- name: "Install CycloneDX CLI manually"
run: |
curl -L -o cyclonedx https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.29.1/cyclonedx-linux-x64
chmod +x cyclonedx
sudo mv cyclonedx /usr/local/bin/
- name: "Sign SBOM with pre-installed CLI"
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./sbom.xml"
signing-key: ${{ secrets.SIGNING_KEY }}
installation-method: "skip"
Invalid Key Format
Problem: Signing key is not in proper PEM format
Solution: Validate key format before use:
- name: "Validate signing key"
run: |
echo "${{ secrets.SIGNING_KEY }}" > temp-key.pem
if ! openssl rsa -in temp-key.pem -check -noout; then
echo "โ Invalid signing key format"
exit 1
fi
rm temp-key.pem
echo "โ
Key validation passed"
Permission Denied on Key File
Problem: Cannot create or access temporary key file
Solution: Check filesystem permissions and working directory:
- name: "Debug file permissions"
run: |
pwd
ls -la .
touch test-file && rm test-file
echo "โ
Filesystem permissions OK"
Signature Verification Failed
Problem: Generated signature cannot be verified
Solution: Check CycloneDX CLI version compatibility and key validity:
- name: "Debug signing process"
run: |
echo "Input SBOM size: $(stat -c%s sbom.xml)"
echo "CycloneDX CLI version: $(cyclonedx --version)"
echo "Platform: ${{ runner.os }}-${{ runner.arch }}"
Debug Tips
- Enable Detailed Logging: Set
show-summary: "true" - Validate Key Locally: Test key format with OpenSSL
- Check File Sizes: Ensure SBOM files are not empty
- Verify CLI Installation: Check CycloneDX CLI availability
๐ Requirements
- GitHub Actions runner (Windows, Linux, or macOS)
- Valid SBOM file in CycloneDX format
- RSA private key in PEM format
- Docker (for the default
installation-method: docker) or Homebrew (forinstallation-method: brew), unless usinginstallation-method: skipwith a pre-installed CLI
๐ง Advanced Features
Conditional Signing
- name: "Check if signing required"
id: check-signing
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]] || [[ "${{ github.ref }}" == refs/tags/* ]]; then
echo "requires-signing=true" >> $GITHUB_OUTPUT
else
echo "requires-signing=false" >> $GITHUB_OUTPUT
fi
- name: "Sign SBOM"
if: steps.check-signing.outputs.requires-signing == 'true'
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./sbom.xml"
signing-key: ${{ secrets.SIGNING_KEY }}
Signature Verification Workflow
- name: "Sign and verify SBOM"
id: sign
uses: laerdal/github_actions/sbom-sign@main
with:
sbom-file-path: "./sbom.xml"
signing-key: ${{ secrets.SIGNING_KEY }}
- name: "Verify signature integrity"
run: |
# Check for signature elements in XML
if ! grep -q "signature" "${{ steps.sign.outputs.signed-sbom-path }}"; then
echo "โ No signature found in SBOM"
exit 1
fi
echo "โ
SBOM signature verification passed"
๐ License
This action is part of the Laerdal Medical GitHub Actions collection and follows the same license terms.
๐ก Tip: Combine this action with our SBOM generation and publishing actions for complete supply chain security workflows.