1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +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
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
// criteria can include resource information (e.g. kind, name, namespace, labels)
// 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])
}
}
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.ExcludeResources.DeepCopyInto(&out.ExcludeResources)
if in.ImageExtractors != nil {

View file

@ -2814,6 +2814,12 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -7673,6 +7679,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -17172,6 +17185,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-

View file

@ -2815,6 +2815,12 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -7675,6 +7681,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -17175,6 +17188,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-

View file

@ -2808,6 +2808,12 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -7667,6 +7673,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -17166,6 +17179,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-

View file

@ -2809,6 +2809,12 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -7669,6 +7675,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -17169,6 +17182,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-

View file

@ -26,11 +26,13 @@ func TestComputeClusterPolicyReports(t *testing.T) {
"pods-require-account",
engineapi.Validation,
"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/",
nil,
),
*engineapi.RulePass(
"pods-require-limits",
engineapi.Validation,
"validation rule 'pods-require-limits' passed.",
nil,
),
)
clustered, namespaced := ComputePolicyReports(false, er)
@ -60,11 +62,13 @@ func TestComputePolicyReports(t *testing.T) {
"pods-require-account",
engineapi.Validation,
"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/",
nil,
),
*engineapi.RulePass(
"pods-require-limits",
engineapi.Validation,
"validation rule 'pods-require-limits' passed.",
nil,
),
)
clustered, namespaced := ComputePolicyReports(false, er)
@ -94,11 +98,13 @@ func TestComputePolicyReportResultsPerPolicyOld(t *testing.T) {
"pods-require-account",
engineapi.Validation,
"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/",
nil,
),
*engineapi.RulePass(
"pods-require-limits",
engineapi.Validation,
"validation rule 'pods-require-limits' passed.",
nil,
),
)
results := ComputePolicyReportResultsPerPolicy(false, er)
@ -175,7 +181,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "skip",
auditWarn: false,
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{
Source: "kyverno",
Policy: "pod-requirements",
@ -191,7 +197,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "pass",
auditWarn: false,
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{
Source: "kyverno",
Policy: "pod-requirements",
@ -207,7 +213,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "fail",
auditWarn: false,
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{
Source: "kyverno",
Policy: "pod-requirements",
@ -223,7 +229,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "fail - audit warn",
auditWarn: true,
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{
Source: "kyverno",
Policy: "pod-requirements",
@ -239,7 +245,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "error",
auditWarn: false,
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{
Source: "kyverno",
Policy: "pod-requirements",
@ -255,7 +261,7 @@ func TestComputePolicyReportResult(t *testing.T) {
name: "warn",
auditWarn: false,
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{
Source: "kyverno",
Policy: "pod-requirements",
@ -294,7 +300,7 @@ func TestPSSComputePolicyReportResult(t *testing.T) {
name: "fail",
auditWarn: false,
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{
Source: "kyverno",
Policy: "psa",

View file

@ -2808,6 +2808,12 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -7667,6 +7673,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -17166,6 +17179,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-

View file

@ -2809,6 +2809,12 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -7669,6 +7675,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -17169,6 +17182,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-

View file

@ -8007,6 +8007,12 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -12866,6 +12872,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -22365,6 +22378,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -27546,6 +27566,12 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -32406,6 +32432,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-
@ -41906,6 +41939,13 @@ spec:
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/
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:
default: true
description: |-

View file

@ -3828,6 +3828,18 @@ string
</tr>
<tr>
<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/>
<em>
<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>
<td><code>match</code>

View file

@ -28,6 +28,7 @@ import (
type RuleApplyConfiguration struct {
Name *string `json:"name,omitempty"`
Context []ContextEntryApplyConfiguration `json:"context,omitempty"`
ReportProperties map[string]string `json:"reportProperties,omitempty"`
MatchResources *MatchResourcesApplyConfiguration `json:"match,omitempty"`
ExcludeResources *MatchResourcesApplyConfiguration `json:"exclude,omitempty"`
ImageExtractors *kyvernov1.ImageExtractorConfigs `json:"imageExtractors,omitempty"`
@ -67,6 +68,20 @@ func (b *RuleApplyConfiguration) WithContext(values ...*ContextEntryApplyConfigu
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
// 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.

View file

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

View file

@ -49,9 +49,11 @@ type RuleResponse struct {
binding *admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding
// emitWarning enable passing rule message as warning to api server warning header
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
if status == RuleStatusError || status == RuleStatusFail || status == RuleStatusWarn {
emitWarn = true
@ -62,30 +64,31 @@ func NewRuleResponse(name string, ruleType RuleType, msg string, status RuleStat
message: msg,
status: status,
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 {
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 {
return NewRuleResponse(name, ruleType, msg, RuleStatusSkip)
func RuleSkip(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusSkip, properties)
}
func RuleWarn(name string, ruleType RuleType, msg string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusWarn)
func RuleWarn(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusWarn, properties)
}
func RulePass(name string, ruleType RuleType, msg string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusPass)
func RulePass(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusPass, properties)
}
func RuleFail(name string, ruleType RuleType, msg string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusFail)
func RuleFail(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusFail, properties)
}
func (r RuleResponse) WithExceptions(exceptions []kyvernov2.PolicyException) *RuleResponse {
@ -173,6 +176,10 @@ func (r *RuleResponse) EmitWarning() bool {
return r.emitWarning
}
func (r *RuleResponse) Properties() map[string]string {
return r.properties
}
// HasStatus checks if rule status is in a given list
func (r *RuleResponse) HasStatus(status ...RuleStatus) bool {
for _, s := range status {

View file

@ -51,6 +51,7 @@ func TestRuleResponse_String(t *testing.T) {
tt.fields.Type,
tt.fields.Message,
tt.fields.Status,
nil,
)
if got := rr.String(); 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.Message,
tt.fields.Status,
nil,
)
if got := r.HasStatus(tt.args.status...); 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])
if err != nil {
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)
}
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()
@ -93,7 +93,7 @@ func (e *engine) filterRule(
if ruleType == engineapi.Generation {
// 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 {
return engineapi.RuleFail(rule.Name, ruleType, "")
return engineapi.RuleFail(rule.Name, ruleType, "", rule.ReportProperties)
}
}
logger.V(4).Info("rule not matched", "reason", err.Error())
@ -113,32 +113,32 @@ func (e *engine) filterRule(
copyConditions, err := engineutils.TransformConditions(rule.GetAnyAllConditions())
if err != nil {
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
pass, msg, err := variables.EvaluateConditions(logger, policyContext.JSONContext(), copyConditions)
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 {
return engineapi.RulePass(rule.Name, ruleType, "")
return engineapi.RulePass(rule.Name, ruleType, "", rule.ReportProperties)
}
if policyContext.OldResource().Object != 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 {
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 {
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)
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}, "; ")
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
exceptions, err := e.GetPolicyExceptions(policyContext.Policy(), rule.Name)
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 {
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 {
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 {
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 {
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 {

View file

@ -152,6 +152,7 @@ func buildRuleResponse(rule *kyvernov1.Rule, mutateResp *mutate.Response, info r
engineapi.Mutation,
message,
mutateResp.Status,
rule.ReportProperties,
)
if mutateResp.Status == engineapi.RuleStatusPass {
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)
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")
targets, err := loadTargets(ctx, h.client, rule.Mutation.Targets, policyContext, logger)
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)
}
@ -76,20 +76,20 @@ func (h mutateExistingHandler) Process(
}
// load target specific context
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)
continue
}
// load target specific preconditions
preconditionsPassed, msg, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), target.preconditions)
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)
continue
}
if !preconditionsPassed {
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)
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)
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)
if err != nil {
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
@ -101,7 +101,7 @@ func (h mutateImageHandler) Process(
rclient, err := h.rclientFactory.GetClient(ctx, imageVerify.ImageRegistryCredentials)
if err != nil {
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)
@ -114,25 +114,25 @@ func (h mutateImageHandler) Process(
decoded, err := json_patch.DecodePatch(patch)
if err != nil {
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}
resourceBytes, err := resource.MarshalJSON()
if err != nil {
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)
if err != nil {
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 {
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)
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)
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
@ -79,7 +79,7 @@ func (h validateAssertHandler) Process(
logger.Error(err, "failed to load context")
}
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
@ -113,12 +113,12 @@ func (h validateAssertHandler) Process(
if len(errs) != 0 {
var responses []*engineapi.RuleResponse
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...)
}
msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name)
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)
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{})
if err != nil {
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 {
@ -174,7 +174,7 @@ func (h validateCELHandler) Process(
params, err := collectParams(ctx, h.client, paramKind, paramRef, ns)
if err != nil {
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
if datautils.DeepEqual(validationResult, validating.ValidateResult{}) {
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:
if decision.Evaluation == validating.EvalError {
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:
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)
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)
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)
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)
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 {
msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name)
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 {
// apply pod security exceptions if exist
@ -120,12 +120,12 @@ func (h validatePssHandler) Process(
podSecurityChecks.Checks = pssChecks
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
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))
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)
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)
@ -120,15 +120,15 @@ func newForEachValidator(
func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
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)
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 {
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 {
@ -137,7 +137,7 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if v.pattern != nil || v.anyPattern != 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()
@ -145,7 +145,7 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if engineutils.IsUpdateRequest(v.policyContext) {
priorResp, err := v.validateOldObject(ctx)
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) {
@ -153,7 +153,7 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if ruleResponse.Status() == engineapi.RuleStatusPass {
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 {
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) {
@ -225,13 +225,13 @@ func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForE
policyContext := v.policyContext.Copy()
if err := engineutils.AddElementToContext(policyContext, element, index, v.nesting, elementScope); err != nil {
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)
if err != nil {
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)
@ -249,16 +249,16 @@ func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForE
continue
}
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())
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++
}
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 {
@ -275,12 +275,12 @@ func (v *validator) loadContext(ctx context.Context) error {
func (v *validator) validateDeny() *engineapi.RuleResponse {
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 {
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())
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 == "" {
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")
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 {
@ -354,14 +354,14 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
anyPatterns, err := deserializeAnyPattern(v.anyPattern)
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 {
err := validate.MatchPattern(v.log, resource.Object, pattern)
if err == nil {
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 {
@ -389,7 +389,7 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
errorStr = append(errorStr, err.Error())
}
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 {
var errorStr []string
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))
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) {

View file

@ -240,7 +240,7 @@ func (iv *ImageVerifier) Verify(
if HasImageVerifiedAnnotationChanged(iv.policyContext, iv.logger) {
msg := kyverno.AnnotationImageVerify + " annotation cannot be changed"
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
}
@ -273,7 +273,7 @@ func (iv *ImageVerifier) Verify(
var digest string
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)
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
} 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)
@ -296,10 +296,10 @@ func (iv *ImageVerifier) Verify(
if imageVerify.MutateDigest {
patch, retrievedDigest, err := iv.handleMutateDigest(ctx, digest, imageInfo)
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 {
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)
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))
if err := iv.policyContext.JSONContext().AddImageInfo(imageInfo, cfg); err != nil {
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 !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) {
iv.logger.Info("skipping image reference", "image", image, "policy", iv.policyContext.Policy().GetName(), "ruleName", iv.rule.Name)
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)
if ruleResp.Status() != engineapi.RuleStatusPass {
@ -381,10 +381,10 @@ func (iv *ImageVerifier) verifyAttestors(
}
}
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)
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)
@ -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())
var netErr *net.OpError
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(
@ -409,7 +409,7 @@ func (iv *ImageVerifier) verifyAttestations(
iv.logger.V(2).Info(fmt.Sprintf("attestation %+v", attestation))
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 != "" {
@ -464,7 +464,7 @@ func (iv *ImageVerifier) verifyAttestations(
}
if verifiedCount < requiredCount {
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)
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(

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) {
mutateResp := Mutate(rule, context.NewContext(jmespath.New(config.NewDefaultConfiguration(false))), resource, logr.Discard())
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) {

View file

@ -94,6 +94,7 @@ func ToPolicyReportResult(policyType engineapi.PolicyType, policyName string, ru
Policy: policyName,
Rule: ruleResult.Name(),
Message: ruleResult.Message(),
Properties: ruleResult.Properties(),
Result: toPolicyResult(ruleResult.Status()),
Scored: annotations[kyverno.AnnotationPolicyScored] != "false",
Timestamp: metav1.Timestamp{

View file

@ -228,23 +228,23 @@ func validateResource(
// no validations are returned if match conditions aren't met
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 {
isPass := true
for _, policyDecision := range validateResult.Decisions {
if policyDecision.Evaluation == validating.EvalError {
isPass = false
ruleResp = engineapi.RuleError(policy.GetName(), engineapi.Validation, policyDecision.Message, nil)
ruleResp = engineapi.RuleError(policy.GetName(), engineapi.Validation, policyDecision.Message, nil, nil)
break
} else if policyDecision.Action == validating.ActionDeny {
isPass = false
ruleResp = engineapi.RuleFail(policy.GetName(), engineapi.Validation, policyDecision.Message)
ruleResp = engineapi.RuleFail(policy.GetName(), engineapi.Validation, policyDecision.Message, nil)
break
}
}
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{
engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditPolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, enforceRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, auditRule, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
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{
engineapi.NewEngineResponse(resource, enforcePolicy, nil).WithPolicyResponse(engineapi.PolicyResponse{
Rules: []engineapi.RuleResponse{
*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail"),
*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil),
*engineapi.RuleFail("rule-fail", engineapi.Validation, "message fail", nil),
*engineapi.RuleError("rule-error", engineapi.Validation, "message error", nil, nil),
},
}),
},

View file

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

View file

@ -114,7 +114,7 @@
"^rbac$/^(aggregate-to-admin|cleanup-policy-with-clusterrole|mutate-policy-with-clusterrole)\\[.*\\]$"
],
"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)\\[.*\\]$"
],
"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