1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 07:26:55 +00:00

feat: enable custom data in policy reports using properties (#10933)

* feat: enable custom data in policy reports using properties

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: dont throw error in variable substitution for properties

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

---------

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
This commit is contained in:
Vishal Choudhary 2024-09-03 23:06:07 +05:30 committed by GitHub
parent 86fa32af7f
commit 95f54a1cb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 543 additions and 189 deletions

View file

@ -51,6 +51,10 @@ type Rule struct {
// +optional // +optional
Context []ContextEntry `json:"context,omitempty" yaml:"context,omitempty"` Context []ContextEntry `json:"context,omitempty" yaml:"context,omitempty"`
// ReportProperties are the additional properties from the rule that will be added to the policy report result
// +optional
ReportProperties map[string]string `json:"reportProperties,omitempty" yaml:"reportProperties,omitempty"`
// MatchResources defines when this policy rule should be applied. The match // MatchResources defines when this policy rule should be applied. The match
// criteria can include resource information (e.g. kind, name, namespace, labels) // criteria can include resource information (e.g. kind, name, namespace, labels)
// and admission review request information like the user name or role. // and admission review request information like the user name or role.

View file

@ -1361,6 +1361,13 @@ func (in *Rule) DeepCopyInto(out *Rule) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
if in.ReportProperties != nil {
in, out := &in.ReportProperties, &out.ReportProperties
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
in.MatchResources.DeepCopyInto(&out.MatchResources) in.MatchResources.DeepCopyInto(&out.MatchResources)
in.ExcludeResources.DeepCopyInto(&out.ExcludeResources) in.ExcludeResources.DeepCopyInto(&out.ExcludeResources)
if in.ImageExtractors != nil { if in.ImageExtractors != nil {

View file

@ -2814,6 +2814,12 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -7673,6 +7679,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -17172,6 +17185,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-

View file

@ -2815,6 +2815,12 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -7675,6 +7681,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -17175,6 +17188,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-

View file

@ -2808,6 +2808,12 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -7667,6 +7673,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -17166,6 +17179,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-

View file

@ -2809,6 +2809,12 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -7669,6 +7675,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -17169,6 +17182,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-

View file

@ -26,11 +26,13 @@ func TestComputeClusterPolicyReports(t *testing.T) {
"pods-require-account", "pods-require-account",
engineapi.Validation, engineapi.Validation,
"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/", "validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/",
nil,
), ),
*engineapi.RulePass( *engineapi.RulePass(
"pods-require-limits", "pods-require-limits",
engineapi.Validation, engineapi.Validation,
"validation rule 'pods-require-limits' passed.", "validation rule 'pods-require-limits' passed.",
nil,
), ),
) )
clustered, namespaced := ComputePolicyReports(false, er) clustered, namespaced := ComputePolicyReports(false, er)
@ -60,11 +62,13 @@ func TestComputePolicyReports(t *testing.T) {
"pods-require-account", "pods-require-account",
engineapi.Validation, engineapi.Validation,
"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/", "validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/",
nil,
), ),
*engineapi.RulePass( *engineapi.RulePass(
"pods-require-limits", "pods-require-limits",
engineapi.Validation, engineapi.Validation,
"validation rule 'pods-require-limits' passed.", "validation rule 'pods-require-limits' passed.",
nil,
), ),
) )
clustered, namespaced := ComputePolicyReports(false, er) clustered, namespaced := ComputePolicyReports(false, er)
@ -94,11 +98,13 @@ func TestComputePolicyReportResultsPerPolicyOld(t *testing.T) {
"pods-require-account", "pods-require-account",
engineapi.Validation, engineapi.Validation,
"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/", "validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/",
nil,
), ),
*engineapi.RulePass( *engineapi.RulePass(
"pods-require-limits", "pods-require-limits",
engineapi.Validation, engineapi.Validation,
"validation rule 'pods-require-limits' passed.", "validation rule 'pods-require-limits' passed.",
nil,
), ),
) )
results := ComputePolicyReportResultsPerPolicy(false, er) results := ComputePolicyReportResultsPerPolicy(false, er)
@ -175,7 +181,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "skip", name: "skip",
auditWarn: false, auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil), engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleSkip("xxx", engineapi.Mutation, "test"), ruleResponse: *engineapi.RuleSkip("xxx", engineapi.Mutation, "test", nil),
want: policyreportv1alpha2.PolicyReportResult{ want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno", Source: "kyverno",
Policy: "pod-requirements", Policy: "pod-requirements",
@ -191,7 +197,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "pass", name: "pass",
auditWarn: false, auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil), engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RulePass("xxx", engineapi.Mutation, "test"), ruleResponse: *engineapi.RulePass("xxx", engineapi.Mutation, "test", nil),
want: policyreportv1alpha2.PolicyReportResult{ want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno", Source: "kyverno",
Policy: "pod-requirements", Policy: "pod-requirements",
@ -207,7 +213,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "fail", name: "fail",
auditWarn: false, auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil), engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleFail("xxx", engineapi.Mutation, "test"), ruleResponse: *engineapi.RuleFail("xxx", engineapi.Mutation, "test", nil),
want: policyreportv1alpha2.PolicyReportResult{ want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno", Source: "kyverno",
Policy: "pod-requirements", Policy: "pod-requirements",
@ -223,7 +229,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "fail - audit warn", name: "fail - audit warn",
auditWarn: true, auditWarn: true,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil), engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleFail("xxx", engineapi.Mutation, "test"), ruleResponse: *engineapi.RuleFail("xxx", engineapi.Mutation, "test", nil),
want: policyreportv1alpha2.PolicyReportResult{ want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno", Source: "kyverno",
Policy: "pod-requirements", Policy: "pod-requirements",
@ -239,7 +245,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "error", name: "error",
auditWarn: false, auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil), engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleError("xxx", engineapi.Mutation, "test", nil), ruleResponse: *engineapi.RuleError("xxx", engineapi.Mutation, "test", nil, nil),
want: policyreportv1alpha2.PolicyReportResult{ want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno", Source: "kyverno",
Policy: "pod-requirements", Policy: "pod-requirements",
@ -255,7 +261,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "warn", name: "warn",
auditWarn: false, auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil), engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleWarn("xxx", engineapi.Mutation, "test"), ruleResponse: *engineapi.RuleWarn("xxx", engineapi.Mutation, "test", nil),
want: policyreportv1alpha2.PolicyReportResult{ want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno", Source: "kyverno",
Policy: "pod-requirements", Policy: "pod-requirements",
@ -294,7 +300,7 @@ func TestPSSComputePolicyReportResult(t *testing.T) {
name: "fail", name: "fail",
auditWarn: false, auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil), engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleFail("xxx", engineapi.Mutation, "test"), ruleResponse: *engineapi.RuleFail("xxx", engineapi.Mutation, "test", nil),
want: policyreportv1alpha2.PolicyReportResult{ want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno", Source: "kyverno",
Policy: "psa", Policy: "psa",

View file

@ -2808,6 +2808,12 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -7667,6 +7673,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -17166,6 +17179,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-

View file

@ -2809,6 +2809,12 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -7669,6 +7675,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -17169,6 +17182,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-

View file

@ -8007,6 +8007,12 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -12866,6 +12872,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -22365,6 +22378,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -27546,6 +27566,12 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -32406,6 +32432,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-
@ -41906,6 +41939,13 @@ spec:
will be deprecated in the next major release. will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/ See: https://kyverno.io/docs/writing-policies/preconditions/
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
reportProperties:
additionalProperties:
type: string
description: ReportProperties are the additional properties
from the rule that will be added to the policy report
result
type: object
skipBackgroundRequests: skipBackgroundRequests:
default: true default: true
description: |- description: |-

View file

@ -3828,6 +3828,18 @@ string
</tr> </tr>
<tr> <tr>
<td> <td>
<code>reportProperties</code><br/>
<em>
map[string]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>ReportProperties are the additional properties from the rule that will be added to the policy report result</p>
</td>
</tr>
<tr>
<td>
<code>match</code><br/> <code>match</code><br/>
<em> <em>
<a href="#kyverno.io/v1.MatchResources"> <a href="#kyverno.io/v1.MatchResources">

View file

@ -7590,6 +7590,33 @@ declaration to specify which resources to exclude.</p>
<tr>
<td><code>reportProperties</code>
</br>
<span style="font-family: monospace">map[string]string</span>
</td>
<td>
<p>ReportProperties are the additional properties from the rule that will be added to the policy report result</p>
</td>
</tr>
<tr> <tr>
<td><code>match</code> <td><code>match</code>

View file

@ -28,6 +28,7 @@ import (
type RuleApplyConfiguration struct { type RuleApplyConfiguration struct {
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
Context []ContextEntryApplyConfiguration `json:"context,omitempty"` Context []ContextEntryApplyConfiguration `json:"context,omitempty"`
ReportProperties map[string]string `json:"reportProperties,omitempty"`
MatchResources *MatchResourcesApplyConfiguration `json:"match,omitempty"` MatchResources *MatchResourcesApplyConfiguration `json:"match,omitempty"`
ExcludeResources *MatchResourcesApplyConfiguration `json:"exclude,omitempty"` ExcludeResources *MatchResourcesApplyConfiguration `json:"exclude,omitempty"`
ImageExtractors *kyvernov1.ImageExtractorConfigs `json:"imageExtractors,omitempty"` ImageExtractors *kyvernov1.ImageExtractorConfigs `json:"imageExtractors,omitempty"`
@ -67,6 +68,20 @@ func (b *RuleApplyConfiguration) WithContext(values ...*ContextEntryApplyConfigu
return b return b
} }
// WithReportProperties puts the entries into the ReportProperties field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the ReportProperties field,
// overwriting an existing map entries in ReportProperties field with the same key.
func (b *RuleApplyConfiguration) WithReportProperties(entries map[string]string) *RuleApplyConfiguration {
if b.ReportProperties == nil && len(entries) > 0 {
b.ReportProperties = make(map[string]string, len(entries))
}
for k, v := range entries {
b.ReportProperties[k] = v
}
return b
}
// WithMatchResources sets the MatchResources field in the declarative configuration to the given value // WithMatchResources sets the MatchResources field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations. // and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the MatchResources field is set to the value of the last call. // If called multiple times, the MatchResources field is set to the value of the last call.

View file

@ -112,7 +112,7 @@ func TestEngineResponse_IsOneOf(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("", Validation, ""), *RuleFail("", Validation, "", nil),
}, },
}, },
}, },
@ -121,7 +121,7 @@ func TestEngineResponse_IsOneOf(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("", Validation, ""), *RuleFail("", Validation, "", nil),
}, },
}, },
}, },
@ -133,7 +133,7 @@ func TestEngineResponse_IsOneOf(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("", Validation, ""), *RuleFail("", Validation, "", nil),
}, },
}, },
}, },
@ -145,7 +145,7 @@ func TestEngineResponse_IsOneOf(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("", Validation, ""), *RuleFail("", Validation, "", nil),
}, },
}, },
}, },
@ -157,7 +157,7 @@ func TestEngineResponse_IsOneOf(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("", Validation, ""), *RuleFail("", Validation, "", nil),
}, },
}, },
}, },
@ -197,7 +197,7 @@ func TestEngineResponse_IsSuccessful(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RulePass("", Validation, ""), *RulePass("", Validation, "", nil),
}, },
}, },
}, },
@ -206,7 +206,7 @@ func TestEngineResponse_IsSuccessful(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("", Validation, ""), *RuleFail("", Validation, "", nil),
}, },
}, },
}, },
@ -215,7 +215,7 @@ func TestEngineResponse_IsSuccessful(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleWarn("", Validation, ""), *RuleWarn("", Validation, "", nil),
}, },
}, },
}, },
@ -224,7 +224,7 @@ func TestEngineResponse_IsSuccessful(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleError("", Validation, "", nil), *RuleError("", Validation, "", nil, nil),
}, },
}, },
}, },
@ -233,7 +233,7 @@ func TestEngineResponse_IsSuccessful(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleSkip("", Validation, ""), *RuleSkip("", Validation, "", nil),
}, },
}, },
}, },
@ -270,7 +270,7 @@ func TestEngineResponse_IsSkipped(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RulePass("", Validation, ""), *RulePass("", Validation, "", nil),
}, },
}, },
}, },
@ -279,7 +279,7 @@ func TestEngineResponse_IsSkipped(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("", Validation, ""), *RuleFail("", Validation, "", nil),
}, },
}, },
}, },
@ -288,7 +288,7 @@ func TestEngineResponse_IsSkipped(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleWarn("", Validation, ""), *RuleWarn("", Validation, "", nil),
}, },
}, },
}, },
@ -297,7 +297,7 @@ func TestEngineResponse_IsSkipped(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleError("", Validation, "", nil), *RuleError("", Validation, "", nil, nil),
}, },
}, },
}, },
@ -306,7 +306,7 @@ func TestEngineResponse_IsSkipped(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleSkip("", Validation, ""), *RuleSkip("", Validation, "", nil),
}, },
}, },
}, },
@ -343,7 +343,7 @@ func TestEngineResponse_IsFailed(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RulePass("", Validation, ""), *RulePass("", Validation, "", nil),
}, },
}, },
}, },
@ -352,7 +352,7 @@ func TestEngineResponse_IsFailed(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("", Validation, ""), *RuleFail("", Validation, "", nil),
}, },
}, },
}, },
@ -361,7 +361,7 @@ func TestEngineResponse_IsFailed(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleWarn("", Validation, ""), *RuleWarn("", Validation, "", nil),
}, },
}, },
}, },
@ -370,7 +370,7 @@ func TestEngineResponse_IsFailed(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleError("", Validation, "", nil), *RuleError("", Validation, "", nil, nil),
}, },
}, },
}, },
@ -379,7 +379,7 @@ func TestEngineResponse_IsFailed(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleSkip("", Validation, ""), *RuleSkip("", Validation, "", nil),
}, },
}, },
}, },
@ -416,7 +416,7 @@ func TestEngineResponse_IsError(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RulePass("", Validation, ""), *RulePass("", Validation, "", nil),
}, },
}, },
}, },
@ -425,7 +425,7 @@ func TestEngineResponse_IsError(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("", Validation, ""), *RuleFail("", Validation, "", nil),
}, },
}, },
}, },
@ -434,7 +434,7 @@ func TestEngineResponse_IsError(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleWarn("", Validation, ""), *RuleWarn("", Validation, "", nil),
}, },
}, },
}, },
@ -443,7 +443,7 @@ func TestEngineResponse_IsError(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleError("", Validation, "", nil), *RuleError("", Validation, "", nil, nil),
}, },
}, },
}, },
@ -452,7 +452,7 @@ func TestEngineResponse_IsError(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleSkip("", Validation, ""), *RuleSkip("", Validation, "", nil),
}, },
}, },
}, },
@ -487,7 +487,7 @@ func TestEngineResponse_GetFailedRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleSkip("skip", Validation, ""), *RuleSkip("skip", Validation, "", nil),
}, },
}, },
}, },
@ -495,7 +495,7 @@ func TestEngineResponse_GetFailedRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleWarn("warn", Validation, ""), *RuleWarn("warn", Validation, "", nil),
}, },
}, },
}, },
@ -503,7 +503,7 @@ func TestEngineResponse_GetFailedRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RulePass("pass", Validation, ""), *RulePass("pass", Validation, "", nil),
}, },
}, },
}, },
@ -511,7 +511,7 @@ func TestEngineResponse_GetFailedRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("fail", Validation, ""), *RuleFail("fail", Validation, "", nil),
}, },
}, },
}, },
@ -520,8 +520,8 @@ func TestEngineResponse_GetFailedRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("fail-1", Validation, ""), *RuleFail("fail-1", Validation, "", nil),
*RuleFail("fail-2", Validation, ""), *RuleFail("fail-2", Validation, "", nil),
}, },
}, },
}, },
@ -530,8 +530,8 @@ func TestEngineResponse_GetFailedRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("fail-1", Validation, ""), *RuleFail("fail-1", Validation, "", nil),
*RuleError("error-1", Validation, "", nil), *RuleError("error-1", Validation, "", nil, nil),
}, },
}, },
}, },
@ -540,8 +540,8 @@ func TestEngineResponse_GetFailedRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleError("error-1", Validation, "", nil), *RuleError("error-1", Validation, "", nil, nil),
*RuleError("error-2", Validation, "", nil), *RuleError("error-2", Validation, "", nil, nil),
}, },
}, },
}, },
@ -576,7 +576,7 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleSkip("skip", Validation, ""), *RuleSkip("skip", Validation, "", nil),
}, },
}, },
}, },
@ -584,7 +584,7 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleWarn("warn", Validation, ""), *RuleWarn("warn", Validation, "", nil),
}, },
}, },
}, },
@ -592,8 +592,8 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RulePass("pass-1", Validation, ""), *RulePass("pass-1", Validation, "", nil),
*RulePass("pass-2", Validation, ""), *RulePass("pass-2", Validation, "", nil),
}, },
}, },
}, },
@ -602,7 +602,7 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RulePass("pass", Validation, ""), *RulePass("pass", Validation, "", nil),
}, },
}, },
}, },
@ -611,8 +611,8 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RulePass("pass", Validation, ""), *RulePass("pass", Validation, "", nil),
*RuleFail("fail", Validation, ""), *RuleFail("fail", Validation, "", nil),
}, },
}, },
}, },
@ -621,8 +621,8 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RulePass("pass", Validation, ""), *RulePass("pass", Validation, "", nil),
*RuleSkip("skip", Validation, ""), *RuleSkip("skip", Validation, "", nil),
}, },
}, },
}, },
@ -631,7 +631,7 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("fail", Validation, ""), *RuleFail("fail", Validation, "", nil),
}, },
}, },
}, },
@ -639,8 +639,8 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("fail-1", Validation, ""), *RuleFail("fail-1", Validation, "", nil),
*RuleFail("fail-2", Validation, ""), *RuleFail("fail-2", Validation, "", nil),
}, },
}, },
}, },
@ -648,8 +648,8 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleFail("fail-1", Validation, ""), *RuleFail("fail-1", Validation, "", nil),
*RuleError("error-1", Validation, "", nil), *RuleError("error-1", Validation, "", nil, nil),
}, },
}, },
}, },
@ -657,8 +657,8 @@ func TestEngineResponse_GetSuccessRules(t *testing.T) {
fields: fields{ fields: fields{
PolicyResponse: PolicyResponse{ PolicyResponse: PolicyResponse{
Rules: []RuleResponse{ Rules: []RuleResponse{
*RuleError("error-1", Validation, "", nil), *RuleError("error-1", Validation, "", nil, nil),
*RuleError("error-2", Validation, "", nil), *RuleError("error-2", Validation, "", nil, nil),
}, },
}, },
}, },

View file

@ -49,9 +49,11 @@ type RuleResponse struct {
binding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding binding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding
// emitWarning enable passing rule message as warning to api server warning header // emitWarning enable passing rule message as warning to api server warning header
emitWarning bool emitWarning bool
// properties are the additional properties from the rule that will be added to the policy report result
properties map[string]string
} }
func NewRuleResponse(name string, ruleType RuleType, msg string, status RuleStatus) *RuleResponse { func NewRuleResponse(name string, ruleType RuleType, msg string, status RuleStatus, properties map[string]string) *RuleResponse {
emitWarn := false emitWarn := false
if status == RuleStatusError || status == RuleStatusFail || status == RuleStatusWarn { if status == RuleStatusError || status == RuleStatusFail || status == RuleStatusWarn {
emitWarn = true emitWarn = true
@ -62,30 +64,31 @@ func NewRuleResponse(name string, ruleType RuleType, msg string, status RuleStat
message: msg, message: msg,
status: status, status: status,
emitWarning: emitWarn, emitWarning: emitWarn,
properties: properties,
} }
} }
func RuleError(name string, ruleType RuleType, msg string, err error) *RuleResponse { func RuleError(name string, ruleType RuleType, msg string, err error, properties map[string]string) *RuleResponse {
if err != nil { if err != nil {
return NewRuleResponse(name, ruleType, fmt.Sprintf("%s: %s", msg, err.Error()), RuleStatusError) return NewRuleResponse(name, ruleType, fmt.Sprintf("%s: %s", msg, err.Error()), RuleStatusError, properties)
} }
return NewRuleResponse(name, ruleType, msg, RuleStatusError) return NewRuleResponse(name, ruleType, msg, RuleStatusError, properties)
} }
func RuleSkip(name string, ruleType RuleType, msg string) *RuleResponse { func RuleSkip(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusSkip) return NewRuleResponse(name, ruleType, msg, RuleStatusSkip, properties)
} }
func RuleWarn(name string, ruleType RuleType, msg string) *RuleResponse { func RuleWarn(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusWarn) return NewRuleResponse(name, ruleType, msg, RuleStatusWarn, properties)
} }
func RulePass(name string, ruleType RuleType, msg string) *RuleResponse { func RulePass(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusPass) return NewRuleResponse(name, ruleType, msg, RuleStatusPass, properties)
} }
func RuleFail(name string, ruleType RuleType, msg string) *RuleResponse { func RuleFail(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusFail) return NewRuleResponse(name, ruleType, msg, RuleStatusFail, properties)
} }
func (r RuleResponse) WithExceptions(exceptions []kyvernov2.PolicyException) *RuleResponse { func (r RuleResponse) WithExceptions(exceptions []kyvernov2.PolicyException) *RuleResponse {
@ -173,6 +176,10 @@ func (r *RuleResponse) EmitWarning() bool {
return r.emitWarning return r.emitWarning
} }
func (r *RuleResponse) Properties() map[string]string {
return r.properties
}
// HasStatus checks if rule status is in a given list // HasStatus checks if rule status is in a given list
func (r *RuleResponse) HasStatus(status ...RuleStatus) bool { func (r *RuleResponse) HasStatus(status ...RuleStatus) bool {
for _, s := range status { for _, s := range status {

View file

@ -51,6 +51,7 @@ func TestRuleResponse_String(t *testing.T) {
tt.fields.Type, tt.fields.Type,
tt.fields.Message, tt.fields.Message,
tt.fields.Status, tt.fields.Status,
nil,
) )
if got := rr.String(); got != tt.want { if got := rr.String(); got != tt.want {
t.Errorf("RuleResponse.ToString() = %v, want %v", got, tt.want) t.Errorf("RuleResponse.ToString() = %v, want %v", got, tt.want)
@ -119,6 +120,7 @@ func TestRuleResponse_HasStatus(t *testing.T) {
tt.fields.Type, tt.fields.Type,
tt.fields.Message, tt.fields.Message,
tt.fields.Status, tt.fields.Status,
nil,
) )
if got := r.HasStatus(tt.args.status...); got != tt.want { if got := r.HasStatus(tt.args.status...); got != tt.want {
t.Errorf("RuleResponse.HasStatus() = %v, want %v", got, tt.want) t.Errorf("RuleResponse.HasStatus() = %v, want %v", got, tt.want)

View file

@ -73,13 +73,13 @@ func (e *engine) filterRule(
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i]) key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil { if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName()) logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return engineapi.RuleError(rule.Name, ruleType, "failed to compute exception key", err) return engineapi.RuleError(rule.Name, ruleType, "failed to compute exception key", err, rule.ReportProperties)
} }
keys = append(keys, key) keys = append(keys, key)
} }
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return engineapi.RuleSkip(rule.Name, ruleType, "rule is skipped due to policy exception "+strings.Join(keys, ", ")).WithExceptions(matchedExceptions) return engineapi.RuleSkip(rule.Name, ruleType, "rule is skipped due to policy exception "+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions)
} }
newResource := policyContext.NewResource() newResource := policyContext.NewResource()
@ -93,7 +93,7 @@ func (e *engine) filterRule(
if ruleType == engineapi.Generation { if ruleType == engineapi.Generation {
// if the oldResource matched, return "false" to delete GR for it // if the oldResource matched, return "false" to delete GR for it
if err = engineutils.MatchesResourceDescription(oldResource, rule, admissionInfo, namespaceLabels, policy.GetNamespace(), gvk, subresource, policyContext.Operation()); err == nil { if err = engineutils.MatchesResourceDescription(oldResource, rule, admissionInfo, namespaceLabels, policy.GetNamespace(), gvk, subresource, policyContext.Operation()); err == nil {
return engineapi.RuleFail(rule.Name, ruleType, "") return engineapi.RuleFail(rule.Name, ruleType, "", rule.ReportProperties)
} }
} }
logger.V(4).Info("rule not matched", "reason", err.Error()) logger.V(4).Info("rule not matched", "reason", err.Error())
@ -113,32 +113,32 @@ func (e *engine) filterRule(
copyConditions, err := engineutils.TransformConditions(rule.GetAnyAllConditions()) copyConditions, err := engineutils.TransformConditions(rule.GetAnyAllConditions())
if err != nil { if err != nil {
logger.V(4).Info("cannot copy AnyAllConditions", "reason", err.Error()) logger.V(4).Info("cannot copy AnyAllConditions", "reason", err.Error())
return engineapi.RuleError(rule.Name, ruleType, "failed to convert AnyAllConditions", err) return engineapi.RuleError(rule.Name, ruleType, "failed to convert AnyAllConditions", err, rule.ReportProperties)
} }
// evaluate pre-conditions // evaluate pre-conditions
pass, msg, err := variables.EvaluateConditions(logger, policyContext.JSONContext(), copyConditions) pass, msg, err := variables.EvaluateConditions(logger, policyContext.JSONContext(), copyConditions)
if err != nil { if err != nil {
return engineapi.RuleError(rule.Name, ruleType, "failed to evaluate conditions", err) return engineapi.RuleError(rule.Name, ruleType, "failed to evaluate conditions", err, rule.ReportProperties)
} }
if pass { if pass {
return engineapi.RulePass(rule.Name, ruleType, "") return engineapi.RulePass(rule.Name, ruleType, "", rule.ReportProperties)
} }
if policyContext.OldResource().Object != nil { if policyContext.OldResource().Object != nil {
if err = policyContext.JSONContext().AddResource(policyContext.OldResource().Object); err != nil { if err = policyContext.JSONContext().AddResource(policyContext.OldResource().Object); err != nil {
return engineapi.RuleError(rule.Name, ruleType, "failed to update JSON context for old resource", err) return engineapi.RuleError(rule.Name, ruleType, "failed to update JSON context for old resource", err, rule.ReportProperties)
} }
if val, msg, err := variables.EvaluateConditions(logger, policyContext.JSONContext(), copyConditions); err != nil { if val, msg, err := variables.EvaluateConditions(logger, policyContext.JSONContext(), copyConditions); err != nil {
return engineapi.RuleError(rule.Name, ruleType, "failed to evaluate conditions for old resource", err) return engineapi.RuleError(rule.Name, ruleType, "failed to evaluate conditions for old resource", err, rule.ReportProperties)
} else { } else {
if val { if val {
return engineapi.RuleFail(rule.Name, ruleType, msg) return engineapi.RuleFail(rule.Name, ruleType, msg, rule.ReportProperties)
} }
} }
} }
logger.V(4).Info("skip rule as preconditions are not met", "rule", rule.Name, "message", msg) logger.V(4).Info("skip rule as preconditions are not met", "rule", rule.Name, "message", msg)
return engineapi.RuleSkip(rule.Name, ruleType, "") return engineapi.RuleSkip(rule.Name, ruleType, "", rule.ReportProperties)
} }

