11package policy
22
33import (
4+ "context"
45 "crypto/sha1" //nolint:gosec // used for git object checksums in tests
56 "encoding/hex"
67 "encoding/json"
@@ -10,7 +11,11 @@ import (
1011
1112 gwpb "github.com/moby/buildkit/frontend/gateway/pb"
1213 "github.com/moby/buildkit/solver/pb"
14+ policyimage "github.com/moby/policy-helpers/image"
15+ policytypes "github.com/moby/policy-helpers/types"
16+ "github.com/opencontainers/go-digest"
1317 ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
18+ "github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
1419 "github.com/stretchr/testify/require"
1520)
1621
@@ -21,6 +26,7 @@ func TestSourceToInputWithLogger(t *testing.T) {
2126 name string
2227 src * gwpb.ResolveSourceMetaResponse
2328 platform * ocispecs.Platform
29+ verifier PolicyVerifierProvider
2430 expInput Input
2531 expUnk []string
2632 expErrMsg string
@@ -255,6 +261,88 @@ func TestSourceToInputWithLogger(t *testing.T) {
255261 "input.image.env" ,
256262 },
257263 },
264+ {
265+ name : "image-attestation-chain-with-mock-verifier-sets-signature-properties" ,
266+ src : & gwpb.ResolveSourceMetaResponse {
267+ Source : & pb.SourceOp {
268+ Identifier : "docker-image://alpine:latest" ,
269+ },
270+ Image : & gwpb.ResolveSourceImageResponse {
271+ Digest : "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" ,
272+ AttestationChain : newTestAttestationChain (t ),
273+ },
274+ },
275+ platform : & ocispecs.Platform {OS : "linux" , Architecture : "amd64" },
276+ verifier : func () (PolicyVerifier , error ) {
277+ return & mockPolicyVerifier {
278+ verifyImage : func (context.Context , policyimage.ReferrersProvider , ocispecs.Descriptor , * ocispecs.Platform ) (* policytypes.SignatureInfo , error ) {
279+ ts := time .Date (2024 , 2 , 3 , 4 , 5 , 6 , 0 , time .UTC )
280+ return & policytypes.SignatureInfo {
281+ Kind : policytypes .KindDockerGithubBuilder ,
282+ SignatureType : policytypes .SignatureSimpleSigningV1 ,
283+ DockerReference : "docker.io/library/alpine:latest" ,
284+ IsDHI : true ,
285+ Timestamps : []policytypes.TimestampVerificationResult {
286+ {Type : "rekor" , URI : "https://rekor.sigstore.dev" , Timestamp : ts },
287+ },
288+ Signer : & certificate.Summary {
289+ CertificateIssuer : "https://token.actions.githubusercontent.com" ,
290+ SubjectAlternativeName : "https://github.com/docker/buildx/.github/workflows/ci.yml@refs/heads/main" ,
291+ Extensions : certificate.Extensions {
292+ BuildSignerURI : "https://github.com/docker/buildx/.github/workflows/ci.yml" ,
293+ BuildSignerDigest : "sha256:1234" ,
294+ RunnerEnvironment : "github-hosted" ,
295+ SourceRepositoryURI : "https://github.com/docker/buildx" ,
296+ SourceRepositoryDigest : "abcdef" ,
297+ SourceRepositoryRef : "refs/heads/main" ,
298+ SourceRepositoryOwnerURI : "https://github.com/docker" ,
299+ BuildConfigURI : "https://github.com/docker/buildx/.github/workflows/ci.yml" ,
300+ BuildConfigDigest : "sha256:5678" ,
301+ RunInvocationURI : "https://github.com/docker/buildx/actions/runs/1" ,
302+ SourceRepositoryIdentifier : "docker/buildx" ,
303+ },
304+ },
305+ }, nil
306+ },
307+ }, nil
308+ },
309+ assert : func (t * testing.T , inp Input , unknowns []string , err error ) {
310+ t .Helper ()
311+ require .NoError (t , err )
312+ require .Equal (t , []string {
313+ "input.image.labels" ,
314+ "input.image.user" ,
315+ "input.image.volumes" ,
316+ "input.image.workingDir" ,
317+ "input.image.env" ,
318+ }, unknowns )
319+ require .NotNil (t , inp .Image )
320+ require .True (t , inp .Image .HasProvenance )
321+ require .Len (t , inp .Image .Signatures , 1 )
322+ sig := inp .Image .Signatures [0 ]
323+ require .Equal (t , SignatureKindDockerGithubBuilder , sig .SignatureKind )
324+ require .Equal (t , SignatureTypeSimpleSigningV1 , sig .SignatureType )
325+ require .Equal (t , "docker.io/library/alpine:latest" , sig .DockerReference )
326+ require .True (t , sig .IsDHI )
327+ require .Len (t , sig .Timestamps , 1 )
328+ require .Equal (t , "rekor" , sig .Timestamps [0 ].Type )
329+ require .Equal (t , "https://rekor.sigstore.dev" , sig .Timestamps [0 ].URI )
330+ require .NotNil (t , sig .Signer )
331+ require .Equal (t , "https://token.actions.githubusercontent.com" , sig .Signer .CertificateIssuer )
332+ require .Equal (t , "https://github.com/docker/buildx/.github/workflows/ci.yml@refs/heads/main" , sig .Signer .SubjectAlternativeName )
333+ require .Equal (t , "https://github.com/docker/buildx/.github/workflows/ci.yml" , sig .Signer .BuildSignerURI )
334+ require .Equal (t , "sha256:1234" , sig .Signer .BuildSignerDigest )
335+ require .Equal (t , "github-hosted" , sig .Signer .RunnerEnvironment )
336+ require .Equal (t , "https://github.com/docker/buildx" , sig .Signer .SourceRepositoryURI )
337+ require .Equal (t , "abcdef" , sig .Signer .SourceRepositoryDigest )
338+ require .Equal (t , "refs/heads/main" , sig .Signer .SourceRepositoryRef )
339+ require .Equal (t , "https://github.com/docker" , sig .Signer .SourceRepositoryOwnerURI )
340+ require .Equal (t , "https://github.com/docker/buildx/.github/workflows/ci.yml" , sig .Signer .BuildConfigURI )
341+ require .Equal (t , "sha256:5678" , sig .Signer .BuildConfigDigest )
342+ require .Equal (t , "https://github.com/docker/buildx/actions/runs/1" , sig .Signer .RunInvocationURI )
343+ require .Equal (t , "docker/buildx" , sig .Signer .SourceRepositoryIdentifier )
344+ },
345+ },
258346 {
259347 name : "image-attestation-chain-without-manifest-keeps-has-provenance-false" ,
260348 src : & gwpb.ResolveSourceMetaResponse {
@@ -635,7 +723,7 @@ func TestSourceToInputWithLogger(t *testing.T) {
635723
636724 for _ , tc := range tests {
637725 t .Run (tc .name , func (t * testing.T ) {
638- inp , unknowns , err := SourceToInputWithLogger (t .Context (), nil , tc .src , tc .platform , nil )
726+ inp , unknowns , err := SourceToInputWithLogger (t .Context (), tc . verifier , tc .src , tc .platform , nil )
639727 if tc .assert != nil {
640728 tc .assert (t , inp , unknowns , err )
641729 return
@@ -665,3 +753,81 @@ func gitObjectSHA1(objType string, raw []byte) string {
665753 sum := sha1 .Sum (append (prefix , raw ... ))
666754 return hex .EncodeToString (sum [:])
667755}
756+
757+ type mockPolicyVerifier struct {
758+ verifyImage func (context.Context , policyimage.ReferrersProvider , ocispecs.Descriptor , * ocispecs.Platform ) (* policytypes.SignatureInfo , error )
759+ }
760+
761+ func (m * mockPolicyVerifier ) VerifyImage (ctx context.Context , provider policyimage.ReferrersProvider , desc ocispecs.Descriptor , platform * ocispecs.Platform ) (* policytypes.SignatureInfo , error ) {
762+ return m .verifyImage (ctx , provider , desc , platform )
763+ }
764+
765+ func newTestAttestationChain (t * testing.T ) * gwpb.AttestationChain {
766+ t .Helper ()
767+
768+ imgDigest := digest .FromString ("image-manifest" )
769+ attDigest := digest .FromString ("attestation-manifest" )
770+
771+ indexBytes := mustMarshalJSON (t , map [string ]any {
772+ "mediaType" : ocispecs .MediaTypeImageIndex ,
773+ "manifests" : []map [string ]any {
774+ {
775+ "mediaType" : ocispecs .MediaTypeImageManifest ,
776+ "digest" : imgDigest .String (),
777+ "size" : int64 (10 ),
778+ "platform" : map [string ]any {
779+ "os" : "linux" ,
780+ "architecture" : "amd64" ,
781+ },
782+ },
783+ {
784+ "mediaType" : ocispecs .MediaTypeImageManifest ,
785+ "digest" : attDigest .String (),
786+ "size" : int64 (10 ),
787+ "annotations" : map [string ]string {
788+ policyimage .AnnotationDockerReferenceType : policyimage .AttestationManifestType ,
789+ policyimage .AnnotationDockerReferenceDigest : imgDigest .String (),
790+ },
791+ },
792+ },
793+ })
794+ indexDigest := digest .FromBytes (indexBytes )
795+
796+ sigManifestBytes := mustMarshalJSON (t , map [string ]any {
797+ "schemaVersion" : 2 ,
798+ "mediaType" : ocispecs .MediaTypeImageManifest ,
799+ "artifactType" : policyimage .ArtifactTypeSigstoreBundle ,
800+ })
801+ sigDigest := digest .FromBytes (sigManifestBytes )
802+
803+ return & gwpb.AttestationChain {
804+ Root : indexDigest .String (),
805+ AttestationManifest : attDigest .String (),
806+ SignatureManifests : []string {sigDigest .String ()},
807+ Blobs : map [string ]* gwpb.Blob {
808+ indexDigest .String (): {
809+ Descriptor_ : & gwpb.Descriptor {
810+ MediaType : ocispecs .MediaTypeImageIndex ,
811+ Digest : indexDigest .String (),
812+ Size : int64 (len (indexBytes )),
813+ },
814+ Data : indexBytes ,
815+ },
816+ sigDigest .String (): {
817+ Descriptor_ : & gwpb.Descriptor {
818+ MediaType : ocispecs .MediaTypeImageManifest ,
819+ Digest : sigDigest .String (),
820+ Size : int64 (len (sigManifestBytes )),
821+ },
822+ Data : sigManifestBytes ,
823+ },
824+ },
825+ }
826+ }
827+
828+ func mustMarshalJSON (t * testing.T , v any ) []byte {
829+ t .Helper ()
830+ dt , err := json .Marshal (v )
831+ require .NoError (t , err )
832+ return dt
833+ }
0 commit comments