Skip to content

Commit 1c4a436

Browse files
committed
policy: match parent unknown keys in collectUnknowns
Allow child refs from partial evaluation to match allowed parent keys on path boundaries and return canonical unknowns for metadata resolution. Add tests for parent-child matching and boundary safety cases. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
1 parent 73ea669 commit 1c4a436

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

policy/utils_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ func TestTrimKey(t *testing.T) {
3131
{"input.git.tag[0]", "git.tag"},
3232
{"input.image.provenance.materials[0].image.hasProvenance", "image.provenance.materials[0].image.hasProvenance"},
3333
{"image.provenance.materials[0].image.labels", "image.provenance.materials[0].image.labels"},
34+
{"input.image.provenance.materials[0].image.provenance.predicateType", "image.provenance.materials[0].image.provenance.predicateType"},
35+
{"input.image.provenance.materials[0].image.signatures[0].signer.certificateIssuer", "image.provenance.materials[0].image.signatures[0].signer.certificateIssuer"},
36+
{"input.image.provenance.materials[10].image.hasProvenance", "image.provenance.materials[10].image.hasProvenance"},
3437

3538
{"a.b.c", "a.b"},
3639
}
@@ -61,6 +64,47 @@ func TestCollectUnknowns(t *testing.T) {
6164
require.ElementsMatch(t, []string{"image.signatures", "image.provenance.materials[0].image.hasProvenance"}, filtered)
6265
}
6366

67+
func TestCollectUnknownsParentAllowedMatchesChildRef(t *testing.T) {
68+
mod, err := ast.ParseModule("x.rego", `
69+
package x
70+
p if {
71+
input.image.provenance.materials[0].image.provenance.predicateType != ""
72+
input.image.provenance.materials[0].image.signatures[0].signer.certificateIssuer != ""
73+
input.image.provenance.materials[0].git.tag.name != ""
74+
input.foo.bar != ""
75+
input.image.provenance.materials[10].image.hasProvenance
76+
}
77+
`)
78+
require.NoError(t, err)
79+
80+
filtered := collectUnknowns([]*ast.Module{mod}, []string{
81+
"input.image.provenance.materials[0].image.provenance",
82+
"input.image.provenance.materials[0].image.signatures",
83+
"input.image.provenance.materials[0].git.tag",
84+
"input.foo.b",
85+
"input.image.provenance.materials[1].image",
86+
})
87+
88+
require.ElementsMatch(t, []string{
89+
"image.provenance.materials[0].image.provenance",
90+
"image.provenance.materials[0].image.signatures",
91+
"image.provenance.materials[0].git.tag",
92+
}, filtered)
93+
}
94+
95+
func TestMatchAllowedOrParentBoundary(t *testing.T) {
96+
allowed := map[string]struct{}{
97+
"foo.b": {},
98+
"image.provenance.materials[1].image": {},
99+
}
100+
101+
_, ok := matchAllowedOrParent("foo.bar", allowed)
102+
require.False(t, ok)
103+
104+
_, ok = matchAllowedOrParent("image.provenance.materials[10].image.hasProvenance", allowed)
105+
require.False(t, ok)
106+
}
107+
64108
func TestRuntimeUnknownInputRefs(t *testing.T) {
65109
require.Nil(t, runtimeUnknownInputRefs(nil))
66110
require.Nil(t, runtimeUnknownInputRefs(&state{}))

policy/validate.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -808,15 +808,42 @@ func collectUnknowns(mods []*ast.Module, allowed []string) []string {
808808
}
809809

810810
filtered := make([]string, 0, len(out))
811+
filteredSeen := map[string]struct{}{}
811812
for _, k := range out {
812-
if _, ok := valid[k]; ok {
813-
filtered = append(filtered, k)
813+
matched, ok := matchAllowedOrParent(k, valid)
814+
if !ok {
815+
continue
816+
}
817+
if _, exists := filteredSeen[matched]; exists {
818+
continue
814819
}
820+
filteredSeen[matched] = struct{}{}
821+
filtered = append(filtered, matched)
815822
}
816823

817824
return filtered
818825
}
819826

827+
func matchAllowedOrParent(key string, allowed map[string]struct{}) (string, bool) {
828+
if _, ok := allowed[key]; ok {
829+
return key, true
830+
}
831+
// Find the nearest parent on a component boundary.
832+
for i := len(key) - 1; i >= 0; i-- {
833+
switch key[i] {
834+
case '.', '[':
835+
if i == 0 {
836+
continue
837+
}
838+
candidate := key[:i]
839+
if _, ok := allowed[candidate]; ok {
840+
return candidate, true
841+
}
842+
}
843+
}
844+
return "", false
845+
}
846+
820847
func runtimeUnknownInputRefs(st *state) []string {
821848
if st == nil || len(st.Unknowns) == 0 {
822849
return nil

0 commit comments

Comments
 (0)