Skip to content

feat: add SLSA provenance generation for package builds #2051

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/wolfi-presubmit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ jobs:
uses: ./melange-src/.github/actions/setup-bubblewrap
- if: matrix.runner == 'bubblewrap'
run: |
make SHELL="/bin/bash" MELANGE="sudo melange" MELANGE_RUNNER="bubblewrap" package/${{ matrix.package }}
make SHELL="/bin/bash" MELANGE="sudo melange" MELANGE_RUNNER="bubblewrap" MELANGE_EXTRA_OPTS="--generate-provenance" package/${{ matrix.package }}

- name: Download kernel for VMs
if: matrix.runner == 'qemu'
Expand Down Expand Up @@ -154,9 +154,18 @@ jobs:
QEMU_KERNEL_IMAGE=/tmp/kernel/boot/vmlinuz-virt \
QEMU_KERNEL_MODULES=/tmp/kernel/lib/modules/ \
MELANGE="/usr/bin/melange" \
MELANGE_EXTRA_OPTS="--runner qemu" \
MELANGE_EXTRA_OPTS="--runner qemu --generate-provenance" \
package/${{ matrix.package }}

- name: Output SLSA provenance
run: |
for pkg in packages/x86_64/*.attest.tar.gz; do
dir="$(basename "${pkg}" .attest.tar.gz)"
sudo mkdir -p packages/x86_64/"${dir}"
sudo tar --xattrs --xattrs-include='*.*' -xf "${pkg}" -C packages/x86_64/"${dir}"
jq . packages/x86_64/"${dir}"/"${dir}.attestation"
done

- name: Run tests to verify xattrs with bubblewrap runner
if: matrix.runner == 'bubblewrap' && matrix.package == 'fping'
run: |
Expand Down
4 changes: 1 addition & 3 deletions docs/cmd/pipeline-reference-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,7 @@ func parseFile(path string) (*config.Pipeline, error) {
return out, nil
}

var (
regex = regexp.MustCompile(`(?s)<!-- start:pipeline-reference-gen -->\n(.*?)<!-- end:pipeline-reference-gen -->`)
)
var regex = regexp.MustCompile(`(?s)<!-- start:pipeline-reference-gen -->\n(.*?)<!-- end:pipeline-reference-gen -->`)

func writeFile(path string, doc []*PipelineDoc) error {
out := new(bytes.Buffer)
Expand Down
1 change: 1 addition & 0 deletions docs/md/melange_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ melange build [flags]
--empty-workspace whether the build workspace should be empty
--env-file string file to use for preloaded environment variables
--generate-index whether to generate APKINDEX.tar.gz (default true)
--generate-provenance generate SLSA provenance for builds (included in a separate .attest.tar.gz file next to the APK)
--git-commit string commit hash of the git repository containing the build config file (defaults to detecting HEAD)
--git-repo-url string URL of the git repository containing the build config file (defaults to detecting from configured git remotes)
-h, --help help for build
Expand Down
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/google/go-github/v54 v54.0.0
github.com/google/licenseclassifier/v2 v2.0.0
github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d
github.com/in-toto/attestation v1.1.2
github.com/invopop/jsonschema v0.13.0
github.com/joho/godotenv v1.5.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
Expand All @@ -44,7 +45,7 @@ require (
golang.org/x/sys v0.33.0
golang.org/x/term v0.32.0
golang.org/x/text v0.25.0
golang.org/x/time v0.11.0
golang.org/x/time v0.12.0
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/v3 v3.11.0
Expand All @@ -53,6 +54,8 @@ require (
)

require (
github.com/fatih/color v1.18.0 // indirect
github.com/google/martian/v3 v3.3.3 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/tools v0.33.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
Expand Down Expand Up @@ -167,8 +170,8 @@ require (
google.golang.org/api v0.231.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6
gopkg.in/warnings.v0 v0.1.2 // indirect
k8s.io/apimachinery v0.32.3 // indirect
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
Expand Down
18 changes: 10 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
Expand Down Expand Up @@ -189,8 +189,8 @@ github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQE
github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg=
github.com/google/licenseclassifier/v2 v2.0.0 h1:1Y57HHILNf4m0ABuMVb6xk4vAJYEUO0gDxNpog0pyeA=
github.com/google/licenseclassifier/v2 v2.0.0/go.mod h1:cOjbdH0kyC9R22sdQbYsFkto4NGCAc+ZSwbeThazEtM=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
Expand All @@ -216,6 +216,8 @@ github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISH
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d h1:LFOmpWrSbtolg0YqYC9hQjj5WSLtRGb6aZ3JAugLfgg=
github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d/go.mod h1:112TOyA+aruNSUBlyBWlKBdLVYTdhjiO2CKD0j/URSU=
github.com/in-toto/attestation v1.1.2 h1:MBFn6lsMq6dptQZJBhalXTcWMb/aJy3V+GX3VYj/V1E=
github.com/in-toto/attestation v1.1.2/go.mod h1:gYFddHMZj3DiQ0b62ltNi1Vj5rC879bTmBbrv9CRHpM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
Expand Down Expand Up @@ -502,8 +504,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down Expand Up @@ -541,8 +543,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
Expand Down
6 changes: 2 additions & 4 deletions internal/gen-jsonschema/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import (
"github.com/invopop/jsonschema"
)

var (
outputFlag = flag.String("o", "", "output path")
)
var outputFlag = flag.String("o", "", "output path")

func main() {
flag.Parse()
Expand All @@ -33,7 +31,7 @@ func main() {
if err := enc.Encode(schema); err != nil {
log.Fatal(err)
}
if err := os.WriteFile(*outputFlag, b.Bytes(), 0644); err != nil {
if err := os.WriteFile(*outputFlag, b.Bytes(), 0o644); err != nil {
log.Fatal(err)
}
}
10 changes: 9 additions & 1 deletion pkg/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ type Build struct {
// how we get "build-time" SBOMs!
SBOMGroup *SBOMGroup

Start time.Time
End time.Time

// Opt-in SLSA provenance generation for initial rollout/testing
GenerateProvenance bool

// The package resolver associated with this build.
//
// This is only applicable when there's a build context. It
Expand All @@ -164,6 +170,7 @@ func New(ctx context.Context, opts ...Option) (*Build, error) {
CacheDir: "./melange-cache/",
Arch: apko_types.ParseArchitecture(runtime.GOARCH),
GuestFS: tarfs.New(),
Start: time.Now(),
}

for _, opt := range opts {
Expand Down Expand Up @@ -317,7 +324,8 @@ func (b *Build) buildGuest(ctx context.Context, imgConfig apko_types.ImageConfig
// Work around LockImageConfiguration assuming multi-arch.
imgConfig.Archs = []apko_types.Architecture{b.Arch}

opts := []apko_build.Option{apko_build.WithImageConfiguration(imgConfig),
opts := []apko_build.Option{
apko_build.WithImageConfiguration(imgConfig),
apko_build.WithArch(b.Arch),
apko_build.WithExtraKeys(b.ExtraKeys),
apko_build.WithExtraBuildRepos(b.ExtraRepos),
Expand Down
3 changes: 1 addition & 2 deletions pkg/build/build_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"testing"

"io"

"chainguard.dev/apko/pkg/sbom/generator/spdx"
"chainguard.dev/melange/pkg/container"
"chainguard.dev/melange/pkg/container/docker"
Expand Down
2 changes: 1 addition & 1 deletion pkg/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ package:
}

f := filepath.Join(t.TempDir(), "config")
if err := os.WriteFile(f, []byte(contents), 0755); err != nil {
if err := os.WriteFile(f, []byte(contents), 0o755); err != nil {
t.Fatal(err)
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/build/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,11 @@ func WithIgnoreSignatures(ignore bool) Option {
return nil
}
}

// WithGenerateProvenance sets whether to generate SLSA provenance during the build.
func WithGenerateProvenance(provenance bool) Option {
return func(b *Build) error {
b.GenerateProvenance = provenance
return nil
}
}
Loading
Loading