View file

@ -280,6 +280,10 @@ func (e *engine) invokeRuleHandler(
s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ") s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ")
return resource, handlers.WithSkip(rule, ruleType, s) return resource, handlers.WithSkip(rule, ruleType, s)
} }
// substitute properties
if err := internal.SubstitutePropertiesInRule(logger, &rule, policyContext.JSONContext()); err != nil {
logger.Error(err, "failed to substitute variables in rule properties")
}
// get policy exceptions that matches both policy and rule name // get policy exceptions that matches both policy and rule name
exceptions, err := e.GetPolicyExceptions(policyContext.Policy(), rule.Name) exceptions, err := e.GetPolicyExceptions(policyContext.Policy(), rule.Name)
if err != nil { if err != nil {

View file

@ -23,19 +23,19 @@ type Handler interface {
} }
func WithError(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string, err error) []engineapi.RuleResponse { func WithError(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string, err error) []engineapi.RuleResponse {
return WithResponses(engineapi.RuleError(rule.Name, ruleType, msg, err)) return WithResponses(engineapi.RuleError(rule.Name, ruleType, msg, err, rule.ReportProperties))
} }
func WithSkip(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) []engineapi.RuleResponse { func WithSkip(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) []engineapi.RuleResponse {
return WithResponses(engineapi.RuleSkip(rule.Name, ruleType, msg)) return WithResponses(engineapi.RuleSkip(rule.Name, ruleType, msg, rule.ReportProperties))
} }
func WithPass(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) []engineapi.RuleResponse { func WithPass(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) []engineapi.RuleResponse {
return WithResponses(engineapi.RulePass(rule.Name, ruleType, msg)) return WithResponses(engineapi.RulePass(rule.Name, ruleType, msg, rule.ReportProperties))
} }
func WithFail(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) []engineapi.RuleResponse { func WithFail(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string) []engineapi.RuleResponse {
return WithResponses(engineapi.RuleFail(rule.Name, ruleType, msg)) return WithResponses(engineapi.RuleFail(rule.Name, ruleType, msg, rule.ReportProperties))
} }
func WithResponses(rrs ...*engineapi.RuleResponse) []engineapi.RuleResponse { func WithResponses(rrs ...*engineapi.RuleResponse) []engineapi.RuleResponse {

View file

@ -152,6 +152,7 @@ func buildRuleResponse(rule *kyvernov1.Rule, mutateResp *mutate.Response, info r
engineapi.Mutation, engineapi.Mutation,
message, message,
mutateResp.Status, mutateResp.Status,
rule.ReportProperties,
) )
if mutateResp.Status == engineapi.RuleStatusPass { if mutateResp.Status == engineapi.RuleStatusPass {
if len(rule.Mutation.Targets) != 0 { if len(rule.Mutation.Targets) != 0 {

View file

@ -53,7 +53,7 @@ func (h mutateExistingHandler) Process(
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions), engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions),
) )
} }
@ -61,7 +61,7 @@ func (h mutateExistingHandler) Process(
logger.V(3).Info("processing mutate rule") logger.V(3).Info("processing mutate rule")
targets, err := loadTargets(ctx, h.client, rule.Mutation.Targets, policyContext, logger) targets, err := loadTargets(ctx, h.client, rule.Mutation.Targets, policyContext, logger)
if err != nil { if err != nil {
rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "", err) rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "", err, rule.ReportProperties)
responses = append(responses, *rr) responses = append(responses, *rr)
} }
@ -76,20 +76,20 @@ func (h mutateExistingHandler) Process(
} }
// load target specific context // load target specific context
if err := contextLoader(ctx, target.context, policyContext.JSONContext()); err != nil { if err := contextLoader(ctx, target.context, policyContext.JSONContext()); err != nil {
rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "failed to load context", err) rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "failed to load context", err, rule.ReportProperties)
responses = append(responses, *rr) responses = append(responses, *rr)
continue continue
} }
// load target specific preconditions // load target specific preconditions
preconditionsPassed, msg, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), target.preconditions) preconditionsPassed, msg, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), target.preconditions)
if err != nil { if err != nil {
rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "failed to evaluate preconditions", err) rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "failed to evaluate preconditions", err, rule.ReportProperties)
responses = append(responses, *rr) responses = append(responses, *rr)
continue continue
} }
if !preconditionsPassed { if !preconditionsPassed {
s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ") s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ")
rr := engineapi.RuleSkip(rule.Name, engineapi.Mutation, s) rr := engineapi.RuleSkip(rule.Name, engineapi.Mutation, s, rule.ReportProperties)
responses = append(responses, *rr) responses = append(responses, *rr)
continue continue
} }

