policy: update unknown keys normalization
Make the code more unified between validation and test command. Normalize to key without the input prefix. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
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>")
|
_, _ = fmt.Fprintln(out, "decision: <nil>")
|
||||||
}
|
}
|
||||||
if len(result.MissingInput) > 0 {
|
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 {
|
if len(result.MetadataNeeded) > 0 {
|
||||||
_, _ = fmt.Fprintf(out, "metadata_resolve: %s\n", strings.Join(result.MetadataNeeded, ", "))
|
_, _ = 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))
|
_, _ = 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 {
|
type policyTestResolver struct {
|
||||||
dockerCli command.Cli
|
dockerCli command.Cli
|
||||||
builderName *string
|
builderName *string
|
||||||
|
|||||||
+14
-15
@@ -311,7 +311,7 @@ func runPolicyTest(ctx context.Context, policyModules map[string]*ast.Module, te
|
|||||||
result.Allow = allow
|
result.Allow = allow
|
||||||
result.DenyMessages = deny
|
result.DenyMessages = deny
|
||||||
|
|
||||||
missing := missingInputRefs(policyPackageModules, effectiveInput)
|
missing := missingInputRefs(policyPackageModules, effectiveInput, runtimeUnknownInputRefs(testState), runtimeUnknownInputRefs(decisionState))
|
||||||
result.MissingInput = uniqueSortedStrings(missing)
|
result.MissingInput = uniqueSortedStrings(missing)
|
||||||
result.MetadataNeeded = summarizeMetadataRequests(result.MissingInput)
|
result.MetadataNeeded = summarizeMetadataRequests(result.MissingInput)
|
||||||
|
|
||||||
@@ -534,7 +534,7 @@ func hasEnv(env Env) bool {
|
|||||||
func filterResolvableMissing(missing []string) []string {
|
func filterResolvableMissing(missing []string) []string {
|
||||||
out := make([]string, 0, len(missing))
|
out := make([]string, 0, len(missing))
|
||||||
for _, m := range 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)
|
out = append(out, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -595,24 +595,27 @@ func modulesForPackage(modules map[string]*ast.Module, pkgPath string) []*ast.Mo
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func missingInputRefs(mods []*ast.Module, input *Input) []string {
|
func missingInputRefs(mods []*ast.Module, input *Input, extraRefs ...[]string) []string {
|
||||||
if len(mods) == 0 {
|
if len(mods) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
inputMap := normalizeInput(input)
|
inputMap := normalizeInput(input)
|
||||||
refs := collectUnknowns(mods, nil)
|
refs := collectUnknowns(mods, nil)
|
||||||
|
for _, er := range extraRefs {
|
||||||
|
refs = append(refs, er...)
|
||||||
|
}
|
||||||
|
seen := map[string]struct{}{}
|
||||||
missing := make([]string, 0, len(refs))
|
missing := make([]string, 0, len(refs))
|
||||||
for _, ref := range refs {
|
for _, key := range refs {
|
||||||
key := strings.TrimPrefix(ref, "input.")
|
|
||||||
if key == ref {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key = trimKey(key)
|
|
||||||
if key == "" {
|
if key == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = struct{}{}
|
||||||
if !inputHasPath(inputMap, strings.Split(key, ".")) {
|
if !inputHasPath(inputMap, strings.Split(key, ".")) {
|
||||||
missing = append(missing, "input."+key)
|
missing = append(missing, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return missing
|
return missing
|
||||||
@@ -703,11 +706,7 @@ func summarizeMetadataRequests(missing []string) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
req := &gwpb.ResolveSourceMetaRequest{}
|
req := &gwpb.ResolveSourceMetaRequest{}
|
||||||
trimmed := make([]string, 0, len(missing))
|
if err := AddUnknowns(req, missing); err != nil {
|
||||||
for _, m := range missing {
|
|
||||||
trimmed = append(trimmed, strings.TrimPrefix(m, "input."))
|
|
||||||
}
|
|
||||||
if err := AddUnknowns(req, trimmed); err != nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var out []string
|
var out []string
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package policy
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/open-policy-agent/opa/v1/ast"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,12 +19,16 @@ func TestTrimKey(t *testing.T) {
|
|||||||
// one separator → stays as-is
|
// one separator → stays as-is
|
||||||
{"git.tag", "git.tag"},
|
{"git.tag", "git.tag"},
|
||||||
{"git[tag", "git[tag"},
|
{"git[tag", "git[tag"},
|
||||||
|
{"input.git.tag", "git.tag"},
|
||||||
|
{"input.git[tag", "git[tag"},
|
||||||
|
|
||||||
// multiple separators → cut before second one
|
// multiple separators → cut before second one
|
||||||
{"git.tag.author", "git.tag"},
|
{"git.tag.author", "git.tag"},
|
||||||
{"git.tag.author.email", "git.tag"},
|
{"git.tag.author.email", "git.tag"},
|
||||||
{"git.tag[0][1]", "git.tag"},
|
{"git.tag[0][1]", "git.tag"},
|
||||||
{"git.tag[0]", "git.tag"},
|
{"git.tag[0]", "git.tag"},
|
||||||
|
{"input.git.tag.author", "git.tag"},
|
||||||
|
{"input.git.tag[0]", "git.tag"},
|
||||||
|
|
||||||
{"a.b.c", "a.b"},
|
{"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)
|
||||||
|
}
|
||||||
|
|||||||
+28
-13
@@ -224,9 +224,7 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
unk := collectUnknowns(pq.Support, unknowns)
|
unk := collectUnknowns(pq.Support, unknowns)
|
||||||
if _, ok := st.Unknowns[funcVerifyGitSignature]; ok {
|
unk = append(unk, runtimeUnknownInputRefs(st)...)
|
||||||
unk = append(unk, "input.git.commit")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(unk) > 0 {
|
if len(unk) > 0 {
|
||||||
next := &gwpb.ResolveSourceMetaRequest{
|
next := &gwpb.ResolveSourceMetaRequest{
|
||||||
@@ -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 {
|
func AddUnknownsWithLogger(logf func(logrus.Level, string), req *gwpb.ResolveSourceMetaRequest, unk []string) error {
|
||||||
unk2 := make([]string, 0, len(unk))
|
unk2 := make([]string, 0, len(unk))
|
||||||
for _, u := range unk {
|
for _, u := range unk {
|
||||||
k := strings.TrimPrefix(u, "input.")
|
switch u {
|
||||||
k = trimKey(k)
|
|
||||||
switch k {
|
|
||||||
case "image", "git", "http", "local":
|
case "image", "git", "http", "local":
|
||||||
// parents are returned as unknowns for some reason, ignore
|
// parents are returned as unknowns for some reason, ignore
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
unk2 = append(unk2, k)
|
unk2 = append(unk2, u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(unk2) == 0 {
|
if len(unk2) == 0 {
|
||||||
@@ -680,8 +676,10 @@ func collectUnknowns(mods []*ast.Module, allowed []string) []string {
|
|||||||
for _, mod := range mods {
|
for _, mod := range mods {
|
||||||
ast.WalkRefs(mod, func(ref ast.Ref) bool {
|
ast.WalkRefs(mod, func(ref ast.Ref) bool {
|
||||||
if ref.HasPrefix(ast.InputRootRef) {
|
if ref.HasPrefix(ast.InputRootRef) {
|
||||||
s := ref.String() // e.g. "input.request.path"
|
s := trimKey(ref.String())
|
||||||
s = "input." + trimKey(strings.TrimPrefix(s, "input."))
|
if s == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if _, ok := seen[s]; !ok {
|
if _, ok := seen[s]; !ok {
|
||||||
seen[s] = struct{}{}
|
seen[s] = struct{}{}
|
||||||
out = append(out, s)
|
out = append(out, s)
|
||||||
@@ -696,6 +694,10 @@ func collectUnknowns(mods []*ast.Module, allowed []string) []string {
|
|||||||
|
|
||||||
valid := map[string]struct{}{}
|
valid := map[string]struct{}{}
|
||||||
for _, k := range allowed {
|
for _, k := range allowed {
|
||||||
|
k = trimKey(k)
|
||||||
|
if k == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
valid[k] = struct{}{}
|
valid[k] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,14 +711,25 @@ func collectUnknowns(mods []*ast.Module, allowed []string) []string {
|
|||||||
return filtered
|
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 {
|
func summarizeUnknownsForLog(unk []string) []string {
|
||||||
out := make([]string, 0, len(unk))
|
out := make([]string, 0, len(unk))
|
||||||
seen := map[string]struct{}{}
|
seen := map[string]struct{}{}
|
||||||
for _, u := range unk {
|
for _, u := range unk {
|
||||||
if strings.HasPrefix(u, "input.image.signatures") {
|
if strings.HasPrefix(u, "image.signatures") {
|
||||||
u = "input.image.signatures"
|
u = "image.signatures"
|
||||||
}
|
}
|
||||||
if u == "input.image" {
|
if u == "image" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := seen[u]; ok {
|
if _, ok := seen[u]; ok {
|
||||||
@@ -730,7 +743,7 @@ func summarizeUnknownsForLog(unk []string) []string {
|
|||||||
|
|
||||||
func hasHTTPUnknowns(unk []string) bool {
|
func hasHTTPUnknowns(unk []string) bool {
|
||||||
for _, u := range unk {
|
for _, u := range unk {
|
||||||
if strings.HasPrefix(u, "input.http.") {
|
if strings.HasPrefix(u, "http.") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -738,6 +751,8 @@ func hasHTTPUnknowns(unk []string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func trimKey(s string) string {
|
func trimKey(s string) string {
|
||||||
|
s = strings.TrimPrefix(s, "input.")
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dot = '.'
|
dot = '.'
|
||||||
sb = '['
|
sb = '['
|
||||||
|
|||||||
Reference in New Issue
Block a user