Skip to content

Commit cd84123

Browse files
authored
Merge pull request #3647 from tonistiigi/policy-test-updates
policy: update unit tests and normalize input keys
2 parents ab03597 + edd2461 commit cd84123

File tree

8 files changed

+1096
-35
lines changed

8 files changed

+1096
-35
lines changed

commands/policy/test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func runTest(ctx context.Context, out io.Writer, path string, opts policy.TestOp
8484
_, _ = fmt.Fprintln(out, "decision: <nil>")
8585
}
8686
if len(result.MissingInput) > 0 {
87-
_, _ = fmt.Fprintf(out, "missing_input: %s\n", strings.Join(result.MissingInput, ", "))
87+
_, _ = fmt.Fprintf(out, "missing_input: %s\n", strings.Join(withInputPrefix(result.MissingInput), ", "))
8888
}
8989
if len(result.MetadataNeeded) > 0 {
9090
_, _ = fmt.Fprintf(out, "metadata_resolve: %s\n", strings.Join(result.MetadataNeeded, ", "))
@@ -106,6 +106,14 @@ func writeJSON(out io.Writer, label string, v any) {
106106
_, _ = fmt.Fprintf(out, "%s:\n%s\n", label, string(dt))
107107
}
108108

109+
func withInputPrefix(keys []string) []string {
110+
out := make([]string, len(keys))
111+
for i, k := range keys {
112+
out[i] = "input." + k
113+
}
114+
return out
115+
}
116+
109117
type policyTestResolver struct {
110118
dockerCli command.Cli
111119
builderName *string

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ require (
4444
github.com/pkg/errors v0.9.1
4545
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
4646
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b
47+
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7
4748
github.com/sirupsen/logrus v1.9.4
4849
github.com/spf13/cobra v1.10.2
4950
github.com/spf13/pflag v1.0.10
@@ -196,7 +197,6 @@ require (
196197
github.com/sigstore/rekor v1.4.3 // indirect
197198
github.com/sigstore/rekor-tiles/v2 v2.0.1 // indirect
198199
github.com/sigstore/sigstore v1.10.0 // indirect
199-
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7 // indirect
200200
github.com/sigstore/timestamp-authority/v2 v2.0.2 // indirect
201201
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
202202
github.com/theupdateframework/go-tuf/v2 v2.3.0 // indirect

policy/add_unknowns_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package policy
2+
3+
import (
4+
"testing"
5+
6+
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestAddUnknowns(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
unknowns []string
14+
initial *gwpb.ResolveSourceMetaRequest
15+
expected *gwpb.ResolveSourceMetaRequest
16+
expErrMsg string
17+
}{
18+
{
19+
name: "empty-unknowns",
20+
unknowns: nil,
21+
initial: &gwpb.ResolveSourceMetaRequest{},
22+
expected: &gwpb.ResolveSourceMetaRequest{},
23+
},
24+
{
25+
name: "parent-key-ignored",
26+
unknowns: []string{"image"},
27+
initial: &gwpb.ResolveSourceMetaRequest{},
28+
expected: &gwpb.ResolveSourceMetaRequest{},
29+
},
30+
{
31+
name: "image-config-fields-enable-image-request",
32+
unknowns: []string{"image.labels"},
33+
initial: &gwpb.ResolveSourceMetaRequest{},
34+
expected: &gwpb.ResolveSourceMetaRequest{
35+
Image: &gwpb.ResolveSourceImageRequest{},
36+
},
37+
},
38+
{
39+
name: "image-attestation-fields-enable-attestation-chain",
40+
unknowns: []string{"image.signatures"},
41+
initial: &gwpb.ResolveSourceMetaRequest{},
42+
expected: &gwpb.ResolveSourceMetaRequest{
43+
Image: &gwpb.ResolveSourceImageRequest{
44+
NoConfig: true,
45+
AttestationChain: true,
46+
},
47+
},
48+
},
49+
{
50+
name: "image-attestation-on-existing-image-request",
51+
unknowns: []string{"image.hasProvenance"},
52+
initial: &gwpb.ResolveSourceMetaRequest{
53+
Image: &gwpb.ResolveSourceImageRequest{
54+
NoConfig: false,
55+
},
56+
},
57+
expected: &gwpb.ResolveSourceMetaRequest{
58+
Image: &gwpb.ResolveSourceImageRequest{
59+
NoConfig: false,
60+
AttestationChain: true,
61+
},
62+
},
63+
},
64+
{
65+
name: "git-ref-field-enables-git-request",
66+
unknowns: []string{"git.ref"},
67+
initial: &gwpb.ResolveSourceMetaRequest{},
68+
expected: &gwpb.ResolveSourceMetaRequest{
69+
Git: &gwpb.ResolveSourceGitRequest{},
70+
},
71+
},
72+
{
73+
name: "git-commit-enables-return-object",
74+
unknowns: []string{"git.commit"},
75+
initial: &gwpb.ResolveSourceMetaRequest{},
76+
expected: &gwpb.ResolveSourceMetaRequest{
77+
Git: &gwpb.ResolveSourceGitRequest{
78+
ReturnObject: true,
79+
},
80+
},
81+
},
82+
{
83+
name: "http-checksum-no-op",
84+
unknowns: []string{"http.checksum"},
85+
initial: &gwpb.ResolveSourceMetaRequest{},
86+
expected: &gwpb.ResolveSourceMetaRequest{},
87+
},
88+
{
89+
name: "non-canonical-input-prefix-errors",
90+
unknowns: []string{"input.image.labels"},
91+
initial: &gwpb.ResolveSourceMetaRequest{},
92+
expErrMsg: "unhandled unknown property input.image.labels",
93+
},
94+
{
95+
name: "unknown-field-errors",
96+
unknowns: []string{"git.notAField"},
97+
initial: &gwpb.ResolveSourceMetaRequest{},
98+
expErrMsg: "unhandled unknown property git.notAField",
99+
},
100+
}
101+
102+
for _, tc := range tests {
103+
t.Run(tc.name, func(t *testing.T) {
104+
req := tc.initial
105+
if req == nil {
106+
req = &gwpb.ResolveSourceMetaRequest{}
107+
}
108+
err := AddUnknowns(req, tc.unknowns)
109+
if tc.expErrMsg != "" {
110+
require.Error(t, err)
111+
require.Equal(t, tc.expErrMsg, err.Error())
112+
return
113+
}
114+
require.NoError(t, err)
115+
require.Equal(t, tc.expected, req)
116+
})
117+
}
118+
}

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/tester.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ func runPolicyTest(ctx context.Context, policyModules map[string]*ast.Module, te
311311
result.Allow = allow
312312
result.DenyMessages = deny
313313

314-
missing := missingInputRefs(policyPackageModules, effectiveInput)
314+
missing := missingInputRefs(policyPackageModules, effectiveInput, runtimeUnknownInputRefs(testState), runtimeUnknownInputRefs(decisionState))
315315
result.MissingInput = uniqueSortedStrings(missing)
316316
result.MetadataNeeded = summarizeMetadataRequests(result.MissingInput)
317317

@@ -534,7 +534,7 @@ func hasEnv(env Env) bool {
534534
func filterResolvableMissing(missing []string) []string {
535535
out := make([]string, 0, len(missing))
536536
for _, m := range missing {
537-
if strings.HasPrefix(m, "input.image.") || strings.HasPrefix(m, "input.git.") {
537+
if strings.HasPrefix(m, "image.") || strings.HasPrefix(m, "git.") {
538538
out = append(out, m)
539539
}
540540
}
@@ -595,24 +595,27 @@ func modulesForPackage(modules map[string]*ast.Module, pkgPath string) []*ast.Mo
595595
return out
596596
}
597597

598-
func missingInputRefs(mods []*ast.Module, input *Input) []string {
598+
func missingInputRefs(mods []*ast.Module, input *Input, extraRefs ...[]string) []string {
599599
if len(mods) == 0 {
600600
return nil
601601
}
602602
inputMap := normalizeInput(input)
603603
refs := collectUnknowns(mods, nil)
604+
for _, er := range extraRefs {
605+
refs = append(refs, er...)
606+
}
607+
seen := map[string]struct{}{}
604608
missing := make([]string, 0, len(refs))
605-
for _, ref := range refs {
606-
key := strings.TrimPrefix(ref, "input.")
607-
if key == ref {
609+
for _, key := range refs {
610+
if key == "" {
608611
continue
609612
}
610-
key = trimKey(key)
611-
if key == "" {
613+
if _, ok := seen[key]; ok {
612614
continue
613615
}
616+
seen[key] = struct{}{}
614617
if !inputHasPath(inputMap, strings.Split(key, ".")) {
615-
missing = append(missing, "input."+key)
618+
missing = append(missing, key)
616619
}
617620
}
618621
return missing
@@ -703,11 +706,7 @@ func summarizeMetadataRequests(missing []string) []string {
703706
return nil
704707
}
705708
req := &gwpb.ResolveSourceMetaRequest{}
706-
trimmed := make([]string, 0, len(missing))
707-
for _, m := range missing {
708-
trimmed = append(trimmed, strings.TrimPrefix(m, "input."))
709-
}
710-
if err := AddUnknowns(req, trimmed); err != nil {
709+
if err := AddUnknowns(req, missing); err != nil {
711710
return nil
712711
}
713712
var out []string

policy/utils_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package policy
33
import (
44
"testing"
55

6+
"github.com/open-policy-agent/opa/v1/ast"
67
"github.com/stretchr/testify/require"
78
)
89

@@ -18,12 +19,16 @@ func TestTrimKey(t *testing.T) {
1819
// one separator → stays as-is
1920
{"git.tag", "git.tag"},
2021
{"git[tag", "git[tag"},
22+
{"input.git.tag", "git.tag"},
23+
{"input.git[tag", "git[tag"},
2124

2225
// multiple separators → cut before second one
2326
{"git.tag.author", "git.tag"},
2427
{"git.tag.author.email", "git.tag"},
2528
{"git.tag[0][1]", "git.tag"},
2629
{"git.tag[0]", "git.tag"},
30+
{"input.git.tag.author", "git.tag"},
31+
{"input.git.tag[0]", "git.tag"},
2732

2833
{"a.b.c", "a.b"},
2934
}
@@ -34,3 +39,51 @@ func TestTrimKey(t *testing.T) {
3439
})
3540
}
3641
}
42+
43+
func TestCollectUnknowns(t *testing.T) {
44+
mod, err := ast.ParseModule("x.rego", `
45+
package x
46+
p if {
47+
input.git.tag[0].author == "a"
48+
input.image.signatures[_].signer.certificateIssuer != ""
49+
data.foo.bar == 1
50+
}
51+
`)
52+
require.NoError(t, err)
53+
54+
all := collectUnknowns([]*ast.Module{mod}, nil)
55+
require.ElementsMatch(t, []string{"git.tag", "image.signatures"}, all)
56+
57+
filtered := collectUnknowns([]*ast.Module{mod}, []string{"input.image.signatures"})
58+
require.Equal(t, []string{"image.signatures"}, filtered)
59+
}
60+
61+
func TestRuntimeUnknownInputRefs(t *testing.T) {
62+
require.Nil(t, runtimeUnknownInputRefs(nil))
63+
require.Nil(t, runtimeUnknownInputRefs(&state{}))
64+
65+
st := &state{
66+
Unknowns: map[string]struct{}{
67+
funcVerifyGitSignature: {},
68+
},
69+
}
70+
require.Equal(t, []string{"git.commit"}, runtimeUnknownInputRefs(st))
71+
}
72+
73+
func TestMissingInputRefsWithRuntimeUnknowns(t *testing.T) {
74+
mod, err := ast.ParseModule("x.rego", `
75+
package x
76+
p if {
77+
input.git.ref != ""
78+
}
79+
`)
80+
require.NoError(t, err)
81+
82+
in := &Input{
83+
Git: &Git{
84+
Ref: "refs/heads/main",
85+
},
86+
}
87+
missing := missingInputRefs([]*ast.Module{mod}, in, []string{"git.commit"})
88+
require.Equal(t, []string{"git.commit"}, missing)
89+
}

0 commit comments

Comments
 (0)