Merge pull request #7304 from crazy-max/gha-cache-tlog-threshold

remotecache: honor gha cache transparency log threshold
This commit is contained in:
CrazyMax authored and GitHub committed 2026-11-03 19:11:43 +01:00
commit b3f8dfd5da
9 files changed
+67 -21

No files matched your search

+6 -11
View File
@@ -364,7 +364,12 @@ func verifySignature(ctx context.Context, dgst digest.Digest, bundle []byte, con
return errors.New("no verifier available for signed github actions cache")
}
sig, err := v.VerifyArtifact(ctx, dgst, bundle, policy.WithSLSANotRequired())
policyRules := config.Verify.Policy
opts := []policy.ArtifactVerifyOpt{policy.WithSLSANotRequired()}
if policyRules.TlogThreshold != nil {
opts = append(opts, policy.WithTlogThreshold(*policyRules.TlogThreshold))
}
sig, err := v.VerifyArtifact(ctx, dgst, bundle, opts...)
if err != nil {
return err
}
@@ -372,19 +377,9 @@ func verifySignature(ctx context.Context, dgst digest.Digest, bundle []byte, con
return errors.New("signature verification failed: no signer found")
}
numTimestamps := len(sig.Timestamps)
numTlog := 0
for _, t := range sig.Timestamps {
if t.Type == "Tlog" {
numTlog++
}
}
policyRules := config.Verify.Policy
if policyRules.TimestampThreshold > numTimestamps {
return errors.Errorf("signature verification failed: not enough timestamp authorities: have %d, need %d", numTimestamps, policyRules.TimestampThreshold)
}
if policyRules.TlogThreshold > numTlog {
return errors.Errorf("signature verification failed: not enough tlog authorities: have %d, need %d", numTlog, policyRules.TlogThreshold)
}
certRules, err := certToStringMap(&config.Verify.Policy.Summary)
if err != nil {
+20
View File
@@ -9,13 +9,33 @@ import (
"testing"
"time"
"github.com/moby/buildkit/cache/remotecache/gha/ghatypes"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/util/testutil/integration"
"github.com/moby/buildkit/util/testutil/workers"
policy "github.com/moby/policy-helpers"
digest "github.com/opencontainers/go-digest"
"github.com/stretchr/testify/require"
)
func TestVerifySignatureNegativeTlogThreshold(t *testing.T) {
config := &Config{
CacheConfig: &ghatypes.CacheConfig{
Verify: ghatypes.VerifyConfig{
Policy: ghatypes.VerifyPolicy{
TlogThreshold: new(-1),
},
},
},
verifier: func() (*policy.Verifier, error) {
return &policy.Verifier{}, nil
},
}
err := verifySignature(t.Context(), digest.FromString("cache index"), nil, config)
require.EqualError(t, err, "transparency log threshold must not be negative")
}
func init() {
if workers.IsTestDockerd() {
workers.InitDockerdWorker()
+2 -2
View File
@@ -17,7 +17,7 @@ type VerifyConfig struct {
}
type VerifyPolicy struct {
TimestampThreshold int `toml:"timestampThreshold"`
TlogThreshold int `toml:"tlogThreshold"`
TimestampThreshold int `toml:"timestampThreshold"`
TlogThreshold *int `toml:"tlogThreshold"`
certificate.Summary
}
+2
View File
@@ -267,6 +267,8 @@ provenanceEnvDir = "/etc/buildkit/provenance.d"
#
# [cache.gha.verify.policy]
# timestampThreshold = 1
# # If omitted, require all distinct transparency logs present in the bundle.
# # Zero skips log verification and requires a verified TSA timestamp instead.
# tlogThreshold = 1
# # certificate properties that need to match. Simple wildcards (*) are supported.
# certificateIssuer = ""
+1 -1
View File
@@ -53,7 +53,7 @@ require (
github.com/moby/go-archive v0.2.0
github.com/moby/locker v1.0.1
github.com/moby/patternmatcher v0.6.1
github.com/moby/policy-helpers v0.0.0-20261006174519-bd98f4747414
github.com/moby/policy-helpers v0.0.0-20261022152518-485a1bec29f6
github.com/moby/profiles/seccomp v0.2.4
github.com/moby/sys/mountinfo v0.7.2
github.com/moby/sys/reexec v0.1.0
+2 -2
View File
@@ -374,8 +374,8 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/policy-helpers v0.0.0-20261006174519-bd98f4747414 h1:gOgxy0eXQlhvC5hB7CE+tJLLzrC9+gjTWlU+Bv5T8Ck=
github.com/moby/policy-helpers v0.0.0-20261006174519-bd98f4747414/go.mod h1:hNMdiq4WjSmuyvy8lWcflH6erzSg/MgZlFyM7vC7Z2c=
github.com/moby/policy-helpers v0.0.0-20261022152518-485a1bec29f6 h1:EROY4N1iyUx4awd7c2JtGnca7F9PhBU8+lJR/YejbYM=
github.com/moby/policy-helpers v0.0.0-20261022152518-485a1bec29f6/go.mod h1:hNMdiq4WjSmuyvy8lWcflH6erzSg/MgZlFyM7vC7Z2c=
github.com/moby/profiles/seccomp v0.2.4 h1:CNKIR4jNYCGIdstSZ3plN/yF3WCqlupmNLwO1ckLwz4=
github.com/moby/profiles/seccomp v0.2.4/go.mod h1:H3JkVP4MWq+Q87sSWRUEdiZvK52WErUbcxr+mJHKVoM=
github.com/moby/sys/mount v0.3.5 h1:eS3fsZTjHaBihwjp4/+5Z3jxqLXYsbwxqpVSfFv3M00=
+3 -3
View File
@@ -87,11 +87,11 @@ target "vendor" {
output = ["."]
}
target "mod-outdated" {
target "gomod-updates" {
inherits = ["_common"]
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
target = "outdated"
no-cache-filter = ["outdated"]
target = "gomod-updates"
no-cache-filter = ["gomod-updates"]
output = ["type=cacheonly"]
}
+30 -1
View File
@@ -52,6 +52,9 @@ func (v *Verifier) VerifyArtifact(ctx context.Context, dgst digest.Digest, bundl
for _, o := range opt {
o(opts)
}
if opts.TlogThreshold != nil && *opts.TlogThreshold < 0 {
return nil, errors.New("transparency log threshold must not be negative")
}
anyCert, err := anyCerificateIdentity()
if err != nil {
@@ -78,7 +81,23 @@ func (v *Verifier) VerifyArtifact(ctx context.Context, dgst digest.Digest, bundl
return nil, errors.Wrap(err, "getting trusted root")
}
gv, err := verify.NewVerifier(trustedRoot, verify.WithSignedCertificateTimestamps(1), verify.WithTransparencyLog(1), verify.WithObserverTimestamps(1))
verifierOpts := []verify.VerifierOption{
verify.WithSignedCertificateTimestamps(1),
verify.WithObserverTimestamps(1),
}
// Sigstore thresholds count distinct logs, not individual entries.
logIDs := make(map[string]struct{})
for _, entry := range b.GetVerificationMaterial().GetTlogEntries() {
logIDs[string(entry.GetLogId().GetKeyId())] = struct{}{}
}
tlogThreshold := len(logIDs)
if opts.TlogThreshold != nil {
tlogThreshold = *opts.TlogThreshold
}
if tlogThreshold > 0 {
verifierOpts = append(verifierOpts, verify.WithTransparencyLog(tlogThreshold))
}
gv, err := verify.NewVerifier(trustedRoot, verifierOpts...)
if err != nil {
return nil, errors.Wrap(err, "creating verifier")
}
@@ -368,6 +387,7 @@ func anyCerificateIdentity() (verify.PolicyOption, error) {
type ArtifactVerifyOpts struct {
SLSANotRequired bool
TlogThreshold *int
}
type ArtifactVerifyOpt func(*ArtifactVerifyOpts)
@@ -378,6 +398,15 @@ func WithSLSANotRequired() ArtifactVerifyOpt {
}
}
// WithTlogThreshold sets the minimum number of verified transparency logs.
// By default, all distinct logs in the bundle are required. Explicit zero skips
// log verification and returns no log timestamps, so a TSA timestamp is required.
func WithTlogThreshold(threshold int) ArtifactVerifyOpt {
return func(o *ArtifactVerifyOpts) {
o.TlogThreshold = &threshold
}
}
func loadBundle(dt []byte) (*bundle.Bundle, error) {
var bundle bundle.Bundle
bundle.Bundle = new(protobundle.Bundle)
+1 -1
View File
@@ -815,7 +815,7 @@ github.com/moby/locker
## explicit; go 1.19
github.com/moby/patternmatcher
github.com/moby/patternmatcher/ignorefile
# github.com/moby/policy-helpers v0.0.0-20261006174519-bd98f4747414
# github.com/moby/policy-helpers v0.0.0-20261022152518-485a1bec29f6
## explicit; go 1.25.8
github.com/moby/policy-helpers
github.com/moby/policy-helpers/image