@@ -73,6 +73,7 @@ import (
7373 "github.com/moby/buildkit/util/testutil/httpserver"
7474 "github.com/moby/buildkit/util/testutil/integration"
7575 "github.com/moby/buildkit/util/testutil/workers"
76+ policyimage "github.com/moby/policy-helpers/image"
7677 digest "github.com/opencontainers/go-digest"
7778 ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
7879 "github.com/pkg/errors"
@@ -251,6 +252,8 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
251252 testHTTPResolveMultiBuild ,
252253 testGitResolveMutatedSource ,
253254 testImageResolveAttestationChainRequiresNetwork ,
255+ testImageResolveAttestationChainLocal ,
256+ testImageResolveProvenanceAttestation ,
254257 testSourcePolicySession ,
255258 testSourcePolicySessionDenyMessages ,
256259 testSourceMetaPolicySession ,
@@ -12404,6 +12407,216 @@ func testImageResolveAttestationChainRequiresNetwork(t *testing.T, sb integratio
1240412407 require .NoError (t , err )
1240512408}
1240612409
12410+ func testImageResolveProvenanceAttestation (t * testing.T , sb integration.Sandbox ) {
12411+ workers .CheckFeatureCompat (t , sb , workers .FeatureDirectPush , workers .FeatureProvenance )
12412+ requiresLinux (t )
12413+
12414+ ctx := sb .Context ()
12415+ c , err := New (ctx , sb .Address ())
12416+ require .NoError (t , err )
12417+ defer c .Close ()
12418+
12419+ target , platform := buildProvenanceImage (ctx , t , c , sb )
12420+
12421+ _ , err = c .Build (ctx , SolveOpt {}, "test" , func (ctx context.Context , c gateway.Client ) (* gateway.Result , error ) {
12422+ md , err := c .ResolveSourceMetadata (ctx , & pb.SourceOp {
12423+ Identifier : "docker-image://" + target ,
12424+ }, sourceresolver.Opt {
12425+ ImageOpt : & sourceresolver.ResolveImageOpt {
12426+ NoConfig : true ,
12427+ ResolveAttestations : []string {
12428+ policyimage .SLSAProvenancePredicateType02 ,
12429+ policyimage .SLSAProvenancePredicateType1 ,
12430+ },
12431+ Platform : & platform ,
12432+ ResolveMode : pb .AttrImageResolveModeForcePull ,
12433+ },
12434+ })
12435+ if err != nil {
12436+ return nil , err
12437+ }
12438+ require .NotNil (t , md .Image )
12439+ require .NotNil (t , md .Image .AttestationChain )
12440+ ac := md .Image .AttestationChain
12441+ require .NotEmpty (t , ac .AttestationManifest )
12442+ att := ac .Blobs [ac .AttestationManifest ]
12443+ require .NotEmpty (t , att .Data )
12444+
12445+ var manifest ocispecs.Manifest
12446+ require .NoError (t , json .Unmarshal (att .Data , & manifest ))
12447+ require .NotEmpty (t , manifest .Layers )
12448+ var (
12449+ stmtBytes []byte
12450+ foundLayer ocispecs.Descriptor
12451+ )
12452+ for _ , layer := range manifest .Layers {
12453+ if ! isSLSAPredicateType (layer .Annotations ["in-toto.io/predicate-type" ]) {
12454+ continue
12455+ }
12456+ blob , ok := ac .Blobs [layer .Digest ]
12457+ if ! ok {
12458+ continue
12459+ }
12460+ stmtBytes = blob .Data
12461+ foundLayer = layer
12462+ break
12463+ }
12464+ require .NotEmpty (t , stmtBytes )
12465+ require .Contains (t , []string {
12466+ policyimage .SLSAProvenancePredicateType02 ,
12467+ policyimage .SLSAProvenancePredicateType1 ,
12468+ }, foundLayer .Annotations ["in-toto.io/predicate-type" ])
12469+
12470+ var stmt intoto.Statement
12471+ require .NoError (t , json .Unmarshal (stmtBytes , & stmt ))
12472+ require .Equal (t , "https://in-toto.io/Statement/v0.1" , stmt .Type )
12473+ require .Contains (t , []string {
12474+ policyimage .SLSAProvenancePredicateType02 ,
12475+ policyimage .SLSAProvenancePredicateType1 ,
12476+ }, stmt .PredicateType )
12477+ require .Equal (t , stmt .Subject [0 ].Digest ["sha256" ], ac .ImageManifest .Hex ())
12478+ return nil , nil
12479+ }, nil )
12480+ require .NoError (t , err )
12481+ }
12482+
12483+ func testImageResolveAttestationChainLocal (t * testing.T , sb integration.Sandbox ) {
12484+ workers .CheckFeatureCompat (t , sb , workers .FeatureDirectPush , workers .FeatureProvenance )
12485+ requiresLinux (t )
12486+
12487+ ctx := sb .Context ()
12488+ c , err := New (ctx , sb .Address ())
12489+ require .NoError (t , err )
12490+ defer c .Close ()
12491+
12492+ target , platform := buildProvenanceImage (ctx , t , c , sb )
12493+
12494+ _ , err = c .Build (ctx , SolveOpt {}, "test" , func (ctx context.Context , c gateway.Client ) (* gateway.Result , error ) {
12495+ md , err := c .ResolveSourceMetadata (ctx , & pb.SourceOp {
12496+ Identifier : "docker-image://" + target ,
12497+ }, sourceresolver.Opt {
12498+ ImageOpt : & sourceresolver.ResolveImageOpt {
12499+ NoConfig : true ,
12500+ AttestationChain : true ,
12501+ Platform : & platform ,
12502+ ResolveMode : pb .AttrImageResolveModeForcePull ,
12503+ },
12504+ })
12505+ if err != nil {
12506+ return nil , err
12507+ }
12508+ require .NotNil (t , md .Image )
12509+ require .NotNil (t , md .Image .AttestationChain )
12510+ ac := md .Image .AttestationChain
12511+ require .NotEmpty (t , ac .AttestationManifest )
12512+ att := ac .Blobs [ac .AttestationManifest ]
12513+ require .NotEmpty (t , att .Data )
12514+
12515+ var manifest ocispecs.Manifest
12516+ require .NoError (t , json .Unmarshal (att .Data , & manifest ))
12517+ require .NotEmpty (t , manifest .Layers )
12518+ found := false
12519+ for _ , layer := range manifest .Layers {
12520+ if isSLSAPredicateType (layer .Annotations ["in-toto.io/predicate-type" ]) {
12521+ found = true
12522+ break
12523+ }
12524+ }
12525+ require .True (t , found )
12526+ return nil , nil
12527+ }, nil )
12528+ require .NoError (t , err )
12529+ }
12530+
12531+ func buildProvenanceImage (ctx context.Context , t * testing.T , c * Client , sb integration.Sandbox ) (string , ocispecs.Platform ) {
12532+ t .Helper ()
12533+
12534+ registry , err := sb .NewRegistry ()
12535+ if errors .Is (err , integration .ErrRequirements ) {
12536+ t .Skip (err .Error ())
12537+ }
12538+ require .NoError (t , err )
12539+
12540+ platform := platforms .Normalize (platforms .DefaultSpec ())
12541+ platformKey := platforms .Format (platform )
12542+ target := registry + "/buildkit/testprovenance:latest"
12543+
12544+ frontend := func (ctx context.Context , c gateway.Client ) (* gateway.Result , error ) {
12545+ res := gateway .NewResult ()
12546+
12547+ st := llb .Scratch ().File (
12548+ llb .Mkfile ("/greeting" , 0600 , []byte ("hello provenance" )),
12549+ )
12550+ def , err := st .Marshal (ctx )
12551+ if err != nil {
12552+ return nil , err
12553+ }
12554+ r , err := c .Solve (ctx , gateway.SolveRequest {
12555+ Definition : def .ToPB (),
12556+ })
12557+ if err != nil {
12558+ return nil , err
12559+ }
12560+ ref , err := r .SingleRef ()
12561+ if err != nil {
12562+ return nil , err
12563+ }
12564+ _ , err = ref .ToState ()
12565+ if err != nil {
12566+ return nil , err
12567+ }
12568+ res .AddRef (platformKey , ref )
12569+
12570+ img := ocispecs.Image {
12571+ Platform : platform ,
12572+ }
12573+ config , err := json .Marshal (img )
12574+ if err != nil {
12575+ return nil , errors .Wrapf (err , "failed to marshal image config" )
12576+ }
12577+ res .AddMeta (fmt .Sprintf ("%s/%s" , exptypes .ExporterImageConfigKey , platformKey ), config )
12578+
12579+ expPlatforms := & exptypes.Platforms {
12580+ Platforms : []exptypes.Platform {{ID : platformKey , Platform : platform }},
12581+ }
12582+ dt , err := json .Marshal (expPlatforms )
12583+ if err != nil {
12584+ return nil , err
12585+ }
12586+ res .AddMeta (exptypes .ExporterPlatformsKey , dt )
12587+
12588+ return res , nil
12589+ }
12590+
12591+ _ , err = c .Build (ctx , SolveOpt {
12592+ FrontendAttrs : map [string ]string {
12593+ "attest:provenance" : "mode=max" ,
12594+ },
12595+ Exports : []ExportEntry {
12596+ {
12597+ Type : ExporterImage ,
12598+ Attrs : map [string ]string {
12599+ "name" : target ,
12600+ "push" : "true" ,
12601+ },
12602+ },
12603+ },
12604+ }, "" , frontend , nil )
12605+ require .NoError (t , err )
12606+
12607+ return target , platform
12608+ }
12609+
12610+ // isSLSAPredicateType reports whether the predicate type represents SLSA provenance.
12611+ func isSLSAPredicateType (v string ) bool {
12612+ switch v {
12613+ case policyimage .SLSAProvenancePredicateType02 , policyimage .SLSAProvenancePredicateType1 :
12614+ return true
12615+ default :
12616+ return false
12617+ }
12618+ }
12619+
1240712620func testHTTPPruneAfterCacheKey (t * testing.T , sb integration.Sandbox ) {
1240812621 // this test depends on hitting race condition in internal functions.
1240912622 // If debugging and expecting failure you can add small sleep in beginning of source/http.Exec() to hit reliably
0 commit comments