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}"