Merge pull request #3647 from tonistiigi/policy-test-updates
policy: update unit tests and normalize input keys
This commit is contained in:
@@ -84,7 +84,7 @@ func runTest(ctx context.Context, out io.Writer, path string, opts policy.TestOp
|
||||
_, _ = fmt.Fprintln(out, "decision: <nil>")
|
||||
}
|
||||
if len(result.MissingInput) > 0 {
|
||||
_, _ = fmt.Fprintf(out, "missing_input: %s\n", strings.Join(result.MissingInput, ", "))
|
||||
_, _ = fmt.Fprintf(out, "missing_input: %s\n", strings.Join(withInputPrefix(result.MissingInput), ", "))
|
||||
}
|
||||
if len(result.MetadataNeeded) > 0 {
|
||||
_, _ = fmt.Fprintf(out, "metadata_resolve: %s\n", strings.Join(result.MetadataNeeded, ", "))
|
||||
@@ -106,6 +106,14 @@ func writeJSON(out io.Writer, label string, v any) {
|
||||
_, _ = fmt.Fprintf(out, "%s:\n%s\n", label, string(dt))
|
||||
}
|
||||
|
||||
func withInputPrefix(keys []string) []string {
|
||||
out := make([]string, len(keys))
|
||||
for i, k := range keys {
|
||||
out[i] = "input." + k
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type policyTestResolver struct {
|
||||
dockerCli command.Cli
|
||||
builderName *string
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAddUnknowns(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
unknowns []string
|
||||
initial *gwpb.ResolveSourceMetaRequest
|
||||
expected *gwpb.ResolveSourceMetaRequest
|
||||
expErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "empty-unknowns",
|
||||
unknowns: nil,
|
||||
initial: &gwpb.ResolveSourceMetaRequest{},
|
||||
expected: &gwpb.ResolveSourceMetaRequest{},
|
||||
},
|
||||
{
|
||||
name: "parent-key-ignored",
|
||||
unknowns: []string{"image"},
|
||||
initial: &gwpb.ResolveSourceMetaRequest{},
|
||||
expected: &gwpb.ResolveSourceMetaRequest{},
|
||||
},
|
||||
{
|
||||
name: "image-config-fields-enable-image-request",
|
||||
unknowns: []string{"image.labels"},
|
||||
initial: &gwpb.ResolveSourceMetaRequest{},
|
||||
expected: &gwpb.ResolveSourceMetaRequest{
|
||||
Image: &gwpb.ResolveSourceImageRequest{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-attestation-fields-enable-attestation-chain",
|
||||
unknowns: []string{"image.signatures"},
|
||||
initial: &gwpb.ResolveSourceMetaRequest{},
|
||||
expected: &gwpb.ResolveSourceMetaRequest{
|
||||
Image: &gwpb.ResolveSourceImageRequest{
|
||||
NoConfig: true,
|
||||
AttestationChain: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-attestation-on-existing-image-request",
|
||||
unknowns: []string{"image.hasProvenance"},
|
||||
initial: &gwpb.ResolveSourceMetaRequest{
|
||||
Image: &gwpb.ResolveSourceImageRequest{
|
||||
NoConfig: false,
|
||||
},
|
||||
},
|
||||
expected: &gwpb.ResolveSourceMetaRequest{
|
||||
Image: &gwpb.ResolveSourceImageRequest{
|
||||
NoConfig: false,
|
||||
AttestationChain: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git-ref-field-enables-git-request",
|
||||
unknowns: []string{"git.ref"},
|
||||
initial: &gwpb.ResolveSourceMetaRequest{},
|
||||
expected: &gwpb.ResolveSourceMetaRequest{
|
||||
Git: &gwpb.ResolveSourceGitRequest{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git-commit-enables-return-object",
|
||||
unknowns: []string{"git.commit"},
|
||||
initial: &gwpb.ResolveSourceMetaRequest{},
|
||||
expected: &gwpb.ResolveSourceMetaRequest{
|
||||
Git: &gwpb.ResolveSourceGitRequest{
|
||||
ReturnObject: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "http-checksum-no-op",
|
||||
unknowns: []string{"http.checksum"},
|
||||
initial: &gwpb.ResolveSourceMetaRequest{},
|
||||
expected: &gwpb.ResolveSourceMetaRequest{},
|
||||
},
|
||||
{
|
||||
name: "non-canonical-input-prefix-errors",
|
||||
unknowns: []string{"input.image.labels"},
|
||||
initial: &gwpb.ResolveSourceMetaRequest{},
|
||||
expErrMsg: "unhandled unknown property input.image.labels",
|
||||
},
|
||||
{
|
||||
name: "unknown-field-errors",
|
||||
unknowns: []string{"git.notAField"},
|
||||
initial: &gwpb.ResolveSourceMetaRequest{},
|
||||
expErrMsg: "unhandled unknown property git.notAField",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := tc.initial
|
||||
if req == nil {
|
||||
req = &gwpb.ResolveSourceMetaRequest{}
|
||||
}
|
||||
err := AddUnknowns(req, tc.unknowns)
|
||||
if tc.expErrMsg != "" {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tc.expErrMsg, err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
|
||||
+14
-15
@@ -311,7 +311,7 @@ func runPolicyTest(ctx context.Context, policyModules map[string]*ast.Module, te
|
||||
result.Allow = allow
|
||||
result.DenyMessages = deny
|
||||
|
||||
missing := missingInputRefs(policyPackageModules, effectiveInput)
|
||||
missing := missingInputRefs(policyPackageModules, effectiveInput, runtimeUnknownInputRefs(testState), runtimeUnknownInputRefs(decisionState))
|
||||
result.MissingInput = uniqueSortedStrings(missing)
|
||||
result.MetadataNeeded = summarizeMetadataRequests(result.MissingInput)
|
||||
|
||||
@@ -534,7 +534,7 @@ func hasEnv(env Env) bool {
|
||||
func filterResolvableMissing(missing []string) []string {
|
||||
out := make([]string, 0, len(missing))
|
||||
for _, m := range missing {
|
||||
if strings.HasPrefix(m, "input.image.") || strings.HasPrefix(m, "input.git.") {
|
||||
if strings.HasPrefix(m, "image.") || strings.HasPrefix(m, "git.") {
|
||||
out = append(out, m)
|
||||
}
|
||||
}
|
||||
@@ -595,24 +595,27 @@ func modulesForPackage(modules map[string]*ast.Module, pkgPath string) []*ast.Mo
|
||||
return out
|
||||
}
|
||||
|
||||
func missingInputRefs(mods []*ast.Module, input *Input) []string {
|
||||
func missingInputRefs(mods []*ast.Module, input *Input, extraRefs ...[]string) []string {
|
||||
if len(mods) == 0 {
|
||||
return nil
|
||||
}
|
||||
inputMap := normalizeInput(input)
|
||||
refs := collectUnknowns(mods, nil)
|
||||
for _, er := range extraRefs {
|
||||
refs = append(refs, er...)
|
||||
}
|
||||
seen := map[string]struct{}{}
|
||||
missing := make([]string, 0, len(refs))
|
||||
for _, ref := range refs {
|
||||
key := strings.TrimPrefix(ref, "input.")
|
||||
if key == ref {
|
||||
continue
|
||||
}
|
||||
key = trimKey(key)
|
||||
for _, key := range refs {
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
if !inputHasPath(inputMap, strings.Split(key, ".")) {
|
||||
missing = append(missing, "input."+key)
|
||||
missing = append(missing, key)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
@@ -703,11 +706,7 @@ func summarizeMetadataRequests(missing []string) []string {
|
||||
return nil
|
||||
}
|
||||
req := &gwpb.ResolveSourceMetaRequest{}
|
||||
trimmed := make([]string, 0, len(missing))
|
||||
for _, m := range missing {
|
||||
trimmed = append(trimmed, strings.TrimPrefix(m, "input."))
|
||||
}
|
||||
if err := AddUnknowns(req, trimmed); err != nil {
|
||||
if err := AddUnknowns(req, missing); err != nil {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
|
||||
@@ -3,6 +3,7 @@ package policy
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -18,12 +19,16 @@ func TestTrimKey(t *testing.T) {
|
||||
// one separator → stays as-is
|
||||
{"git.tag", "git.tag"},
|
||||
{"git[tag", "git[tag"},
|
||||
{"input.git.tag", "git.tag"},
|
||||
{"input.git[tag", "git[tag"},
|
||||
|
||||
// multiple separators → cut before second one
|
||||
{"git.tag.author", "git.tag"},
|
||||
{"git.tag.author.email", "git.tag"},
|
||||
{"git.tag[0][1]", "git.tag"},
|
||||
{"git.tag[0]", "git.tag"},
|
||||
{"input.git.tag.author", "git.tag"},
|
||||
{"input.git.tag[0]", "git.tag"},
|
||||
|
||||
{"a.b.c", "a.b"},
|
||||
}
|
||||
@@ -34,3 +39,51 @@ func TestTrimKey(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectUnknowns(t *testing.T) {
|
||||
mod, err := ast.ParseModule("x.rego", `
|
||||
package x
|
||||
p if {
|
||||
input.git.tag[0].author == "a"
|
||||
input.image.signatures[_].signer.certificateIssuer != ""
|
||||
data.foo.bar == 1
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
all := collectUnknowns([]*ast.Module{mod}, nil)
|
||||
require.ElementsMatch(t, []string{"git.tag", "image.signatures"}, all)
|
||||
|
||||
filtered := collectUnknowns([]*ast.Module{mod}, []string{"input.image.signatures"})
|
||||
require.Equal(t, []string{"image.signatures"}, filtered)
|
||||
}
|
||||
|
||||
func TestRuntimeUnknownInputRefs(t *testing.T) {
|
||||
require.Nil(t, runtimeUnknownInputRefs(nil))
|
||||
require.Nil(t, runtimeUnknownInputRefs(&state{}))
|
||||
|
||||
st := &state{
|
||||
Unknowns: map[string]struct{}{
|
||||
funcVerifyGitSignature: {},
|
||||
},
|
||||
}
|
||||
require.Equal(t, []string{"git.commit"}, runtimeUnknownInputRefs(st))
|
||||
}
|
||||
|
||||
func TestMissingInputRefsWithRuntimeUnknowns(t *testing.T) {
|
||||
mod, err := ast.ParseModule("x.rego", `
|
||||
package x
|
||||
p if {
|
||||
input.git.ref != ""
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
|
||||
in := &Input{
|
||||
Git: &Git{
|
||||
Ref: "refs/heads/main",
|
||||
},
|
||||
}
|
||||
missing := missingInputRefs([]*ast.Module{mod}, in, []string{"git.commit"})
|
||||
require.Equal(t, []string{"git.commit"}, missing)
|
||||
}
|
||||
|
||||
+31
-16
@@ -224,9 +224,7 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy
|
||||
return nil, nil, err
|
||||
}
|
||||
unk := collectUnknowns(pq.Support, unknowns)
|
||||
if _, ok := st.Unknowns[funcVerifyGitSignature]; ok {
|
||||
unk = append(unk, "input.git.commit")
|
||||
}
|
||||
unk = append(unk, runtimeUnknownInputRefs(st)...)
|
||||
|
||||
if len(unk) > 0 {
|
||||
next := &gwpb.ResolveSourceMetaRequest{
|
||||
@@ -405,7 +403,7 @@ func SourceToInputWithLogger(ctx context.Context, getVerifier PolicyVerifierProv
|
||||
g.Subdir = ""
|
||||
}
|
||||
}
|
||||
if v, ok := src.Source.Attrs[pb.AttrFullRemoteURL]; !ok {
|
||||
if v, ok := src.Source.Attrs[pb.AttrFullRemoteURL]; ok {
|
||||
if !gitutil.IsGitTransport(v) {
|
||||
v = "https://" + v
|
||||
}
|
||||
@@ -418,11 +416,11 @@ func SourceToInputWithLogger(ctx context.Context, getVerifier PolicyVerifierProv
|
||||
g.Host = u.Host
|
||||
g.FullURL = v
|
||||
}
|
||||
if tag, ok := strings.CutPrefix(g.Ref, "refs/tags/"); ok {
|
||||
if tag, ok := strings.CutPrefix(ref, "refs/tags/"); ok {
|
||||
g.TagName = tag
|
||||
isFullRef = true
|
||||
}
|
||||
if branch, ok := strings.CutPrefix(g.Ref, "refs/heads/"); ok {
|
||||
if branch, ok := strings.CutPrefix(ref, "refs/heads/"); ok {
|
||||
g.Branch = branch
|
||||
isFullRef = true
|
||||
}
|
||||
@@ -621,14 +619,12 @@ func AddUnknowns(req *gwpb.ResolveSourceMetaRequest, unk []string) error {
|
||||
func AddUnknownsWithLogger(logf func(logrus.Level, string), req *gwpb.ResolveSourceMetaRequest, unk []string) error {
|
||||
unk2 := make([]string, 0, len(unk))
|
||||
for _, u := range unk {
|
||||
k := strings.TrimPrefix(u, "input.")
|
||||
k = trimKey(k)
|
||||
switch k {
|
||||
switch u {
|
||||
case "image", "git", "http", "local":
|
||||
// parents are returned as unknowns for some reason, ignore
|
||||
continue
|
||||
default:
|
||||
unk2 = append(unk2, k)
|
||||
unk2 = append(unk2, u)
|
||||
}
|
||||
}
|
||||
if len(unk2) == 0 {
|
||||
@@ -680,8 +676,10 @@ func collectUnknowns(mods []*ast.Module, allowed []string) []string {
|
||||
for _, mod := range mods {
|
||||
ast.WalkRefs(mod, func(ref ast.Ref) bool {
|
||||
if ref.HasPrefix(ast.InputRootRef) {
|
||||
s := ref.String() // e.g. "input.request.path"
|
||||
s = "input." + trimKey(strings.TrimPrefix(s, "input."))
|
||||
s := trimKey(ref.String())
|
||||
if s == "" {
|
||||
return true
|
||||
}
|
||||
if _, ok := seen[s]; !ok {
|
||||
seen[s] = struct{}{}
|
||||
out = append(out, s)
|
||||
@@ -696,6 +694,10 @@ func collectUnknowns(mods []*ast.Module, allowed []string) []string {
|
||||
|
||||
valid := map[string]struct{}{}
|
||||
for _, k := range allowed {
|
||||
k = trimKey(k)
|
||||
if k == "" {
|
||||
continue
|
||||
}
|
||||
valid[k] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -709,14 +711,25 @@ func collectUnknowns(mods []*ast.Module, allowed []string) []string {
|
||||
return filtered
|
||||
}
|
||||
|
||||
func runtimeUnknownInputRefs(st *state) []string {
|
||||
if st == nil || len(st.Unknowns) == 0 {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
if _, ok := st.Unknowns[funcVerifyGitSignature]; ok {
|
||||
out = append(out, "git.commit")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func summarizeUnknownsForLog(unk []string) []string {
|
||||
out := make([]string, 0, len(unk))
|
||||
seen := map[string]struct{}{}
|
||||
for _, u := range unk {
|
||||
if strings.HasPrefix(u, "input.image.signatures") {
|
||||
u = "input.image.signatures"
|
||||
if strings.HasPrefix(u, "image.signatures") {
|
||||
u = "image.signatures"
|
||||
}
|
||||
if u == "input.image" {
|
||||
if u == "image" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[u]; ok {
|
||||
@@ -730,7 +743,7 @@ func summarizeUnknownsForLog(unk []string) []string {
|
||||
|
||||
func hasHTTPUnknowns(unk []string) bool {
|
||||
for _, u := range unk {
|
||||
if strings.HasPrefix(u, "input.http.") {
|
||||
if strings.HasPrefix(u, "http.") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -738,6 +751,8 @@ func hasHTTPUnknowns(unk []string) bool {
|
||||
}
|
||||
|
||||
func trimKey(s string) string {
|
||||
s = strings.TrimPrefix(s, "input.")
|
||||
|
||||
const (
|
||||
dot = '.'
|
||||
sb = '['
|
||||
|
||||
@@ -0,0 +1,863 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1" //nolint:gosec // used for git object checksums in tests
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func TestSourceToInputWithLogger(t *testing.T) {
|
||||
tm := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
src *gwpb.ResolveSourceMetaResponse
|
||||
platform *ocispecs.Platform
|
||||
verifier PolicyVerifierProvider
|
||||
expInput Input
|
||||
expUnk []string
|
||||
expErrMsg string
|
||||
assert func(*testing.T, Input, []string, error)
|
||||
}{
|
||||
{
|
||||
name: "nil-source-metadata",
|
||||
src: nil,
|
||||
expErrMsg: "no source info in request",
|
||||
},
|
||||
{
|
||||
name: "invalid-source-identifier",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{Identifier: "not-a-source"},
|
||||
},
|
||||
expErrMsg: "invalid source identifier: not-a-source",
|
||||
},
|
||||
{
|
||||
name: "http-source-with-checksum-and-auth",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "https://example.com/foo.tar.gz?download=1",
|
||||
Attrs: map[string]string{
|
||||
pb.AttrHTTPAuthHeaderSecret: "my-secret",
|
||||
},
|
||||
},
|
||||
HTTP: &gwpb.ResolveSourceHTTPResponse{
|
||||
Checksum: "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
HTTP: &HTTP{
|
||||
URL: "https://example.com/foo.tar.gz?download=1",
|
||||
Schema: "https",
|
||||
Host: "example.com",
|
||||
Path: "/foo.tar.gz",
|
||||
Query: map[string][]string{"download": {"1"}},
|
||||
HasAuth: true,
|
||||
Checksum: "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "http-source-without-checksum",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "http://example.com/archive.tgz",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
HTTP: &HTTP{
|
||||
URL: "http://example.com/archive.tgz",
|
||||
Schema: "http",
|
||||
Host: "example.com",
|
||||
Path: "/archive.tgz",
|
||||
Query: map[string][]string{},
|
||||
},
|
||||
},
|
||||
expUnk: []string{"input.http.checksum"},
|
||||
},
|
||||
{
|
||||
name: "http-with-query-and-fragment-parses-fields-correctly",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "https://example.com/a/b.tar.gz?x=1&x=2#frag",
|
||||
},
|
||||
HTTP: &gwpb.ResolveSourceHTTPResponse{
|
||||
Checksum: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
HTTP: &HTTP{
|
||||
URL: "https://example.com/a/b.tar.gz?x=1&x=2#frag",
|
||||
Schema: "https",
|
||||
Host: "example.com",
|
||||
Path: "/a/b.tar.gz",
|
||||
Query: map[string][]string{"x": {"1", "2"}},
|
||||
Checksum: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "http-with-nil-attrs-does-not-set-auth",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "https://example.com/secure.tgz",
|
||||
},
|
||||
HTTP: &gwpb.ResolveSourceHTTPResponse{
|
||||
Checksum: "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
HTTP: &HTTP{
|
||||
URL: "https://example.com/secure.tgz",
|
||||
Schema: "https",
|
||||
Host: "example.com",
|
||||
Path: "/secure.tgz",
|
||||
Query: map[string][]string{},
|
||||
Checksum: "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "local-source",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "local://context",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Local: &Local{Name: "context"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-source-without-platform",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "docker-image://alpine:latest",
|
||||
},
|
||||
},
|
||||
expErrMsg: "platform required for image source",
|
||||
},
|
||||
{
|
||||
name: "image-source-without-resolved-metadata",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "docker-image://alpine:latest",
|
||||
},
|
||||
},
|
||||
platform: &ocispecs.Platform{OS: "linux", Architecture: "amd64"},
|
||||
expInput: Input{
|
||||
Image: &Image{
|
||||
Ref: "docker.io/library/alpine:latest",
|
||||
Host: "docker.io",
|
||||
Repo: "alpine",
|
||||
FullRepo: "docker.io/library/alpine",
|
||||
Tag: "latest",
|
||||
Platform: "linux/amd64",
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
},
|
||||
expUnk: []string{
|
||||
"input.image.checksum",
|
||||
"input.image.labels",
|
||||
"input.image.user",
|
||||
"input.image.volumes",
|
||||
"input.image.workingDir",
|
||||
"input.image.env",
|
||||
"input.image.hasProvenance",
|
||||
"input.image.signatures",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "docker-image-canonical-ref-does-not-request-checksum-unknown",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "docker-image://alpine@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
},
|
||||
},
|
||||
platform: &ocispecs.Platform{OS: "linux", Architecture: "amd64"},
|
||||
expInput: Input{
|
||||
Image: &Image{
|
||||
Ref: "docker.io/library/alpine@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
Host: "docker.io",
|
||||
Repo: "alpine",
|
||||
FullRepo: "docker.io/library/alpine",
|
||||
IsCanonical: true,
|
||||
Checksum: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
Platform: "linux/amd64",
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
},
|
||||
expUnk: []string{
|
||||
"input.image.labels",
|
||||
"input.image.user",
|
||||
"input.image.volumes",
|
||||
"input.image.workingDir",
|
||||
"input.image.env",
|
||||
"input.image.hasProvenance",
|
||||
"input.image.signatures",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "docker-image-invalid-config-bytes-returns-error",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "docker-image://alpine:latest",
|
||||
},
|
||||
Image: &gwpb.ResolveSourceImageResponse{
|
||||
Digest: "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
Config: []byte("{"),
|
||||
},
|
||||
},
|
||||
platform: &ocispecs.Platform{OS: "linux", Architecture: "amd64"},
|
||||
expErrMsg: "failed to unmarshal image config",
|
||||
},
|
||||
{
|
||||
name: "image-attestation-chain-sets-has-provenance-without-verifier",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "docker-image://alpine:latest",
|
||||
},
|
||||
Image: &gwpb.ResolveSourceImageResponse{
|
||||
Digest: "sha256:abababababababababababababababababababababababababababababababab",
|
||||
AttestationChain: &gwpb.AttestationChain{
|
||||
AttestationManifest: "sha256:bcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbc",
|
||||
},
|
||||
},
|
||||
},
|
||||
platform: &ocispecs.Platform{OS: "linux", Architecture: "amd64"},
|
||||
expInput: Input{
|
||||
Image: &Image{
|
||||
Ref: "docker.io/library/alpine:latest",
|
||||
Host: "docker.io",
|
||||
Repo: "alpine",
|
||||
FullRepo: "docker.io/library/alpine",
|
||||
Tag: "latest",
|
||||
Platform: "linux/amd64",
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
Checksum: "sha256:abababababababababababababababababababababababababababababababab",
|
||||
HasProvenance: true,
|
||||
},
|
||||
},
|
||||
expUnk: []string{
|
||||
"input.image.labels",
|
||||
"input.image.user",
|
||||
"input.image.volumes",
|
||||
"input.image.workingDir",
|
||||
"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{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "docker-image://alpine:latest",
|
||||
},
|
||||
Image: &gwpb.ResolveSourceImageResponse{
|
||||
Digest: "sha256:babababababababababababababababababababababababababababababababa",
|
||||
AttestationChain: &gwpb.AttestationChain{
|
||||
AttestationManifest: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
platform: &ocispecs.Platform{OS: "linux", Architecture: "amd64"},
|
||||
expInput: Input{
|
||||
Image: &Image{
|
||||
Ref: "docker.io/library/alpine:latest",
|
||||
Host: "docker.io",
|
||||
Repo: "alpine",
|
||||
FullRepo: "docker.io/library/alpine",
|
||||
Tag: "latest",
|
||||
Platform: "linux/amd64",
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
Checksum: "sha256:babababababababababababababababababababababababababababababababa",
|
||||
},
|
||||
},
|
||||
expUnk: []string{
|
||||
"input.image.labels",
|
||||
"input.image.user",
|
||||
"input.image.volumes",
|
||||
"input.image.workingDir",
|
||||
"input.image.env",
|
||||
},
|
||||
assert: func(t *testing.T, inp Input, unknowns []string, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, inp.Image)
|
||||
require.False(t, inp.Image.HasProvenance)
|
||||
require.NotContains(t, unknowns, "input.image.hasProvenance")
|
||||
require.NotContains(t, unknowns, "input.image.signatures")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-source-with-config-and-no-attestation-chain",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "docker-image://alpine:latest",
|
||||
},
|
||||
Image: &gwpb.ResolveSourceImageResponse{
|
||||
Digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
Config: mustMarshalImageConfig(t, ocispecs.Image{
|
||||
Created: &tm,
|
||||
Config: ocispecs.ImageConfig{
|
||||
Labels: map[string]string{"a": "b"},
|
||||
Env: []string{"A=B"},
|
||||
User: "root",
|
||||
Volumes: map[string]struct{}{
|
||||
"/data": {},
|
||||
},
|
||||
WorkingDir: "/work",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
platform: &ocispecs.Platform{OS: "linux", Architecture: "amd64"},
|
||||
expInput: Input{
|
||||
Image: &Image{
|
||||
Ref: "docker.io/library/alpine:latest",
|
||||
Host: "docker.io",
|
||||
Repo: "alpine",
|
||||
FullRepo: "docker.io/library/alpine",
|
||||
Tag: "latest",
|
||||
Platform: "linux/amd64",
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
Checksum: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
CreatedTime: "2024-01-02T03:04:05Z",
|
||||
Labels: map[string]string{"a": "b"},
|
||||
Env: []string{"A=B"},
|
||||
User: "root",
|
||||
Volumes: []string{"/data"},
|
||||
WorkingDir: "/work",
|
||||
},
|
||||
},
|
||||
expUnk: []string{"input.image.hasProvenance", "input.image.signatures"},
|
||||
},
|
||||
{
|
||||
name: "git-source-missing-full-remote-url-attr",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "https",
|
||||
Host: "github.com",
|
||||
Remote: "https://github.com/docker/buildx.git",
|
||||
},
|
||||
},
|
||||
expUnk: []string{
|
||||
"input.git.tagName",
|
||||
"input.git.branch",
|
||||
"input.git.ref",
|
||||
"input.git.checksum",
|
||||
"input.git.isAnnotatedTag",
|
||||
"input.git.commitChecksum",
|
||||
"input.git.isSHA256",
|
||||
"input.git.tag",
|
||||
"input.git.commit",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git-source-with-full-remote-url-attr-uses-attr",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
Attrs: map[string]string{
|
||||
pb.AttrFullRemoteURL: "https://github.com/docker/buildx.git",
|
||||
},
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "https",
|
||||
Host: "github.com",
|
||||
Remote: "https://github.com/docker/buildx.git",
|
||||
FullURL: "https://github.com/docker/buildx.git",
|
||||
},
|
||||
},
|
||||
expUnk: []string{
|
||||
"input.git.tagName",
|
||||
"input.git.branch",
|
||||
"input.git.ref",
|
||||
"input.git.checksum",
|
||||
"input.git.isAnnotatedTag",
|
||||
"input.git.commitChecksum",
|
||||
"input.git.isSHA256",
|
||||
"input.git.tag",
|
||||
"input.git.commit",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git-source-with-full-remote-url-attr-ssh-uses-attr",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
Attrs: map[string]string{
|
||||
pb.AttrFullRemoteURL: "ssh://git@github.com/docker/buildx.git",
|
||||
},
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "ssh",
|
||||
Host: "github.com",
|
||||
Remote: "ssh://git@github.com/docker/buildx.git",
|
||||
FullURL: "ssh://git@github.com/docker/buildx.git",
|
||||
},
|
||||
},
|
||||
expUnk: []string{
|
||||
"input.git.tagName",
|
||||
"input.git.branch",
|
||||
"input.git.ref",
|
||||
"input.git.checksum",
|
||||
"input.git.isAnnotatedTag",
|
||||
"input.git.commitChecksum",
|
||||
"input.git.isSHA256",
|
||||
"input.git.tag",
|
||||
"input.git.commit",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git-source-with-full-remote-url-attr-ssh2-uses-attr",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
Attrs: map[string]string{
|
||||
pb.AttrFullRemoteURL: "git@github.com:docker/buildx.git",
|
||||
},
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "ssh",
|
||||
Host: "github.com",
|
||||
Remote: "git@github.com:docker/buildx.git",
|
||||
FullURL: "git@github.com:docker/buildx.git",
|
||||
},
|
||||
},
|
||||
expUnk: []string{
|
||||
"input.git.tagName",
|
||||
"input.git.branch",
|
||||
"input.git.ref",
|
||||
"input.git.checksum",
|
||||
"input.git.isAnnotatedTag",
|
||||
"input.git.commitChecksum",
|
||||
"input.git.isSHA256",
|
||||
"input.git.tag",
|
||||
"input.git.commit",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git-source-with-full-remote-url-attr-ssh-meta-with-objects-sets-commit-and-tag",
|
||||
src: func() *gwpb.ResolveSourceMetaResponse {
|
||||
commitRaw := []byte("" +
|
||||
"tree 0123456789abcdef0123456789abcdef01234567\n" +
|
||||
"author Alice <alice@example.com> 1700000000 +0000\n" +
|
||||
"committer Bob <bob@example.com> 1700003600 +0000\n" +
|
||||
"\n" +
|
||||
"hello from commit\n")
|
||||
commitSHA := gitObjectSHA1("commit", commitRaw)
|
||||
tagRaw := []byte("" +
|
||||
"object " + commitSHA + "\n" +
|
||||
"type commit\n" +
|
||||
"tag v1.2.3\n" +
|
||||
"tagger Carol <carol@example.com> 1700007200 +0000\n" +
|
||||
"\n" +
|
||||
"release v1.2.3\n")
|
||||
tagSHA := gitObjectSHA1("tag", tagRaw)
|
||||
return &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
Attrs: map[string]string{
|
||||
pb.AttrFullRemoteURL: "ssh://git@github.com/docker/buildx.git",
|
||||
},
|
||||
},
|
||||
Git: &gwpb.ResolveSourceGitResponse{
|
||||
Ref: "refs/tags/v1.2.3",
|
||||
Checksum: tagSHA,
|
||||
CommitChecksum: commitSHA,
|
||||
CommitObject: commitRaw,
|
||||
TagObject: tagRaw,
|
||||
},
|
||||
}
|
||||
}(),
|
||||
assert: func(t *testing.T, inp Input, unknowns []string, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, unknowns)
|
||||
require.NotNil(t, inp.Git)
|
||||
require.Equal(t, "ssh", inp.Git.Schema)
|
||||
require.Equal(t, "github.com", inp.Git.Host)
|
||||
require.Equal(t, "ssh://git@github.com/docker/buildx.git", inp.Git.Remote)
|
||||
require.Equal(t, "ssh://git@github.com/docker/buildx.git", inp.Git.FullURL)
|
||||
require.Equal(t, "refs/tags/v1.2.3", inp.Git.Ref)
|
||||
require.Equal(t, "v1.2.3", inp.Git.TagName)
|
||||
require.True(t, inp.Git.IsAnnotatedTag)
|
||||
require.NotEmpty(t, inp.Git.Checksum)
|
||||
require.NotEmpty(t, inp.Git.CommitChecksum)
|
||||
require.NotNil(t, inp.Git.Commit)
|
||||
require.Equal(t, "0123456789abcdef0123456789abcdef01234567", inp.Git.Commit.Tree)
|
||||
require.Equal(t, "hello from commit", inp.Git.Commit.Message)
|
||||
require.Equal(t, "Alice", inp.Git.Commit.Author.Name)
|
||||
require.Equal(t, "alice@example.com", inp.Git.Commit.Author.Email)
|
||||
require.Equal(t, "Bob", inp.Git.Commit.Committer.Name)
|
||||
require.Equal(t, "bob@example.com", inp.Git.Commit.Committer.Email)
|
||||
require.NotNil(t, inp.Git.Tag)
|
||||
require.Equal(t, inp.Git.CommitChecksum, inp.Git.Tag.Object)
|
||||
require.Equal(t, "commit", inp.Git.Tag.Type)
|
||||
require.Equal(t, "v1.2.3", inp.Git.Tag.Tag)
|
||||
require.Equal(t, "release v1.2.3", inp.Git.Tag.Message)
|
||||
require.Equal(t, "Carol", inp.Git.Tag.Tagger.Name)
|
||||
require.Equal(t, "carol@example.com", inp.Git.Tag.Tagger.Email)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git-meta-ref-heads-main-sets-branch",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
},
|
||||
Git: &gwpb.ResolveSourceGitResponse{
|
||||
Ref: "refs/heads/main",
|
||||
Checksum: "1111111111111111111111111111111111111111",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "https",
|
||||
Host: "github.com",
|
||||
Remote: "https://github.com/docker/buildx.git",
|
||||
Ref: "refs/heads/main",
|
||||
Branch: "main",
|
||||
Checksum: "1111111111111111111111111111111111111111",
|
||||
CommitChecksum: "1111111111111111111111111111111111111111",
|
||||
},
|
||||
},
|
||||
expUnk: []string{"input.git.commit", "input.git.tag"},
|
||||
},
|
||||
{
|
||||
name: "git-meta-ref-tags-v1-sets-tag-name",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
},
|
||||
Git: &gwpb.ResolveSourceGitResponse{
|
||||
Ref: "refs/tags/v1.2.3",
|
||||
Checksum: "2222222222222222222222222222222222222222",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "https",
|
||||
Host: "github.com",
|
||||
Remote: "https://github.com/docker/buildx.git",
|
||||
Ref: "refs/tags/v1.2.3",
|
||||
TagName: "v1.2.3",
|
||||
Checksum: "2222222222222222222222222222222222222222",
|
||||
CommitChecksum: "2222222222222222222222222222222222222222",
|
||||
},
|
||||
},
|
||||
expUnk: []string{"input.git.commit", "input.git.tag"},
|
||||
},
|
||||
{
|
||||
name: "git-meta-empty-commit-checksum-falls-back-to-checksum",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
},
|
||||
Git: &gwpb.ResolveSourceGitResponse{
|
||||
Checksum: "3333333333333333333333333333333333333333",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "https",
|
||||
Host: "github.com",
|
||||
Remote: "https://github.com/docker/buildx.git",
|
||||
Checksum: "3333333333333333333333333333333333333333",
|
||||
CommitChecksum: "3333333333333333333333333333333333333333",
|
||||
},
|
||||
},
|
||||
expUnk: []string{"input.git.commit", "input.git.tag"},
|
||||
},
|
||||
{
|
||||
name: "git-meta-sha256-checksum-sets-is-sha256",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
},
|
||||
Git: &gwpb.ResolveSourceGitResponse{
|
||||
Checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "https",
|
||||
Host: "github.com",
|
||||
Remote: "https://github.com/docker/buildx.git",
|
||||
Checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
CommitChecksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
IsSHA256: true,
|
||||
},
|
||||
},
|
||||
expUnk: []string{"input.git.commit", "input.git.tag"},
|
||||
},
|
||||
{
|
||||
name: "git-meta-checksum-ne-commit-checksum-sets-annotated-tag",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
},
|
||||
Git: &gwpb.ResolveSourceGitResponse{
|
||||
Checksum: "4444444444444444444444444444444444444444",
|
||||
CommitChecksum: "5555555555555555555555555555555555555555",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "https",
|
||||
Host: "github.com",
|
||||
Remote: "https://github.com/docker/buildx.git",
|
||||
Checksum: "4444444444444444444444444444444444444444",
|
||||
CommitChecksum: "5555555555555555555555555555555555555555",
|
||||
IsAnnotatedTag: true,
|
||||
},
|
||||
},
|
||||
expUnk: []string{"input.git.commit", "input.git.tag"},
|
||||
},
|
||||
{
|
||||
name: "git-meta-missing-commit-object-adds-commit-and-tag-unknowns",
|
||||
src: &gwpb.ResolveSourceMetaResponse{
|
||||
Source: &pb.SourceOp{
|
||||
Identifier: "git://github.com/docker/buildx.git",
|
||||
},
|
||||
Git: &gwpb.ResolveSourceGitResponse{
|
||||
Ref: "refs/heads/main",
|
||||
Checksum: "6666666666666666666666666666666666666666",
|
||||
CommitChecksum: "6666666666666666666666666666666666666666",
|
||||
},
|
||||
},
|
||||
expInput: Input{
|
||||
Git: &Git{
|
||||
Schema: "https",
|
||||
Host: "github.com",
|
||||
Remote: "https://github.com/docker/buildx.git",
|
||||
Ref: "refs/heads/main",
|
||||
Branch: "main",
|
||||
Checksum: "6666666666666666666666666666666666666666",
|
||||
CommitChecksum: "6666666666666666666666666666666666666666",
|
||||
},
|
||||
},
|
||||
expUnk: []string{"input.git.commit", "input.git.tag"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
inp, unknowns, err := SourceToInputWithLogger(t.Context(), tc.verifier, tc.src, tc.platform, nil)
|
||||
if tc.assert != nil {
|
||||
tc.assert(t, inp, unknowns, err)
|
||||
return
|
||||
}
|
||||
if tc.expErrMsg != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.expErrMsg)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expInput, inp)
|
||||
require.Equal(t, tc.expUnk, unknowns)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustMarshalImageConfig(t *testing.T, img ocispecs.Image) []byte {
|
||||
t.Helper()
|
||||
dt, err := json.Marshal(img)
|
||||
require.NoError(t, err)
|
||||
return dt
|
||||
}
|
||||
|
||||
func gitObjectSHA1(objType string, raw []byte) string {
|
||||
prefix := fmt.Appendf(nil, "%s %d\x00", objType, len(raw))
|
||||
//nolint:gosec // Git object IDs are defined using SHA-1 for this test fixture.
|
||||
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