View file

@ -84,7 +84,7 @@ func (h mutateImageHandler) Process(
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions), engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions),
) )
} }
@ -92,7 +92,7 @@ func (h mutateImageHandler) Process(
ruleCopy, err := substituteVariables(rule, jsonContext, logger) ruleCopy, err := substituteVariables(rule, jsonContext, logger)
if err != nil { if err != nil {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to substitute variables", err), engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to substitute variables", err, rule.ReportProperties),
) )
} }
var engineResponses []*engineapi.RuleResponse var engineResponses []*engineapi.RuleResponse
@ -101,7 +101,7 @@ func (h mutateImageHandler) Process(
rclient, err := h.rclientFactory.GetClient(ctx, imageVerify.ImageRegistryCredentials) rclient, err := h.rclientFactory.GetClient(ctx, imageVerify.ImageRegistryCredentials)
if err != nil { if err != nil {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to fetch secrets", err), engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to fetch secrets", err, rule.ReportProperties),
) )
} }
iv := internal.NewImageVerifier(logger, rclient, h.ivCache, policyContext, *ruleCopy, h.ivm) iv := internal.NewImageVerifier(logger, rclient, h.ivCache, policyContext, *ruleCopy, h.ivm)
@ -114,25 +114,25 @@ func (h mutateImageHandler) Process(
decoded, err := json_patch.DecodePatch(patch) decoded, err := json_patch.DecodePatch(patch)
if err != nil { if err != nil {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to decode patch", err), engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to decode patch", err, rule.ReportProperties),
) )
} }
options := &json_patch.ApplyOptions{SupportNegativeIndices: true, AllowMissingPathOnRemove: true, EnsurePathExistsOnAdd: true} options := &json_patch.ApplyOptions{SupportNegativeIndices: true, AllowMissingPathOnRemove: true, EnsurePathExistsOnAdd: true}
resourceBytes, err := resource.MarshalJSON() resourceBytes, err := resource.MarshalJSON()
if err != nil { if err != nil {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to marshal resource", err), engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to marshal resource", err, rule.ReportProperties),
) )
} }
patchedResourceBytes, err := decoded.ApplyWithOptions(resourceBytes, options) patchedResourceBytes, err := decoded.ApplyWithOptions(resourceBytes, options)
if err != nil { if err != nil {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to apply patch", err), engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to apply patch", err, rule.ReportProperties),
) )
} }
if err := resource.UnmarshalJSON(patchedResourceBytes); err != nil { if err := resource.UnmarshalJSON(patchedResourceBytes); err != nil {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to unmarshal resource", err), engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to unmarshal resource", err, rule.ReportProperties),
) )
} }
} }

