Prominent attacks on software development pipelines have resulted in significant financial impact for companies and brought their build processes under scrutiny. While the attack vectors on pipelines are virtually limitless, this article focuses on securing components, processes, and tools involved in building and deploying containerized software through signing, attesting, and verifying a build image. I chose to implement a do-it-yourself (DIY) approach to help understand these concepts. I also compared my approach to Red Hat Advanced Developer Suite, which includes Red Hat Trusted Artifact Signer and Red Hat Trusted Profile Analyzer, and addresses the complexity involved in implementation.
For my home lab container platform, I deployed Red Hat build of MicroShift with repurposed hardware and specs not too different from what I describe in my article on running MicroShift. I deployed Gitea, ArgoCD, and Sonatype Nexus Repository Community Edition as lightweight solutions for a self-hosted Git repository, GitOps, and image registry, respectively. My build pipeline uses Gitea Actions, which is largely compatible with GitHub Actions. I then explored the tools and configurations needed for securing my software supply chain. You can find the configurations I used in this article in my GitHub repository.
Trusting your supply chain
A software supply chain describes how software is built, signed, attested, and validated. A security framework known as Supply-chain Levels for Software Artifacts (SLSA) provides guidelines on hardened security practices for software supply chains. The SLSA 1.2 specification provides SLSA Levels 0 through 3 for building code, with each higher level providing better supply chain security guarantees.
To implement the security framework, we look to Sigstore, an Open Source Security Foundation (OpenSSF) project. Sigstore provides Rekor to record the artifacts in a transparency log that is verifiable and tamper-resistant. A public Rekor instance exists for development use, but I did not want to divulge sensitive information, such as my server hostnames or build platform, to the internet. Instead, I deployed my own Rekor instance using a Helm chart. I also opted to use public and private keys for signing, skipping the deployment of Fulcio (used as a certificate authority for keyless signing).
Sign, attest, and verify
Sigstore also provides Cosign, a command-line utility to sign, attest, and verify artifacts. In my case, the artifact is a container image built through my Gitea Actions build pipeline. I generated a Cosign key pair, signed my built image with the private key, and wrote it to Rekor.
Cosign v2 stored signatures and attestations as separate .sig and .att tags in an image registry, but Cosign v3 (which I'm using) bundles them in an OCI format for offline verification without querying Rekor. I attested to the build by producing a provenance file, an authenticated statement about the software artifact and how it was built. The build platform is responsible for generating provenance, but because my Gitea platform uses self-hosted runners and doesn't have this capability, I handcrafted generating the provenance into my pipeline by following its specifications. Here are the commands I've invoked so far on the pipeline:
$ cosign sign --yes --key <cosign-private-key> --rekor-url=https://<my-rekor> <image>
$ cosign attest --yes --type slsaprovenance1 --predicate my-provenance.json --key <cosign-private-key> --rekor-url=https://<my-rekor> <image>A Software Bill of Materials (SBOM) describes the packages, libraries, and dependencies of a container image. The two dominant industry-standard formats for SBOMs are SPDX for legal and license compliance and CycloneDX for application security and vulnerability tracking. I used Syft to generate an SBOM in CycloneDX format for my image and then attested it with Cosign. I used a GitHub Action for Syft, but the underlying commands are:
$ syft <image> -o cyclonedx-json=sbom.cdx.json
$ cosign attest --yes --type cyclonedx --predicate sbom.cdx.json --key <cosign-private-key> --rekor-url=https://<my-rekor> <image>I can display an image's relaed signatures and attestations using cosign:
$ cosign tree <image>I can also verify that these artifacts were signed by my Cosign key pair. Using Cosign v3, I first generate a Sigstore trusted root bundle using my self-hosted Rekor public key:
$ curl -k https://<my-rekor>/api/v1/log/publicKey -o rekor.pub
$ export SIGSTORE_REKOR_PUBLIC_KEY=rekor.pub
$ cosign trusted-root create --rekor=https://<my-rekor>,public-key=rekor.pub,start-time=2026-01-01T00:00:00Z --out trusted-root.jsonWith the trusted root bundle and Cosign public key, I can now verify the image signature and SLSA provenance and SBOM attestations:
$ cosign verify --key cosign.pub <image>@<image-digest> --new-bundle-format=true --trusted-root=trusted-root.json
$ cosign verify-attestation --type slsaprovenance1 --key cosign.pub <image>@<image-digest> --new-bundle-format=true --trusted-root=trusted-root.json
$ cosign verify-attestation --type cyclonedx --key cosign.pub <image>@<image-digest> --new-bundle-format=true --trusted-root=trusted-root.jsonAs a more comprehensive approach, Conforma (formerly known as Enterprise Contract) is a policy-driven tool that runs validation checks to validate the image was signed and attested by known and trusted build systems. A Conforma policy is defined using the Rego policy language and applies a set of rules as policy checks. The community has provided a number of predefined Conforma release policies. I started with the SLSA3 base policy and customized it by removing a check for Tekton Pipelines (because I'm using Gitea Actions) and a check for CVE scanning (which isn't integrated into my workflow). I then generate an ApplicationSnapshot consisting of an image digest mapped to a Git repository and revision and validate my Conforma policy against it:
$ ec validate image --images artifacts/app-snapshot-generated.json \
--policy artifacts/conforma-policy.json \
--rekor-url https://<my-rekor> \
--public-key cosign.pub \
--timeout 15m0s \
--ignore-rekorBuilding a workflow
Having executed the end-to-end software supply chain, I wrap these tasks into a Gitea Actions workflow, as in the example in my Git repository. I use a custom self-hosted runner to execute my Gitea Actions workflow because I need to incorporate the various CLI tools (such as cosign and ec) into the container image that the runner uses. I've provided an example of my Containerfile.
A comparison
Previously, I deployed a self-hosted Rekor using a Helm chart. As a comparison, Red Hat's Trusted Artifact Signer provides an operator for ease of deployment of the Sigstore ecosystem, including a self-hosted Rekor and Fulcio. Trusted Artifact Signer also bundles a Rekor search UI allowing you to view signature and attestation metadata from a web console.
Red Hat Trusted Profile Analyzer can also be deployed with an operator, and is designed to ingest, manage, and store the SBOMs that are created. Trusted Profile Analyzer can also analyze and monitor the SBOMs against live data feeds of security advisories such as the Vulnerability Exploitability Exchange (VEX) and Common Vulnerabilities and Exposures (CVE). At the time of this writing, I couldn't find equivalent tools deployed outside of Red Hat OpenShift that provides a web console to view generated supply chain security artifacts.
Red Hat Advanced Developer Suite integrates with a number of CI/CD platforms, although at the time of this writing, Gitea Actions integration is not supported. Red Hat OpenShift's native tool for CI/CD pipelines is OpenShift Pipelines, also known as Tekton, so I'll highlight the integrations with it. As part of OpenShift Pipelines, Tekton Chains observes, signs, and attests the executed Tekton pipeline and task runs with Cosign, so we no longer need a self-hosted runner with the Cosign binary. It generates the provenance manifest for the build automatically, which increases velocity by shifting security from manually in my DIY approach to machine speed automation. Tekton Chains also provides an easy way to generate a Cosign key pair stored on the OpenShift cluster and can be configured to use our self-hosted Rekor and Fulcio. I did find that generating SBOMs required custom Tekton tasks which, while documented, was still custom pipeline development.
Conclusion
As I've demonstrated in this article, there are numerous considerations when building a pipeline with software supply chain security in mind. From self-hosting pieces of infrastructure like Rekor and build runners to setting up a pipeline with Cosign to sign and attest a build, the implementation may be complex. While a DIY approach allows the most flexibility to build a custom pipeline (allowing me to use Gitea Actions for my pipeline, for instance), Red Hat Advanced Developer Suite offers a number of built-in capabilities that makes the implementation of software supply chain security significantly easier.