policy: add mock signature unit test
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
@@ -44,6 +44,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b
|
||||
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
@@ -196,7 +197,6 @@ require (
|
||||
github.com/sigstore/rekor v1.4.3 // indirect
|
||||
github.com/sigstore/rekor-tiles/v2 v2.0.1 // indirect
|
||||
github.com/sigstore/sigstore v1.10.0 // indirect
|
||||
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7 // indirect
|
||||
github.com/sigstore/timestamp-authority/v2 v2.0.2 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
|
||||
github.com/theupdateframework/go-tuf/v2 v2.3.0 // indirect
|
||||
|
||||
@@ -14,12 +14,17 @@ import (
|
||||
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||
policyverifier "github.com/moby/policy-helpers"
|
||||
policyimage "github.com/moby/policy-helpers/image"
|
||||
policytypes "github.com/moby/policy-helpers/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type PolicyVerifierProvider func() (*policyverifier.Verifier, error)
|
||||
type PolicyVerifier interface {
|
||||
VerifyImage(context.Context, policyimage.ReferrersProvider, ocispecs.Descriptor, *ocispecs.Platform) (*policytypes.SignatureInfo, error)
|
||||
}
|
||||
|
||||
type PolicyVerifierProvider func() (PolicyVerifier, error)
|
||||
|
||||
func SignatureVerifier(cfg *confutil.Config) PolicyVerifierProvider {
|
||||
if cfg == nil {
|
||||
@@ -27,9 +32,9 @@ func SignatureVerifier(cfg *confutil.Config) PolicyVerifierProvider {
|
||||
}
|
||||
var (
|
||||
mu sync.Mutex
|
||||
v *policyverifier.Verifier
|
||||
v PolicyVerifier
|
||||
)
|
||||
return func() (*policyverifier.Verifier, error) {
|
||||
return func() (PolicyVerifier, error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
|
||||
+167
-1
@@ -1,6 +1,7 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1" //nolint:gosec // used for git object checksums in tests
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -10,7 +11,11 @@ import (
|
||||
|
||||
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
policyimage "github.com/moby/policy-helpers/image"
|
||||
policytypes "github.com/moby/policy-helpers/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -21,6 +26,7 @@ func TestSourceToInputWithLogger(t *testing.T) {
|
||||
name string
|
||||
src *gwpb.ResolveSourceMetaResponse
|
||||
platform *ocispecs.Platform
|
||||
verifier PolicyVerifierProvider
|
||||
expInput Input
|
||||
expUnk []string
|
||||
expErrMsg string
|
||||
@@ -255,6 +261,88 @@ func TestSourceToInputWithLogger(t *testing.T) {
|
||||
"input.image.env",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-attestation-chain-with-mock-verifier-sets-signature-properties",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "docker-image://alpine:latest",
|
||||
},
|
||||
Image: &gwpb.ResolveSourceImageResponse{
|
||||
Digest: "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
|
||||
AttestationChain: newTestAttestationChain(t),
|
||||
},
|
||||
},
|
||||
platform: &ocispecs.Platform{OS: "linux", Architecture: "amd64"},
|
||||
verifier: func() (PolicyVerifier, error) {
|
||||
return &mockPolicyVerifier{
|
||||
verifyImage: func(context.Context, policyimage.ReferrersProvider, ocispecs.Descriptor, *ocispecs.Platform) (*policytypes.SignatureInfo, error) {
|
||||
ts := time.Date(2024, 2, 3, 4, 5, 6, 0, time.UTC)
|
||||
return &policytypes.SignatureInfo{
|
||||
Kind: policytypes.KindDockerGithubBuilder,
|
||||
SignatureType: policytypes.SignatureSimpleSigningV1,
|
||||
DockerReference: "docker.io/library/alpine:latest",
|
||||
IsDHI: true,
|
||||
Timestamps: []policytypes.TimestampVerificationResult{
|
||||
{Type: "rekor", URI: "https://rekor.sigstore.dev", Timestamp: ts},
|
||||
},
|
||||
Signer: &certificate.Summary{
|
||||
CertificateIssuer: "https://token.actions.githubusercontent.com",
|
||||
SubjectAlternativeName: "https://github.com/docker/buildx/.github/workflows/ci.yml@refs/heads/main",
|
||||
Extensions: certificate.Extensions{
|
||||
BuildSignerURI: "https://github.com/docker/buildx/.github/workflows/ci.yml",
|
||||
BuildSignerDigest: "sha256:1234",
|
||||
RunnerEnvironment: "github-hosted",
|
||||
SourceRepositoryURI: "https://github.com/docker/buildx",
|
||||
SourceRepositoryDigest: "abcdef",
|
||||
SourceRepositoryRef: "refs/heads/main",
|
||||
SourceRepositoryOwnerURI: "https://github.com/docker",
|
||||
BuildConfigURI: "https://github.com/docker/buildx/.github/workflows/ci.yml",
|
||||
BuildConfigDigest: "sha256:5678",
|
||||
RunInvocationURI: "https://github.com/docker/buildx/actions/runs/1",
|
||||
SourceRepositoryIdentifier: "docker/buildx",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
assert: func(t *testing.T, inp Input, unknowns []string, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{
|
||||
"input.image.labels",
|
||||
"input.image.user",
|
||||
"input.image.volumes",
|
||||
"input.image.workingDir",
|
||||
"input.image.env",
|
||||
}, unknowns)
|
||||
require.NotNil(t, inp.Image)
|
||||
require.True(t, inp.Image.HasProvenance)
|
||||
require.Len(t, inp.Image.Signatures, 1)
|
||||
sig := inp.Image.Signatures[0]
|
||||
require.Equal(t, SignatureKindDockerGithubBuilder, sig.SignatureKind)
|
||||
require.Equal(t, SignatureTypeSimpleSigningV1, sig.SignatureType)
|
||||
require.Equal(t, "docker.io/library/alpine:latest", sig.DockerReference)
|
||||
require.True(t, sig.IsDHI)
|
||||
require.Len(t, sig.Timestamps, 1)
|
||||
require.Equal(t, "rekor", sig.Timestamps[0].Type)
|
||||
require.Equal(t, "https://rekor.sigstore.dev", sig.Timestamps[0].URI)
|
||||
require.NotNil(t, sig.Signer)
|
||||
require.Equal(t, "https://token.actions.githubusercontent.com", sig.Signer.CertificateIssuer)
|
||||
require.Equal(t, "https://github.com/docker/buildx/.github/workflows/ci.yml@refs/heads/main", sig.Signer.SubjectAlternativeName)
|
||||
require.Equal(t, "https://github.com/docker/buildx/.github/workflows/ci.yml", sig.Signer.BuildSignerURI)
|
||||
require.Equal(t, "sha256:1234", sig.Signer.BuildSignerDigest)
|
||||
require.Equal(t, "github-hosted", sig.Signer.RunnerEnvironment)
|
||||
require.Equal(t, "https://github.com/docker/buildx", sig.Signer.SourceRepositoryURI)
|
||||
require.Equal(t, "abcdef", sig.Signer.SourceRepositoryDigest)
|
||||
require.Equal(t, "refs/heads/main", sig.Signer.SourceRepositoryRef)
|
||||
require.Equal(t, "https://github.com/docker", sig.Signer.SourceRepositoryOwnerURI)
|
||||
require.Equal(t, "https://github.com/docker/buildx/.github/workflows/ci.yml", sig.Signer.BuildConfigURI)
|
||||
require.Equal(t, "sha256:5678", sig.Signer.BuildConfigDigest)
|
||||
require.Equal(t, "https://github.com/docker/buildx/actions/runs/1", sig.Signer.RunInvocationURI)
|
||||
require.Equal(t, "docker/buildx", sig.Signer.SourceRepositoryIdentifier)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-attestation-chain-without-manifest-keeps-has-provenance-false",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
@@ -635,7 +723,7 @@ func TestSourceToInputWithLogger(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
inp, unknowns, err := SourceToInputWithLogger(t.Context(), nil, tc.src, tc.platform, nil)
|
||||
inp, unknowns, err := SourceToInputWithLogger(t.Context(), tc.verifier, tc.src, tc.platform, nil)
|
||||
if tc.assert != nil {
|
||||
tc.assert(t, inp, unknowns, err)
|
||||
return
|
||||
@@ -665,3 +753,81 @@ func gitObjectSHA1(objType string, raw []byte) string {
|
||||
sum := sha1.Sum(append(prefix, raw...))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
type mockPolicyVerifier struct {
|
||||
verifyImage func(context.Context, policyimage.ReferrersProvider, ocispecs.Descriptor, *ocispecs.Platform) (*policytypes.SignatureInfo, error)
|
||||
}
|
||||
|
||||
func (m *mockPolicyVerifier) VerifyImage(ctx context.Context, provider policyimage.ReferrersProvider, desc ocispecs.Descriptor, platform *ocispecs.Platform) (*policytypes.SignatureInfo, error) {
|
||||
return m.verifyImage(ctx, provider, desc, platform)
|
||||
}
|
||||
|
||||
func newTestAttestationChain(t *testing.T) *gwpb.AttestationChain {
|
||||
t.Helper()
|
||||
|
||||
imgDigest := digest.FromString("image-manifest")
|
||||
attDigest := digest.FromString("attestation-manifest")
|
||||
|
||||
indexBytes := mustMarshalJSON(t, map[string]any{
|
||||
"mediaType": ocispecs.MediaTypeImageIndex,
|
||||
"manifests": []map[string]any{
|
||||
{
|
||||
"mediaType": ocispecs.MediaTypeImageManifest,
|
||||
"digest": imgDigest.String(),
|
||||
"size": int64(10),
|
||||
"platform": map[string]any{
|
||||
"os": "linux",
|
||||
"architecture": "amd64",
|
||||
},
|
||||
},
|
||||
{
|
||||
"mediaType": ocispecs.MediaTypeImageManifest,
|
||||
"digest": attDigest.String(),
|
||||
"size": int64(10),
|
||||
"annotations": map[string]string{
|
||||
policyimage.AnnotationDockerReferenceType: policyimage.AttestationManifestType,
|
||||
policyimage.AnnotationDockerReferenceDigest: imgDigest.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
indexDigest := digest.FromBytes(indexBytes)
|
||||
|
||||
sigManifestBytes := mustMarshalJSON(t, map[string]any{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": ocispecs.MediaTypeImageManifest,
|
||||
"artifactType": policyimage.ArtifactTypeSigstoreBundle,
|
||||
})
|
||||
sigDigest := digest.FromBytes(sigManifestBytes)
|
||||
|
||||
return &gwpb.AttestationChain{
|
||||
Root: indexDigest.String(),
|
||||
AttestationManifest: attDigest.String(),
|
||||
SignatureManifests: []string{sigDigest.String()},
|
||||
Blobs: map[string]*gwpb.Blob{
|
||||
indexDigest.String(): {
|
||||
Descriptor_: &gwpb.Descriptor{
|
||||
MediaType: ocispecs.MediaTypeImageIndex,
|
||||
Digest: indexDigest.String(),
|
||||
Size: int64(len(indexBytes)),
|
||||
},
|
||||
Data: indexBytes,
|
||||
},
|
||||
sigDigest.String(): {
|
||||
Descriptor_: &gwpb.Descriptor{
|
||||
MediaType: ocispecs.MediaTypeImageManifest,
|
||||
Digest: sigDigest.String(),
|
||||
Size: int64(len(sigManifestBytes)),
|
||||
},
|
||||
Data: sigManifestBytes,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mustMarshalJSON(t *testing.T, v any) []byte {
|
||||
t.Helper()
|
||||
dt, err := json.Marshal(v)
|
||||
require.NoError(t, err)
|
||||
return dt
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user