diff --git a/policy/parser.go b/policy/parser.go index 20042848..9c42e489 100644 --- a/policy/parser.go +++ b/policy/parser.go @@ -204,9 +204,10 @@ func NewMatch() *Match { // Match declares a condition (defaults to true) as well as an output or a rule. // Either the output or the rule field may be set, but not both. type Match struct { - condition ValueString - output *ValueString - rule *Rule + condition ValueString + output *ValueString + explanation *ValueString + rule *Rule } // Condition returns the condition CEL expression. @@ -227,6 +228,19 @@ func (m *Match) Output() ValueString { return ValueString{} } +// HasExplanation indicates whether the explanation field is set of the match. +func (m *Match) HasExplanation() bool { + return m.explanation != nil +} + +// Explanation returns the explanation expression, or empty expression if output is not set. +func (m *Match) Explanation() ValueString { + if m.HasExplanation() { + return *m.explanation + } + return ValueString{} +} + // HasRule indicates whether the rule field is set on a match. func (m *Match) HasRule() bool { return m.rule != nil @@ -247,6 +261,11 @@ func (m *Match) SetOutput(o ValueString) { m.output = &o } +// SetExplanation sets the explanation expression for the match. +func (m *Match) SetExplanation(e ValueString) { + m.explanation = &e +} + // SetRule sets the rule for the match. func (m *Match) SetRule(r *Rule) { m.rule = r @@ -633,10 +652,18 @@ func (p *parserImpl) ParseMatch(ctx ParserContext, policy *Policy, node *yaml.No p.ReportErrorAtID(keyID, "only the rule or the output may be set") } m.SetOutput(ctx.NewString(val)) + case "explanation": + if m.HasRule() { + p.ReportErrorAtID(keyID, "explanation can only be set on output match cases, not nested rules") + } + m.SetExplanation(ctx.NewString(val)) case "rule": if m.HasOutput() { p.ReportErrorAtID(keyID, "only the rule or the output may be set") } + if m.HasExplanation() { + p.ReportErrorAtID(keyID, "explanation can only be set on output match cases, not nested rules") + } m.SetRule(p.ParseRule(ctx, policy, val)) default: p.visitor.MatchTag(ctx, keyID, fieldName, val, policy, m) diff --git a/policy/parser_test.go b/policy/parser_test.go index e520bfc9..063ceeb1 100644 --- a/policy/parser_test.go +++ b/policy/parser_test.go @@ -121,6 +121,32 @@ rule: output: "world"`, err: `ERROR: :8:7: only the rule or the output may be set | output: "world" + | ......^`, + }, + { + txt: ` +rule: + match: + - condition: "true" + explanation: "hi" + rule: + match: + - output: "hello"`, + err: `ERROR: :6:7: explanation can only be set on output match cases, not nested rules + | rule: + | ......^`, + }, + { + txt: ` +rule: + match: + - condition: "true" + rule: + match: + - output: "hello" + explanation: "hi"`, + err: `ERROR: :8:7: explanation can only be set on output match cases, not nested rules + | explanation: "hi" | ......^`, }, } diff --git a/policy/testdata/nested_rule/policy.yaml b/policy/testdata/nested_rule/policy.yaml index a12cbbd9..1c79cfb3 100644 --- a/policy/testdata/nested_rule/policy.yaml +++ b/policy/testdata/nested_rule/policy.yaml @@ -32,6 +32,7 @@ rule: resource.origin in variables.banned_regions && !(resource.origin in variables.permitted_regions) output: "{'banned': true}" + explanation: "'resource is in the banned region ' + resource.origin" - condition: resource.origin in variables.permitted_regions output: "{'banned': false}" - output: "{'banned': true}"