View file

@ -46,7 +46,7 @@ func (h mutateResourceHandler) Process(
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions), engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions),
) )
} }

View file

@ -65,7 +65,7 @@ func (h validateAssertHandler) Process(
} }
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions), engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions),
) )
} }
// load context // load context
@ -79,7 +79,7 @@ func (h validateAssertHandler) Process(
logger.Error(err, "failed to load context") logger.Error(err, "failed to load context")
} }
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.Validation, "failed to load context", err), engineapi.RuleError(rule.Name, engineapi.Validation, "failed to load context", err, rule.ReportProperties),
) )
} }
// prepare bindings // prepare bindings
@ -113,12 +113,12 @@ func (h validateAssertHandler) Process(
if len(errs) != 0 { if len(errs) != 0 {
var responses []*engineapi.RuleResponse var responses []*engineapi.RuleResponse
for _, err := range errs { for _, err := range errs {
responses = append(responses, engineapi.RuleFail(rule.Name, engineapi.Validation, err.Error())) responses = append(responses, engineapi.RuleFail(rule.Name, engineapi.Validation, err.Error(), rule.ReportProperties))
} }
return resource, handlers.WithResponses(responses...) return resource, handlers.WithResponses(responses...)
} }
msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name) msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RulePass(rule.Name, engineapi.Validation, msg), engineapi.RulePass(rule.Name, engineapi.Validation, msg, rule.ReportProperties),
) )
} }

