mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-28 02:18:15 +00:00
feat: apply .matchConditions
when generating reports (#9599)
* enable matchconditions for reports Signed-off-by: ShutingZhao <shuting@nirmata.com> * update Signed-off-by: ShutingZhao <shuting@nirmata.com> * add chainsaw tests Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix: linter issues Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: move files Signed-off-by: ShutingZhao <shuting@nirmata.com> --------- Signed-off-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
parent
c6d4f33ae9
commit
5f0d53fe34
22 changed files with 372 additions and 10 deletions
|
@ -91,7 +91,7 @@ func (e *engine) Validate(
|
|||
startTime := time.Now()
|
||||
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
|
||||
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.validate"), policyContext)
|
||||
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
|
||||
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
|
||||
policyResponse := e.validate(ctx, logger, policyContext)
|
||||
response = response.WithPolicyResponse(policyResponse)
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ func (e *engine) Mutate(
|
|||
startTime := time.Now()
|
||||
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
|
||||
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.mutate"), policyContext)
|
||||
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
|
||||
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
|
||||
policyResponse, patchedResource := e.mutate(ctx, logger, policyContext)
|
||||
response = response.
|
||||
WithPolicyResponse(policyResponse).
|
||||
|
@ -125,7 +125,7 @@ func (e *engine) Generate(
|
|||
startTime := time.Now()
|
||||
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
|
||||
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.generate"), policyContext)
|
||||
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
|
||||
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
|
||||
policyResponse := e.generateResponse(ctx, logger, policyContext)
|
||||
response = response.WithPolicyResponse(policyResponse)
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ func (e *engine) VerifyAndPatchImages(
|
|||
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
|
||||
ivm := engineapi.ImageVerificationMetadata{}
|
||||
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.verify"), policyContext)
|
||||
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
|
||||
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
|
||||
policyResponse, patchedResource, innerIvm := e.verifyAndPatchImages(ctx, logger, policyContext)
|
||||
response, ivm = response.
|
||||
WithPolicyResponse(policyResponse).
|
||||
|
@ -160,7 +160,7 @@ func (e *engine) ApplyBackgroundChecks(
|
|||
startTime := time.Now()
|
||||
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
|
||||
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.background"), policyContext)
|
||||
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
|
||||
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
|
||||
policyResponse := e.applyBackgroundChecks(ctx, logger, policyContext)
|
||||
response = response.WithPolicyResponse(policyResponse)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/internal"
|
||||
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
celutils "github.com/kyverno/kyverno/pkg/utils/cel"
|
||||
vaputils "github.com/kyverno/kyverno/pkg/validatingadmissionpolicy"
|
||||
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -108,7 +109,7 @@ func (h validateCELHandler) Process(
|
|||
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true}
|
||||
expressionOptionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}
|
||||
// compile CEL expressions
|
||||
compiler, err := celutils.NewCompiler(validations, auditAnnotations, matchConditions, variables)
|
||||
compiler, err := celutils.NewCompiler(validations, auditAnnotations, vaputils.ConvertMatchConditionsV1(matchConditions), variables)
|
||||
if err != nil {
|
||||
return resource, handlers.WithError(rule, engineapi.Validation, "Error while creating composited compiler", err)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
celutils "github.com/kyverno/kyverno/pkg/utils/cel"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
)
|
||||
|
||||
func MatchPolicyContext(logger logr.Logger, policyContext engineapi.PolicyContext, configuration config.Configuration) bool {
|
||||
func MatchPolicyContext(logger logr.Logger, client engineapi.Client, policyContext engineapi.PolicyContext, configuration config.Configuration) bool {
|
||||
policy := policyContext.Policy()
|
||||
old := policyContext.OldResource()
|
||||
new := policyContext.NewResource()
|
||||
|
@ -22,6 +29,13 @@ func MatchPolicyContext(logger logr.Logger, policyContext engineapi.PolicyContex
|
|||
logger.V(2).Info("configuration resource filters doesn't match resource")
|
||||
return false
|
||||
}
|
||||
|
||||
if policy.GetSpec().GetMatchConditions() != nil {
|
||||
if !checkMatchConditions(logger, client, policyContext, gvk, subresource) {
|
||||
logger.V(2).Info("webhookConfiguration.matchConditions doesn't match request")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -51,3 +65,33 @@ func checkNamespacedPolicy(policy kyvernov1.PolicyInterface, resources ...unstru
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkMatchConditions(logger logr.Logger, client engineapi.Client, policyContext engineapi.PolicyContext, gvk schema.GroupVersionKind, subresource string) bool {
|
||||
policy := policyContext.Policy()
|
||||
old := policyContext.OldResource()
|
||||
new := policyContext.NewResource()
|
||||
new.SetGroupVersionKind(gvk)
|
||||
old.SetGroupVersionKind(gvk)
|
||||
gvr := schema.GroupVersionResource(policyContext.RequestResource())
|
||||
requestInfo := policyContext.AdmissionInfo().AdmissionUserInfo
|
||||
userInfo := NewUser(requestInfo.Username, requestInfo.UID, requestInfo.Groups)
|
||||
admissionAttributes := admission.NewAttributesRecord(new.DeepCopyObject(), old.DeepCopyObject(), gvk, new.GetNamespace(), new.GetName(), gvr, subresource, admission.Operation(policyContext.Operation()), nil, false, &userInfo)
|
||||
scheme := runtime.NewScheme()
|
||||
scheme.AddKnownTypes(gvk.GroupVersion())
|
||||
versionedAttr, err := admission.NewVersionedAttributes(admissionAttributes, admissionAttributes.GetKind(), admission.NewObjectInterfacesFromScheme(scheme))
|
||||
if err != nil {
|
||||
logger.Error(err, "error creating versioned attributes")
|
||||
return false
|
||||
}
|
||||
|
||||
optionalVars := cel.OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false}
|
||||
compiler, err := celutils.NewCompiler(nil, nil, policy.GetSpec().GetMatchConditions(), nil)
|
||||
if err != nil {
|
||||
logger.Error(err, "error creating composited compiler")
|
||||
return false
|
||||
}
|
||||
matchConditionFilter := compiler.CompileMatchExpressions(optionalVars)
|
||||
matcher := matchconditions.NewMatcher(matchConditionFilter, nil, policy.GetKind(), "", policy.GetName())
|
||||
result := matcher.Match(context.TODO(), versionedAttr, nil, nil)
|
||||
return result.Matches
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cel
|
||||
|
||||
import (
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
|
||||
|
@ -13,14 +14,14 @@ type Compiler struct {
|
|||
// CEL expressions
|
||||
validateExpressions []admissionregistrationv1alpha1.Validation
|
||||
auditAnnotationExpressions []admissionregistrationv1alpha1.AuditAnnotation
|
||||
matchExpressions []admissionregistrationv1alpha1.MatchCondition
|
||||
matchExpressions []admissionregistrationv1.MatchCondition
|
||||
variables []admissionregistrationv1alpha1.Variable
|
||||
}
|
||||
|
||||
func NewCompiler(
|
||||
validations []admissionregistrationv1alpha1.Validation,
|
||||
auditAnnotations []admissionregistrationv1alpha1.AuditAnnotation,
|
||||
matchConditions []admissionregistrationv1alpha1.MatchCondition,
|
||||
matchConditions []admissionregistrationv1.MatchCondition,
|
||||
variables []admissionregistrationv1alpha1.Variable,
|
||||
) (*Compiler, error) {
|
||||
compositedCompiler, err := cel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
)
|
||||
|
@ -37,6 +38,14 @@ func convertMatchConditions(v1alpha1conditions []v1alpha1.MatchCondition) []v1be
|
|||
return v1beta1conditions
|
||||
}
|
||||
|
||||
func ConvertMatchConditionsV1(v1alpha1conditions []v1alpha1.MatchCondition) []admissionregistrationv1.MatchCondition {
|
||||
var v1conditions []admissionregistrationv1.MatchCondition
|
||||
for _, m := range v1alpha1conditions {
|
||||
v1conditions = append(v1conditions, admissionregistrationv1.MatchCondition(m))
|
||||
}
|
||||
return v1conditions
|
||||
}
|
||||
|
||||
func convertVariables(v1alpha1variables []v1alpha1.Variable) []v1beta1.Variable {
|
||||
var v1beta1variables []v1beta1.Variable
|
||||
for _, v := range v1alpha1variables {
|
||||
|
|
|
@ -202,7 +202,8 @@ func validateResource(policy v1alpha1.ValidatingAdmissionPolicy, binding *v1alph
|
|||
var ruleResp *engineapi.RuleResponse
|
||||
|
||||
// compile CEL expressions
|
||||
compiler, err := celutils.NewCompiler(policy.Spec.Validations, policy.Spec.AuditAnnotations, policy.Spec.MatchConditions, policy.Spec.Variables)
|
||||
matchConditions := ConvertMatchConditionsV1(policy.Spec.MatchConditions)
|
||||
compiler, err := celutils.NewCompiler(policy.Spec.Validations, policy.Spec.AuditAnnotations, matchConditions, policy.Spec.Variables)
|
||||
if err != nil {
|
||||
return engineResponse, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
## Description
|
||||
|
||||
This test creates a policy with `matchConditions` and two pods, it then expects a background scan report to be created for the pod in the selected namespace `match-conditions-standard-ns` other than `default`.
|
||||
|
||||
## Steps
|
||||
|
||||
1. - Create the testing namespace `match-conditions-standard-ns`
|
||||
1. - Create pods in `match-conditions-standard-ns` and `default` namespaces
|
||||
1. - Create a cluster policy
|
||||
- Assert the policy becomes ready
|
||||
1. - Assert a policy report is created for the pod in `match-conditions-standard-ns`
|
||||
1. - Assert a policy report is not created for the pod in `default`
|
|
@ -0,0 +1,33 @@
|
|||
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: report-deletion
|
||||
spec:
|
||||
steps:
|
||||
- name: step-00
|
||||
try:
|
||||
- apply:
|
||||
file: ns.yaml
|
||||
- assert:
|
||||
file: ns.yaml
|
||||
- name: step-01
|
||||
try:
|
||||
- apply:
|
||||
file: pod.yaml
|
||||
- assert:
|
||||
file: pod.yaml
|
||||
- name: step-02
|
||||
try:
|
||||
- apply:
|
||||
file: policy.yaml
|
||||
- assert:
|
||||
file: policy-assert.yaml
|
||||
- name: step-03
|
||||
try:
|
||||
- assert:
|
||||
file: report-assert.yaml
|
||||
- name: step-04
|
||||
try:
|
||||
- error:
|
||||
file: report-error.yaml
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: match-conditions-standard-ns
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-latest-tag
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:latest
|
||||
name: pod
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-latest-tag
|
||||
namespace: match-conditions-standard-ns
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:latest
|
||||
name: pod
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: cpol-match-conditions-standard
|
||||
status:
|
||||
conditions:
|
||||
- reason: Succeeded
|
||||
status: "True"
|
||||
type: Ready
|
|
@ -0,0 +1,40 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
annotations:
|
||||
pod-policies.kyverno.io/autogen-controllers: none
|
||||
name: cpol-match-conditions-standard
|
||||
spec:
|
||||
admission: true
|
||||
background: true
|
||||
webhookConfiguration:
|
||||
matchConditions:
|
||||
- name: "select-namespace"
|
||||
expression: '(object.metadata.namespace == "match-conditions-standard-ns")'
|
||||
rules:
|
||||
- match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
name: require-image-tag
|
||||
validate:
|
||||
message: An image tag is required
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: '*:*'
|
||||
- match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
name: validate-image-tag
|
||||
validate:
|
||||
message: Using a mutable image tag e.g. 'latest' is not allowed
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: '!*:latest'
|
||||
validationFailureAction: Enforce
|
||||
failurePolicy: Ignore
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: wgpolicyk8s.io/v1alpha2
|
||||
kind: PolicyReport
|
||||
metadata:
|
||||
namespace: match-conditions-standard-ns
|
||||
ownerReferences:
|
||||
- apiVersion: v1
|
||||
kind: Pod
|
||||
name: pod-latest-tag
|
||||
scope:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
name: pod-latest-tag
|
||||
summary:
|
||||
error: 0
|
||||
fail: 1
|
||||
pass: 1
|
||||
skip: 0
|
||||
warn: 0
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: wgpolicyk8s.io/v1alpha2
|
||||
kind: PolicyReport
|
||||
metadata:
|
||||
namespace: default
|
||||
ownerReferences:
|
||||
- apiVersion: v1
|
||||
kind: Pod
|
||||
name: pod-latest-tag
|
||||
scope:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
name: pod-latest-tag
|
||||
summary:
|
||||
error: 0
|
||||
fail: 1
|
||||
pass: 1
|
||||
skip: 0
|
||||
warn: 0
|
|
@ -0,0 +1,12 @@
|
|||
## Description
|
||||
|
||||
This test creates a policy with `matchConditions` and two pods, it then expects a background scan report to be created for the pod in the selected namespace `match-conditions-standard-ns` other than `default`.
|
||||
|
||||
## Steps
|
||||
|
||||
1. - Create the testing namespace `match-conditions-standard-ns`
|
||||
1. - Create pods in `match-conditions-standard-ns` and `default` namespaces
|
||||
1. - Create a cluster policy
|
||||
- Assert the policy becomes ready
|
||||
1. - Assert a policy report is created for the pod in `match-conditions-standard-ns`
|
||||
1. - Assert a policy report is not created for the pod in `default`
|
|
@ -0,0 +1,33 @@
|
|||
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: report-deletion
|
||||
spec:
|
||||
steps:
|
||||
- name: step-00
|
||||
try:
|
||||
- apply:
|
||||
file: ns.yaml
|
||||
- assert:
|
||||
file: ns.yaml
|
||||
- name: step-01
|
||||
try:
|
||||
- apply:
|
||||
file: pod.yaml
|
||||
- assert:
|
||||
file: pod.yaml
|
||||
- name: step-02
|
||||
try:
|
||||
- apply:
|
||||
file: policy.yaml
|
||||
- assert:
|
||||
file: policy-assert.yaml
|
||||
- name: step-03
|
||||
try:
|
||||
- error:
|
||||
file: report-error-1.yaml
|
||||
- name: step-04
|
||||
try:
|
||||
- error:
|
||||
file: report-error-2.yaml
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: match-conditions-userinfo-ns
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-latest-tag
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:latest
|
||||
name: pod
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-latest-tag
|
||||
namespace: match-conditions-userinfo-ns
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:latest
|
||||
name: pod
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: cpol-match-conditions-userinfo
|
||||
status:
|
||||
conditions:
|
||||
- reason: Succeeded
|
||||
status: "True"
|
||||
type: Ready
|
|
@ -0,0 +1,42 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
annotations:
|
||||
pod-policies.kyverno.io/autogen-controllers: none
|
||||
name: cpol-match-conditions-userinfo
|
||||
spec:
|
||||
admission: true
|
||||
background: true
|
||||
webhookConfiguration:
|
||||
matchConditions:
|
||||
- name: "select-namespace"
|
||||
expression: '(object.metadata.namespace == "match-conditions-userinfo-ns")'
|
||||
- name: 'select-by-groups'
|
||||
expression: '("system:masters" in request.userInfo.groups)'
|
||||
rules:
|
||||
- match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
name: require-image-tag
|
||||
validate:
|
||||
message: An image tag is required
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: '*:*'
|
||||
- match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
name: validate-image-tag
|
||||
validate:
|
||||
message: Using a mutable image tag e.g. 'latest' is not allowed
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: '!*:latest'
|
||||
validationFailureAction: Enforce
|
||||
failurePolicy: Ignore
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: wgpolicyk8s.io/v1alpha2
|
||||
kind: PolicyReport
|
||||
metadata:
|
||||
namespace: match-conditions-userinfo-ns
|
||||
ownerReferences:
|
||||
- apiVersion: v1
|
||||
kind: Pod
|
||||
name: pod-latest-tag
|
||||
scope:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
name: pod-latest-tag
|
||||
summary:
|
||||
error: 0
|
||||
fail: 1
|
||||
pass: 1
|
||||
skip: 0
|
||||
warn: 0
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: wgpolicyk8s.io/v1alpha2
|
||||
kind: PolicyReport
|
||||
metadata:
|
||||
namespace: default
|
||||
ownerReferences:
|
||||
- apiVersion: v1
|
||||
kind: Pod
|
||||
name: pod-latest-tag
|
||||
scope:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
name: pod-latest-tag
|
||||
summary:
|
||||
error: 0
|
||||
fail: 1
|
||||
pass: 1
|
||||
skip: 0
|
||||
warn: 0
|
Loading…
Add table
Reference in a new issue