Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion commands/policy/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, ", "))
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
118 changes: 118 additions & 0 deletions policy/add_unknowns_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
11 changes: 8 additions & 3 deletions policy/signatures.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,27 @@ 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 {
return nil
}
var (
mu sync.Mutex
v *policyverifier.Verifier
v PolicyVerifier
)
return func() (*policyverifier.Verifier, error) {
return func() (PolicyVerifier, error) {
mu.Lock()
defer mu.Unlock()

Expand Down
27 changes: 13 additions & 14 deletions policy/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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 {
for _, key := range refs {
if key == "" {
continue
}
key = trimKey(key)
if key == "" {
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
Expand Down Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions policy/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package policy
import (
"testing"

"github.com/open-policy-agent/opa/v1/ast"
"github.com/stretchr/testify/require"
)

Expand All @@ -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"},
}
Expand All @@ -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)
}
Loading