View file

@ -63,7 +63,7 @@ func (h validateCELHandler) Process(
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions), engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions),
) )
} }
@ -144,7 +144,7 @@ func (h validateCELHandler) Process(
namespace, err = h.client.GetNamespace(ctx, ns, metav1.GetOptions{}) namespace, err = h.client.GetNamespace(ctx, ns, metav1.GetOptions{})
if err != nil { if err != nil {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.Validation, "Error getting the resource's namespace", err), engineapi.RuleError(rule.Name, engineapi.Validation, "Error getting the resource's namespace", err, rule.ReportProperties),
) )
} }
} else { } else {
@ -174,7 +174,7 @@ func (h validateCELHandler) Process(
params, err := collectParams(ctx, h.client, paramKind, paramRef, ns) params, err := collectParams(ctx, h.client, paramKind, paramRef, ns)
if err != nil { if err != nil {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.Validation, "error in parameterized resource", err), engineapi.RuleError(rule.Name, engineapi.Validation, "error in parameterized resource", err, rule.ReportProperties),
) )
} }
@ -189,7 +189,7 @@ func (h validateCELHandler) Process(
// no validations are returned if preconditions aren't met // no validations are returned if preconditions aren't met
if datautils.DeepEqual(validationResult, validating.ValidateResult{}) { if datautils.DeepEqual(validationResult, validating.ValidateResult{}) {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "cel preconditions not met"), engineapi.RuleSkip(rule.Name, engineapi.Validation, "cel preconditions not met", rule.ReportProperties),
) )
} }
@ -198,12 +198,12 @@ func (h validateCELHandler) Process(
case validating.ActionAdmit: case validating.ActionAdmit:
if decision.Evaluation == validating.EvalError { if decision.Evaluation == validating.EvalError {
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleError(rule.Name, engineapi.Validation, decision.Message, nil), engineapi.RuleError(rule.Name, engineapi.Validation, decision.Message, nil, rule.ReportProperties),
) )
} }
case validating.ActionDeny: case validating.ActionDeny:
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleFail(rule.Name, engineapi.Validation, decision.Message), engineapi.RuleFail(rule.Name, engineapi.Validation, decision.Message, rule.ReportProperties),
) )
} }
} }
@ -211,7 +211,7 @@ func (h validateCELHandler) Process(
msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name) msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RulePass(rule.Name, engineapi.Validation, msg), engineapi.RulePass(rule.Name, engineapi.Validation, msg, rule.ReportProperties),
) )
} }

View file

@ -62,7 +62,7 @@ func (h validateImageHandler) Process(
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions), engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions),
) )
} }

View file

@ -74,7 +74,7 @@ func (h validateManifestHandler) Process(
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions), engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions),
) )
} }

View file

@ -66,7 +66,7 @@ func (h validatePssHandler) Process(
} }
logger.V(3).Info("policy rule is skipped due to policy exception", "exception", key) logger.V(3).Info("policy rule is skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exception "+key).WithExceptions([]kyvernov2.PolicyException{polex}), engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exception "+key, rule.ReportProperties).WithExceptions([]kyvernov2.PolicyException{polex}),
) )
} }
} }
@ -99,7 +99,7 @@ func (h validatePssHandler) Process(
if allowed { if allowed {
msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name) msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RulePass(rule.Name, engineapi.Validation, msg).WithPodSecurityChecks(podSecurityChecks), engineapi.RulePass(rule.Name, engineapi.Validation, msg, rule.ReportProperties).WithPodSecurityChecks(podSecurityChecks),
) )
} else { } else {
// apply pod security exceptions if exist // apply pod security exceptions if exist
@ -120,12 +120,12 @@ func (h validatePssHandler) Process(
podSecurityChecks.Checks = pssChecks podSecurityChecks.Checks = pssChecks
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions "+strings.Join(keys, ", ")).WithExceptions(matchedExceptions).WithPodSecurityChecks(podSecurityChecks), engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions "+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions).WithPodSecurityChecks(podSecurityChecks),
) )
} }
msg := fmt.Sprintf(`Validation rule '%s' failed. It violates PodSecurity "%s:%s": %s`, rule.Name, podSecurity.Level, podSecurity.Version, pss.FormatChecksPrint(pssChecks)) msg := fmt.Sprintf(`Validation rule '%s' failed. It violates PodSecurity "%s:%s": %s`, rule.Name, podSecurity.Level, podSecurity.Version, pss.FormatChecksPrint(pssChecks))
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleFail(rule.Name, engineapi.Validation, msg).WithPodSecurityChecks(podSecurityChecks), engineapi.RuleFail(rule.Name, engineapi.Validation, msg, rule.ReportProperties).WithPodSecurityChecks(podSecurityChecks),
) )
} }
} }

View file

