This repository provides a comprehensive, hands-on example of how to create and attach signed evidence to your software assets at every stage of the development lifecycle using JFrog Artifactory and JFrog CLI.
By following this guide, you will learn how to build a verifiable, auditable trail for your software, from a single artifact to an immutable Release Bundle. This is a foundational practice for securing your software supply chain and enabling automated, policy-driven governance.
- Build and Publish a multi-platform Docker image.
- Attach Signed Evidence to four distinct types of subjects:
- A Generic Artifact (a README.md file).
- A Software Package (eg: a Docker image, npm, go,…etc).
- A Build Info object (the CI build record).
- An immutable Release Bundle v2.
- Understand the "Why": Learn how this chain of evidence enables secure, policy-based promotions.
The GitHub Actions workflow (.github/workflows/build.yml) performs a sequence of operations to build a chain of trust. Evidence is attached at each critical step.
For more information about evidence on the JFrog platform, see Evidence Management.
graph TD
subgraph "1. Build & Initial Evidence"
A[Start] --> B(Build & Push Docker Image);
B --> C{Attach Evidence to Package};
D[Upload README.md] --> E{Attach Evidence to Generic File};
end
subgraph "2. Aggregate into a Build"
C --> F(Publish Build Info);
E --> F;
F --> G{Attach Evidence to Build};
end
subgraph "3. Create Immutable Release"
G --> H(Create Release Bundle v2 from Build);
H --> I{Attach Evidence to Release Bundle};
end
subgraph "4. Enable Governance"
I --> J[Ready for Secure Promotion with Policy Checks];
end
Before running the workflow, please ensure you have the following set up.
- Make sure JFrog CLI 2.65.0 or above is installed and in your system PATH. For installation instructions, see Install JFrog CLI.
- Make sure Artifactory can be used as a Docker registry. Please refer to Getting Started with Artifactory as a Docker Registry in the JFrog Artifactory User Guide. You should end up with a Docker registry URL, which is mapped to a local Docker repository (or a virtual Docker repository with a local deployment target) in Artifactory. You'll need to know the name of the Docker repository to later collect the published image build-info.
- Make sure the following repository variables are configured in GitHub settings:
- ARTIFACTORY_URL (location of your Artifactory installation)
- BUILD_NAME (planned name for the build of the Docker image)
- BUNDLE_NAME (planned name for the Release Bundle created from the build)
- Make sure the following repository secrets are configured in GitHub settings:
- ARTIFACTORY_ACCESS_TOKEN (access token used for authentication)
- JF_USER (your username in Artifactory)
- PRIVATE_KEY (the key used to sign evidence)
GitHub Secrets
Secret Name | Description | Example Value |
---|---|---|
ARTIFACTORY_ACCESS_TOKEN | A valid JFrog Access Token with permissions to deploy and annotate. | |
JF_USER | Your JFrog Platform username, used for the docker login action. | my-user |
PRIVATE_KEY | The raw private key used to sign all evidence created in the workflow. | -----BEGIN PRIVATE KEY-----... |
GitHub Variables
Variable Name | Description | Example Value |
---|---|---|
ARTIFACTORY_URL | The base URL of your JFrog Platform instance. | https://mycompany.jfrog.io |
BUILD_NAME | A unique name for the build information in Artifactory. | evidence-example-build |
BUNDLE_NAME | The name of the Release Bundle that will be created. | evidence-example-bundle |
This workflow demonstrates how evidence adds value at different levels of abstraction. For the complete implementation, please refer to the build.yml workflow file.
This step uses the jfrog/setup-jfrog-cli
action to install the JFrog CLI into the GitHub Actions runner and configures it with your Artifactory URL and access token.
jobs:
Docker-build-with-evidence:
runs-on: ubuntu-latest
steps:
- name: Install jfrog cli
uses: jfrog/setup-jfrog-cli@v4
env:
JF_URL: ${{ vars.ARTIFACTORY_URL }}
JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }}
- uses: actions/checkout@v4
Here, we securely log in to the Artifactory Docker registry. This step also sets up QEMU and Docker Buildx, which are required to build multi-platform Docker images (e.g., for linux/amd64
and linux/arm64
).
- name: Log in to Artifactory Docker Registry
uses: docker/login-action@v3
with:
registry: ${{ vars.ARTIFACTORY_URL }}
username: ${{ secrets.JF_USER }}
password: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64
install: true
This step builds a multi-platform Docker image using the provided Dockerfile
and pushes it to your Artifactory repository. The jfrog rt build-docker-create
command captures the image details and associates them with a build name and number, laying the groundwork for creating build information.
- name: Build Docker image
run: |
URL=$(echo ${{ vars.ARTIFACTORY_URL }} | sed 's|^https://||')
REPO_URL=${URL}'/example-project-docker-dev-virtual'
docker build --build-arg REPO_URL=${REPO_URL} -f Dockerfile . \
--tag ${REPO_URL}/example-project-app:${{ github.run_number }} \
--output=type=image --platform linux/amd64 --metadata-file=build-metadata --push
jfrog rt build-docker-create example-project-docker-dev --image-file build-metadata --build-name ${{ vars.BUILD_NAME }} --build-number ${{ github.run_number }}
Now we attach the first piece of evidence directly to the Docker image package. This signed evidence acts as a verifiable record, attesting that a specific action (like a signature from a CI process) was performed on this package version.
- name: Evidence on docker
run: |
echo '{ "actor": "${{ github.actor }}", "date": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" }' > sign.json
jf evd create --package-name example-project-app --package-version 32 --package-repo-name example-project-docker-dev \
--key "${{ secrets.PRIVATE_KEY }}" \
--predicate ./sign.json --predicate-type https://jfrog.com/evidence/signature/v1
echo ' Evidence attached: `signature` '
To demonstrate that evidence can be attached to any artifact, not just packages, this step uploads the README.md
file to a generic repository and attaches similar signed evidence to it.
- name: Upload readme file
run: |
jf rt upload ./README.md example-project-generic-dev/readme/${{ github.run_number }}/ --build-name ${{ vars.BUILD_NAME }} --build-number ${{ github.run_number }}
jf evd create --subject-repo-path example-project-generic-dev/readme/${{ github.run_number }}/README.md \
--key "${{ secrets.PRIVATE_KEY }}" \
--predicate ./sign.json --predicate-type https://jfrog.com/evidence/signature/v1
This step aggregates all the artifacts and metadata captured so far (the Docker image and the README) into a comprehensive Build Info object in Artifactory. Immediately after, it attaches signed evidence to this entire build, attesting to the integrity of the build process itself.
- name: Publish build info
run: jfrog rt build-publish ${{ vars.BUILD_NAME }} ${{ github.run_number }}
- name: Sign build evidence
run: |
echo '{ "actor": "${{ github.actor }}", "date": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" }' > sign.json
jf evd create --build-name ${{ vars.BUILD_NAME }} --build-number ${{ github.run_number }} \
--predicate ./sign.json --predicate-type https://jfrog.com/evidence/build-signature/v1 \
--key "${{ secrets.PRIVATE_KEY }}"
echo ' Evidence attached: `build-signature` ' >> $GITHUB_STEP_SUMMARY
The workflow now creates an immutable Release Bundle from the build we just published. This bundle is a tamper-proof, versioned collection that is ideal for promoting through your SDLC stages.
- name: Create release bundle
run: |
echo '{ "files": [ {"build": "'"${{ vars.BUILD_NAME }}/${{ github.run_number }}"'" } ] }' > bundle-spec.json
jf release-bundle-create ${{ vars.BUNDLE_NAME }} ${{ github.run_number }} --signing-key PGP-RSA-2048 --spec bundle-spec.json --sync=true
NAME_LINK=${{ vars.ARTIFACTORY_URL }}'/ui/artifactory/lifecycle/?bundleName='${{ vars.BUNDLE_NAME }}'&bundleToFlash='${{ vars.BUNDLE_NAME }}'&repositoryKey=example-project-release-bundles-v2&activeKanbanTab=promotion'
VER_LINK=${{ vars.ARTIFACTORY_URL }}'/ui/artifactory/lifecycle/?bundleName='${{ vars.BUNDLE_NAME }}'&bundleToFlash='${{ vars.BUNDLE_NAME }}'&releaseBundleVersion='${{ github.run_number }}'&repositoryKey=example-project-release-bundles-v2&activeVersionTab=Version%20Timeline&activeKanbanTab=promotion'
echo ' Release bundle ['${{ vars.BUNDLE_NAME }}']('${NAME_LINK}'):['${{ github.run_number }}']('${VER_LINK}') created' >> $GITHUB_STEP_SUMMARY
Finally, we attach signed evidence to the immutable Release Bundle itself. This could represent a successful integration test or final approval, providing a verifiable gate for promotion to production environments.
- name: Evidence on release-bundle v2
run: |
echo '{ "actor": "${{ github.actor }}", "date": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" }' > rbv2_evidence.json
JF_LINK=${{ vars.ARTIFACTORY_URL }}'/ui/artifactory/lifecycle/?bundleName='${{ vars.BUNDLE_NAME }}'&bundleToFlash='${{ vars.BUNDLE_NAME }}'&releaseBundleVersion='${{ github.run_number }}'&repositoryKey=release-bundles-v2&activeVersionTab=Version%20Timeline&activeKanbanTab=promotion'
echo 'Test on Release bundle ['${{ vars.BUNDLE_NAME }}':'${{ github.run_number }}']('${JF_LINK}') success' >> $GITHUB_STEP_SUMMARY
jf evd create --release-bundle ${{ vars.BUNDLE_NAME }} --release-bundle-version ${{ github.run_number }} \
--predicate ./rbv2_evidence.json --predicate-type https://jfrog.com/evidence/rbv2-signature/v1 \
--key "${{ secrets.PRIVATE_KEY }}"
echo ' Evidence attached: integration-test ' >> $GITHUB_STEP_SUMMARY
When the Evidence service is used in conjunction with JFrog Xray, each Release Bundle promotion generates evidence in the form of a CycloneDX SBOM. You can create a policy in an external tool (for example, a rego policy) that reviews the contents of the CycloneDX evidence file and decides whether to block the promotion (because the Release Bundle fails to meet all your organization's requirements for promotion to the next stage of your SDLC).
To see a sample rego policy, go here. For more information about integrating Release Lifecycle Management and Evidence with Xray, see Scan Release Bundles (v2) with Xray.
- Product Documentation:
- JFrog CLI Documentation: