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>
This commit is contained in:
@@ -31,6 +31,9 @@ func TestTrimKey(t *testing.T) {
|
|||||||
{"input.git.tag[0]", "git.tag"},
|
{"input.git.tag[0]", "git.tag"},
|
||||||
{"input.image.provenance.materials[0].image.hasProvenance", "image.provenance.materials[0].image.hasProvenance"},
|
{"input.image.provenance.materials[0].image.hasProvenance", "image.provenance.materials[0].image.hasProvenance"},
|
||||||
{"image.provenance.materials[0].image.labels", "image.provenance.materials[0].image.labels"},
|
{"image.provenance.materials[0].image.labels", "image.provenance.materials[0].image.labels"},
|
||||||
|
{"input.image.provenance.materials[0].image.provenance.predicateType", "image.provenance.materials[0].image.provenance.predicateType"},
|
||||||
|
{"input.image.provenance.materials[0].image.signatures[0].signer.certificateIssuer", "image.provenance.materials[0].image.signatures[0].signer.certificateIssuer"},
|
||||||
|
{"input.image.provenance.materials[10].image.hasProvenance", "image.provenance.materials[10].image.hasProvenance"},
|
||||||
|
|
||||||
{"a.b.c", "a.b"},
|
{"a.b.c", "a.b"},
|
||||||
}
|
}
|
||||||
@@ -61,6 +64,47 @@ func TestCollectUnknowns(t *testing.T) {
|
|||||||
require.ElementsMatch(t, []string{"image.signatures", "image.provenance.materials[0].image.hasProvenance"}, filtered)
|
require.ElementsMatch(t, []string{"image.signatures", "image.provenance.materials[0].image.hasProvenance"}, filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCollectUnknownsParentAllowedMatchesChildRef(t *testing.T) {
|
||||||
|
mod, err := ast.ParseModule("x.rego", `
|
||||||
|
package x
|
||||||
|
p if {
|
||||||
|
input.image.provenance.materials[0].image.provenance.predicateType != ""
|
||||||
|
input.image.provenance.materials[0].image.signatures[0].signer.certificateIssuer != ""
|
||||||
|
input.image.provenance.materials[0].git.tag.name != ""
|
||||||
|
input.foo.bar != ""
|
||||||
|
input.image.provenance.materials[10].image.hasProvenance
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
filtered := collectUnknowns([]*ast.Module{mod}, []string{
|
||||||
|
"input.image.provenance.materials[0].image.provenance",
|
||||||
|
"input.image.provenance.materials[0].image.signatures",
|
||||||
|
"input.image.provenance.materials[0].git.tag",
|
||||||
|
"input.foo.b",
|
||||||
|
"input.image.provenance.materials[1].image",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.ElementsMatch(t, []string{
|
||||||
|
"image.provenance.materials[0].image.provenance",
|
||||||
|
"image.provenance.materials[0].image.signatures",
|
||||||
|
"image.provenance.materials[0].git.tag",
|
||||||
|
}, filtered)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatchAllowedOrParentBoundary(t *testing.T) {
|
||||||
|
allowed := map[string]struct{}{
|
||||||
|
"foo.b": {},
|
||||||
|
"image.provenance.materials[1].image": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := matchAllowedOrParent("foo.bar", allowed)
|
||||||
|
require.False(t, ok)
|
||||||
|
|
||||||
|
_, ok = matchAllowedOrParent("image.provenance.materials[10].image.hasProvenance", allowed)
|
||||||
|
require.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRuntimeUnknownInputRefs(t *testing.T) {
|
func TestRuntimeUnknownInputRefs(t *testing.T) {
|
||||||
require.Nil(t, runtimeUnknownInputRefs(nil))
|
require.Nil(t, runtimeUnknownInputRefs(nil))
|
||||||
require.Nil(t, runtimeUnknownInputRefs(&state{}))
|
require.Nil(t, runtimeUnknownInputRefs(&state{}))
|
||||||
|
|||||||
+29
-2
@@ -808,15 +808,42 @@ func collectUnknowns(mods []*ast.Module, allowed []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filtered := make([]string, 0, len(out))
|
filtered := make([]string, 0, len(out))
|
||||||
|
filteredSeen := map[string]struct{}{}
|
||||||
for _, k := range out {
|
for _, k := range out {
|
||||||
if _, ok := valid[k]; ok {
|
matched, ok := matchAllowedOrParent(k, valid)
|
||||||
filtered = append(filtered, k)
|
if !ok {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
if _, exists := filteredSeen[matched]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filteredSeen[matched] = struct{}{}
|
||||||
|
filtered = append(filtered, matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchAllowedOrParent(key string, allowed map[string]struct{}) (string, bool) {
|
||||||
|
if _, ok := allowed[key]; ok {
|
||||||
|
return key, true
|
||||||
|
}
|
||||||
|
// Find the nearest parent on a component boundary.
|
||||||
|
for i := len(key) - 1; i >= 0; i-- {
|
||||||
|
switch key[i] {
|
||||||
|
case '.', '[':
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
candidate := key[:i]
|
||||||
|
if _, ok := allowed[candidate]; ok {
|
||||||
|
return candidate, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
func runtimeUnknownInputRefs(st *state) []string {
|
func runtimeUnknownInputRefs(st *state) []string {
|
||||||
if st == nil || len(st.Unknowns) == 0 {
|
if st == nil || len(st.Unknowns) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user