@ -53,7 +53,7 @@ func (h validateResourceHandler) Process(
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys) logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions), engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions),
) )
} }
v := newValidator(logger, contextLoader, policyContext, rule) v := newValidator(logger, contextLoader, policyContext, rule)
@ -120,15 +120,15 @@ func newForEachValidator(
func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse { func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if err := v.loadContext(ctx); err != nil { if err := v.loadContext(ctx); err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to load context", err) return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to load context", err, v.rule.ReportProperties)
} }
preconditionsPassed, msg, err := internal.CheckPreconditions(v.log, v.policyContext.JSONContext(), v.anyAllConditions) preconditionsPassed, msg, err := internal.CheckPreconditions(v.log, v.policyContext.JSONContext(), v.anyAllConditions)
if err != nil { if err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to evaluate preconditions", err) return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to evaluate preconditions", err, v.rule.ReportProperties)
} }
if !preconditionsPassed { if !preconditionsPassed {
s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ") s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ")
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, s) return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, s, v.rule.ReportProperties)
} }
if v.deny != nil { if v.deny != nil {
@ -137,7 +137,7 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if v.pattern != nil || v.anyPattern != nil { if v.pattern != nil || v.anyPattern != nil {
if err = v.substitutePatterns(); err != nil { if err = v.substitutePatterns(); err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "variable substitution failed", err) return engineapi.RuleError(v.rule.Name, engineapi.Validation, "variable substitution failed", err, v.rule.ReportProperties)
} }
ruleResponse := v.validateResourceWithRule() ruleResponse := v.validateResourceWithRule()
@ -145,7 +145,7 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if engineutils.IsUpdateRequest(v.policyContext) { if engineutils.IsUpdateRequest(v.policyContext) {
priorResp, err := v.validateOldObject(ctx) priorResp, err := v.validateOldObject(ctx)
if err != nil { if err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to validate old object", err) return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to validate old object", err, v.rule.ReportProperties)
} }
if engineutils.IsSameRuleResponse(ruleResponse, priorResp) { if engineutils.IsSameRuleResponse(ruleResponse, priorResp) {
@ -153,7 +153,7 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if ruleResponse.Status() == engineapi.RuleStatusPass { if ruleResponse.Status() == engineapi.RuleStatusPass {
return ruleResponse return ruleResponse
} }
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, "skipping modified resource as validation results have not changed") return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, "skipping modified resource as validation results have not changed", v.rule.ReportProperties)
} }
} }
@ -208,7 +208,7 @@ func (v *validator) validateForEach(ctx context.Context) *engineapi.RuleResponse
if applyCount == 0 { if applyCount == 0 {
return nil return nil
} }
return engineapi.RulePass(v.rule.Name, engineapi.Validation, "rule passed") return engineapi.RulePass(v.rule.Name, engineapi.Validation, "rule passed", v.rule.ReportProperties)
} }
func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*engineapi.RuleResponse, int) { func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*engineapi.RuleResponse, int) {
@ -225,13 +225,13 @@ func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForE
policyContext := v.policyContext.Copy() policyContext := v.policyContext.Copy()
if err := engineutils.AddElementToContext(policyContext, element, index, v.nesting, elementScope); err != nil { if err := engineutils.AddElementToContext(policyContext, element, index, v.nesting, elementScope); err != nil {
v.log.Error(err, "failed to add element to context") v.log.Error(err, "failed to add element to context")
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to process foreach", err), applyCount return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to process foreach", err, v.rule.ReportProperties), applyCount
} }
foreachValidator, err := newForEachValidator(foreach, v.contextLoader, v.nesting+1, v.rule, policyContext, v.log) foreachValidator, err := newForEachValidator(foreach, v.contextLoader, v.nesting+1, v.rule, policyContext, v.log)
if err != nil { if err != nil {
v.log.Error(err, "failed to create foreach validator") v.log.Error(err, "failed to create foreach validator")
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to create foreach validator", err), applyCount return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to create foreach validator", err, v.rule.ReportProperties), applyCount
} }
r := foreachValidator.validate(ctx) r := foreachValidator.validate(ctx)
@ -249,16 +249,16 @@ func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForE
continue continue
} }
msg := fmt.Sprintf("validation failure: %v", r.Message()) msg := fmt.Sprintf("validation failure: %v", r.Message())
return engineapi.NewRuleResponse(v.rule.Name, engineapi.Validation, msg, status), applyCount return engineapi.NewRuleResponse(v.rule.Name, engineapi.Validation, msg, status, v.rule.ReportProperties), applyCount
} }
msg := fmt.Sprintf("validation failure: %v", r.Message()) msg := fmt.Sprintf("validation failure: %v", r.Message())
return engineapi.NewRuleResponse(v.rule.Name, engineapi.Validation, msg, status), applyCount return engineapi.NewRuleResponse(v.rule.Name, engineapi.Validation, msg, status, v.rule.ReportProperties), applyCount
} }
applyCount++ applyCount++
} }
return engineapi.RulePass(v.rule.Name, engineapi.Validation, ""), applyCount return engineapi.RulePass(v.rule.Name, engineapi.Validation, "", v.rule.ReportProperties), applyCount
} }
func (v *validator) loadContext(ctx context.Context) error { func (v *validator) loadContext(ctx context.Context) error {
@ -275,12 +275,12 @@ func (v *validator) loadContext(ctx context.Context) error {
func (v *validator) validateDeny() *engineapi.RuleResponse { func (v *validator) validateDeny() *engineapi.RuleResponse {
if deny, msg, err := internal.CheckDenyPreconditions(v.log, v.policyContext.JSONContext(), v.deny.GetAnyAllConditions()); err != nil { if deny, msg, err := internal.CheckDenyPreconditions(v.log, v.policyContext.JSONContext(), v.deny.GetAnyAllConditions()); err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to check deny conditions", err) return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to check deny conditions", err, v.rule.ReportProperties)
} else { } else {
if deny { if deny {
return engineapi.RuleFail(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny, msg)) return engineapi.RuleFail(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny, msg), v.rule.ReportProperties)
} }
return engineapi.RulePass(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny, msg)) return engineapi.RulePass(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny, msg), v.rule.ReportProperties)
} }
} }
@ -329,22 +329,22 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
v.log.V(3).Info("validation error", "path", pe.Path, "error", err.Error()) v.log.V(3).Info("validation error", "path", pe.Path, "error", err.Error())
if pe.Skip { if pe.Skip {
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, pe.Error()) return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, pe.Error(), v.rule.ReportProperties)
} }
if pe.Path == "" { if pe.Path == "" {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, v.buildErrorMessage(err, ""), nil) return engineapi.RuleError(v.rule.Name, engineapi.Validation, v.buildErrorMessage(err, ""), nil, v.rule.ReportProperties)
} }
return engineapi.RuleFail(v.rule.Name, engineapi.Validation, v.buildErrorMessage(err, pe.Path)) return engineapi.RuleFail(v.rule.Name, engineapi.Validation, v.buildErrorMessage(err, pe.Path), v.rule.ReportProperties)
} }
return engineapi.RuleError(v.rule.Name, engineapi.Validation, v.buildErrorMessage(err, ""), nil) return engineapi.RuleError(v.rule.Name, engineapi.Validation, v.buildErrorMessage(err, ""), nil, v.rule.ReportProperties)
} }
v.log.V(4).Info("successfully processed rule") v.log.V(4).Info("successfully processed rule")
msg := fmt.Sprintf("validation rule '%s' passed.", v.rule.Name) msg := fmt.Sprintf("validation rule '%s' passed.", v.rule.Name)
return engineapi.RulePass(v.rule.Name, engineapi.Validation, msg) return engineapi.RulePass(v.rule.Name, engineapi.Validation, msg, v.rule.ReportProperties)
} }
if v.anyPattern != nil { if v.anyPattern != nil {
@ -354,14 +354,14 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
anyPatterns, err := deserializeAnyPattern(v.anyPattern) anyPatterns, err := deserializeAnyPattern(v.anyPattern)
if err != nil { if err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to deserialize anyPattern, expected type array", err) return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to deserialize anyPattern, expected type array", err, v.rule.ReportProperties)
} }
for idx, pattern := range anyPatterns { for idx, pattern := range anyPatterns {
err := validate.MatchPattern(v.log, resource.Object, pattern) err := validate.MatchPattern(v.log, resource.Object, pattern)
if err == nil { if err == nil {
msg := fmt.Sprintf("validation rule '%s' anyPattern[%d] passed.", v.rule.Name, idx) msg := fmt.Sprintf("validation rule '%s' anyPattern[%d] passed.", v.rule.Name, idx)
return engineapi.RulePass(v.rule.Name, engineapi.Validation, msg) return engineapi.RulePass(v.rule.Name, engineapi.Validation, msg, v.rule.ReportProperties)
} }
if pe, ok := err.(*validate.PatternError); ok { if pe, ok := err.(*validate.PatternError); ok {
@ -389,7 +389,7 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
errorStr = append(errorStr, err.Error()) errorStr = append(errorStr, err.Error())
} }
v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' skipped. %s", v.rule.Name, errorStr)) v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' skipped. %s", v.rule.Name, errorStr))
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, strings.Join(errorStr, " ")) return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, strings.Join(errorStr, " "), v.rule.ReportProperties)
} else if len(failedAnyPatternsErrors) > 0 { } else if len(failedAnyPatternsErrors) > 0 {
var errorStr []string var errorStr []string
for _, err := range failedAnyPatternsErrors { for _, err := range failedAnyPatternsErrors {
@ -398,11 +398,11 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' failed. %s", v.rule.Name, errorStr)) v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' failed. %s", v.rule.Name, errorStr))
msg := v.buildAnyPatternErrorMessage(errorStr) msg := v.buildAnyPatternErrorMessage(errorStr)
return engineapi.RuleFail(v.rule.Name, engineapi.Validation, msg) return engineapi.RuleFail(v.rule.Name, engineapi.Validation, msg, v.rule.ReportProperties)
} }
} }
return engineapi.RulePass(v.rule.Name, engineapi.Validation, v.rule.Validation.Message) return engineapi.RulePass(v.rule.Name, engineapi.Validation, v.rule.Validation.Message, v.rule.ReportProperties)
} }
func deserializeAnyPattern(anyPattern apiextensions.JSON) ([]interface{}, error) { func deserializeAnyPattern(anyPattern apiextensions.JSON) ([]interface{}, error) {

View file

@ -240,7 +240,7 @@ func (iv *ImageVerifier) Verify(
if HasImageVerifiedAnnotationChanged(iv.policyContext, iv.logger) { if HasImageVerifiedAnnotationChanged(iv.policyContext, iv.logger) {
msg := kyverno.AnnotationImageVerify + " annotation cannot be changed" msg := kyverno.AnnotationImageVerify + " annotation cannot be changed"
iv.logger.Info("image verification error", "reason", msg, "image", image) iv.logger.Info("image verification error", "reason", msg, "image", image)
responses = append(responses, engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, msg)) responses = append(responses, engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, msg, iv.rule.ReportProperties))
continue continue
} }
@ -273,7 +273,7 @@ func (iv *ImageVerifier) Verify(
var digest string var digest string
if isInCache { if isInCache {
iv.logger.V(2).Info("cache entry found", "namespace", iv.policyContext.Policy().GetNamespace(), "policy", iv.policyContext.Policy().GetName(), "ruleName", iv.rule.Name, "imageRef", image) iv.logger.V(2).Info("cache entry found", "namespace", iv.policyContext.Policy().GetNamespace(), "policy", iv.policyContext.Policy().GetName(), "ruleName", iv.rule.Name, "imageRef", image)
ruleResp = engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, "verified from cache") ruleResp = engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, "verified from cache", iv.rule.ReportProperties)
digest = imageInfo.Digest digest = imageInfo.Digest
} else { } else {
iv.logger.V(2).Info("cache entry not found", "namespace", iv.policyContext.Policy().GetNamespace(), "policy", iv.policyContext.Policy().GetName(), "ruleName", iv.rule.Name, "imageRef", image) iv.logger.V(2).Info("cache entry not found", "namespace", iv.policyContext.Policy().GetNamespace(), "policy", iv.policyContext.Policy().GetName(), "ruleName", iv.rule.Name, "imageRef", image)
@ -296,10 +296,10 @@ func (iv *ImageVerifier) Verify(
if imageVerify.MutateDigest { if imageVerify.MutateDigest {
patch, retrievedDigest, err := iv.handleMutateDigest(ctx, digest, imageInfo) patch, retrievedDigest, err := iv.handleMutateDigest(ctx, digest, imageInfo)
if err != nil { if err != nil {
responses = append(responses, engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, "failed to update digest", err)) responses = append(responses, engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, "failed to update digest", err, iv.rule.ReportProperties))
} else if patch != nil { } else if patch != nil {
if ruleResp == nil { if ruleResp == nil {
ruleResp = engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, "mutated image digest") ruleResp = engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, "mutated image digest", iv.rule.ReportProperties)
} }
patches = append(patches, *patch) patches = append(patches, *patch)
imageInfo.Digest = retrievedDigest imageInfo.Digest = retrievedDigest
@ -335,17 +335,17 @@ func (iv *ImageVerifier) verifyImage(
iv.logger.V(2).Info("verifying image signatures", "image", image, "attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations)) iv.logger.V(2).Info("verifying image signatures", "image", image, "attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations))
if err := iv.policyContext.JSONContext().AddImageInfo(imageInfo, cfg); err != nil { if err := iv.policyContext.JSONContext().AddImageInfo(imageInfo, cfg); err != nil {
iv.logger.Error(err, "failed to add image to context", "image", image) iv.logger.Error(err, "failed to add image to context", "image", image)
return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("failed to add image to context %s", image), err), "" return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("failed to add image to context %s", image), err, iv.rule.ReportProperties), ""
} }
if len(imageVerify.Attestors) > 0 { if len(imageVerify.Attestors) > 0 {
if !matchReferences(imageVerify.ImageReferences, image) { if !matchReferences(imageVerify.ImageReferences, image) {
return engineapi.RuleSkip(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("skipping image reference image %s, policy %s ruleName %s", image, iv.policyContext.Policy().GetName(), iv.rule.Name)), "" return engineapi.RuleSkip(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("skipping image reference image %s, policy %s ruleName %s", image, iv.policyContext.Policy().GetName(), iv.rule.Name), iv.rule.ReportProperties), ""
} }
if matchReferences(imageVerify.SkipImageReferences, image) { if matchReferences(imageVerify.SkipImageReferences, image) {
iv.logger.Info("skipping image reference", "image", image, "policy", iv.policyContext.Policy().GetName(), "ruleName", iv.rule.Name) iv.logger.Info("skipping image reference", "image", image, "policy", iv.policyContext.Policy().GetName(), "ruleName", iv.rule.Name)
iv.ivm.Add(image, engineapi.ImageVerificationSkip) iv.ivm.Add(image, engineapi.ImageVerificationSkip)
return engineapi.RuleSkip(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("skipping image reference image %s, policy %s ruleName %s", image, iv.policyContext.Policy().GetName(), iv.rule.Name)).WithEmitWarning(true), "" return engineapi.RuleSkip(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("skipping image reference image %s, policy %s ruleName %s", image, iv.policyContext.Policy().GetName(), iv.rule.Name), iv.rule.ReportProperties).WithEmitWarning(true), ""
} }
ruleResp, cosignResp := iv.verifyAttestors(ctx, imageVerify.Attestors, imageVerify, imageInfo) ruleResp, cosignResp := iv.verifyAttestors(ctx, imageVerify.Attestors, imageVerify, imageInfo)
if ruleResp.Status() != engineapi.RuleStatusPass { if ruleResp.Status() != engineapi.RuleStatusPass {
@ -381,10 +381,10 @@ func (iv *ImageVerifier) verifyAttestors(
} }
} }
if cosignResponse == nil { if cosignResponse == nil {
return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, "invalid response", fmt.Errorf("nil")), nil return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, "invalid response", fmt.Errorf("nil"), iv.rule.ReportProperties), nil
} }
msg := fmt.Sprintf("verified image signatures for %s", image) msg := fmt.Sprintf("verified image signatures for %s", image)
return engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, msg), cosignResponse return engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, msg, iv.rule.ReportProperties), cosignResponse
} }
// handle registry network errors as a rule error (instead of a policy failure) // handle registry network errors as a rule error (instead of a policy failure)
@ -392,9 +392,9 @@ func (iv *ImageVerifier) handleRegistryErrors(image string, err error) *engineap
msg := fmt.Sprintf("failed to verify image %s: %s", image, err.Error()) msg := fmt.Sprintf("failed to verify image %s: %s", image, err.Error())
var netErr *net.OpError var netErr *net.OpError
if errors.As(err, &netErr) { if errors.As(err, &netErr) {
return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("failed to verify image %s", image), err) return engineapi.RuleError(iv.rule.Name, engineapi.ImageVerify, fmt.Sprintf("failed to verify image %s", image), err, iv.rule.ReportProperties)
} }
return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, msg) return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, msg, iv.rule.ReportProperties)
} }
func (iv *ImageVerifier) verifyAttestations( func (iv *ImageVerifier) verifyAttestations(
@ -409,7 +409,7 @@ func (iv *ImageVerifier) verifyAttestations(
iv.logger.V(2).Info(fmt.Sprintf("attestation %+v", attestation)) iv.logger.V(2).Info(fmt.Sprintf("attestation %+v", attestation))
if attestation.Type == "" && attestation.PredicateType == "" { if attestation.Type == "" && attestation.PredicateType == "" {
return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, path+": missing type"), "" return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, path+": missing type", iv.rule.ReportProperties), ""
} }
if attestation.Type == "" && attestation.PredicateType != "" { if attestation.Type == "" && attestation.PredicateType != "" {
@ -464,7 +464,7 @@ func (iv *ImageVerifier) verifyAttestations(
} }
if verifiedCount < requiredCount { if verifiedCount < requiredCount {
msg := fmt.Sprintf("image attestations verification failed, verifiedCount: %v, requiredCount: %v, error: %s", verifiedCount, requiredCount, errMsg) msg := fmt.Sprintf("image attestations verification failed, verifiedCount: %v, requiredCount: %v, error: %s", verifiedCount, requiredCount, errMsg)
return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, msg), "" return engineapi.RuleFail(iv.rule.Name, engineapi.ImageVerify, msg, iv.rule.ReportProperties), ""
} }
} }
@ -473,7 +473,7 @@ func (iv *ImageVerifier) verifyAttestations(
msg := fmt.Sprintf("verified image attestations for %s", image) msg := fmt.Sprintf("verified image attestations for %s", image)
iv.logger.V(2).Info(msg) iv.logger.V(2).Info(msg)
return engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, msg), imageInfo.Digest return engineapi.RulePass(iv.rule.Name, engineapi.ImageVerify, msg, iv.rule.ReportProperties), imageInfo.Digest
} }
func (iv *ImageVerifier) verifyAttestorSet( func (iv *ImageVerifier) verifyAttestorSet(

View file

@ -0,0 +1,21 @@
package internal
import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/variables"
)
func SubstitutePropertiesInRule(log logr.Logger, rule *kyvernov1.Rule, jsonContext enginecontext.Interface) error {
if len(rule.ReportProperties) == 0 {
return nil
}
properties := rule.ReportProperties
updatedProperties, err := variables.SubstituteAllInType(log, jsonContext, &properties)
if err != nil {
return err
}
rule.ReportProperties = *updatedProperties
return nil
}

View file

@ -38,9 +38,9 @@ type jsonPatch struct {
func applyPatches(rule *types.Rule, resource unstructured.Unstructured) (*engineapi.RuleResponse, unstructured.Unstructured) { func applyPatches(rule *types.Rule, resource unstructured.Unstructured) (*engineapi.RuleResponse, unstructured.Unstructured) {
mutateResp := Mutate(rule, context.NewContext(jmespath.New(config.NewDefaultConfiguration(false))), resource, logr.Discard()) mutateResp := Mutate(rule, context.NewContext(jmespath.New(config.NewDefaultConfiguration(false))), resource, logr.Discard())
if mutateResp.Status != engineapi.RuleStatusPass { if mutateResp.Status != engineapi.RuleStatusPass {
return engineapi.NewRuleResponse("", engineapi.Mutation, mutateResp.Message, mutateResp.Status), resource return engineapi.NewRuleResponse("", engineapi.Mutation, mutateResp.Message, mutateResp.Status, rule.ReportProperties), resource
} }
return engineapi.RulePass("", engineapi.Mutation, mutateResp.Message), mutateResp.PatchedResource return engineapi.RulePass("", engineapi.Mutation, mutateResp.Message, rule.ReportProperties), mutateResp.PatchedResource
} }
func TestProcessPatches_EmptyPatches(t *testing.T) { func TestProcessPatches_EmptyPatches(t *testing.T) {

View file

@ -90,12 +90,13 @@ func SeverityFromString(severity string) policyreportv1alpha2.PolicySeverity {
func ToPolicyReportResult(policyType engineapi.PolicyType, policyName string, ruleResult engineapi.RuleResponse, annotations map[string]string, resource *corev1.ObjectReference) policyreportv1alpha2.PolicyReportResult { func ToPolicyReportResult(policyType engineapi.PolicyType, policyName string, ruleResult engineapi.RuleResponse, annotations map[string]string, resource *corev1.ObjectReference) policyreportv1alpha2.PolicyReportResult {
result := policyreportv1alpha2.PolicyReportResult{ result := policyreportv1alpha2.PolicyReportResult{
Source: kyverno.ValueKyvernoApp, Source: kyverno.ValueKyvernoApp,
Policy: policyName, Policy: policyName,
Rule: ruleResult.Name(), Rule: ruleResult.Name(),
Message: ruleResult.Message(), Message: ruleResult.Message(),
Result: toPolicyResult(ruleResult.Status()), Properties: ruleResult.Properties(),
Scored: annotations[kyverno.AnnotationPolicyScored] != "false", Result: toPolicyResult(ruleResult.Status()),
Scored: annotations[kyverno.AnnotationPolicyScored] != "false",
Timestamp: metav1.Timestamp{ Timestamp: metav1.Timestamp{
Seconds: time.Now().Unix(), Seconds: time.Now().Unix(),
}, },

View file

@ -228,23 +228,23 @@ func validateResource(
// no validations are returned if match conditions aren't met // no validations are returned if match conditions aren't met
if datautils.DeepEqual(validateResult, validating.ValidateResult{}) { if datautils.DeepEqual(validateResult, validating.ValidateResult{}) {
ruleResp = engineapi.RuleSkip(policy.GetName(), engineapi.Validation, "match conditions aren't met") ruleResp = engineapi.RuleSkip(policy.GetName(), engineapi.Validation, "match conditions aren't met", nil)
} else { } else {
isPass := true isPass := true
for _, policyDecision := range validateResult.Decisions { for _, policyDecision := range validateResult.Decisions {
if policyDecision.Evaluation == validating.EvalError { if policyDecision.Evaluation == validating.EvalError {
isPass = false isPass = false
ruleResp = engineapi.RuleError(policy.GetName(), engineapi.Validation, policyDecision.Message, nil) ruleResp = engineapi.RuleError(policy.GetName(), engineapi.Validation, policyDecision.Message, nil, nil)
break break
} else if policyDecision.Action == validating.ActionDeny { } else if policyDecision.Action == validating.ActionDeny {
isPass = false isPass = false
ruleResp = engineapi.RuleFail(policy.GetName(), engineapi.Validation, policyDecision.Message) ruleResp = engineapi.RuleFail(policy.GetName(), engineapi.Validation, policyDecision.Message, nil)
break break
} }
} }
if isPass { if isPass {
ruleResp = engineapi.RulePass(policy.GetName(), engineapi.Validation, "") ruleResp = engineapi.RulePass(policy.GetName(), engineapi.Validation, "", nil)
} }
} }

View file

@ -118,7 +118,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"), *engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail", nil),
}, },
}), }),
}, },
@ -132,7 +132,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"), *engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail", nil),
}, },
}), }),
}, },
@ -146,7 +146,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil), *engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil, nil),
}, },
}), }),
}, },
@ -160,7 +160,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil), *engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil, nil),
}, },
}), }),
}, },
@ -174,7 +174,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn), *engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn, nil),
}, },
}), }),
}, },
@ -188,7 +188,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn), *engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn, nil),
}, },
}), }),
}, },
@ -202,7 +202,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, enforceRule, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, enforceRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"), *engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail", nil),
}, },
}), }),
}, },
@ -216,7 +216,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"), *engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail", nil),
}, },
}), }),
}, },
@ -230,7 +230,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil), *engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil, nil),
}, },
}), }),
}, },
@ -244,7 +244,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil), *engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil, nil),
}, },
}), }),
}, },
@ -258,7 +258,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn), *engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn, nil),
}, },
}), }),
}, },
@ -272,7 +272,7 @@ func TestBlockRequest(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn), *engineapi.NewRuleResponse("rule-warning", engineapi.Validation, "message warning", engineapi.RuleStatusWarn, nil),
}, },
}), }),
}, },
@ -320,7 +320,7 @@ func TestGetBlockedMessages(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"), *engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail", nil),
}, },
}), }),
}, },
@ -332,7 +332,7 @@ func TestGetBlockedMessages(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil), *engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil, nil),
}, },
}), }),
}, },
@ -344,8 +344,8 @@ func TestGetBlockedMessages(t *testing.T) {
engineResponses: []engineapi.EngineResponse{ engineResponses: []engineapi.EngineResponse{
engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{ engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"), *engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail", nil),
*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil), *engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil, nil),
}, },
}), }),
}, },

