Skip to content

Commit e10cb40

Browse files
committed
policy: add mock signature unit test
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
1 parent 22b3d36 commit e10cb40

File tree

2 files changed

+175
-4
lines changed

2 files changed

+175
-4
lines changed

policy/signatures.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,27 @@ import (
1414
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
1515
policyverifier "github.com/moby/policy-helpers"
1616
policyimage "github.com/moby/policy-helpers/image"
17+
policytypes "github.com/moby/policy-helpers/types"
1718
"github.com/opencontainers/go-digest"
1819
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
1920
"github.com/pkg/errors"
2021
)
2122

22-
type PolicyVerifierProvider func() (*policyverifier.Verifier, error)
23+
type PolicyVerifier interface {
24+
VerifyImage(context.Context, policyimage.ReferrersProvider, ocispecs.Descriptor, *ocispecs.Platform) (*policytypes.SignatureInfo, error)
25+
}
26+
27+
type PolicyVerifierProvider func() (PolicyVerifier, error)
2328

2429
func SignatureVerifier(cfg *confutil.Config) PolicyVerifierProvider {
2530
if cfg == nil {
2631
return nil
2732
}
2833
var (
2934
mu sync.Mutex
30-
v *policyverifier.Verifier
35+
v PolicyVerifier
3136
)
32-
return func() (*policyverifier.Verifier, error) {
37+
return func() (PolicyVerifier, error) {
3338
mu.Lock()
3439
defer mu.Unlock()
3540

policy/validate_test.go

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package policy
22

33
import (
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

Comments
 (0)