View file

@ -31,7 +31,7 @@ func TestGetWarningMessages(t *testing.T) {
engineapi.EngineResponse{ engineapi.EngineResponse{
PolicyResponse: engineapi.PolicyResponse{ PolicyResponse: engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.NewRuleResponse("rule", engineapi.Validation, "message warn", engineapi.RuleStatusWarn), *engineapi.NewRuleResponse("rule", engineapi.Validation, "message warn", engineapi.RuleStatusWarn, nil),
}, },
}, },
}.WithPolicy(engineapi.NewKyvernoPolicy(&v1.ClusterPolicy{ }.WithPolicy(engineapi.NewKyvernoPolicy(&v1.ClusterPolicy{
@ -49,11 +49,11 @@ func TestGetWarningMessages(t *testing.T) {
engineapi.EngineResponse{ engineapi.EngineResponse{
PolicyResponse: engineapi.PolicyResponse{ PolicyResponse: engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{ Rules: []engineapi.RuleResponse{
*engineapi.RulePass("rule-pass", engineapi.Validation, "message pass"), *engineapi.RulePass("rule-pass", engineapi.Validation, "message pass", nil),
*engineapi.NewRuleResponse("rule-warn", engineapi.Validation, "message warn", engineapi.RuleStatusWarn), *engineapi.NewRuleResponse("rule-warn", engineapi.Validation, "message warn", engineapi.RuleStatusWarn, nil),
*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"), *engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail", nil),
*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil), *engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil, nil),
*engineapi.RuleSkip("rule-skip", engineapi.Validation, "message skip"), *engineapi.RuleSkip("rule-skip", engineapi.Validation, "message skip", nil),
}, },
}, },
}.WithPolicy(engineapi.NewKyvernoPolicy(&v1.ClusterPolicy{ }.WithPolicy(engineapi.NewKyvernoPolicy(&v1.ClusterPolicy{

View file

@ -114,7 +114,7 @@
"^rbac$/^(aggregate-to-admin|cleanup-policy-with-clusterrole|mutate-policy-with-clusterrole)\\[.*\\]$" "^rbac$/^(aggregate-to-admin|cleanup-policy-with-clusterrole|mutate-policy-with-clusterrole)\\[.*\\]$"
], ],
"reports": [ "reports": [
"^reports$/^admission$/^(exception|namespaceselector|namespaceselector-assert|test-report-admission-mode|two-rules-with-different-modes|update)\\[.*\\]$", "^reports$/^admission$/^(exception|namespaceselector|namespaceselector-assert|test-report-admission-mode|test-report-properties|two-rules-with-different-modes|update)\\[.*\\]$",
"^reports$/^background$/^(exception|exception-assert|exception-with-conditions|exception-with-podsecurity|multiple-exceptions-with-pod-security|report-deletion|test-report-background-mode|two-rules-with-different-modes|verify-image-fail|verify-image-pass)\\[.*\\]$" "^reports$/^background$/^(exception|exception-assert|exception-with-conditions|exception-with-podsecurity|multiple-exceptions-with-pod-security|report-deletion|test-report-background-mode|two-rules-with-different-modes|verify-image-fail|verify-image-pass)\\[.*\\]$"
], ],
"ttl": [ "ttl": [

View file

@ -0,0 +1,3 @@
# Title
This test checks that a Policy Report in admission mode is created with an entry that is as expected.

View file

@ -0,0 +1,27 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-owner
spec:
background: false
rules:
- match:
any:
- resources:
kinds:
- Namespace
name: check-owner
context:
- name: objName
variable:
jmesPath: request.object.metadata.name
reportProperties:
operation: '{{ request.operation }}'
objName: '{{ objName }}'
validate:
validationFailureAction: Audit
message: The `owner` label is required for all Namespaces.
pattern:
metadata:
labels:
owner: ?*

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-owner
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
labels:
owner: david
name: bar

View file

@ -0,0 +1,21 @@
apiVersion: wgpolicyk8s.io/v1alpha2
kind: ClusterPolicyReport
metadata:
ownerReferences:
- apiVersion: v1
kind: Namespace
name: bar
results:
- message: validation rule 'check-owner' passed.
policy: require-owner
result: pass
rule: check-owner
scored: true
source: kyverno
properties:
objName: bar
operation: CREATE
scope:
apiVersion: v1
kind: Namespace
name: bar

View file

@ -0,0 +1,21 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: test-report-admission-mode
spec:
steps:
- name: step-01
try:
- apply:
file: chainsaw-step-01-apply-1.yaml
- assert:
file: chainsaw-step-01-assert-1.yaml
- name: step-02
try:
- apply:
file: chainsaw-step-02-apply-1.yaml
- name: step-03
try:
- assert:
file: chainsaw-step-03-assert-1.yaml