From 2a656f6de00173295a6f65ddd71fae9fbfd3d097 Mon Sep 17 00:00:00 2001 From: shuting Date: Mon, 25 Apr 2022 20:20:40 +0800 Subject: [PATCH] feat: mutate existing resources (#3669) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: mutate existing, replace GR by UR in webhook server (#3601) * add attributes for post mutation Signed-off-by: ShutingZhao * add UR informer to webhook server Signed-off-by: ShutingZhao * - replace gr with ur in the webhook server; - create ur for mutateExsiting policies Signed-off-by: ShutingZhao * replace gr by ur across entire packages Signed-off-by: ShutingZhao * add YAMLs Signed-off-by: ShutingZhao * update api docs & fix unit tests Signed-off-by: ShutingZhao * add UR deletion handler Signed-off-by: ShutingZhao * add api docs for v1beta1 Signed-off-by: ShutingZhao * fix clientset method Signed-off-by: ShutingZhao * fix v1beta1 client registration Signed-off-by: ShutingZhao * feat: mutate existing - generates UR for admission requests (#3623) Signed-off-by: ShutingZhao * replace with UR in policy controller generate rules (#3635) Signed-off-by: prateekpandey14 * - enable mutate engine to process mutateExisting rules; - add unit tests Signed-off-by: ShutingZhao * implemented ur background reconciliation for mutateExisting policies Signed-off-by: ShutingZhao * fix webhook update error Signed-off-by: ShutingZhao * temporary comment out new unit tests Signed-off-by: ShutingZhao * feat: mutate existing, replace GR by UR in webhook server (#3601) * add attributes for post mutation Signed-off-by: ShutingZhao * add UR informer to webhook server Signed-off-by: ShutingZhao * - replace gr with ur in the webhook server; - create ur for mutateExsiting policies Signed-off-by: ShutingZhao * replace gr by ur across entire packages Signed-off-by: ShutingZhao * fix missing policy.kyverno.io/policy-name label (#3599) Signed-off-by: prateekpandey14 * refactor cli code from pkg to cmd (#3591) * refactor cli code from pkg to cmd Signed-off-by: Mritunjay Sharma * fixes in imports Signed-off-by: Mritunjay Sharma * fixes tests Signed-off-by: Mritunjay Sharma * fixed conflicts Signed-off-by: Mritunjay Sharma * moved non-commands to utils Signed-off-by: Mritunjay Sharma Co-authored-by: Vyankatesh Kudtarkar * add YAMLs Signed-off-by: ShutingZhao * update api docs & fix unit tests Signed-off-by: ShutingZhao * add UR deletion handler Signed-off-by: ShutingZhao * add api docs for v1beta1 Signed-off-by: ShutingZhao * fix clientset method Signed-off-by: ShutingZhao * add-kms-libraries for cosign (#3603) * add-kms-libraries Signed-off-by: anushkamittal20 * Shifted providers to cosign package Signed-off-by: anushkamittal20 Signed-off-by: ShutingZhao * Add support for custom image extractors (#3596) Signed-off-by: Sambhav Kothari * Update vulnerable dependencies (#3577) Signed-off-by: Shubham Gupta Co-authored-by: Jim Bugwadia Signed-off-by: ShutingZhao * fix v1beta1 client registration Signed-off-by: ShutingZhao * feat: mutate existing - generates UR for admission requests (#3623) Signed-off-by: ShutingZhao * updating version in Chart.yaml (#3618) * updatimg version in Chart.yaml Signed-off-by: Prateeknandle * changes from, make gen-helm Signed-off-by: Prateeknandle Co-authored-by: Vyankatesh Kudtarkar Signed-off-by: ShutingZhao * Allow kyverno-policies to have preconditions defined (#3606) * Allow kyverno-policies to have preconditions defined Signed-off-by: Trey Dockendorf * Fix docs Signed-off-by: Trey Dockendorf Signed-off-by: ShutingZhao * replace with UR in policy controller generate rules (#3635) Signed-off-by: prateekpandey14 Signed-off-by: ShutingZhao * - enable mutate engine to process mutateExisting rules; - add unit tests Signed-off-by: ShutingZhao * implemented ur background reconciliation for mutateExisting policies Signed-off-by: ShutingZhao * fix webhook update error Signed-off-by: ShutingZhao * temporary comment out new unit tests Signed-off-by: ShutingZhao * Image verify attestors (#3614) * fix logs Signed-off-by: Jim Bugwadia * fix logs Signed-off-by: Jim Bugwadia * support multiple attestors Signed-off-by: Jim Bugwadia * rm CLI tests (not currently supported) Signed-off-by: Jim Bugwadia * apply attestor repo Signed-off-by: Jim Bugwadia * fix linter issues Signed-off-by: Jim Bugwadia * fix entryError assignment Signed-off-by: Jim Bugwadia * fix tests Signed-off-by: Jim Bugwadia * format Signed-off-by: Jim Bugwadia * add intermediary certs Signed-off-by: Jim Bugwadia * Allow defining imagePullSecrets (#3633) * Allow defining imagePullSecrets Signed-off-by: Trey Dockendorf * Use dict for imagePullSecrets Signed-off-by: Trey Dockendorf * Simplify how imagePullSecrets is defined Signed-off-by: Trey Dockendorf Signed-off-by: ShutingZhao * Fix race condition in pCache (#3632) * fix race condition in pCache Signed-off-by: ShutingZhao * refact: remove unused Run function from generate (#3638) Signed-off-by: prateekpandey14 * Remove helm mode setting (#3628) Signed-off-by: Charles-Edouard Brétéché Signed-off-by: ShutingZhao * refactor: image utils (#3630) Signed-off-by: Charles-Edouard Brétéché Signed-off-by: ShutingZhao * -resolve lift comments; -fix informer sync issue Signed-off-by: ShutingZhao * refact the update request cleanup controller Signed-off-by: prateekpandey14 * - fix delete request for mutateExisting; - fix context variable substitution; - improve logging Signed-off-by: ShutingZhao * - enable events; - add last applied annotation Signed-off-by: ShutingZhao * enable mutate existing on policy creation Signed-off-by: ShutingZhao * update autogen code Signed-off-by: ShutingZhao * merge main Signed-off-by: ShutingZhao * add unit tests Signed-off-by: ShutingZhao * address list comments Signed-off-by: ShutingZhao * update api docs Signed-off-by: ShutingZhao * fix "Implicit memory aliasing in for loop" Signed-off-by: ShutingZhao * remove unused definitions Signed-off-by: ShutingZhao * update api docs Signed-off-by: ShutingZhao Co-authored-by: Prateek Pandey Co-authored-by: Mritunjay Kumar Sharma Co-authored-by: Vyankatesh Kudtarkar Co-authored-by: Anushka Mittal <55237170+anushkamittal20@users.noreply.github.com> Co-authored-by: Sambhav Kothari Co-authored-by: Shubham Gupta Co-authored-by: Jim Bugwadia Co-authored-by: Prateek Nandle <56027872+Prateeknandle@users.noreply.github.com> Co-authored-by: treydock Co-authored-by: Charles-Edouard Brétéché --- Makefile | 1 + api/kyverno/v1/common_types.go | 18 + api/kyverno/v1/resource_description_types.go | 2 +- api/kyverno/v1/rule_types.go | 5 + api/kyverno/v1/spec_types.go | 2 +- api/kyverno/v1/zz_generated.deepcopy.go | 21 + api/kyverno/v1beta1/doc.go | 21 + api/kyverno/v1beta1/updaterequest_types.go | 17 +- charts/kyverno/templates/crds.yaml | 378 ++++++++-- .../kubectl-kyverno/apply/apply_command.go | 4 +- cmd/cli/kubectl-kyverno/test/test_command.go | 3 +- .../kubectl-kyverno/utils/common/common.go | 14 +- .../utils/common/common_test.go | 4 +- cmd/kyverno/main.go | 27 +- config/crds/kustomization.yaml | 1 + config/crds/kyverno.io_clusterpolicies.yaml | 222 +++--- config/crds/kyverno.io_policies.yaml | 222 +++--- config/crds/kyverno.io_updaterequests.yaml | 8 +- config/install.yaml | 650 +++++++++++++----- config/install_debug.yaml | 644 ++++++++++++----- config/k8s-resource/clusterroles.yaml | 2 + docs/crd/v1/index.html | 101 ++- docs/crd/v1beta1/index.html | 436 ++++++++++++ go.mod | 1 + pkg/background/common/context.go | 118 ++++ pkg/background/common/report.go | 34 + pkg/background/common/resource.go | 55 ++ pkg/background/common/status.go | 69 +- pkg/background/common/util.go | 6 +- pkg/background/generate/cleanup/cleanup.go | 8 +- pkg/background/generate/cleanup/controller.go | 62 +- pkg/background/generate/cleanup/resource.go | 2 +- pkg/background/generate/generate.go | 81 +-- pkg/background/generate/report.go | 21 - pkg/background/generate/resource.go | 42 -- pkg/background/mutate/mutate.go | 243 +++++++ pkg/background/request_process.go | 22 +- pkg/background/update_request_controller.go | 111 ++- pkg/client/clientset/versioned/clientset.go | 4 +- .../versioned/fake/clientset_generated.go | 2 +- .../clientset/versioned/fake/register.go | 2 + .../typed/kyverno/v1beta1/kyverno_client.go | 4 +- .../informers/externalversions/generic.go | 4 + .../externalversions/kyverno/interface.go | 8 + pkg/common/common.go | 6 +- pkg/engine/background.go | 143 ++++ pkg/engine/context/context.go | 6 +- pkg/engine/context/context_test.go | 4 +- pkg/engine/generation.go | 133 +--- pkg/engine/imageVerify.go | 18 +- pkg/engine/loadtargets.go | 61 ++ pkg/engine/mutate/mutation_test.go | 2 +- pkg/engine/mutation.go | 68 +- pkg/engine/mutation_test.go | 257 ++++++- pkg/engine/policyContext.go | 8 +- pkg/engine/response/response.go | 3 + pkg/engine/utils.go | 28 +- pkg/engine/utils_test.go | 60 +- pkg/engine/validation.go | 34 +- pkg/engine/validation_test.go | 6 +- pkg/engine/variables/variables_test.go | 11 +- pkg/engine/variables/vars.go | 4 +- pkg/event/controller.go | 28 +- pkg/event/source.go | 3 + pkg/openapi/validation.go | 2 +- pkg/policy/background.go | 6 + pkg/policy/policy_controller.go | 167 ++++- pkg/policycache/policy_cache.go | 2 +- pkg/testrunner/scenario.go | 2 +- pkg/utils/annotations.go | 142 ++++ pkg/{webhooks => utils}/annotations_test.go | 16 +- pkg/webhookconfig/certmanager.go | 3 +- pkg/webhookconfig/configmanager.go | 20 +- pkg/webhooks/common.go | 3 +- pkg/webhooks/generation.go | 78 +-- pkg/webhooks/handlers.go | 50 +- pkg/webhooks/handlers/admission.go | 13 +- pkg/webhooks/mutation.go | 2 +- pkg/webhooks/server.go | 26 +- pkg/webhooks/updaterequest.go | 71 ++ pkg/webhooks/updaterequest/generator.go | 224 ++++++ pkg/webhooks/validate_audit.go | 18 +- 82 files changed, 4239 insertions(+), 1191 deletions(-) create mode 100644 api/kyverno/v1beta1/doc.go create mode 100644 docs/crd/v1beta1/index.html create mode 100644 pkg/background/common/context.go create mode 100644 pkg/background/common/report.go create mode 100644 pkg/background/common/resource.go delete mode 100644 pkg/background/generate/report.go delete mode 100644 pkg/background/generate/resource.go create mode 100644 pkg/background/mutate/mutate.go create mode 100644 pkg/engine/background.go create mode 100644 pkg/engine/loadtargets.go create mode 100644 pkg/utils/annotations.go rename pkg/{webhooks => utils}/annotations_test.go (91%) create mode 100644 pkg/webhooks/updaterequest.go create mode 100644 pkg/webhooks/updaterequest/generator.go diff --git a/Makefile b/Makefile index 18e3f37135..b35c7da238 100644 --- a/Makefile +++ b/Makefile @@ -156,6 +156,7 @@ generate-api-docs: gen-crd-api-reference-docs ## Generate api reference docs rm -rf docs/crd mkdir docs/crd gen-crd-api-reference-docs -v 6 -api-dir ./api/kyverno/v1alpha2 -config docs/config.json -template-dir docs/template -out-file docs/crd/v1alpha2/index.html + gen-crd-api-reference-docs -v 6 -api-dir ./api/kyverno/v1beta1 -config docs/config.json -template-dir docs/template -out-file docs/crd/v1beta1/index.html gen-crd-api-reference-docs -v 6 -api-dir ./api/kyverno/v1 -config docs/config.json -template-dir docs/template -out-file docs/crd/v1/index.html .PHONY: verify-api-docs diff --git a/api/kyverno/v1/common_types.go b/api/kyverno/v1/common_types.go index 5e31ec1130..db6a99eef5 100755 --- a/api/kyverno/v1/common_types.go +++ b/api/kyverno/v1/common_types.go @@ -215,6 +215,18 @@ type ResourceFilter struct { // Mutation defines how resource are modified. type Mutation struct { + + // mutateExisting controls whether to mutate existing resource ONLY + // The existing resources will be mutated ONLY if set to "true". + // Otherwise all resources including admission requests are mutated. + // Optional. Defaults to "false" if not specified. + // +optional + MutateExisting bool `json:"mutateExisting,omitempty" yaml:"mutatingExisting,omitempty"` + + // Targets defines the target resources to be mutated. + // +optional + Targets []TargetMutation `json:"targets,omitempty" yaml:"targets,omitempty"` + // PatchStrategicMerge is a strategic merge patch used to modify resources. // See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ // and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. @@ -231,6 +243,12 @@ type Mutation struct { ForEachMutation []*ForEachMutation `json:"foreach,omitempty" yaml:"foreach,omitempty"` } +type TargetMutation struct { + // ResourceSpec specifies the target resource information. + // +optional + ResourceSpec `json:",omitempty" yaml:",omitempty"` +} + func (m *Mutation) GetPatchStrategicMerge() apiextensions.JSON { return FromJSON(m.RawPatchStrategicMerge) } diff --git a/api/kyverno/v1/resource_description_types.go b/api/kyverno/v1/resource_description_types.go index 17fac7b3c3..d12533b4ca 100644 --- a/api/kyverno/v1/resource_description_types.go +++ b/api/kyverno/v1/resource_description_types.go @@ -17,12 +17,12 @@ type ResourceDescription struct { // Name is the name of the resource. The name supports wildcard characters // "*" (matches zero or many characters) and "?" (at least one character). + // NOTE: "Name" is being deprecated in favor of "Names". // +optional Name string `json:"name,omitempty" yaml:"name,omitempty"` // Names are the names of the resources. Each name supports wildcard characters // "*" (matches zero or many characters) and "?" (at least one character). - // NOTE: "Name" is being deprecated in favor of "Names". // +optional Names []string `json:"names,omitempty" yaml:"names,omitempty"` diff --git a/api/kyverno/v1/rule_types.go b/api/kyverno/v1/rule_types.go index c201d437d8..936d501ccb 100644 --- a/api/kyverno/v1/rule_types.go +++ b/api/kyverno/v1/rule_types.go @@ -87,6 +87,11 @@ func (r *Rule) HasGenerate() bool { return !reflect.DeepEqual(r.Generation, Generation{}) } +// IsMutatingExisting checks if the mutate rule applies to existing resources +func (r *Rule) IsMutateExisting() bool { + return r.Mutation.Targets != nil +} + func (r *Rule) GetAnyAllConditions() apiextensions.JSON { return FromJSON(r.RawAnyAllConditions) } diff --git a/api/kyverno/v1/spec_types.go b/api/kyverno/v1/spec_types.go index b35de068c9..594f6a2e9f 100644 --- a/api/kyverno/v1/spec_types.go +++ b/api/kyverno/v1/spec_types.go @@ -14,7 +14,7 @@ type ValidationFailureAction string const ( // Enforce blocks the request on failure Enforce ValidationFailureAction = "enforce" - // Audit indicates not to block the request on failure, but report failiures as policy violations + // Audit indicates not to block the request on failure, but report failures as policy violations Audit ValidationFailureAction = "audit" ) diff --git a/api/kyverno/v1/zz_generated.deepcopy.go b/api/kyverno/v1/zz_generated.deepcopy.go index abc739f855..7e33bd5408 100755 --- a/api/kyverno/v1/zz_generated.deepcopy.go +++ b/api/kyverno/v1/zz_generated.deepcopy.go @@ -745,6 +745,11 @@ func (in *MatchResources) DeepCopy() *MatchResources { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Mutation) DeepCopyInto(out *Mutation) { *out = *in + if in.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make([]TargetMutation, len(*in)) + copy(*out, *in) + } if in.RawPatchStrategicMerge != nil { in, out := &in.RawPatchStrategicMerge, &out.RawPatchStrategicMerge *out = new(apiextensionsv1.JSON) @@ -1110,6 +1115,22 @@ func (in *StaticKeyAttestor) DeepCopy() *StaticKeyAttestor { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetMutation) DeepCopyInto(out *TargetMutation) { + *out = *in + out.ResourceSpec = in.ResourceSpec +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetMutation. +func (in *TargetMutation) DeepCopy() *TargetMutation { + if in == nil { + return nil + } + out := new(TargetMutation) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UserInfo) DeepCopyInto(out *UserInfo) { *out = *in diff --git a/api/kyverno/v1beta1/doc.go b/api/kyverno/v1beta1/doc.go new file mode 100644 index 0000000000..c57fb8c11e --- /dev/null +++ b/api/kyverno/v1beta1/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2020 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1beta1 contains API Schema definitions for the policy v1alpha1 API group +// +k8s:deepcopy-gen=package +// +kubebuilder:object:generate=true +// +groupName=kyverno.io +package v1beta1 diff --git a/api/kyverno/v1beta1/updaterequest_types.go b/api/kyverno/v1beta1/updaterequest_types.go index a8c262e8d9..35b01b0767 100644 --- a/api/kyverno/v1beta1/updaterequest_types.go +++ b/api/kyverno/v1beta1/updaterequest_types.go @@ -52,7 +52,7 @@ type UpdateRequestStatus struct { // +kubebuilder:printcolumn:name="ResourceNamespace",type="string",JSONPath=".spec.resource.namespace" // +kubebuilder:printcolumn:name="status",type="string",JSONPath=".status.state" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" -// +kubebuilder:resource:shortName=gr +// +kubebuilder:resource:shortName=ur type UpdateRequest struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -65,8 +65,19 @@ type UpdateRequest struct { Status UpdateRequestStatus `json:"status,omitempty"` } +type RequestType string + +const ( + Mutate RequestType = "mutate" + Generate RequestType = "generate" +) + // UpdateRequestSpec stores the request specification. type UpdateRequestSpec struct { + // Type represents request type for background processing + // +kubebuilder:validation:Enum=mutate;generate + Type RequestType `json:"requestType,omitempty" yaml:"requestType,omitempty"` + // Specifies the name of the policy. Policy string `json:"policy" yaml:"policy"` @@ -139,3 +150,7 @@ type UpdateRequestList struct { func init() { SchemeBuilder.Register(&UpdateRequest{}, &UpdateRequestList{}) } + +func (s *UpdateRequestSpec) GetRequestType() RequestType { + return s.Type +} diff --git a/charts/kyverno/templates/crds.yaml b/charts/kyverno/templates/crds.yaml index af40189b88..4fe60e11a0 100644 --- a/charts/kyverno/templates/crds.yaml +++ b/charts/kyverno/templates/crds.yaml @@ -5,7 +5,6 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.8.0 config.kubernetes.io/index: '1' - internal.config.kubernetes.io/index: '1' creationTimestamp: null labels: app.kubernetes.io/component: kyverno @@ -155,10 +154,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -281,10 +280,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -402,10 +401,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -585,10 +584,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -711,10 +710,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -832,10 +831,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -1082,12 +1081,34 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing resource ONLY The existing resources will be mutated ONLY if set to "true". Otherwise all resources including admission requests are mutated. Optional. Defaults to "false" if not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. x-kubernetes-preserve-unknown-fields: true patchesJson6902: description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be unique within the policy. @@ -1661,10 +1682,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -1787,10 +1808,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -1908,10 +1929,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -2091,10 +2112,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -2217,10 +2238,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -2338,10 +2359,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -2588,12 +2609,34 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing resource ONLY The existing resources will be mutated ONLY if set to "true". Otherwise all resources including admission requests are mutated. Optional. Defaults to "false" if not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. x-kubernetes-preserve-unknown-fields: true patchesJson6902: description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be unique within the policy. @@ -3000,7 +3043,6 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.8.0 config.kubernetes.io/index: '2' - internal.config.kubernetes.io/index: '2' creationTimestamp: null labels: app.kubernetes.io/component: kyverno @@ -3272,7 +3314,6 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.8.0 config.kubernetes.io/index: '3' - internal.config.kubernetes.io/index: '3' creationTimestamp: null labels: app.kubernetes.io/component: kyverno @@ -3544,7 +3585,6 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.8.0 config.kubernetes.io/index: '4' - internal.config.kubernetes.io/index: '4' creationTimestamp: null labels: app.kubernetes.io/component: kyverno @@ -3728,7 +3768,6 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.8.0 config.kubernetes.io/index: '5' - internal.config.kubernetes.io/index: '5' creationTimestamp: null labels: app.kubernetes.io/component: kyverno @@ -3878,10 +3917,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -4004,10 +4043,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -4125,10 +4164,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -4308,10 +4347,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -4434,10 +4473,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -4555,10 +4594,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -4805,12 +4844,34 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing resource ONLY The existing resources will be mutated ONLY if set to "true". Otherwise all resources including admission requests are mutated. Optional. Defaults to "false" if not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. x-kubernetes-preserve-unknown-fields: true patchesJson6902: description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be unique within the policy. @@ -5384,10 +5445,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -5510,10 +5571,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -5631,10 +5692,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -5814,10 +5875,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -5940,10 +6001,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -6061,10 +6122,10 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). items: type: string type: array @@ -6311,12 +6372,34 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing resource ONLY The existing resources will be mutated ONLY if set to "true". Otherwise all resources including admission requests are mutated. Optional. Defaults to "false" if not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. x-kubernetes-preserve-unknown-fields: true patchesJson6902: description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be unique within the policy. @@ -6723,7 +6806,6 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.8.0 config.kubernetes.io/index: '6' - internal.config.kubernetes.io/index: '6' creationTimestamp: null labels: app.kubernetes.io/component: kyverno @@ -6995,7 +7077,6 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.8.0 config.kubernetes.io/index: '7' - internal.config.kubernetes.io/index: '7' creationTimestamp: null labels: app.kubernetes.io/component: kyverno @@ -7260,4 +7341,191 @@ status: plural: "" conditions: [] storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + config.kubernetes.io/index: '8' + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: latest + name: updaterequests.kyverno.io +spec: + group: kyverno.io + names: + kind: UpdateRequest + listKind: UpdateRequestList + plural: updaterequests + shortNames: + - ur + singular: updaterequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.policy + name: Policy + type: string + - jsonPath: .spec.resource.kind + name: ResourceKind + type: string + - jsonPath: .spec.resource.name + name: ResourceName + type: string + - jsonPath: .spec.resource.namespace + name: ResourceNamespace + type: string + - jsonPath: .status.state + name: status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: UpdateRequestStatus is a request to process mutate and generate rules in background. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec is the information to identify the update request. + properties: + context: + description: Context ... + properties: + admissionRequestInfo: + description: AdmissionRequestInfoObject stores the admission request and operation details + properties: + admissionRequest: + type: string + operation: + description: Operation is the type of resource operation being checked for admission control + type: string + type: object + userInfo: + description: RequestInfo contains permission info carried in an admission request. + properties: + clusterRoles: + description: ClusterRoles is a list of possible clusterRoles send the request. + items: + type: string + nullable: true + type: array + roles: + description: Roles is a list of possible role send the request. + items: + type: string + nullable: true + type: array + userInfo: + description: UserInfo is the userInfo carried in the admission request. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate + items: + type: string + type: array + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: + type: string + type: array + uid: + description: A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs. + type: string + username: + description: The name that uniquely identifies this user among all active users. + type: string + type: object + type: object + type: object + policy: + description: Specifies the name of the policy. + type: string + requestType: + description: Type represents request type for background processing + enum: + - mutate + - generate + type: string + resource: + description: ResourceSpec is the information to identify the update request. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + required: + - context + - policy + - resource + type: object + status: + description: Status contains statistics related to update request. + properties: + generatedResources: + description: This will track the resources that are updated by the generate Policy. Will be used during clean up resources. + items: + description: ResourceSpec contains information to identify a resource. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + message: + description: Specifies request status message. + type: string + state: + description: State represents state of the generate request. + type: string + required: + - state + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/cmd/cli/kubectl-kyverno/apply/apply_command.go b/cmd/cli/kubectl-kyverno/apply/apply_command.go index e6898f1b86..944013050a 100644 --- a/cmd/cli/kubectl-kyverno/apply/apply_command.go +++ b/cmd/cli/kubectl-kyverno/apply/apply_command.go @@ -8,7 +8,7 @@ import ( "time" "github.com/go-git/go-billy/v5/memfs" - v1 "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store" @@ -246,7 +246,7 @@ func applyCommandHelper(resourcePaths []string, userInfoPath string, cluster boo } // get the user info as request info from a different file - var userInfo v1.RequestInfo + var userInfo v1beta1.RequestInfo if userInfoPath != "" { userInfo, err = common.GetUserInfoFromPath(fs, userInfoPath, false, "") if err != nil { diff --git a/cmd/cli/kubectl-kyverno/test/test_command.go b/cmd/cli/kubectl-kyverno/test/test_command.go index 6e18a78204..ae20537dc2 100644 --- a/cmd/cli/kubectl-kyverno/test/test_command.go +++ b/cmd/cli/kubectl-kyverno/test/test_command.go @@ -17,6 +17,7 @@ import ( "github.com/go-git/go-billy/v5/memfs" "github.com/kataras/tablewriter" v1 "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/api/kyverno/v1beta1" report "github.com/kyverno/kyverno/api/policyreport/v1alpha2" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError" @@ -750,7 +751,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool, } // get the user info as request info from a different file - var userInfo v1.RequestInfo + var userInfo v1beta1.RequestInfo if userInfoFile != "" { userInfo, err = common.GetUserInfoFromPath(fs, userInfoFile, isGit, policyResourcePath) if err != nil { diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go index 70a67b2c24..14b5f4302a 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common.go @@ -13,21 +13,21 @@ import ( "reflect" "strings" - "github.com/kyverno/kyverno/pkg/autogen" - "github.com/kyverno/kyverno/pkg/engine/variables" - jsonpatch "github.com/evanphx/json-patch/v5" "github.com/go-git/go-billy/v5" "github.com/go-logr/logr" v1 "github.com/kyverno/kyverno/api/kyverno/v1" + v1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" report "github.com/kyverno/kyverno/api/policyreport/v1alpha2" sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store" + "github.com/kyverno/kyverno/pkg/autogen" client "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/engine" "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" ut "github.com/kyverno/kyverno/pkg/engine/utils" + "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/policymutation" "github.com/kyverno/kyverno/pkg/policyreport" "github.com/kyverno/kyverno/pkg/utils" @@ -442,7 +442,7 @@ func MutatePolicies(policies []v1.PolicyInterface) ([]v1.PolicyInterface, error) // ApplyPolicyOnResource - function to apply policy on resource func ApplyPolicyOnResource(policy v1.PolicyInterface, resource *unstructured.Unstructured, - mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, userInfo v1.RequestInfo, policyReport bool, + mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, userInfo v1beta1.RequestInfo, policyReport bool, namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts, printPatchResource bool) ([]*response.EngineResponse, policyreport.Info, error) { @@ -595,7 +595,7 @@ OuterLoop: JSONContext: context.NewContext(), NamespaceLabels: namespaceLabels, } - generateResponse := engine.Generate(policyContext) + generateResponse := engine.ApplyBackgroundChecks(policyContext) if generateResponse != nil { engineResponses = append(engineResponses, generateResponse) } @@ -1051,8 +1051,8 @@ func GetPatchedResourceFromPath(fs billy.Filesystem, path string, isGit bool, po } //GetUserInfoFromPath - get the request info as user info from a given path -func GetUserInfoFromPath(fs billy.Filesystem, path string, isGit bool, policyResourcePath string) (v1.RequestInfo, error) { - userInfo := &v1.RequestInfo{} +func GetUserInfoFromPath(fs billy.Filesystem, path string, isGit bool, policyResourcePath string) (v1beta1.RequestInfo, error) { + userInfo := &v1beta1.RequestInfo{} if isGit { filep, err := fs.Open(filepath.Join(policyResourcePath, path)) diff --git a/cmd/cli/kubectl-kyverno/utils/common/common_test.go b/cmd/cli/kubectl-kyverno/utils/common/common_test.go index b8af56adc2..7cb26fe48c 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common_test.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common_test.go @@ -3,7 +3,7 @@ package common import ( "testing" - v1 "github.com/kyverno/kyverno/api/kyverno/v1" + v1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/toggle" ut "github.com/kyverno/kyverno/pkg/utils" "gotest.tools/assert" @@ -100,7 +100,7 @@ func Test_NamespaceSelector(t *testing.T) { for _, tc := range testcases { policyArray, _ := ut.GetPolicy(tc.policy) resourceArray, _ := GetResource(tc.resource) - ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, v1.RequestInfo{}, false, tc.namespaceSelectorMap, false, rc, false) + ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, v1beta1.RequestInfo{}, false, tc.namespaceSelectorMap, false, rc, false) assert.Equal(t, int64(rc.Pass), int64(tc.result.Pass)) assert.Equal(t, int64(rc.Fail), int64(tc.result.Fail)) // TODO: autogen rules seem to not be present when autogen internals is disabled diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index 77d3d5a25c..c1c0828870 100755 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -43,7 +43,7 @@ import ( "github.com/kyverno/kyverno/pkg/version" "github.com/kyverno/kyverno/pkg/webhookconfig" "github.com/kyverno/kyverno/pkg/webhooks" - webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/generate" + webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/updaterequest" ) const resyncPeriod = 15 * time.Minute @@ -137,10 +137,6 @@ func main() { } // KYVERNO CRD CLIENT - // access CRD resources - // - ClusterPolicy, Policy - // - ClusterPolicyReport, PolicyReport - // - GenerateRequest pclient, err := kyvernoclient.NewForConfig(clientConfig) if err != nil { setupLog.Error(err, "Failed to create client") @@ -192,12 +188,7 @@ func main() { cosign.ImageSignatureRepository = imageSignatureRepository } - // KYVERNO CRD INFORMER - // watches CRD resources: - // - ClusterPolicy, Policy - // - ClusterPolicyReport, PolicyReport - // - GenerateRequest - // - ClusterReportChangeRequest, ReportChangeRequest + // KYVERNO CRD INFORMERS pInformer := kyvernoinformer.NewSharedInformerFactoryWithOptions(pclient, policyControllerResyncPeriod) // EVENT GENERATOR @@ -312,6 +303,7 @@ func main() { pInformer.Kyverno().V1().ClusterPolicies(), pInformer.Kyverno().V1().Policies(), pInformer.Kyverno().V1().GenerateRequests(), + pInformer.Kyverno().V1beta1().UpdateRequests(), configData, eventGenerator, reportReqGen, @@ -328,7 +320,11 @@ func main() { } // GENERATE REQUEST GENERATOR - grgen := webhookgenerate.NewGenerator(pclient, pInformer.Kyverno().V1().GenerateRequests(), stopCh, log.Log.WithName("GenerateRequestGenerator")) + grgen := webhookgenerate.NewGenerator(pclient, + pInformer.Kyverno().V1().GenerateRequests(), + pInformer.Kyverno().V1beta1().UpdateRequests(), + stopCh, + log.Log.WithName("UpdateRequestGenerator")) // GENERATE CONTROLLER // - applies generate rules on resources based on generate requests created by webhook @@ -339,9 +335,10 @@ func main() { pInformer.Kyverno().V1().ClusterPolicies(), pInformer.Kyverno().V1().Policies(), pInformer.Kyverno().V1().GenerateRequests(), + pInformer.Kyverno().V1beta1().UpdateRequests(), eventGenerator, kubeInformer.Core().V1().Namespaces(), - log.Log.WithName("GenerateController"), + log.Log.WithName("BackgroundController"), configData, ) if err != nil { @@ -358,6 +355,7 @@ func main() { pInformer.Kyverno().V1().ClusterPolicies(), pInformer.Kyverno().V1().Policies(), pInformer.Kyverno().V1().GenerateRequests(), + pInformer.Kyverno().V1beta1().UpdateRequests(), kubeInformer.Core().V1().Namespaces(), log.Log.WithName("GenerateCleanUpController"), ) @@ -399,7 +397,7 @@ func main() { os.Exit(1) } - registerWrapperRetry := common.RetryFunc(time.Second, webhookRegistrationTimeout, webhookCfg.Register, setupLog) + registerWrapperRetry := common.RetryFunc(time.Second, webhookRegistrationTimeout, webhookCfg.Register, "failed to register webhook", setupLog) registerWebhookConfigurations := func() { certManager.InitTLSPemPair() webhookCfg.Start() @@ -460,6 +458,7 @@ func main() { client, tlsPair, pInformer.Kyverno().V1().GenerateRequests(), + pInformer.Kyverno().V1beta1().UpdateRequests(), pInformer.Kyverno().V1().ClusterPolicies(), kubeInformer.Rbac().V1().RoleBindings(), kubeInformer.Rbac().V1().ClusterRoleBindings(), diff --git a/config/crds/kustomization.yaml b/config/crds/kustomization.yaml index fd2e13cbdd..fb2162dd12 100755 --- a/config/crds/kustomization.yaml +++ b/config/crds/kustomization.yaml @@ -7,5 +7,6 @@ resources: - ./kyverno.io_generaterequests.yaml - ./kyverno.io_policies.yaml - ./kyverno.io_reportchangerequests.yaml +- ./kyverno.io_updaterequests.yaml - ./wgpolicyk8s.io_clusterpolicyreports.yaml - ./wgpolicyk8s.io_policyreports.yaml \ No newline at end of file diff --git a/config/crds/kyverno.io_clusterpolicies.yaml b/config/crds/kyverno.io_clusterpolicies.yaml index a66d449f61..767101998a 100644 --- a/config/crds/kyverno.io_clusterpolicies.yaml +++ b/config/crds/kyverno.io_clusterpolicies.yaml @@ -198,17 +198,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -413,17 +413,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -624,15 +624,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -909,17 +909,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -1124,17 +1124,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -1335,15 +1335,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -1730,6 +1730,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -1740,6 +1747,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -2636,17 +2663,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -2851,17 +2878,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3062,15 +3089,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -3347,17 +3374,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3562,17 +3589,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3773,15 +3800,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -4168,6 +4195,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -4178,6 +4212,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be diff --git a/config/crds/kyverno.io_policies.yaml b/config/crds/kyverno.io_policies.yaml index 979424a889..b62e35186c 100644 --- a/config/crds/kyverno.io_policies.yaml +++ b/config/crds/kyverno.io_policies.yaml @@ -199,17 +199,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -414,17 +414,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -625,15 +625,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -910,17 +910,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -1125,17 +1125,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -1336,15 +1336,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -1731,6 +1731,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -1741,6 +1748,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -2638,17 +2665,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -2853,17 +2880,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3064,15 +3091,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -3349,17 +3376,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3564,17 +3591,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3775,15 +3802,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -4170,6 +4197,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -4180,6 +4214,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be diff --git a/config/crds/kyverno.io_updaterequests.yaml b/config/crds/kyverno.io_updaterequests.yaml index 48758dd6ec..d1c2c67776 100644 --- a/config/crds/kyverno.io_updaterequests.yaml +++ b/config/crds/kyverno.io_updaterequests.yaml @@ -13,7 +13,7 @@ spec: listKind: UpdateRequestList plural: updaterequests shortNames: - - gr + - ur singular: updaterequest scope: Namespaced versions: @@ -123,6 +123,12 @@ spec: policy: description: Specifies the name of the policy. type: string + requestType: + description: Type represents request type for background processing + enum: + - mutate + - generate + type: string resource: description: ResourceSpec is the information to identify the update request. diff --git a/config/install.yaml b/config/install.yaml index 5727912e4a..df3d1b8222 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -215,17 +215,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -430,17 +430,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -641,15 +641,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -926,17 +926,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -1141,17 +1141,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -1352,15 +1352,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -1747,6 +1747,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -1757,6 +1764,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -2653,17 +2680,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -2868,17 +2895,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3079,15 +3106,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -3364,17 +3391,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3579,17 +3606,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3790,15 +3817,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -4185,6 +4212,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -4195,6 +4229,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -5981,17 +6035,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -6196,17 +6250,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -6407,15 +6461,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -6692,17 +6746,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -6907,17 +6961,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -7118,15 +7172,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -7513,6 +7567,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -7523,6 +7584,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -8420,17 +8501,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -8635,17 +8716,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -8846,15 +8927,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -9131,17 +9212,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -9346,17 +9427,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -9557,15 +9638,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -9952,6 +10033,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -9962,6 +10050,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -11342,6 +11450,210 @@ status: conditions: [] storedVersions: [] --- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: latest + name: updaterequests.kyverno.io +spec: + group: kyverno.io + names: + kind: UpdateRequest + listKind: UpdateRequestList + plural: updaterequests + shortNames: + - ur + singular: updaterequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.policy + name: Policy + type: string + - jsonPath: .spec.resource.kind + name: ResourceKind + type: string + - jsonPath: .spec.resource.name + name: ResourceName + type: string + - jsonPath: .spec.resource.namespace + name: ResourceNamespace + type: string + - jsonPath: .status.state + name: status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: UpdateRequestStatus is a request to process mutate and generate + rules in background. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec is the information to identify the update request. + properties: + context: + description: Context ... + properties: + admissionRequestInfo: + description: AdmissionRequestInfoObject stores the admission request + and operation details + properties: + admissionRequest: + type: string + operation: + description: Operation is the type of resource operation being + checked for admission control + type: string + type: object + userInfo: + description: RequestInfo contains permission info carried in an + admission request. + properties: + clusterRoles: + description: ClusterRoles is a list of possible clusterRoles + send the request. + items: + type: string + nullable: true + type: array + roles: + description: Roles is a list of possible role send the request. + items: + type: string + nullable: true + type: array + userInfo: + description: UserInfo is the userInfo carried in the admission + request. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf + can generate + items: + type: string + type: array + description: Any additional information provided by the + authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: + type: string + type: array + uid: + description: A unique value that identifies this user + across time. If this user is deleted and another user + by the same name is added, they will have different + UIDs. + type: string + username: + description: The name that uniquely identifies this user + among all active users. + type: string + type: object + type: object + type: object + policy: + description: Specifies the name of the policy. + type: string + requestType: + description: Type represents request type for background processing + enum: + - mutate + - generate + type: string + resource: + description: ResourceSpec is the information to identify the update + request. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + required: + - context + - policy + - resource + type: object + status: + description: Status contains statistics related to update request. + properties: + generatedResources: + description: This will track the resources that are updated by the + generate Policy. Will be used during clean up resources. + items: + description: ResourceSpec contains information to identify a resource. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + message: + description: Specifies request status message. + type: string + state: + description: State represents state of the generate request. + type: string + required: + - state + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- apiVersion: v1 kind: ServiceAccount metadata: @@ -11586,6 +11898,8 @@ rules: - clusterpolicies/status - generaterequests - generaterequests/status + - updaterequests + - updaterequests/status - reportchangerequests - reportchangerequests/status - clusterreportchangerequests diff --git a/config/install_debug.yaml b/config/install_debug.yaml index f7bdded60e..4b9f22dada 100755 --- a/config/install_debug.yaml +++ b/config/install_debug.yaml @@ -204,17 +204,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -419,17 +419,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -630,15 +630,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -915,17 +915,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -1130,17 +1130,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -1341,15 +1341,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -1736,6 +1736,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -1746,6 +1753,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -2642,17 +2669,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -2857,17 +2884,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3068,15 +3095,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -3353,17 +3380,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3568,17 +3595,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -3779,15 +3806,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -4174,6 +4201,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -4184,6 +4218,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -5946,17 +6000,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -6161,17 +6215,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -6372,15 +6426,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -6657,17 +6711,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -6872,17 +6926,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -7083,15 +7137,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -7478,6 +7532,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -7488,6 +7549,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -8385,17 +8466,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -8600,17 +8681,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -8811,15 +8892,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -9096,17 +9177,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -9311,17 +9392,17 @@ spec: type: string type: array name: - description: Name is the name of the resource. + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one - character). - type: string - names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. + Each name supports wildcard characters "*" (matches + zero or many characters) and "?" (at least one + character). items: type: string type: array @@ -9522,15 +9603,15 @@ spec: type: string type: array name: - description: Name is the name of the resource. The name - supports wildcard characters "*" (matches zero or - many characters) and "?" (at least one character). + description: 'Name is the name of the resource. The + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). + NOTE: "Name" is being deprecated in favor of "Names".' type: string names: - description: 'Names are the names of the resources. - Each name supports wildcard characters "*" (matches - zero or many characters) and "?" (at least one character). - NOTE: "Name" is being deprecated in favor of "Names".' + description: Names are the names of the resources. Each + name supports wildcard characters "*" (matches zero + or many characters) and "?" (at least one character). items: type: string type: array @@ -9917,6 +9998,13 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: array + mutateExisting: + description: mutateExisting controls whether to mutate existing + resource ONLY The existing resources will be mutated ONLY + if set to "true". Otherwise all resources including admission + requests are mutated. Optional. Defaults to "false" if + not specified. + type: boolean patchStrategicMerge: description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ @@ -9927,6 +10015,26 @@ spec: Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. type: string + targets: + description: Targets defines the target resources to be + mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array type: object name: description: Name is a label to identify the rule, It must be @@ -11295,6 +11403,204 @@ status: conditions: [] storedVersions: [] --- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: updaterequests.kyverno.io +spec: + group: kyverno.io + names: + kind: UpdateRequest + listKind: UpdateRequestList + plural: updaterequests + shortNames: + - ur + singular: updaterequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.policy + name: Policy + type: string + - jsonPath: .spec.resource.kind + name: ResourceKind + type: string + - jsonPath: .spec.resource.name + name: ResourceName + type: string + - jsonPath: .spec.resource.namespace + name: ResourceNamespace + type: string + - jsonPath: .status.state + name: status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: UpdateRequestStatus is a request to process mutate and generate + rules in background. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec is the information to identify the update request. + properties: + context: + description: Context ... + properties: + admissionRequestInfo: + description: AdmissionRequestInfoObject stores the admission request + and operation details + properties: + admissionRequest: + type: string + operation: + description: Operation is the type of resource operation being + checked for admission control + type: string + type: object + userInfo: + description: RequestInfo contains permission info carried in an + admission request. + properties: + clusterRoles: + description: ClusterRoles is a list of possible clusterRoles + send the request. + items: + type: string + nullable: true + type: array + roles: + description: Roles is a list of possible role send the request. + items: + type: string + nullable: true + type: array + userInfo: + description: UserInfo is the userInfo carried in the admission + request. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf + can generate + items: + type: string + type: array + description: Any additional information provided by the + authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: + type: string + type: array + uid: + description: A unique value that identifies this user + across time. If this user is deleted and another user + by the same name is added, they will have different + UIDs. + type: string + username: + description: The name that uniquely identifies this user + among all active users. + type: string + type: object + type: object + type: object + policy: + description: Specifies the name of the policy. + type: string + requestType: + description: Type represents request type for background processing + enum: + - mutate + - generate + type: string + resource: + description: ResourceSpec is the information to identify the update + request. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + required: + - context + - policy + - resource + type: object + status: + description: Status contains statistics related to update request. + properties: + generatedResources: + description: This will track the resources that are updated by the + generate Policy. Will be used during clean up resources. + items: + description: ResourceSpec contains information to identify a resource. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + maxLength: 63 + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + message: + description: Specifies request status message. + type: string + state: + description: State represents state of the generate request. + type: string + required: + - state + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- apiVersion: v1 kind: ServiceAccount metadata: @@ -11494,6 +11800,8 @@ rules: - clusterpolicies/status - generaterequests - generaterequests/status + - updaterequests + - updaterequests/status - reportchangerequests - reportchangerequests/status - clusterreportchangerequests diff --git a/config/k8s-resource/clusterroles.yaml b/config/k8s-resource/clusterroles.yaml index b39bdaa40d..034d3be07b 100755 --- a/config/k8s-resource/clusterroles.yaml +++ b/config/k8s-resource/clusterroles.yaml @@ -15,6 +15,8 @@ rules: - clusterpolicies/status - generaterequests - generaterequests/status + - updaterequests + - updaterequests/status - reportchangerequests - reportchangerequests/status - clusterreportchangerequests diff --git a/docs/crd/v1/index.html b/docs/crd/v1/index.html index 52e1df85c7..f944213c17 100644 --- a/docs/crd/v1/index.html +++ b/docs/crd/v1/index.html @@ -209,8 +209,8 @@ string conditions
- -[]*./api/kyverno/v1.AnyAllConditions + +[]*github.com/realshuting/kyverno/api/kyverno/v1.AnyAllConditions @@ -330,8 +330,8 @@ value N, then N must be less than or equal to the size of entries, and at least entries
- -[]*./api/kyverno/v1.Attestor + +[]*github.com/realshuting/kyverno/api/kyverno/v1.Attestor @@ -1573,8 +1573,8 @@ Deprecated.

attestors
- -[]*./api/kyverno/v1.AttestorSet + +[]*github.com/realshuting/kyverno/api/kyverno/v1.AttestorSet @@ -1586,8 +1586,8 @@ Deprecated.

attestations
- -[]*./api/kyverno/v1.Attestation + +[]*github.com/realshuting/kyverno/api/kyverno/v1.Attestation @@ -1832,6 +1832,35 @@ Please specify under “any” or “all” instead.

+mutateExisting
+ +bool + + + +(Optional) +

mutateExisting controls whether to mutate existing resource ONLY +The existing resources will be mutated ONLY if set to “true”. +Otherwise all resources including admission requests are mutated. +Optional. Defaults to “false” if not specified.

+ + + + +targets
+ + +[]TargetMutation + + + + +(Optional) +

Targets defines the target resources to be mutated.

+ + + + patchStrategicMerge
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON @@ -1861,8 +1890,8 @@ See https://tools.ietf.org/html/rf foreach
-
-[]*./api/kyverno/v1.ForEachMutation + +[]*github.com/realshuting/kyverno/api/kyverno/v1.ForEachMutation @@ -2218,7 +2247,8 @@ string (Optional)

Name is the name of the resource. The name supports wildcard characters -“*” (matches zero or many characters) and “?” (at least one character).

+“*” (matches zero or many characters) and “?” (at least one character). +NOTE: “Name” is being deprecated in favor of “Names”.

@@ -2231,8 +2261,7 @@ string (Optional)

Names are the names of the resources. Each name supports wildcard characters -“*” (matches zero or many characters) and “?” (at least one character). -NOTE: “Name” is being deprecated in favor of “Names”.

+“*” (matches zero or many characters) and “?” (at least one character).

@@ -2344,7 +2373,7 @@ ResourceDescription

ResourceFilters -([]./api/kyverno/v1.ResourceFilter alias)

+([]github.com/realshuting/kyverno/api/kyverno/v1.ResourceFilter alias)

(Appears on: MatchResources) @@ -2358,7 +2387,8 @@ ResourceDescription (Appears on: GenerateRequestSpec, GenerateRequestStatus, -Generation) +Generation, +TargetMutation)

ResourceSpec contains information to identify a resource.

@@ -2572,8 +2602,8 @@ Generation verifyImages
- -[]*./api/kyverno/v1.ImageVerification + +[]*github.com/realshuting/kyverno/api/kyverno/v1.ImageVerification @@ -2766,6 +2796,39 @@ If not provided, the system roots are used.


+

TargetMutation +

+

+(Appears on: +Mutation) +

+

+

+ + + + + + + + + + + + + +
FieldDescription
+ResourceSpec
+ + +ResourceSpec + + +
+(Optional) +

ResourceSpec specifies the target resource information.

+
+

UserInfo

@@ -2858,8 +2921,8 @@ string foreach
- -[]*./api/kyverno/v1.ForEachValidation + +[]*github.com/realshuting/kyverno/api/kyverno/v1.ForEachValidation diff --git a/docs/crd/v1beta1/index.html b/docs/crd/v1beta1/index.html new file mode 100644 index 0000000000..475a682e10 --- /dev/null +++ b/docs/crd/v1beta1/index.html @@ -0,0 +1,436 @@ + + + + + + +Kyverno API + + + +

+ +

kyverno.io/v1beta1

+

+

Package v1beta1 contains API Schema definitions for the policy v1alpha1 API group

+

+Resource Types: +
    +
    +

    AdmissionRequestInfoObject +

    +

    +(Appears on: +UpdateRequestSpecContext) +

    +

    +

    AdmissionRequestInfoObject stores the admission request and operation details

    +

    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +admissionRequest
    + +string + +
    +(Optional) +
    +operation
    + + +Kubernetes admission/v1.Operation + + +
    +(Optional) +
    +
    +

    RequestInfo +

    +

    +(Appears on: +UpdateRequestSpecContext) +

    +

    +

    RequestInfo contains permission info carried in an admission request.

    +

    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +roles
    + +[]string + +
    +(Optional) +

    Roles is a list of possible role send the request.

    +
    +clusterRoles
    + +[]string + +
    +(Optional) +

    ClusterRoles is a list of possible clusterRoles send the request.

    +
    +userInfo
    + + +Kubernetes authentication/v1.UserInfo + + +
    +(Optional) +

    UserInfo is the userInfo carried in the admission request.

    +
    +
    +

    RequestType +(string alias)

    +

    +(Appears on: +UpdateRequestSpec) +

    +

    +

    +

    UpdateRequest +

    +

    +

    UpdateRequestStatus is a request to process mutate and generate rules in background.

    +

    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +metadata
    + + +Kubernetes meta/v1.ObjectMeta + + +
    +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
    +spec
    + + +UpdateRequestSpec + + +
    +

    Spec is the information to identify the update request.

    +
    +
    + + + + + + + + + + + + + + + + + +
    +requestType
    + + +RequestType + + +
    +

    Type represents request type for background processing

    +
    +policy
    + +string + +
    +

    Specifies the name of the policy.

    +
    +resource
    + +github.com/kyverno/kyverno/api/kyverno/v1.ResourceSpec + +
    +

    ResourceSpec is the information to identify the update request.

    +
    +context
    + + +UpdateRequestSpecContext + + +
    +

    Context …

    +
    +
    +status
    + + +UpdateRequestStatus + + +
    +(Optional) +

    Status contains statistics related to update request.

    +
    +
    +

    UpdateRequestSpec +

    +

    +(Appears on: +UpdateRequest) +

    +

    +

    UpdateRequestSpec stores the request specification.

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +requestType
    + + +RequestType + + +
    +

    Type represents request type for background processing

    +
    +policy
    + +string + +
    +

    Specifies the name of the policy.

    +
    +resource
    + +github.com/kyverno/kyverno/api/kyverno/v1.ResourceSpec + +
    +

    ResourceSpec is the information to identify the update request.

    +
    +context
    + + +UpdateRequestSpecContext + + +
    +

    Context …

    +
    +
    +

    UpdateRequestSpecContext +

    +

    +(Appears on: +UpdateRequestSpec) +

    +

    +

    UpdateRequestSpecContext stores the context to be shared.

    +

    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +userInfo
    + + +RequestInfo + + +
    +(Optional) +
    +admissionRequestInfo
    + + +AdmissionRequestInfoObject + + +
    +(Optional) +
    +
    +

    UpdateRequestState +(string alias)

    +

    +(Appears on: +UpdateRequestStatus) +

    +

    +

    UpdateRequestState defines the state of request.

    +

    +

    UpdateRequestStatus +

    +

    +(Appears on: +UpdateRequest) +

    +

    +

    UpdateRequestStatus defines the observed state of UpdateRequest

    +

    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +state
    + + +UpdateRequestState + + +
    +

    State represents state of the generate request.

    +
    +message
    + +string + +
    +(Optional) +

    Specifies request status message.

    +
    +generatedResources
    + +[]github.com/kyverno/kyverno/api/kyverno/v1.ResourceSpec + +
    +

    This will track the resources that are updated by the generate Policy. +Will be used during clean up resources.

    +
    +
    +
    + + + + + diff --git a/go.mod b/go.mod index 282c607dac..1bb983847c 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/go-logr/logr v1.2.2 github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839 github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20220301182634-bfe2ffc6b6bd + github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible github.com/googleapis/gnostic v0.5.5 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/in-toto/in-toto-golang v0.3.4-0.20211211042327-af1f9fb822bf diff --git a/pkg/background/common/context.go b/pkg/background/common/context.go new file mode 100644 index 0000000000..b8c347ec71 --- /dev/null +++ b/pkg/background/common/context.go @@ -0,0 +1,118 @@ +package common + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/go-logr/logr" + kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/config" + dclient "github.com/kyverno/kyverno/pkg/dclient" + "github.com/kyverno/kyverno/pkg/engine" + "github.com/kyverno/kyverno/pkg/engine/context" + utils "github.com/kyverno/kyverno/pkg/utils" + admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func NewBackgroundContext(dclient *dclient.Client, ur *urkyverno.UpdateRequest, + policy kyverno.PolicyInterface, trigger *unstructured.Unstructured, + cfg config.Interface, namespaceLabels map[string]string, logger logr.Logger) (*engine.PolicyContext, bool, error) { + + ctx := context.NewContext() + requestString := ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest + var request admissionv1.AdmissionRequest + + err := json.Unmarshal([]byte(requestString), &request) + if err != nil { + logger.Error(err, "error parsing the request string") + } + + if err := ctx.AddRequest(&request); err != nil { + logger.Error(err, "failed to load request in context") + return nil, false, err + } + + new, old, err := utils.ExtractResources(nil, &request) + if err != nil { + logger.Error(err, "failed to load request in context") + return nil, false, err + } + + if !reflect.DeepEqual(new, unstructured.Unstructured{}) { + if !check(&new, trigger) { + err := fmt.Errorf("resources don't match") + logger.Error(err, "", "resource", ur.Spec.Resource) + return nil, false, err + } + } + + if trigger == nil { + trigger = &old + } + + err = ctx.AddResource(trigger.Object) + if err != nil { + logger.Error(err, "failed to load resource in context") + return nil, false, err + } + + err = ctx.AddOldResource(old.Object) + if err != nil { + logger.Error(err, "failed to load resource in context") + return nil, false, err + } + + err = ctx.AddUserInfo(ur.Spec.Context.UserRequestInfo) + if err != nil { + logger.Error(err, "failed to load SA in context") + return nil, false, err + } + + err = ctx.AddServiceAccount(ur.Spec.Context.UserRequestInfo.AdmissionUserInfo.Username) + if err != nil { + logger.Error(err, "failed to load UserInfo in context") + return nil, false, err + } + + if err := ctx.AddImageInfos(trigger); err != nil { + logger.Error(err, "unable to add image info to variables context") + } + + policyContext := &engine.PolicyContext{ + NewResource: *trigger, + OldResource: old, + Policy: policy, + AdmissionInfo: ur.Spec.Context.UserRequestInfo, + ExcludeGroupRole: cfg.GetExcludeGroupRole(), + ExcludeResourceFunc: cfg.ToFilter, + JSONContext: ctx, + NamespaceLabels: namespaceLabels, + Client: dclient, + AdmissionOperation: false, + } + + return policyContext, false, nil +} + +func check(admissionRsc, existingRsc *unstructured.Unstructured) bool { + if existingRsc == nil { + return admissionRsc == nil + } + + if admissionRsc.GetName() != existingRsc.GetName() { + return false + } + if admissionRsc.GetNamespace() != existingRsc.GetNamespace() { + return false + } + if admissionRsc.GetKind() != existingRsc.GetKind() { + return false + } + if admissionRsc.GetAPIVersion() != existingRsc.GetAPIVersion() { + return false + } + return true +} diff --git a/pkg/background/common/report.go b/pkg/background/common/report.go new file mode 100644 index 0000000000..a9f4296ca8 --- /dev/null +++ b/pkg/background/common/report.go @@ -0,0 +1,34 @@ +package common + +import ( + "fmt" + + "github.com/kyverno/kyverno/pkg/event" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func FailedEvents(err error, policy, rule string, source event.Source, resource unstructured.Unstructured) []event.Info { + re := newEvent(policy, rule, source, resource) + + re.Reason = event.PolicyFailed.String() + re.Message = fmt.Sprintf("policy %s/%s failed to apply to %s/%s/%s: %v", policy, rule, resource.GetKind(), resource.GetNamespace(), resource.GetName(), err) + + return []event.Info{re} +} + +func SucceedEvents(policy, rule string, source event.Source, resource unstructured.Unstructured) []event.Info { + re := newEvent(policy, rule, source, resource) + + re.Reason = event.PolicyApplied.String() + re.Message = fmt.Sprintf("policy %s/%s applied to %s/%s/%s successfully", policy, rule, resource.GetKind(), resource.GetNamespace(), resource.GetName()) + + return []event.Info{re} +} + +func newEvent(policy, rule string, source event.Source, resource unstructured.Unstructured) (re event.Info) { + re.Kind = resource.GetKind() + re.Namespace = resource.GetNamespace() + re.Name = resource.GetName() + re.Source = source + return +} diff --git a/pkg/background/common/resource.go b/pkg/background/common/resource.go new file mode 100644 index 0000000000..8f1cc0354a --- /dev/null +++ b/pkg/background/common/resource.go @@ -0,0 +1,55 @@ +package common + +import ( + "fmt" + "time" + + logr "github.com/go-logr/logr" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/common" + dclient "github.com/kyverno/kyverno/pkg/dclient" + v1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func GetResource(client *dclient.Client, urSpec urkyverno.UpdateRequestSpec, log logr.Logger) (*unstructured.Unstructured, error) { + resourceSpec := urSpec.Resource + + get := func() (*unstructured.Unstructured, error) { + if resourceSpec.Kind == "Namespace" { + resourceSpec.Namespace = "" + } + resource, err := client.GetResource(resourceSpec.APIVersion, resourceSpec.Kind, resourceSpec.Namespace, resourceSpec.Name) + if err != nil { + if urSpec.Type == urkyverno.Mutate && errors.IsNotFound(err) && urSpec.Context.AdmissionRequestInfo.Operation == v1.Delete { + log.V(4).Info("trigger resource does not exist for mutateExisting rule", "operation", urSpec.Context.AdmissionRequestInfo.Operation) + return nil, nil + } + + return nil, fmt.Errorf("resource %s/%s/%s/%s: %v", resourceSpec.APIVersion, resourceSpec.Kind, resourceSpec.Namespace, resourceSpec.Name, err) + } + + if resource.GetDeletionTimestamp() != nil { + log.V(4).Info("trigger resource is in termination", "operation", urSpec.Context.AdmissionRequestInfo.Operation) + return nil, nil + } + + return resource, nil + } + + var resource *unstructured.Unstructured + var err error + retry := func() error { + resource, err = get() + return err + } + + f := common.RetryFunc(time.Second, 5*time.Second, retry, "failed to get resource", log.WithName("getResource")) + if err := f(); err != nil { + return nil, err + } + + log.Info("fetched trigger resource", "resourceSpec", resourceSpec) + return resource, err +} diff --git a/pkg/background/common/status.go b/pkg/background/common/status.go index 504b94b470..0b062bd9d3 100644 --- a/pkg/background/common/status.go +++ b/pkg/background/common/status.go @@ -2,6 +2,7 @@ package common import ( kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" jsonutils "github.com/kyverno/kyverno/pkg/utils/json" "k8s.io/apimachinery/pkg/api/errors" @@ -10,9 +11,9 @@ import ( //StatusControlInterface provides interface to update status subresource type StatusControlInterface interface { - Failed(gr kyverno.GenerateRequest, message string, genResources []kyverno.ResourceSpec) error - Success(gr kyverno.GenerateRequest, genResources []kyverno.ResourceSpec) error - Skip(gr kyverno.GenerateRequest, genResources []kyverno.ResourceSpec) error + Failed(ur urkyverno.UpdateRequest, message string, genResources []kyverno.ResourceSpec) error + Success(ur urkyverno.UpdateRequest, genResources []kyverno.ResourceSpec) error + Skip(gr urkyverno.UpdateRequest, genResources []kyverno.ResourceSpec) error } // StatusControl is default implementaation of GRStatusControlInterface @@ -21,61 +22,75 @@ type StatusControl struct { } //Failed sets gr status.state to failed with message -func (sc StatusControl) Failed(gr kyverno.GenerateRequest, message string, genResources []kyverno.ResourceSpec) error { +func (sc StatusControl) Failed(gr urkyverno.UpdateRequest, message string, genResources []kyverno.ResourceSpec) error { + genR := &urkyverno.UpdateRequestStatus{ + State: urkyverno.Failed, + Message: message, + } + if genResources != nil { + genR.GeneratedResources = genResources + } + patch := jsonutils.NewPatch( "/status", "replace", - &kyverno.GenerateRequestStatus{ - State: kyverno.Failed, - Message: message, - GeneratedResources: genResources, // Update Generated Resources - }, + genR, ) _, err := PatchGenerateRequest(&gr, patch, sc.Client, "status") if err != nil && !errors.IsNotFound(err) { - log.Log.Error(err, "failed to patch generate request status", "name", gr.Name) + log.Log.Error(err, "failed to patch update request status", "name", gr.Name) return err } - log.Log.V(3).Info("updated generate request status", "name", gr.Name, "status", string(kyverno.Failed)) + log.Log.V(3).Info("updated update request status", "name", gr.Name, "status", string(kyverno.Failed)) return nil } // Success sets the gr status.state to completed and clears message -func (sc StatusControl) Success(gr kyverno.GenerateRequest, genResources []kyverno.ResourceSpec) error { +func (sc StatusControl) Success(gr urkyverno.UpdateRequest, genResources []kyverno.ResourceSpec) error { + genR := &urkyverno.UpdateRequestStatus{ + State: urkyverno.Completed, + Message: "", + } + + if genResources != nil { + genR.GeneratedResources = genResources + } + patch := jsonutils.NewPatch( "/status", "replace", - &kyverno.GenerateRequestStatus{ - State: kyverno.Completed, - Message: "", - GeneratedResources: genResources, // Update Generated Resources - }, + genR, ) _, err := PatchGenerateRequest(&gr, patch, sc.Client, "status") if err != nil && !errors.IsNotFound(err) { - log.Log.Error(err, "failed to patch generate request status", "name", gr.Name) + log.Log.Error(err, "failed to patch update request status", "name", gr.Name) return err } - log.Log.V(3).Info("updated generate request status", "name", gr.Name, "status", string(kyverno.Completed)) + log.Log.V(3).Info("updated update request status", "name", gr.Name, "status", string(kyverno.Completed)) return nil } // Success sets the gr status.state to completed and clears message -func (sc StatusControl) Skip(gr kyverno.GenerateRequest, genResources []kyverno.ResourceSpec) error { +func (sc StatusControl) Skip(gr urkyverno.UpdateRequest, genResources []kyverno.ResourceSpec) error { + genR := &urkyverno.UpdateRequestStatus{ + State: urkyverno.Skip, + Message: "", + } + + if genResources != nil { + genR.GeneratedResources = genResources + } + patch := jsonutils.NewPatch( "/status", "replace", - &kyverno.GenerateRequestStatus{ - State: kyverno.Skip, - Message: "", - GeneratedResources: genResources, // Update Generated Resources - }, + genR, ) _, err := PatchGenerateRequest(&gr, patch, sc.Client, "status") if err != nil && !errors.IsNotFound(err) { - log.Log.Error(err, "failed to patch generate request status", "name", gr.Name) + log.Log.Error(err, "failed to update generate request status", "name", gr.Name) return err } - log.Log.V(3).Info("updated generate request status", "name", gr.Name, "status", string(kyverno.Skip)) + log.Log.V(3).Info("updated update request status", "name", gr.Name, "status", string(kyverno.Skip)) return nil } diff --git a/pkg/background/common/util.go b/pkg/background/common/util.go index 48010f0c45..ca952953bd 100644 --- a/pkg/background/common/util.go +++ b/pkg/background/common/util.go @@ -3,7 +3,7 @@ package common import ( "context" - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" "github.com/kyverno/kyverno/pkg/config" jsonutils "github.com/kyverno/kyverno/pkg/utils/json" @@ -12,12 +12,12 @@ import ( ) // PatchGenerateRequest patches a generate request object -func PatchGenerateRequest(gr *kyverno.GenerateRequest, patch jsonutils.Patch, client kyvernoclient.Interface, subresources ...string) (*kyverno.GenerateRequest, error) { +func PatchGenerateRequest(gr *urkyverno.UpdateRequest, patch jsonutils.Patch, client kyvernoclient.Interface, subresources ...string) (*urkyverno.UpdateRequest, error) { data, err := patch.ToPatchBytes() if nil != err { return gr, err } - newGR, err := client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Patch(context.TODO(), gr.Name, types.JSONPatchType, data, metav1.PatchOptions{}, subresources...) + newGR, err := client.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).Patch(context.TODO(), gr.Name, types.JSONPatchType, data, metav1.PatchOptions{}, subresources...) if err != nil { return gr, err } diff --git a/pkg/background/generate/cleanup/cleanup.go b/pkg/background/generate/cleanup/cleanup.go index e1f910b092..d3fd715c78 100644 --- a/pkg/background/generate/cleanup/cleanup.go +++ b/pkg/background/generate/cleanup/cleanup.go @@ -4,12 +4,12 @@ import ( "strconv" "github.com/go-logr/logr" - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" dclient "github.com/kyverno/kyverno/pkg/dclient" apierrors "k8s.io/apimachinery/pkg/api/errors" ) -func (c *Controller) processGR(gr kyverno.GenerateRequest) error { +func (c *Controller) processGR(gr urkyverno.UpdateRequest) error { logger := c.log.WithValues("kind", gr.Kind, "namespace", gr.Namespace, "name", gr.Name) // 1- Corresponding policy has been deleted // then we don't delete the generated resources @@ -44,7 +44,7 @@ func (c *Controller) processGR(gr kyverno.GenerateRequest) error { return nil } -func ownerResourceExists(log logr.Logger, client *dclient.Client, gr kyverno.GenerateRequest) bool { +func ownerResourceExists(log logr.Logger, client *dclient.Client, gr urkyverno.UpdateRequest) bool { _, err := client.GetResource("", gr.Spec.Resource.Kind, gr.Spec.Resource.Namespace, gr.Spec.Resource.Name) // trigger resources has been deleted if apierrors.IsNotFound(err) { @@ -58,7 +58,7 @@ func ownerResourceExists(log logr.Logger, client *dclient.Client, gr kyverno.Gen return true } -func deleteGeneratedResources(log logr.Logger, client *dclient.Client, gr kyverno.GenerateRequest) error { +func deleteGeneratedResources(log logr.Logger, client *dclient.Client, gr urkyverno.UpdateRequest) error { for _, genResource := range gr.Status.GeneratedResources { err := client.DeleteResource("", genResource.Kind, genResource.Namespace, genResource.Name, false) if err != nil && !apierrors.IsNotFound(err) { diff --git a/pkg/background/generate/cleanup/controller.go b/pkg/background/generate/cleanup/controller.go index a2064b384d..bdbb2849c2 100644 --- a/pkg/background/generate/cleanup/controller.go +++ b/pkg/background/generate/cleanup/controller.go @@ -3,14 +3,15 @@ package cleanup import ( "time" - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" - "k8s.io/client-go/kubernetes" - "github.com/go-logr/logr" + kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/autogen" kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1" + urkyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1beta1" kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1" + urkyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1" pkgCommon "github.com/kyverno/kyverno/pkg/common" "github.com/kyverno/kyverno/pkg/config" dclient "github.com/kyverno/kyverno/pkg/dclient" @@ -19,6 +20,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" coreinformers "k8s.io/client-go/informers/core/v1" + "k8s.io/client-go/kubernetes" corelister "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" @@ -55,6 +57,9 @@ type Controller struct { // grLister can list/get generate request from the shared informer's store grLister kyvernolister.GenerateRequestNamespaceLister + // urLister can list/get update request from the shared informer's store + urLister urkyvernolister.UpdateRequestNamespaceLister + // nsLister can list/get namespaces from the shared informer's store nsLister corelister.NamespaceLister @@ -67,12 +72,12 @@ type Controller struct { // grSynced returns true if the generate request store has been synced at least once grSynced cache.InformerSynced + // urSynced returns true if the update request store has been synced at least once + urSynced cache.InformerSynced + // nsListerSynced returns true if the namespace store has been synced at least once nsListerSynced cache.InformerSynced - // namespaceInformer for re-evaluation on namespace updates - namespaceInformer coreinformers.NamespaceInformer - // logger log logr.Logger } @@ -85,17 +90,17 @@ func NewController( pInformer kyvernoinformer.ClusterPolicyInformer, npInformer kyvernoinformer.PolicyInformer, grInformer kyvernoinformer.GenerateRequestInformer, + urInformer urkyvernoinformer.UpdateRequestInformer, namespaceInformer coreinformers.NamespaceInformer, log logr.Logger, ) (*Controller, error) { c := Controller{ - kyvernoClient: kyvernoclient, - client: client, - pInformer: pInformer, - grInformer: grInformer, - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "generate-request-cleanup"), - namespaceInformer: namespaceInformer, - log: log, + kyvernoClient: kyvernoclient, + client: client, + pInformer: pInformer, + grInformer: grInformer, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "generate-request-cleanup"), + log: log, } c.control = Control{client: kyvernoclient} @@ -108,6 +113,7 @@ func NewController( c.pSynced = pInformer.Informer().HasSynced c.npSynced = npInformer.Informer().HasSynced c.grSynced = grInformer.Informer().HasSynced + c.urSynced = urInformer.Informer().HasSynced c.nsListerSynced = namespaceInformer.Informer().HasSynced return &c, nil @@ -163,24 +169,24 @@ func (c *Controller) deletePolicy(obj interface{}) { for _, gr := range grs { logger.V(4).Info("enqueue the gr for cleanup", "gr name", gr.Name) - c.addGR(gr) + c.addUR(gr) } } } -func (c *Controller) addGR(obj interface{}) { - gr := obj.(*kyverno.GenerateRequest) +func (c *Controller) addUR(obj interface{}) { + gr := obj.(*urkyverno.UpdateRequest) c.enqueue(gr) } -func (c *Controller) updateGR(old, cur interface{}) { - gr := cur.(*kyverno.GenerateRequest) +func (c *Controller) updateUR(old, cur interface{}) { + gr := cur.(*urkyverno.UpdateRequest) c.enqueue(gr) } -func (c *Controller) deleteGR(obj interface{}) { +func (c *Controller) deleteUR(obj interface{}) { logger := c.log - gr, ok := obj.(*kyverno.GenerateRequest) + gr, ok := obj.(*urkyverno.UpdateRequest) if !ok { tombstone, ok := obj.(cache.DeletedFinalStateUnknown) if !ok { @@ -188,7 +194,7 @@ func (c *Controller) deleteGR(obj interface{}) { return } - _, ok = tombstone.Obj.(*kyverno.GenerateRequest) + _, ok = tombstone.Obj.(*urkyverno.UpdateRequest) if !ok { logger.Info("ombstone contained object that is not a Generate Request", "obj", obj) return @@ -215,9 +221,9 @@ func (c *Controller) deleteGR(obj interface{}) { c.enqueue(gr) } -func (c *Controller) enqueue(gr *kyverno.GenerateRequest) { +func (c *Controller) enqueue(gr *urkyverno.UpdateRequest) { // skip enqueueing Pending requests - if gr.Status.State == kyverno.Pending { + if gr.Status.State == urkyverno.Pending { return } @@ -240,7 +246,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}) { logger.Info("starting") defer logger.Info("shutting down") - if !cache.WaitForCacheSync(stopCh, c.pSynced, c.grSynced, c.npSynced) { + if !cache.WaitForCacheSync(stopCh, c.pSynced, c.grSynced, c.urSynced, c.npSynced, c.nsListerSynced) { logger.Info("failed to sync informer cache") return } @@ -250,9 +256,9 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}) { }) c.grInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: c.addGR, - UpdateFunc: c.updateGR, - DeleteFunc: c.deleteGR, + AddFunc: c.addUR, + UpdateFunc: c.updateUR, + DeleteFunc: c.deleteUR, }) for i := 0; i < workers; i++ { @@ -320,7 +326,7 @@ func (c *Controller) syncGenerateRequest(key string) error { if err != nil { return err } - gr, err := c.grLister.Get(grName) + gr, err := c.urLister.Get(grName) if err != nil { return err } diff --git a/pkg/background/generate/cleanup/resource.go b/pkg/background/generate/cleanup/resource.go index cade7410a1..255b7df61a 100644 --- a/pkg/background/generate/cleanup/resource.go +++ b/pkg/background/generate/cleanup/resource.go @@ -20,5 +20,5 @@ type Control struct { //Delete deletes the specified resource func (c Control) Delete(gr string) error { - return c.client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Delete(context.TODO(), gr, metav1.DeleteOptions{}) + return c.client.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).Delete(context.TODO(), gr, metav1.DeleteOptions{}) } diff --git a/pkg/background/generate/generate.go b/pkg/background/generate/generate.go index 8034d8cfb5..88149767c8 100644 --- a/pkg/background/generate/generate.go +++ b/pkg/background/generate/generate.go @@ -10,29 +10,27 @@ import ( "strings" "time" + "github.com/go-logr/logr" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/background/common" kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1" - "github.com/kyverno/kyverno/pkg/engine/response" - "github.com/kyverno/kyverno/pkg/event" - - "github.com/go-logr/logr" pkgcommon "github.com/kyverno/kyverno/pkg/common" "github.com/kyverno/kyverno/pkg/config" dclient "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/engine" "github.com/kyverno/kyverno/pkg/engine/context" + "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/utils" "github.com/kyverno/kyverno/pkg/engine/variables" + "github.com/kyverno/kyverno/pkg/event" kyvernoutils "github.com/kyverno/kyverno/pkg/utils" - admissionv1 "k8s.io/api/admission/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" - coreinformers "k8s.io/client-go/informers/core/v1" corelister "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" ) @@ -75,7 +73,7 @@ func NewGenerateController( npolicyLister kyvernolister.PolicyLister, grLister kyvernolister.GenerateRequestNamespaceLister, eventGen event.Interface, - namespaceInformer coreinformers.NamespaceInformer, + nsLister corelister.NamespaceLister, log logr.Logger, dynamicConfig config.Interface, ) (*GenerateController, error) { @@ -92,12 +90,12 @@ func NewGenerateController( } c.statusControl = common.StatusControl{Client: kyvernoClient} - c.nsLister = namespaceInformer.Lister() + c.nsLister = nsLister return &c, nil } -func (c *GenerateController) ProcessGR(gr *kyverno.GenerateRequest) error { +func (c *GenerateController) ProcessGR(gr *urkyverno.UpdateRequest) error { logger := c.log.WithValues("name", gr.Name, "policy", gr.Spec.Policy, "kind", gr.Spec.Resource.Kind, "apiVersion", gr.Spec.Resource.APIVersion, "namespace", gr.Spec.Resource.Namespace, "name", gr.Spec.Resource.Name) var err error var resource *unstructured.Unstructured @@ -105,7 +103,7 @@ func (c *GenerateController) ProcessGR(gr *kyverno.GenerateRequest) error { var precreatedResource bool // 1 - Check if the resource exists - resource, err = getResource(c.client, gr.Spec.Resource, c.log) + resource, err = common.GetResource(c.client, gr.Spec, c.log) if err != nil { // Don't update status // re-queueing the GR by updating the annotation @@ -142,7 +140,7 @@ func (c *GenerateController) ProcessGR(gr *kyverno.GenerateRequest) error { if updateAnnotation { gr.SetAnnotations(grAnnotations) - _, err := c.kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(contextdefault.TODO(), gr, metav1.UpdateOptions{}) + _, err := c.kyvernoClient.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).Update(contextdefault.TODO(), gr, metav1.UpdateOptions{}) if err != nil { logger.Error(err, "failed to update annotation in generate request for the resource", "generate request", gr.Name) return err @@ -169,7 +167,7 @@ func (c *GenerateController) ProcessGR(gr *kyverno.GenerateRequest) error { } // 3 - Report failure Events - events := failedEvents(err, *gr, *resource) + events := common.FailedEvents(err, gr.Spec.Policy, "", event.GeneratePolicyController, *resource) c.eventGen.Add(events...) } @@ -179,12 +177,11 @@ func (c *GenerateController) ProcessGR(gr *kyverno.GenerateRequest) error { const doesNotApply = "policy does not apply to resource" -func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, gr kyverno.GenerateRequest, namespaceLabels map[string]string) ([]kyverno.ResourceSpec, bool, error) { +func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, gr urkyverno.UpdateRequest, namespaceLabels map[string]string) ([]kyverno.ResourceSpec, bool, error) { logger := c.log.WithValues("name", gr.Name, "policy", gr.Spec.Policy, "kind", gr.Spec.Resource.Kind, "apiVersion", gr.Spec.Resource.APIVersion, "namespace", gr.Spec.Resource.Namespace, "name", gr.Spec.Resource.Name) // Get the list of rules to be applied // get policy // build context - ctx := context.NewContext() logger.V(3).Info("applying generate policy rule") @@ -211,53 +208,9 @@ func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, g return nil, false, err } - requestString := gr.Spec.Context.AdmissionRequestInfo.AdmissionRequest - var request admissionv1.AdmissionRequest - err = json.Unmarshal([]byte(requestString), &request) + policyContext, precreatedResource, err := common.NewBackgroundContext(c.client, &gr, &policy, &resource, c.Config, namespaceLabels, logger) if err != nil { - logger.Error(err, "error parsing the request string") - } - - if gr.Spec.Context.AdmissionRequestInfo.Operation == admissionv1.Update { - request.Operation = gr.Spec.Context.AdmissionRequestInfo.Operation - } - - if err := ctx.AddRequest(&request); err != nil { - logger.Error(err, "failed to load request in context") - return nil, false, err - } - - err = ctx.AddResource(resource.Object) - if err != nil { - logger.Error(err, "failed to load resource in context") - return nil, false, err - } - - err = ctx.AddUserInfo(gr.Spec.Context.UserRequestInfo) - if err != nil { - logger.Error(err, "failed to load SA in context") - return nil, false, err - } - - err = ctx.AddServiceAccount(gr.Spec.Context.UserRequestInfo.AdmissionUserInfo.Username) - if err != nil { - logger.Error(err, "failed to load UserInfo in context") - return nil, false, err - } - - if err := ctx.AddImageInfos(&resource); err != nil { - logger.Error(err, "unable to add image info to variables context") - } - - policyContext := &engine.PolicyContext{ - NewResource: resource, - Policy: &policy, - AdmissionInfo: gr.Spec.Context.UserRequestInfo, - ExcludeGroupRole: c.Config.GetExcludeGroupRole(), - ExcludeResourceFunc: c.Config.ToFilter, - JSONContext: ctx, - NamespaceLabels: namespaceLabels, - Client: c.client, + return nil, precreatedResource, err } // check if the policy still applies to the resource @@ -300,7 +253,7 @@ func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, g } // getPolicySpec gets the policy spec from the ClusterPolicy/Policy -func (c *GenerateController) getPolicySpec(gr kyverno.GenerateRequest) (kyverno.ClusterPolicy, error) { +func (c *GenerateController) getPolicySpec(gr urkyverno.UpdateRequest) (kyverno.ClusterPolicy, error) { var policy kyverno.ClusterPolicy pNamespace, pName, err := cache.SplitMetaNamespaceKey(gr.Spec.Policy) @@ -328,7 +281,7 @@ func (c *GenerateController) getPolicySpec(gr kyverno.GenerateRequest) (kyverno. } } -func updateStatus(statusControl common.StatusControlInterface, gr kyverno.GenerateRequest, err error, genResources []kyverno.ResourceSpec, precreatedResource bool) error { +func updateStatus(statusControl common.StatusControlInterface, gr urkyverno.UpdateRequest, err error, genResources []kyverno.ResourceSpec, precreatedResource bool) error { if err != nil { return statusControl.Failed(gr, err.Error(), genResources) } else if precreatedResource { @@ -339,7 +292,7 @@ func updateStatus(statusControl common.StatusControlInterface, gr kyverno.Genera return statusControl.Success(gr, genResources) } -func (c *GenerateController) applyGeneratePolicy(log logr.Logger, policyContext *engine.PolicyContext, gr kyverno.GenerateRequest, applicableRules []string) (genResources []kyverno.ResourceSpec, processExisting bool, err error) { +func (c *GenerateController) applyGeneratePolicy(log logr.Logger, policyContext *engine.PolicyContext, gr urkyverno.UpdateRequest, applicableRules []string) (genResources []kyverno.ResourceSpec, processExisting bool, err error) { // Get the response as the actions to be performed on the resource // - - substitute values policy := policyContext.Policy @@ -417,7 +370,7 @@ func getResourceInfo(object map[string]interface{}) (kind, name, namespace, apiv return } -func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, policy string, gr kyverno.GenerateRequest) (kyverno.ResourceSpec, error) { +func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, policy string, gr urkyverno.UpdateRequest) (kyverno.ResourceSpec, error) { var rdata map[string]interface{} var err error var mode ResourceMode diff --git a/pkg/background/generate/report.go b/pkg/background/generate/report.go deleted file mode 100644 index d4ba801531..0000000000 --- a/pkg/background/generate/report.go +++ /dev/null @@ -1,21 +0,0 @@ -package generate - -import ( - "fmt" - - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/event" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -func failedEvents(err error, gr kyverno.GenerateRequest, resource unstructured.Unstructured) []event.Info { - re := event.Info{} - re.Kind = resource.GetKind() - re.Namespace = resource.GetNamespace() - re.Name = resource.GetName() - re.Reason = event.PolicyFailed.String() - re.Source = event.GeneratePolicyController - re.Message = fmt.Sprintf("policy %s failed to apply: %v", gr.Spec.Policy, err) - - return []event.Info{re} -} diff --git a/pkg/background/generate/resource.go b/pkg/background/generate/resource.go deleted file mode 100644 index c33db182ff..0000000000 --- a/pkg/background/generate/resource.go +++ /dev/null @@ -1,42 +0,0 @@ -package generate - -import ( - "time" - - logr "github.com/go-logr/logr" - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/common" - dclient "github.com/kyverno/kyverno/pkg/dclient" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -func getResource(client *dclient.Client, resourceSpec kyverno.ResourceSpec, log logr.Logger) (*unstructured.Unstructured, error) { - - get := func() (*unstructured.Unstructured, error) { - if resourceSpec.Kind == "Namespace" { - resourceSpec.Namespace = "" - } - resource, err := client.GetResource(resourceSpec.APIVersion, resourceSpec.Kind, resourceSpec.Namespace, resourceSpec.Name) - if err != nil { - return nil, err - } - - if resource.GetDeletionTimestamp() != nil { - return nil, nil - } - - return resource, nil - } - - retry := func() error { - _, err := get() - return err - } - - f := common.RetryFunc(time.Second, 30*time.Second, retry, log.WithName("getResource")) - if err := f(); err != nil { - return nil, err - } - - return get() -} diff --git a/pkg/background/mutate/mutate.go b/pkg/background/mutate/mutate.go new file mode 100644 index 0000000000..9ab8384e7f --- /dev/null +++ b/pkg/background/mutate/mutate.go @@ -0,0 +1,243 @@ +package mutate + +import ( + "encoding/json" + "fmt" + + "github.com/go-logr/logr" + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/background/common" + kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" + kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1" + urlister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/config" + dclient "github.com/kyverno/kyverno/pkg/dclient" + "github.com/kyverno/kyverno/pkg/engine" + "github.com/kyverno/kyverno/pkg/engine/response" + engineUtils "github.com/kyverno/kyverno/pkg/engine/utils" + "github.com/kyverno/kyverno/pkg/event" + "github.com/kyverno/kyverno/pkg/utils" + yamlv2 "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + cache "k8s.io/client-go/tools/cache" +) + +type MutateExistingController struct { + client *dclient.Client + + // typed client for Kyverno CRDs + kyvernoClient *kyvernoclient.Clientset + + // urStatusControl is used to update UR status + statusControl common.StatusControlInterface + + // event generator interface + eventGen event.Interface + + log logr.Logger + + // urLister can list/get update request from the shared informer's store + urLister urlister.UpdateRequestNamespaceLister + + // policyLister can list/get cluster policy from the shared informer's store + policyLister kyvernolister.ClusterPolicyLister + + // policyLister can list/get Namespace policy from the shared informer's store + npolicyLister kyvernolister.PolicyLister + + Config config.Interface +} + +// NewMutateExistingController returns an instance of the MutateExistingController +func NewMutateExistingController( + kyvernoClient *kyvernoclient.Clientset, + client *dclient.Client, + policyLister kyvernolister.ClusterPolicyLister, + npolicyLister kyvernolister.PolicyLister, + urLister urlister.UpdateRequestNamespaceLister, + eventGen event.Interface, + log logr.Logger, + dynamicConfig config.Interface, +) (*MutateExistingController, error) { + + c := MutateExistingController{ + client: client, + kyvernoClient: kyvernoClient, + eventGen: eventGen, + log: log, + policyLister: policyLister, + npolicyLister: npolicyLister, + urLister: urLister, + Config: dynamicConfig, + } + + c.statusControl = common.StatusControl{Client: kyvernoClient} + return &c, nil +} + +func (c *MutateExistingController) ProcessUR(ur *urkyverno.UpdateRequest) error { + logger := c.log.WithValues("name", ur.Name, "policy", ur.Spec.Policy, "kind", ur.Spec.Resource.Kind, "apiVersion", ur.Spec.Resource.APIVersion, "namespace", ur.Spec.Resource.Namespace, "name", ur.Spec.Resource.Name) + var errs []error + + policy, err := c.getPolicy(ur.Spec.Policy) + if err != nil { + logger.Error(err, "failed to get policy") + return err + } + + for _, rule := range policy.GetSpec().Rules { + if !rule.IsMutateExisting() { + continue + } + + trigger, err := common.GetResource(c.client, ur.Spec, c.log) + if err != nil { + logger.WithName(rule.Name).Error(err, "failed to get trigger resource") + errs = append(errs, err) + continue + } + + policyContext, _, err := common.NewBackgroundContext(c.client, ur, policy, trigger, c.Config, nil, logger) + if err != nil { + logger.WithName(rule.Name).Error(err, "failed to build policy context") + errs = append(errs, err) + continue + } + + er := engine.Mutate(policyContext) + for _, r := range er.PolicyResponse.Rules { + patched := r.PatchedTarget + switch r.Status { + case response.RuleStatusFail, response.RuleStatusError, response.RuleStatusWarn: + err := fmt.Errorf("failed to mutate existing resource, rule response%v: %s", r.Status, r.Message) + logger.Error(err, "") + errs = append(errs, err) + c.report(err, ur.Spec.Policy, rule.Name, patched) + + case response.RuleStatusSkip: + logger.Info("mutate existing rule skipped", "rule", r.Name, "message", r.Message) + c.report(err, ur.Spec.Policy, rule.Name, patched) + + case response.RuleStatusPass: + + patchedNew, err := addAnnotation(policy, patched, r) + if err != nil { + logger.Error(err, "failed to apply patches") + errs = append(errs, err) + } + + if patchedNew == nil { + err := fmt.Errorf("empty resource to patch") + logger.Error(err, "", "rule", r.Name, "message", r.Message) + errs = append(errs, err) + continue + } + + if r.Status == response.RuleStatusPass { + _, updateErr := c.client.UpdateResource(patchedNew.GetAPIVersion(), patchedNew.GetKind(), patchedNew.GetNamespace(), patchedNew.Object, false) + if updateErr != nil { + errs = append(errs, updateErr) + logger.WithName(rule.Name).Error(updateErr, "failed to update target resource", "namespace", patchedNew.GetNamespace(), "name", patchedNew.GetName()) + } else { + logger.WithName(rule.Name).V(4).Info("successfully mutated existing resource", "namespace", patchedNew.GetNamespace(), "name", patchedNew.GetName()) + } + + c.report(updateErr, ur.Spec.Policy, rule.Name, patched) + } + } + } + } + + return updateURStatus(c.statusControl, *ur, engineUtils.CombineErrors(errs)) +} + +func (c *MutateExistingController) getPolicy(key string) (kyvernov1.PolicyInterface, error) { + pNamespace, pName, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, err + } + + if pNamespace != "" { + return c.npolicyLister.Policies(pNamespace).Get(pName) + } + + return c.policyLister.Get(pName) +} + +func (c *MutateExistingController) report(err error, policy, rule string, target *unstructured.Unstructured) { + var events []event.Info + + if target == nil { + c.log.WithName("mutateExisting").Info("cannot generate events for empty target resource", "policy", policy, "rule", rule, "err", err.Error()) + } + + if err != nil { + events = common.FailedEvents(err, policy, rule, event.MutateExistingController, *target) + } else { + events = common.SucceedEvents(policy, rule, event.MutateExistingController, *target) + } + + c.eventGen.Add(events...) +} + +func updateURStatus(statusControl common.StatusControlInterface, ur urkyverno.UpdateRequest, err error) error { + if err != nil { + return statusControl.Failed(ur, err.Error(), nil) + } + + return statusControl.Success(ur, nil) +} + +func addAnnotation(policy kyvernov1.PolicyInterface, patched *unstructured.Unstructured, r response.RuleResponse) (patchedNew *unstructured.Unstructured, err error) { + if patched == nil { + return + } + + patchedNew = patched + var rulePatches []utils.RulePatch + + for _, patch := range r.Patches { + var patchmap map[string]interface{} + if err := json.Unmarshal(patch, &patchmap); err != nil { + return nil, fmt.Errorf("failed to parse JSON patch bytes: %v", err) + } + + rp := struct { + RuleName string `json:"rulename"` + Op string `json:"op"` + Path string `json:"path"` + }{ + RuleName: r.Name, + Op: patchmap["op"].(string), + Path: patchmap["path"].(string), + } + + rulePatches = append(rulePatches, rp) + } + + var annotationContent = make(map[string]string) + policyName := policy.GetName() + if policy.GetNamespace() != "" { + policyName = policy.GetNamespace() + "/" + policy.GetName() + } + + for _, rulePatch := range rulePatches { + annotationContent[rulePatch.RuleName+"."+policyName+".kyverno.io"] = utils.OperationToPastTense[rulePatch.Op] + " " + rulePatch.Path + } + + if len(annotationContent) == 0 { + return + } + + result, _ := yamlv2.Marshal(annotationContent) + + ann := patchedNew.GetAnnotations() + if ann == nil { + ann = make(map[string]string) + } + ann[utils.PolicyAnnotation] = string(result) + patchedNew.SetAnnotations(ann) + + return +} diff --git a/pkg/background/request_process.go b/pkg/background/request_process.go index cb7df68bd1..f319d024f3 100644 --- a/pkg/background/request_process.go +++ b/pkg/background/request_process.go @@ -1,13 +1,23 @@ package background import ( - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/background/generate" + "github.com/kyverno/kyverno/pkg/background/mutate" ) -func (c *Controller) ProcessGR(gr *kyverno.GenerateRequest) error { - ctrl, _ := generate.NewGenerateController(c.kyvernoClient, c.client, - c.policyLister, c.npolicyLister, c.grLister, c.eventGen, c.namespaceInformer, c.log, c.Config, - ) - return ctrl.ProcessGR(gr) +func (c *Controller) ProcessUR(ur *urkyverno.UpdateRequest) error { + switch ur.Spec.Type { + case urkyverno.Mutate: + ctrl, _ := mutate.NewMutateExistingController(c.kyvernoClient, c.client, + c.policyLister, c.npolicyLister, c.urLister, c.eventGen, c.log, c.Config) + return ctrl.ProcessUR(ur) + + case urkyverno.Generate: + ctrl, _ := generate.NewGenerateController(c.kyvernoClient, c.client, + c.policyLister, c.npolicyLister, c.grLister, c.eventGen, c.nsLister, c.log, c.Config, + ) + return ctrl.ProcessGR(ur) + } + return nil } diff --git a/pkg/background/update_request_controller.go b/pkg/background/update_request_controller.go index 9d1cbde2de..4993eae842 100644 --- a/pkg/background/update_request_controller.go +++ b/pkg/background/update_request_controller.go @@ -6,11 +6,14 @@ import ( "github.com/go-logr/logr" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/autogen" common "github.com/kyverno/kyverno/pkg/background/common" kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1" + urkyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1beta1" kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1" + urlister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/config" dclient "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/event" @@ -57,6 +60,9 @@ type Controller struct { // grLister can list/get generate request from the shared informer's store grLister kyvernolister.GenerateRequestNamespaceLister + // urLister can list/get update request from the shared informer's store + urLister urlister.UpdateRequestNamespaceLister + // nsLister can list/get namespaces from the shared informer's store nsLister corelister.NamespaceLister @@ -69,8 +75,10 @@ type Controller struct { // grSynced returns true if the Generate Request store has been synced at least once grSynced cache.InformerSynced - // namespaceInformer for re-evaluation on namespace updates - namespaceInformer coreinformers.NamespaceInformer + // urSynced returns true if the Update Request store has been synced at least once + urSynced cache.InformerSynced + + nsSynced cache.InformerSynced log logr.Logger @@ -85,6 +93,7 @@ func NewController( policyInformer kyvernoinformer.ClusterPolicyInformer, npolicyInformer kyvernoinformer.PolicyInformer, grInformer kyvernoinformer.GenerateRequestInformer, + urInformer urkyvernoinformer.UpdateRequestInformer, eventGen event.Interface, namespaceInformer coreinformers.NamespaceInformer, log logr.Logger, @@ -92,14 +101,13 @@ func NewController( ) (*Controller, error) { c := Controller{ - client: client, - kyvernoClient: kyvernoClient, - policyInformer: policyInformer, - eventGen: eventGen, - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "generate-request"), - namespaceInformer: namespaceInformer, - log: log, - Config: dynamicConfig, + client: client, + kyvernoClient: kyvernoClient, + policyInformer: policyInformer, + eventGen: eventGen, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "generate-request"), + log: log, + Config: dynamicConfig, } c.statusControl = common.StatusControl{Client: kyvernoClient} @@ -116,10 +124,20 @@ func NewController( DeleteFunc: c.deleteGR, }) + c.urSynced = urInformer.Informer().HasSynced + urInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.addUR, + UpdateFunc: c.updateUR, + DeleteFunc: c.deleteUR, + }) + c.policyLister = policyInformer.Lister() c.npolicyLister = npolicyInformer.Lister() c.grLister = grInformer.Lister().GenerateRequests(config.KyvernoNamespace) + c.urLister = urInformer.Lister().UpdateRequests(config.KyvernoNamespace) + c.nsLister = namespaceInformer.Lister() + c.nsSynced = namespaceInformer.Informer().HasSynced return &c, nil } @@ -130,7 +148,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}) { defer c.queue.ShutDown() defer c.log.Info("shutting down") - if !cache.WaitForCacheSync(stopCh, c.policySynced, c.grSynced, c.npolicySynced) { + if !cache.WaitForCacheSync(stopCh, c.policySynced, c.grSynced, c.urSynced, c.npolicySynced, c.nsSynced) { c.log.Info("failed to sync informer cache") return } @@ -203,7 +221,7 @@ func (c *Controller) syncGenerateRequest(key string) error { return err } - gr, err := c.grLister.Get(grName) + gr, err := c.urLister.Get(grName) if err != nil { if apierrors.IsNotFound(err) { return nil @@ -213,7 +231,7 @@ func (c *Controller) syncGenerateRequest(key string) error { return err } - return c.ProcessGR(gr) + return c.ProcessUR(gr) } // EnqueueGenerateRequestFromWebhook - enqueueing generate requests from webhook @@ -221,14 +239,14 @@ func (c *Controller) EnqueueGenerateRequestFromWebhook(gr *kyverno.GenerateReque c.enqueueGenerateRequest(gr) } -func (c *Controller) enqueueGenerateRequest(gr *kyverno.GenerateRequest) { - c.log.V(5).Info("enqueuing generate request", "gr", gr.Name) - key, err := cache.MetaNamespaceKeyFunc(gr) +func (c *Controller) enqueueGenerateRequest(obj interface{}) { + key, err := cache.MetaNamespaceKeyFunc(obj) if err != nil { c.log.Error(err, "failed to extract name") return } + c.log.V(5).Info("enqueued update request", "ur", key) c.queue.Add(key) } @@ -324,7 +342,66 @@ func (c *Controller) deleteGR(obj interface{}) { } } - logger.V(3).Info("deleting generate request", "name", gr.Name) + logger.V(3).Info("deleting update request", "name", gr.Name) + + // sync Handler will remove it from the queue + c.enqueueGenerateRequest(gr) +} + +func (c *Controller) addUR(obj interface{}) { + ur := obj.(*urkyverno.UpdateRequest) + c.enqueueGenerateRequest(ur) +} + +func (c *Controller) updateUR(old, cur interface{}) { + oldUr := old.(*urkyverno.UpdateRequest) + curUr := cur.(*urkyverno.UpdateRequest) + if oldUr.ResourceVersion == curUr.ResourceVersion { + // Periodic resync will send update events for all known Namespace. + // Two different versions of the same replica set will always have different RVs. + return + } + // only process the ones that are in "Pending"/"Completed" state + // if the Generate Request fails due to incorrect policy, it will be requeued during policy update + if curUr.Status.State == urkyverno.Failed { + return + } + c.enqueueGenerateRequest(curUr) +} + +func (c *Controller) deleteUR(obj interface{}) { + logger := c.log + gr, ok := obj.(*urkyverno.UpdateRequest) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + logger.Info("Couldn't get object from tombstone", "obj", obj) + return + } + _, ok = tombstone.Obj.(*kyverno.GenerateRequest) + if !ok { + logger.Info("tombstone contained object that is not a Generate Request CR", "obj", obj) + return + } + } + + if gr.Spec.GetRequestType() == urkyverno.Generate { + for _, resource := range gr.Status.GeneratedResources { + r, err := c.client.GetResource(resource.APIVersion, resource.Kind, resource.Namespace, resource.Name) + if err != nil && !apierrors.IsNotFound(err) { + logger.Error(err, "Generated resource is not deleted", "Resource", resource.Name) + continue + } + + if r != nil && r.GetLabels()["policy.kyverno.io/synchronize"] == "enable" { + if err := c.client.DeleteResource(r.GetAPIVersion(), r.GetKind(), r.GetNamespace(), r.GetName(), false); err != nil && !apierrors.IsNotFound(err) { + logger.Error(err, "Generated resource is not deleted", "Resource", r.GetName()) + } + } + } + + logger.V(3).Info("deleting update request", "name", gr.Name) + } // sync Handler will remove it from the queue c.enqueueGenerateRequest(gr) diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index d8e08590d9..f2ea804965 100755 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -53,9 +53,9 @@ func (c *Clientset) KyvernoV1() kyvernov1.KyvernoV1Interface { return c.kyvernoV1 } -// KyvernoV1 retrieves the KyvernoV1Client +// KyvernoV1beta1 retrieves the KyvernoV1beta1Client func (c *Clientset) KyvernoV1beta1() kyvernov1beta1.KyvernoV1beta1Interface { - return c.KyvernoV1beta1() + return c.kyvernoV1beta1 } // KyvernoV1alpha2 retrieves the KyvernoV1alpha2Client diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index 0c909b36ea..170bc2042d 100755 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -87,7 +87,7 @@ func (c *Clientset) KyvernoV1() kyvernov1.KyvernoV1Interface { return &fakekyvernov1.FakeKyvernoV1{Fake: &c.Fake} } -// KyvernoV1alpha2 retrieves the KyvernoV1alpha2Client +// KyvernoV1beta1 retrieves the KyvernoV1beta1Client func (c *Clientset) KyvernoV1beta1() kyvernov1beta1.KyvernoV1beta1Interface { return &fakekyvernov1beta1.FakeKyvernoV1beta1{Fake: &c.Fake} } diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index 75eeb29362..a93604b6d1 100755 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -21,6 +21,7 @@ package fake import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1alpha2 "github.com/kyverno/kyverno/api/kyverno/v1alpha2" + kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" wgpolicyk8sv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -34,6 +35,7 @@ var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ kyvernov1.AddToScheme, + kyvernov1beta1.AddToScheme, kyvernov1alpha2.AddToScheme, wgpolicyk8sv1alpha2.AddToScheme, } diff --git a/pkg/client/clientset/versioned/typed/kyverno/v1beta1/kyverno_client.go b/pkg/client/clientset/versioned/typed/kyverno/v1beta1/kyverno_client.go index fdc5396519..13ac8af831 100644 --- a/pkg/client/clientset/versioned/typed/kyverno/v1beta1/kyverno_client.go +++ b/pkg/client/clientset/versioned/typed/kyverno/v1beta1/kyverno_client.go @@ -19,7 +19,7 @@ limitations under the License. package v1beta1 import ( - v1 "github.com/kyverno/kyverno/api/kyverno/v1" + v1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" scheme "github.com/kyverno/kyverno/pkg/client/clientset/versioned/scheme" rest "k8s.io/client-go/rest" ) @@ -52,7 +52,7 @@ func NewForConfig(c *rest.Config) (*KyvernoV1beta1Client, error) { } func setConfigDefaults(config *rest.Config) error { - gv := v1.SchemeGroupVersion + gv := v1beta1.GroupVersion config.GroupVersion = &gv config.APIPath = "/apis" config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index f53eda674e..a3e65270e4 100755 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -23,6 +23,7 @@ import ( v1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/api/kyverno/v1alpha2" + v1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" policyreportv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" @@ -62,6 +63,9 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v1.SchemeGroupVersion.WithResource("policies"): return &genericInformer{resource: resource.GroupResource(), informer: f.Kyverno().V1().Policies().Informer()}, nil + case v1beta1.GroupVersion.WithResource("updaterequests"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Kyverno().V1beta1().UpdateRequests().Informer()}, nil + // Group=kyverno.io, Version=v1alpha2 case v1alpha2.SchemeGroupVersion.WithResource("clusterreportchangerequests"): return &genericInformer{resource: resource.GroupResource(), informer: f.Kyverno().V1alpha2().ClusterReportChangeRequests().Informer()}, nil diff --git a/pkg/client/informers/externalversions/kyverno/interface.go b/pkg/client/informers/externalversions/kyverno/interface.go index c5062abc7c..749c186593 100755 --- a/pkg/client/informers/externalversions/kyverno/interface.go +++ b/pkg/client/informers/externalversions/kyverno/interface.go @@ -22,12 +22,15 @@ import ( internalinterfaces "github.com/kyverno/kyverno/pkg/client/informers/externalversions/internalinterfaces" v1 "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1" v1alpha2 "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1alpha2" + v1beta1 "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1beta1" ) // Interface provides access to each of this group's versions. type Interface interface { // V1 provides access to shared informers for resources in V1. V1() v1.Interface + // V1beta1 provides access to shared informers for resources in V1beta1. + V1beta1() v1beta1.Interface // V1alpha2 provides access to shared informers for resources in V1alpha2. V1alpha2() v1alpha2.Interface } @@ -48,6 +51,11 @@ func (g *group) V1() v1.Interface { return v1.New(g.factory, g.namespace, g.tweakListOptions) } +// V1beta1 returns a new v1beta1.Interface. +func (g *group) V1beta1() v1beta1.Interface { + return v1beta1.New(g.factory, g.namespace, g.tweakListOptions) +} + // V1alpha2 returns a new v1alpha2.Interface. func (g *group) V1alpha2() v1alpha2.Interface { return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) diff --git a/pkg/common/common.go b/pkg/common/common.go index 28f25459c5..0251c3ba1e 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -101,7 +101,7 @@ func VariableToJSON(key, value string) []byte { } // RetryFunc allows retrying a function on error within a given timeout -func RetryFunc(retryInterval, timeout time.Duration, run func() error, logger logr.Logger) func() error { +func RetryFunc(retryInterval, timeout time.Duration, run func() error, msg string, logger logr.Logger) func() error { return func() error { registerTimeout := time.After(timeout) registerTicker := time.NewTicker(retryInterval) @@ -114,13 +114,13 @@ func RetryFunc(retryInterval, timeout time.Duration, run func() error, logger lo case <-registerTicker.C: err = run() if err != nil { - logger.V(3).Info("Failed to register admission control webhooks", "reason", err.Error()) + logger.V(3).Info(msg, "reason", err.Error()) } else { break loop } case <-registerTimeout: - return errors.Wrap(err, "Timeout registering admission control webhooks") + return errors.Wrap(err, "retry times out") } } return nil diff --git a/pkg/engine/background.go b/pkg/engine/background.go new file mode 100644 index 0000000000..fba25dc4dd --- /dev/null +++ b/pkg/engine/background.go @@ -0,0 +1,143 @@ +package engine + +import ( + "time" + + kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/autogen" + "github.com/kyverno/kyverno/pkg/engine/common" + "github.com/kyverno/kyverno/pkg/engine/response" + "github.com/kyverno/kyverno/pkg/engine/variables" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// ApplyBackgroundChecks checks for validity of generate and mutateExisting rules on the resource +// 1. validate variables to be substitute in the general ruleInfo (match,exclude,condition) +// - the caller has to check the ruleResponse to determine whether the path exist +// 2. returns the list of rules that are applicable on this policy and resource, if 1 succeed +func ApplyBackgroundChecks(policyContext *PolicyContext) (resp *response.EngineResponse) { + policyStartTime := time.Now() + return filterRules(policyContext, policyStartTime) +} + +func filterRules(policyContext *PolicyContext, startTime time.Time) *response.EngineResponse { + kind := policyContext.NewResource.GetKind() + name := policyContext.NewResource.GetName() + namespace := policyContext.NewResource.GetNamespace() + apiVersion := policyContext.NewResource.GetAPIVersion() + resp := &response.EngineResponse{ + PolicyResponse: response.PolicyResponse{ + Policy: response.PolicySpec{ + Name: policyContext.Policy.GetName(), + Namespace: policyContext.Policy.GetNamespace(), + }, + PolicyStats: response.PolicyStats{ + PolicyExecutionTimestamp: startTime.Unix(), + }, + Resource: response.ResourceSpec{ + Kind: kind, + Name: name, + Namespace: namespace, + APIVersion: apiVersion, + }, + }, + } + + if policyContext.ExcludeResourceFunc(kind, namespace, name) { + log.Log.WithName("ApplyBackgroundChecks").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name) + return resp + } + + for _, rule := range autogen.ComputeRules(policyContext.Policy) { + if ruleResp := filterRule(rule, policyContext); ruleResp != nil { + resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) + } + } + + return resp +} + +func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleResponse { + if !rule.HasGenerate() && !rule.IsMutateExisting() { + return nil + } + + ruleType := response.Mutation + if rule.HasGenerate() { + ruleType = response.Generation + } + + var err error + startTime := time.Now() + + policy := policyContext.Policy + newResource := policyContext.NewResource + oldResource := policyContext.OldResource + admissionInfo := policyContext.AdmissionInfo + ctx := policyContext.JSONContext + excludeGroupRole := policyContext.ExcludeGroupRole + namespaceLabels := policyContext.NamespaceLabels + + logger := log.Log.WithName(string(ruleType)).WithValues("policy", policy.GetName(), + "kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName()) + + if err = MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, ""); err != nil { + + if ruleType == response.Generation { + // if the oldResource matched, return "false" to delete GR for it + if err = MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, ""); err == nil { + return &response.RuleResponse{ + Name: rule.Name, + Type: ruleType, + Status: response.RuleStatusFail, + RuleStats: response.RuleStats{ + ProcessingTime: time.Since(startTime), + RuleExecutionTimestamp: startTime.Unix(), + }, + } + } + } + + return nil + } + + policyContext.JSONContext.Checkpoint() + defer policyContext.JSONContext.Restore() + + if err = LoadContext(logger, rule.Context, policyContext, rule.Name); err != nil { + logger.V(4).Info("cannot add external data to the context", "reason", err.Error()) + return nil + } + + ruleCopy := rule.DeepCopy() + if after, err := variables.SubstituteAllInPreconditions(logger, ctx, ruleCopy.GetAnyAllConditions()); err != nil { + logger.V(4).Info("failed to substitute vars in preconditions, skip current rule", "rule name", ruleCopy.Name) + return nil + } else { + ruleCopy.SetAnyAllConditions(after) + } + + // operate on the copy of the conditions, as we perform variable substitution + copyConditions, err := common.TransformConditions(ruleCopy.GetAnyAllConditions()) + if err != nil { + logger.V(4).Info("cannot copy AnyAllConditions", "reason", err.Error()) + return nil + } + + // evaluate pre-conditions + if !variables.EvaluateConditions(logger, ctx, copyConditions) { + logger.V(4).Info("skip rule as preconditions are not met", "rule", ruleCopy.Name) + return nil + } + + // build rule Response + return &response.RuleResponse{ + Name: ruleCopy.Name, + Type: ruleType, + Status: response.RuleStatusPass, + RuleStats: response.RuleStats{ + ProcessingTime: time.Since(startTime), + RuleExecutionTimestamp: startTime.Unix(), + }, + } +} diff --git a/pkg/engine/context/context.go b/pkg/engine/context/context.go index 4f1dfe5fba..9eab358c91 100644 --- a/pkg/engine/context/context.go +++ b/pkg/engine/context/context.go @@ -6,7 +6,7 @@ import ( "sync" jsonpatch "github.com/evanphx/json-patch/v5" - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" pkgcommon "github.com/kyverno/kyverno/pkg/common" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "github.com/pkg/errors" @@ -50,7 +50,7 @@ type Interface interface { AddOldResource(data map[string]interface{}) error // AddUserInfo merges userInfo json under kyverno.userInfo - AddUserInfo(userInfo kyverno.RequestInfo) error + AddUserInfo(userInfo urkyverno.RequestInfo) error // AddServiceAccount merges ServiceAccount types AddServiceAccount(userName string) error @@ -166,7 +166,7 @@ func (ctx *context) AddOldResource(data map[string]interface{}) error { } // AddUserInfo adds userInfo at path request.userInfo -func (ctx *context) AddUserInfo(userRequestInfo kyverno.RequestInfo) error { +func (ctx *context) AddUserInfo(userRequestInfo urkyverno.RequestInfo) error { return addToContext(ctx, userRequestInfo, "request") } diff --git a/pkg/engine/context/context_test.go b/pkg/engine/context/context_test.go index b72bbe68e7..d4c3ca0715 100644 --- a/pkg/engine/context/context_test.go +++ b/pkg/engine/context/context_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" authenticationv1 "k8s.io/api/authentication/v1" ) @@ -48,7 +48,7 @@ func Test_addResourceAndUserContext(t *testing.T) { Username: "system:serviceaccount:nirmata:user1", UID: "014fbff9a07c", } - userRequestInfo := kyverno.RequestInfo{ + userRequestInfo := urkyverno.RequestInfo{ Roles: nil, ClusterRoles: nil, AdmissionUserInfo: userInfo} diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index 9022659155..18d77dea00 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -3,27 +3,15 @@ package engine import ( "time" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/autogen" - "github.com/kyverno/kyverno/pkg/engine/common" - "k8s.io/client-go/tools/cache" - - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/engine/response" - "github.com/kyverno/kyverno/pkg/engine/variables" + "k8s.io/client-go/tools/cache" "sigs.k8s.io/controller-runtime/pkg/log" ) -// Generate checks for validity of generate rule on the resource -// 1. validate variables to be substitute in the general ruleInfo (match,exclude,condition) -// - the caller has to check the ruleResponse to determine whether the path exist -// 2. returns the list of rules that are applicable on this policy and resource, if 1 succeed -func Generate(policyContext *PolicyContext) (resp *response.EngineResponse) { - policyStartTime := time.Now() - return filterRules(policyContext, policyStartTime) -} - // GenerateResponse checks for validity of generate rule on the resource -func GenerateResponse(policyContext *PolicyContext, gr kyverno.GenerateRequest) (resp *response.EngineResponse) { +func GenerateResponse(policyContext *PolicyContext, gr urkyverno.UpdateRequest) (resp *response.EngineResponse) { policyStartTime := time.Now() return filterGenerateRules(policyContext, gr.Spec.Policy, policyStartTime) } @@ -69,118 +57,3 @@ func filterGenerateRules(policyContext *PolicyContext, policyNameKey string, sta return resp } - -func filterRules(policyContext *PolicyContext, startTime time.Time) *response.EngineResponse { - kind := policyContext.NewResource.GetKind() - name := policyContext.NewResource.GetName() - namespace := policyContext.NewResource.GetNamespace() - apiVersion := policyContext.NewResource.GetAPIVersion() - resp := &response.EngineResponse{ - PolicyResponse: response.PolicyResponse{ - Policy: response.PolicySpec{ - Name: policyContext.Policy.GetName(), - Namespace: policyContext.Policy.GetNamespace(), - }, - PolicyStats: response.PolicyStats{ - PolicyExecutionTimestamp: startTime.Unix(), - }, - Resource: response.ResourceSpec{ - Kind: kind, - Name: name, - Namespace: namespace, - APIVersion: apiVersion, - }, - }, - } - - if policyContext.ExcludeResourceFunc(kind, namespace, name) { - log.Log.WithName("Generate").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name) - return resp - } - - for _, rule := range autogen.ComputeRules(policyContext.Policy) { - if ruleResp := filterRule(rule, policyContext); ruleResp != nil { - resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) - } - } - - return resp -} - -func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleResponse { - if !rule.HasGenerate() { - return nil - } - - var err error - startTime := time.Now() - - policy := policyContext.Policy - newResource := policyContext.NewResource - oldResource := policyContext.OldResource - admissionInfo := policyContext.AdmissionInfo - ctx := policyContext.JSONContext - excludeGroupRole := policyContext.ExcludeGroupRole - namespaceLabels := policyContext.NamespaceLabels - - logger := log.Log.WithName("Generate").WithValues("policy", policy.GetName(), - "kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName()) - - if err = MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, ""); err != nil { - - // if the oldResource matched, return "false" to delete GR for it - if err = MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, ""); err == nil { - return &response.RuleResponse{ - Name: rule.Name, - Type: "Generation", - Status: response.RuleStatusFail, - RuleStats: response.RuleStats{ - ProcessingTime: time.Since(startTime), - RuleExecutionTimestamp: startTime.Unix(), - }, - } - } - - return nil - } - - policyContext.JSONContext.Checkpoint() - defer policyContext.JSONContext.Restore() - - if err = LoadContext(logger, rule.Context, policyContext, rule.Name); err != nil { - logger.V(4).Info("cannot add external data to the context", "reason", err.Error()) - return nil - } - - ruleCopy := rule.DeepCopy() - if after, err := variables.SubstituteAllInPreconditions(logger, ctx, ruleCopy.GetAnyAllConditions()); err != nil { - logger.V(4).Info("failed to substitute vars in preconditions, skip current rule", "rule name", ruleCopy.Name) - return nil - } else { - ruleCopy.SetAnyAllConditions(after) - } - - // operate on the copy of the conditions, as we perform variable substitution - copyConditions, err := common.TransformConditions(ruleCopy.GetAnyAllConditions()) - if err != nil { - logger.V(4).Info("cannot copy AnyAllConditions", "reason", err.Error()) - return nil - } - - // evaluate pre-conditions - if !variables.EvaluateConditions(logger, ctx, copyConditions) { - logger.V(4).Info("skip rule as preconditions are not met", "rule", ruleCopy.Name) - return nil - } - - // build rule Response - return &response.RuleResponse{ - Name: ruleCopy.Name, - Type: "Generation", - Status: response.RuleStatusPass, - RuleStats: response.RuleStats{ - ProcessingTime: time.Since(startTime), - RuleExecutionTimestamp: startTime.Unix(), - }, - } -} diff --git a/pkg/engine/imageVerify.go b/pkg/engine/imageVerify.go index 24cf067e08..a169430f2f 100644 --- a/pkg/engine/imageVerify.go +++ b/pkg/engine/imageVerify.go @@ -102,7 +102,7 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe } func appendError(resp *response.EngineResponse, rule *v1.Rule, msg string, status response.RuleStatus) { - rr := ruleResponse(rule, response.ImageVerify, msg, status) + rr := ruleResponse(*rule, response.ImageVerify, msg, status, nil) resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *rr) incrementErrorCount(resp) } @@ -168,7 +168,7 @@ func (iv *imageVerifier) verify(imageVerify *v1.ImageVerification, images map[st if imageInfo.Digest == "" && *imageVerify.MutateDigest && ruleResp.Status == response.RuleStatusPass { err := iv.patchDigest(path, imageInfo, digest, ruleResp) if err != nil { - ruleResp = ruleResponse(iv.rule, response.ImageVerify, err.Error(), response.RuleStatusFail) + ruleResp = ruleResponse(*iv.rule, response.ImageVerify, err.Error(), response.RuleStatusFail, nil) } } } else { @@ -177,11 +177,11 @@ func (iv *imageVerifier) verify(imageVerify *v1.ImageVerification, images map[st digest, err := fetchImageDigest(imageInfo.String()) if err != nil { msg := fmt.Sprintf("fetching image digest from registry error: %s", err) - ruleResp = ruleResponse(iv.rule, response.ImageVerify, msg, response.RuleStatusFail) + ruleResp = ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil) } else { err = iv.patchDigest(path, imageInfo, digest, ruleResp) if err != nil { - ruleResp = ruleResponse(iv.rule, response.ImageVerify, err.Error(), response.RuleStatusFail) + ruleResp = ruleResponse(*iv.rule, response.ImageVerify, err.Error(), response.RuleStatusFail, nil) } } } @@ -227,12 +227,12 @@ func (iv *imageVerifier) verifySignatures(imageVerify *v1.ImageVerification, ima if err != nil { iv.logger.Error(err, "failed to verify signature", "attestorSet", attestorSet) msg := fmt.Sprintf("failed to verify signature for %s: %s", image, err.Error()) - return ruleResponse(iv.rule, response.ImageVerify, msg, response.RuleStatusFail), "" + return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil), "" } } msg := fmt.Sprintf("verified image signatures for %s", image) - return ruleResponse(iv.rule, response.ImageVerify, msg, response.RuleStatusPass), digest + return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil), digest } func (iv *imageVerifier) verifyAttestorSet(attestorSet *v1.AttestorSet, imageVerify *v1.ImageVerification, image, path string) (string, error) { @@ -418,7 +418,7 @@ func (iv *imageVerifier) attestImage(imageVerify *v1.ImageVerification, imageInf statements := statementsByPredicate[ac.PredicateType] if statements == nil { msg := fmt.Sprintf("predicate type %s not found", ac.PredicateType) - return ruleResponse(iv.rule, response.ImageVerify, msg, response.RuleStatusFail) + return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil) } for _, s := range statements { @@ -429,14 +429,14 @@ func (iv *imageVerifier) attestImage(imageVerify *v1.ImageVerification, imageInf if !val { msg := fmt.Sprintf("attestation checks failed for %s and predicate %s", imageInfo.String(), ac.PredicateType) - return ruleResponse(iv.rule, response.ImageVerify, msg, response.RuleStatusFail) + return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil) } } } msg := fmt.Sprintf("attestation checks passed for %s", imageInfo.String()) iv.logger.V(2).Info(msg) - return ruleResponse(iv.rule, response.ImageVerify, msg, response.RuleStatusPass) + return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil) } func buildStatementMap(statements []map[string]interface{}) map[string][]map[string]interface{} { diff --git a/pkg/engine/loadtargets.go b/pkg/engine/loadtargets.go new file mode 100644 index 0000000000..f06bdaafd6 --- /dev/null +++ b/pkg/engine/loadtargets.go @@ -0,0 +1,61 @@ +package engine + +import ( + "fmt" + + "github.com/go-logr/logr" + kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + engineUtils "github.com/kyverno/kyverno/pkg/engine/utils" + "github.com/kyverno/kyverno/pkg/engine/variables" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func loadTargets(logger logr.Logger, targets []kyverno.TargetMutation, ctx *PolicyContext) ([]unstructured.Unstructured, error) { + targetObjects := make([]unstructured.Unstructured, len(targets)) + var errors []error + + for i, target := range targets { + apiversion, err := variables.SubstituteAll(logger, ctx.JSONContext, target.APIVersion) + if err != nil { + errors = append(errors, fmt.Errorf("failed to substitute variables in target[%d].APIVersion %s: %v", i, target.APIVersion, err)) + continue + } + + kind, err := variables.SubstituteAll(logger, ctx.JSONContext, target.Kind) + if err != nil { + errors = append(errors, fmt.Errorf("failed to substitute variables in target[%d].Kind %s: %v", i, target.Kind, err)) + continue + } + + name, err := variables.SubstituteAll(logger, ctx.JSONContext, target.Name) + if err != nil { + errors = append(errors, fmt.Errorf("failed to substitute variables in target[%d].Name %s: %v", i, target.Name, err)) + continue + } + + namespace, err := variables.SubstituteAll(logger, ctx.JSONContext, target.Namespace) + if err != nil { + errors = append(errors, fmt.Errorf("failed to substitute variables in target[%d].Namespace %s: %v", i, target.Namespace, err)) + continue + } + + if namespace == "" { + namespace = "default" + } + + obj, err := ctx.Client.GetResource(apiversion.(string), kind.(string), namespace.(string), name.(string)) + if err != nil { + errors = append(errors, fmt.Errorf("failed to get target %s/%s %s/%s : %v", apiversion, kind, namespace, name, err)) + continue + } + + if obj.GetKind() == "" { + obj.SetKind(kind.(string)) + } + + obj.SetAPIVersion(apiversion.(string)) + targetObjects = append(targetObjects, *obj) + } + + return targetObjects, engineUtils.CombineErrors(errors) +} diff --git a/pkg/engine/mutate/mutation_test.go b/pkg/engine/mutate/mutation_test.go index a66ee1d6be..faed4882cf 100644 --- a/pkg/engine/mutate/mutation_test.go +++ b/pkg/engine/mutate/mutation_test.go @@ -218,7 +218,7 @@ func assertEqStringAndData(t *testing.T, str string, data []byte) { json.Unmarshal([]byte(str), &p1) var p2 jsonPatch - json.Unmarshal([]byte(str), &p2) + json.Unmarshal([]byte(data), &p2) assert.Equal(t, p1, p2) } diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index 61a4a298c4..9ef2e641aa 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -22,16 +22,16 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { resp = &response.EngineResponse{ Policy: policy, } - patchedResource := policyContext.NewResource + matchedResource := policyContext.NewResource ctx := policyContext.JSONContext var skippedRules []string - logger := log.Log.WithName("EngineMutate").WithValues("policy", policy.GetName(), "kind", patchedResource.GetKind(), - "namespace", patchedResource.GetNamespace(), "name", patchedResource.GetName()) + logger := log.Log.WithName("EngineMutate").WithValues("policy", policy.GetName(), "kind", matchedResource.GetKind(), + "namespace", matchedResource.GetNamespace(), "name", matchedResource.GetName()) logger.V(4).Info("start mutate policy processing", "startTime", startTime) - startMutateResultResponse(resp, policy, patchedResource) + startMutateResultResponse(resp, policy, matchedResource) defer endMutateResultResponse(logger, resp, startTime) policyContext.JSONContext.Checkpoint() @@ -50,7 +50,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { excludeResource = policyContext.ExcludeGroupRole } - if err = MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels, policyContext.Policy.GetNamespace()); err != nil { + if err = MatchesResourceDescription(matchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels, policyContext.Policy.GetNamespace()); err != nil { logger.V(4).Info("rule not matched", "reason", err.Error()) skippedRules = append(skippedRules, rule.Name) continue @@ -78,19 +78,41 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { } ruleCopy := rule.DeepCopy() - var ruleResp *response.RuleResponse - if rule.Mutation.ForEachMutation != nil { - ruleResp, patchedResource = mutateForEach(ruleCopy, policyContext, patchedResource, logger) + var patchedResources []unstructured.Unstructured + if !policyContext.AdmissionOperation && rule.IsMutateExisting() { + targets, err := loadTargets(logger, ruleCopy.Mutation.Targets, policyContext) + if err != nil { + rr := ruleResponse(rule, response.Mutation, err.Error(), response.RuleStatusError, nil) + resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *rr) + } else { + patchedResources = append(patchedResources, targets...) + } } else { - ruleResp, patchedResource = mutateResource(ruleCopy, policyContext, patchedResource, logger) + patchedResources = append(patchedResources, matchedResource) } - if ruleResp != nil { - resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) - if ruleResp.Status == response.RuleStatusError { - incrementErrorCount(resp) + for _, patchedResource := range patchedResources { + if reflect.DeepEqual(patchedResource, unstructured.Unstructured{}) { + continue + } + + logger.V(4).Info("apply rule to resource", "rule", rule.Name, "resource namespace", patchedResource.GetNamespace(), "resource name", patchedResource.GetName()) + var ruleResp *response.RuleResponse + if rule.Mutation.ForEachMutation != nil { + ruleResp, patchedResource = mutateForEach(ruleCopy, policyContext, patchedResource, logger) } else { - incrementAppliedCount(resp) + ruleResp, patchedResource = mutateResource(ruleCopy, policyContext, patchedResource, logger) + } + + matchedResource = patchedResource + + if ruleResp != nil { + resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) + if ruleResp.Status == response.RuleStatusError { + incrementErrorCount(resp) + } else { + incrementAppliedCount(resp) + } } } } @@ -104,7 +126,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { } } - resp.PatchedResource = patchedResource + resp.PatchedResource = matchedResource return resp } @@ -115,11 +137,11 @@ func mutateResource(rule *kyverno.Rule, ctx *PolicyContext, resource unstructure } if !preconditionsPassed { - return ruleResponse(rule, response.Mutation, "preconditions not met", response.RuleStatusSkip), resource + return ruleResponse(*rule, response.Mutation, "preconditions not met", response.RuleStatusSkip, &resource), resource } mutateResp := mutate.Mutate(rule, ctx.JSONContext, resource, logger) - ruleResp := buildRuleResponse(rule, mutateResp) + ruleResp := buildRuleResponse(rule, mutateResp, &mutateResp.PatchedResource) return ruleResp, mutateResp.PatchedResource } @@ -145,7 +167,7 @@ func mutateForEach(rule *kyverno.Rule, ctx *PolicyContext, resource unstructured } if !preconditionsPassed { - return ruleResponse(rule, response.Mutation, "preconditions not met", response.RuleStatusSkip), resource + return ruleResponse(*rule, response.Mutation, "preconditions not met", response.RuleStatusSkip, &patchedResource), resource } elements, err := evaluateList(foreach.List, ctx.JSONContext) @@ -157,7 +179,7 @@ func mutateForEach(rule *kyverno.Rule, ctx *PolicyContext, resource unstructured mutateResp := mutateElements(rule.Name, foreach, ctx, elements, patchedResource, logger) if mutateResp.Status == response.RuleStatusError { logger.Error(err, "failed to mutate elements") - return buildRuleResponse(rule, mutateResp), resource + return buildRuleResponse(rule, mutateResp, nil), resource } if mutateResp.Status != response.RuleStatusSkip { @@ -170,10 +192,10 @@ func mutateForEach(rule *kyverno.Rule, ctx *PolicyContext, resource unstructured } if applyCount == 0 { - return ruleResponse(rule, response.Mutation, "0 elements processed", response.RuleStatusSkip), resource + return ruleResponse(*rule, response.Mutation, "0 elements processed", response.RuleStatusSkip, &resource), resource } - r := ruleResponse(rule, response.Mutation, fmt.Sprintf("%d elements processed", applyCount), response.RuleStatusPass) + r := ruleResponse(*rule, response.Mutation, fmt.Sprintf("%d elements processed", applyCount), response.RuleStatusPass, &patchedResource) r.Patches = allPatches return r, patchedResource } @@ -237,8 +259,8 @@ func mutateError(err error, message string) *mutate.Response { } } -func buildRuleResponse(rule *kyverno.Rule, mutateResp *mutate.Response) *response.RuleResponse { - resp := ruleResponse(rule, response.Mutation, mutateResp.Message, mutateResp.Status) +func buildRuleResponse(rule *kyverno.Rule, mutateResp *mutate.Response, patchedResource *unstructured.Unstructured) *response.RuleResponse { + resp := ruleResponse(*rule, response.Mutation, mutateResp.Message, mutateResp.Status, patchedResource) if resp.Status == response.RuleStatusPass { resp.Patches = mutateResp.Patches resp.Message = buildSuccessMessage(mutateResp.PatchedResource) diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go index 1a4f2ff6e6..bf4f28675e 100644 --- a/pkg/engine/mutation_test.go +++ b/pkg/engine/mutation_test.go @@ -7,13 +7,15 @@ import ( "testing" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/engine/response" - "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store" + client "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/engine/context" + "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/utils" "gotest.tools/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) func Test_VariableSubstitutionPatchStrategicMerge(t *testing.T) { @@ -1027,3 +1029,254 @@ func Test_foreach_order_mutation_(t *testing.T) { } } } + +func Test_mutate_existing_resources(t *testing.T) { + tests := []struct { + name string + policy []byte + trigger []byte + target []byte + targetList string + patches []string + }{ + { + name: "test-different-trigger-target", + policy: []byte(`{ + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "test-post-mutation" + }, + "spec": { + "rules": [ + { + "name": "mutate-deploy-on-configmap-update", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "ConfigMap" + ], + "names": [ + "dictionary" + ], + "namespaces": [ + "staging" + ] + } + } + ] + }, + "preconditions": { + "any": [ + { + "key": "{{ request.object.data.foo }}", + "operator": "Equals", + "value": "bar" + } + ] + }, + "mutate": { + "targets": [ + { + "apiVersion": "v1", + "kind": "Deployment", + "name": "example-A", + "namespace": "staging" + } + ], + "patchStrategicMerge": { + "metadata": { + "labels": { + "foo": "bar" + } + } + } + } + } + ] + } + }`), + trigger: []byte(`{ + "apiVersion": "v1", + "data": { + "foo": "bar" + }, + "kind": "ConfigMap", + "metadata": { + "name": "dictionary", + "namespace": "staging" + } +}`), + target: []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "example-A", + "namespace": "staging", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.14.2", + "ports": [ + { + "containerPort": 80 + } + ] + } + ] + } + } + } +}`), + targetList: "DeploymentList", + patches: []string{`{"op":"add","path":"/metadata/labels/foo","value":"bar"}`}, + }, + { + name: "test-same-trigger-target", + policy: []byte(`{ + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "test-post-mutation" + }, + "spec": { + "rules": [ + { + "name": "mutate-deploy-on-configmap-update", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "ConfigMap" + ], + "names": [ + "dictionary" + ], + "namespaces": [ + "staging" + ] + } + } + ] + }, + "preconditions": { + "any": [ + { + "key": "{{ request.object.data.foo }}", + "operator": "Equals", + "value": "bar" + } + ] + }, + "mutate": { + "targets": [ + { + "apiVersion": "v1", + "kind": "ConfigMap", + "name": "dictionary", + "namespace": "staging" + } + ], + "patchStrategicMerge": { + "metadata": { + "labels": { + "foo": "bar" + } + } + } + } + } + ] + } + }`), + trigger: []byte(`{ + "apiVersion": "v1", + "data": { + "foo": "bar" + }, + "kind": "ConfigMap", + "metadata": { + "name": "dictionary", + "namespace": "staging" + } +}`), + target: []byte(`{ + "apiVersion": "v1", + "data": { + "foo": "bar" + }, + "kind": "ConfigMap", + "metadata": { + "name": "dictionary", + "namespace": "staging" + } +}`), + targetList: "ComfigMapList", + patches: []string{`{"op":"add","path":"/metadata/labels","value":{"foo":"bar"}}`}, + }, + } + + for _, test := range tests { + var policy kyverno.ClusterPolicy + err := json.Unmarshal(test.policy, &policy) + assert.NilError(t, err) + + trigger, err := utils.ConvertToUnstructured(test.trigger) + assert.NilError(t, err) + + target, err := utils.ConvertToUnstructured(test.target) + assert.NilError(t, err) + + ctx := context.NewContext() + err = ctx.AddResource(trigger.Object) + assert.NilError(t, err) + + gvrToListKind := map[schema.GroupVersionResource]string{ + {Group: target.GroupVersionKind().Group, Version: target.GroupVersionKind().Version, Resource: target.GroupVersionKind().Kind}: test.targetList, + } + + objects := []runtime.Object{target} + scheme := runtime.NewScheme() + dclient, err := client.NewMockClient(scheme, gvrToListKind, objects...) + assert.NilError(t, err) + dclient.SetDiscovery(client.NewFakeDiscoveryClient(nil)) + + _, err = dclient.GetResource(target.GetAPIVersion(), target.GetKind(), target.GetNamespace(), target.GetName()) + assert.NilError(t, err) + + policyContext := &PolicyContext{ + Client: dclient, + Policy: &policy, + JSONContext: ctx, + NewResource: *trigger, + } + + er := Mutate(policyContext) + + for _, rr := range er.PolicyResponse.Rules { + assert.Equal(t, test.patches[0], string(rr.Patches[0])) + assert.Equal(t, rr.Status, response.RuleStatusPass, rr.Status) + } + } +} diff --git a/pkg/engine/policyContext.go b/pkg/engine/policyContext.go index 23eaaa5e86..24ba2d73bc 100644 --- a/pkg/engine/policyContext.go +++ b/pkg/engine/policyContext.go @@ -2,6 +2,7 @@ package engine import ( kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" client "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/engine/context" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -22,9 +23,9 @@ type PolicyContext struct { Element unstructured.Unstructured // AdmissionInfo contains the admission request information - AdmissionInfo kyverno.RequestInfo + AdmissionInfo urkyverno.RequestInfo - // Dynamic client - used by generate + // Dynamic client - used for api lookups Client *client.Client // Config handler @@ -37,6 +38,9 @@ type PolicyContext struct { // NamespaceLabels stores the label of namespace to be processed by namespace selector NamespaceLabels map[string]string + + // AdmissionOperation represents if the caller is from the webhook server + AdmissionOperation bool } func (pc *PolicyContext) Copy() *PolicyContext { diff --git a/pkg/engine/response/response.go b/pkg/engine/response/response.go index 2a56d807cf..99823a39b0 100644 --- a/pkg/engine/response/response.go +++ b/pkg/engine/response/response.go @@ -109,6 +109,9 @@ type RuleResponse struct { // statistics RuleStats `json:",inline"` + + // PatchedTarget is the patched resource for mutate.targets + PatchedTarget *unstructured.Unstructured } //ToString ... diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index 0326780e4e..143a6bfe5c 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -6,18 +6,17 @@ import ( "strings" "time" - "github.com/kyverno/kyverno/pkg/engine/common" - "github.com/go-logr/logr" + wildcard "github.com/kyverno/go-wildcard" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/engine/common" "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/variables" - "github.com/pkg/errors" - - wildcard "github.com/kyverno/go-wildcard" "github.com/kyverno/kyverno/pkg/engine/wildcards" "github.com/kyverno/kyverno/pkg/utils" + "github.com/pkg/errors" authenticationv1 "k8s.io/api/authentication/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" @@ -127,7 +126,7 @@ func checkSelector(labelSelector *metav1.LabelSelector, resourceLabels map[strin // should be: AND across attributes but an OR inside attributes that of type list // To filter out the targeted resources with UserInfo, the check // should be: OR (across & inside) attributes -func doesResourceMatchConditionBlock(conditionBlock kyverno.ResourceDescription, userInfo kyverno.UserInfo, admissionInfo kyverno.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string, namespaceLabels map[string]string) []error { +func doesResourceMatchConditionBlock(conditionBlock kyverno.ResourceDescription, userInfo kyverno.UserInfo, admissionInfo urkyverno.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string, namespaceLabels map[string]string) []error { var errs []error if len(conditionBlock.Kinds) > 0 { @@ -263,7 +262,7 @@ func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.User } //MatchesResourceDescription checks if the resource matches resource description of the rule or not -func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef kyverno.RequestInfo, dynamicConfig []string, namespaceLabels map[string]string, policyNamespace string) error { +func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef urkyverno.RequestInfo, dynamicConfig []string, namespaceLabels map[string]string, policyNamespace string) error { rule := ruleRef.DeepCopy() resource := *resourceRef.DeepCopy() @@ -336,7 +335,7 @@ func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef k return nil } -func matchesResourceDescriptionMatchHelper(rmr kyverno.ResourceFilter, admissionInfo kyverno.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string, namespaceLabels map[string]string) []error { +func matchesResourceDescriptionMatchHelper(rmr kyverno.ResourceFilter, admissionInfo urkyverno.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string, namespaceLabels map[string]string) []error { var errs []error if reflect.DeepEqual(admissionInfo, kyverno.RequestInfo{}) { rmr.UserInfo = kyverno.UserInfo{} @@ -353,7 +352,7 @@ func matchesResourceDescriptionMatchHelper(rmr kyverno.ResourceFilter, admission return errs } -func matchesResourceDescriptionExcludeHelper(rer kyverno.ResourceFilter, admissionInfo kyverno.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string, namespaceLabels map[string]string) []error { +func matchesResourceDescriptionExcludeHelper(rer kyverno.ResourceFilter, admissionInfo urkyverno.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string, namespaceLabels map[string]string) []error { var errs []error // checking if resource matches the rule if !reflect.DeepEqual(rer.ResourceDescription, kyverno.ResourceDescription{}) || @@ -474,16 +473,21 @@ func evaluateList(jmesPath string, ctx context.EvalInterface) ([]interface{}, er func ruleError(rule *kyverno.Rule, ruleType response.RuleType, msg string, err error) *response.RuleResponse { msg = fmt.Sprintf("%s: %s", msg, err.Error()) - return ruleResponse(rule, ruleType, msg, response.RuleStatusError) + return ruleResponse(*rule, ruleType, msg, response.RuleStatusError, nil) } -func ruleResponse(rule *kyverno.Rule, ruleType response.RuleType, msg string, status response.RuleStatus) *response.RuleResponse { - return &response.RuleResponse{ +func ruleResponse(rule kyverno.Rule, ruleType response.RuleType, msg string, status response.RuleStatus, patchedResource *unstructured.Unstructured) *response.RuleResponse { + resp := &response.RuleResponse{ Name: rule.Name, Type: ruleType, Message: msg, Status: status, } + + if rule.Mutation.Targets != nil { + resp.PatchedTarget = patchedResource + } + return resp } func incrementAppliedCount(resp *response.EngineResponse) { diff --git a/pkg/engine/utils_test.go b/pkg/engine/utils_test.go index 62e65ad375..081b235e12 100644 --- a/pkg/engine/utils_test.go +++ b/pkg/engine/utils_test.go @@ -5,6 +5,7 @@ import ( "testing" v1 "github.com/kyverno/kyverno/api/kyverno/v1" + v1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/engine/utils" "gotest.tools/assert" @@ -14,14 +15,14 @@ import ( func TestMatchesResourceDescription(t *testing.T) { tcs := []struct { Description string - AdmissionInfo v1.RequestInfo + AdmissionInfo v1beta1.RequestInfo Resource []byte Policy []byte areErrorsExpected bool }{ { Description: "Match Any matches the Pod", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ @@ -106,7 +107,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Match Any does not match the Pod", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ @@ -191,7 +192,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Match All matches the Pod", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ @@ -276,7 +277,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Match All does not match the Pod", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ @@ -361,7 +362,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Exclude Any excludes the Pod", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ @@ -461,7 +462,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Exclude Any does not exclude the Pod", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ @@ -561,7 +562,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Exclude All excludes the Pod", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ @@ -661,7 +662,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Exclude All does not exclude the Pod", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ @@ -761,7 +762,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Should match pod and not exclude it", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`), @@ -770,7 +771,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Should exclude resource since it matches the exclude block", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"system:node"}, }, Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`), @@ -790,7 +791,10 @@ func TestMatchesResourceDescription(t *testing.T) { areErrorsExpected: true, }, { - Description: "Should pass since resource matches a name in the names field", + Description: "Should pass since resource matches a name in the names field", + AdmissionInfo: v1beta1.RequestInfo{ + ClusterRoles: []string{"system:node"}, + }, Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`), Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"],"names": ["dev-*","hello-world"]},"clusterRoles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`), areErrorsExpected: false, @@ -809,7 +813,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Should fail since resource does not match policy", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{"apiVersion":"v1","kind":"Service","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`), @@ -818,7 +822,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Should not fail since resource does not match exclude block", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"system:node"}, }, Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world2","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`), @@ -827,7 +831,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Should pass since group, version, kind match", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "name": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}}`), @@ -836,7 +840,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Should pass since version and kind match", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } }`), @@ -845,7 +849,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Should fail since resource does not match ", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{"apiVersion":"v1","kind":"Service","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`), @@ -854,7 +858,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Should fail since version not match", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ "apiVersion": "apps/v1beta1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "name": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}}`), @@ -863,7 +867,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Should fail since cluster role version not match", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ "kind": "ClusterRole", "apiVersion": "rbac.authorization.k8s.io/v1", "metadata": { "name": "secret-reader-demo", "namespace": "default" }, "rules": [ { "apiGroups": [ "" ], "resources": [ "secrets" ], "verbs": [ "get", "watch", "list" ] } ] }`), @@ -872,7 +876,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Test for GVK case sensitive", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "myapp-pod2", "labels": { "app": "myapp2" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx" } ] } }`), @@ -881,7 +885,7 @@ func TestMatchesResourceDescription(t *testing.T) { }, { Description: "Test should pass for GVK case sensitive", - AdmissionInfo: v1.RequestInfo{ + AdmissionInfo: v1beta1.RequestInfo{ ClusterRoles: []string{"admin"}, }, Resource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "name": "qos-demo", "labels": { "test": "qos" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "creationTimestamp": "2020-09-21T12:56:35Z", "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:latest", "resources": { "limits": { "cpu": "50m" } } } ]}}}}`), @@ -902,7 +906,7 @@ func TestMatchesResourceDescription(t *testing.T) { err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil, "") if err != nil { if !tc.areErrorsExpected { - t.Errorf("Testcase %d Unexpected error: %v", i+1, err) + t.Errorf("Testcase %d Unexpected error: %v\nmsg: %s", i+1, err, tc.Description) } } else { if tc.areErrorsExpected { @@ -967,7 +971,7 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(*resource, rule, v1.RequestInfo{}, []string{}, nil, ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } @@ -1028,7 +1032,7 @@ func TestResourceDescriptionMatch_Name(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(*resource, rule, v1.RequestInfo{}, []string{}, nil, ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -1088,7 +1092,7 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(*resource, rule, v1.RequestInfo{}, []string{}, nil, ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -1156,7 +1160,7 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(*resource, rule, v1.RequestInfo{}, []string{}, nil, ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -1225,7 +1229,7 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(*resource, rule, v1.RequestInfo{}, []string{}, nil, ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -1305,7 +1309,7 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) { rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}, ExcludeResources: v1.MatchResources{ResourceDescription: resourceDescriptionExclude}} - if err := MatchesResourceDescription(*resource, rule, v1.RequestInfo{}, []string{}, nil, ""); err == nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, ""); err == nil { t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was supposed to fail") } } diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 220ee32471..bf3b155369 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -208,7 +208,7 @@ func (v *validator) validate() *response.RuleResponse { } if !preconditionsPassed && v.ctx.Policy.GetSpec().ValidationFailureAction != kyverno.Audit { - return ruleResponse(v.rule, response.Validation, "preconditions not met", response.RuleStatusSkip) + return ruleResponse(*v.rule, response.Validation, "preconditions not met", response.RuleStatusSkip, nil) } if v.deny != nil { @@ -249,7 +249,7 @@ func (v *validator) validateForEach() *response.RuleResponse { if err != nil { return ruleError(v.rule, response.Validation, "failed to evaluate preconditions", err) } else if !preconditionsPassed && v.ctx.Policy.GetSpec().ValidationFailureAction != kyverno.Audit { - return ruleResponse(v.rule, response.Validation, "preconditions not met", response.RuleStatusSkip) + return ruleResponse(*v.rule, response.Validation, "preconditions not met", response.RuleStatusSkip, nil) } foreachList := v.rule.Validation.ForEachValidation @@ -279,10 +279,10 @@ func (v *validator) validateForEach() *response.RuleResponse { } if applyCount == 0 { - return ruleResponse(v.rule, response.Validation, "rule skipped", response.RuleStatusSkip) + return ruleResponse(*v.rule, response.Validation, "rule skipped", response.RuleStatusSkip, nil) } - return ruleResponse(v.rule, response.Validation, "rule passed", response.RuleStatusPass) + return ruleResponse(*v.rule, response.Validation, "rule passed", response.RuleStatusPass, nil) } func (v *validator) validateElements(foreach *kyverno.ForEachValidation, elements []interface{}, elementScope bool) (*response.RuleResponse, int) { @@ -309,13 +309,13 @@ func (v *validator) validateElements(foreach *kyverno.ForEachValidation, element continue } else if r.Status != response.RuleStatusPass { msg := fmt.Sprintf("validation failure: %v", r.Message) - return ruleResponse(v.rule, response.Validation, msg, r.Status), applyCount + return ruleResponse(*v.rule, response.Validation, msg, r.Status, nil), applyCount } applyCount++ } - return ruleResponse(v.rule, response.Validation, "", response.RuleStatusPass), applyCount + return ruleResponse(*v.rule, response.Validation, "", response.RuleStatusPass, nil), applyCount } func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, elementScope bool) error { @@ -366,10 +366,10 @@ func (v *validator) validateDeny() *response.RuleResponse { deny := variables.EvaluateConditions(v.log, v.ctx.JSONContext, denyConditions) if deny { - return ruleResponse(v.rule, response.Validation, v.getDenyMessage(deny), response.RuleStatusFail) + return ruleResponse(*v.rule, response.Validation, v.getDenyMessage(deny), response.RuleStatusFail, nil) } - return ruleResponse(v.rule, response.Validation, v.getDenyMessage(deny), response.RuleStatusPass) + return ruleResponse(*v.rule, response.Validation, v.getDenyMessage(deny), response.RuleStatusPass, nil) } func (v *validator) getDenyMessage(deny bool) string { @@ -473,22 +473,22 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *respon v.log.V(3).Info("validation error", "path", pe.Path, "error", err.Error()) if pe.Skip { - return ruleResponse(v.rule, response.Validation, pe.Error(), response.RuleStatusSkip) + return ruleResponse(*v.rule, response.Validation, pe.Error(), response.RuleStatusSkip, nil) } if pe.Path == "" { - return ruleResponse(v.rule, response.Validation, v.buildErrorMessage(err, ""), response.RuleStatusError) + return ruleResponse(*v.rule, response.Validation, v.buildErrorMessage(err, ""), response.RuleStatusError, nil) } - return ruleResponse(v.rule, response.Validation, v.buildErrorMessage(err, pe.Path), response.RuleStatusFail) + return ruleResponse(*v.rule, response.Validation, v.buildErrorMessage(err, pe.Path), response.RuleStatusFail, nil) } - return ruleResponse(v.rule, response.Validation, v.buildErrorMessage(err, pe.Path), response.RuleStatusError) + return ruleResponse(*v.rule, response.Validation, v.buildErrorMessage(err, pe.Path), response.RuleStatusError, nil) } v.log.V(4).Info("successfully processed rule") msg := fmt.Sprintf("validation rule '%s' passed.", v.rule.Name) - return ruleResponse(v.rule, response.Validation, msg, response.RuleStatusPass) + return ruleResponse(*v.rule, response.Validation, msg, response.RuleStatusPass, nil) } if v.anyPattern != nil { @@ -498,14 +498,14 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *respon anyPatterns, err := deserializeAnyPattern(v.anyPattern) if err != nil { msg := fmt.Sprintf("failed to deserialize anyPattern, expected type array: %v", err) - return ruleResponse(v.rule, response.Validation, msg, response.RuleStatusError) + return ruleResponse(*v.rule, response.Validation, msg, response.RuleStatusError, nil) } 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 ruleResponse(v.rule, response.Validation, msg, response.RuleStatusPass) + return ruleResponse(*v.rule, response.Validation, msg, response.RuleStatusPass, nil) } if pe, ok := err.(*validate.PatternError); ok { @@ -529,11 +529,11 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *respon v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' failed. %s", v.rule.Name, errorStr)) msg := buildAnyPatternErrorMessage(v.rule, errorStr) - return ruleResponse(v.rule, response.Validation, msg, response.RuleStatusFail) + return ruleResponse(*v.rule, response.Validation, msg, response.RuleStatusFail, nil) } } - return ruleResponse(v.rule, response.Validation, v.rule.Validation.Message, response.RuleStatusPass) + return ruleResponse(*v.rule, response.Validation, v.rule.Validation.Message, response.RuleStatusPass, nil) } func deserializeAnyPattern(anyPattern apiextensions.JSON) ([]interface{}, error) { diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index 9fd049327d..495c9d05d8 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -6,10 +6,10 @@ import ( "testing" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/engine/response" - + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store" "github.com/kyverno/kyverno/pkg/engine/context" + "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/utils" utils2 "github.com/kyverno/kyverno/pkg/utils" "gotest.tools/assert" @@ -2110,7 +2110,7 @@ func executeTest(t *testing.T, err error, test testCase) { t.Fatal(err) } - var userInfo kyverno.RequestInfo + var userInfo urkyverno.RequestInfo err = json.Unmarshal(test.userInfo, &userInfo) if err != nil { t.Fatal(err) diff --git a/pkg/engine/variables/variables_test.go b/pkg/engine/variables/variables_test.go index 0d62f5dd2c..a03e3dc091 100644 --- a/pkg/engine/variables/variables_test.go +++ b/pkg/engine/variables/variables_test.go @@ -5,12 +5,11 @@ import ( "reflect" "testing" - kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/engine/context" "gotest.tools/assert" authenticationv1 "k8s.io/api/authentication/v1" "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/kyverno/kyverno/pkg/engine/context" ) func Test_variablesub1(t *testing.T) { @@ -52,7 +51,7 @@ func Test_variablesub1(t *testing.T) { } `) // userInfo - userReqInfo := kyverno.RequestInfo{ + userReqInfo := urkyverno.RequestInfo{ AdmissionUserInfo: authenticationv1.UserInfo{ Username: "user1", }, @@ -140,7 +139,7 @@ func Test_variablesub_multiple(t *testing.T) { } `) // userInfo - userReqInfo := kyverno.RequestInfo{ + userReqInfo := urkyverno.RequestInfo{ AdmissionUserInfo: authenticationv1.UserInfo{ Username: "user1", }, @@ -230,7 +229,7 @@ func Test_variablesubstitution(t *testing.T) { resultMap := []byte(`{"data":{"rules":[{"apiGroups":[""],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"name":"ns-owner-user1"}`) // userInfo - userReqInfo := kyverno.RequestInfo{ + userReqInfo := urkyverno.RequestInfo{ AdmissionUserInfo: authenticationv1.UserInfo{ Username: "user1", }, diff --git a/pkg/engine/variables/vars.go b/pkg/engine/variables/vars.go index 38331c9916..73df1126f2 100644 --- a/pkg/engine/variables/vars.go +++ b/pkg/engine/variables/vars.go @@ -333,7 +333,7 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var return data.Element, nil } - isDeleteRequest := isDeleteRequest(ctx) + isDeleteRequest := IsDeleteRequest(ctx) vars := RegexVariables.FindAllString(value, -1) for len(vars) > 0 { @@ -406,7 +406,7 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var }) } -func isDeleteRequest(ctx context.EvalInterface) bool { +func IsDeleteRequest(ctx context.EvalInterface) bool { if ctx == nil { return false } diff --git a/pkg/event/controller.go b/pkg/event/controller.go index 961ea528b5..874c991833 100644 --- a/pkg/event/controller.go +++ b/pkg/event/controller.go @@ -39,7 +39,10 @@ type Generator struct { admissionCtrRecorder record.EventRecorder // events generated at namespaced policy controller to process 'generate' rule genPolicyRecorder record.EventRecorder - log logr.Logger + // events generated at mutateExisting controller + mutateExistingRecorder record.EventRecorder + + log logr.Logger } //Interface to generate event @@ -50,16 +53,17 @@ type Interface interface { //NewEventGenerator to generate a new event controller func NewEventGenerator(client *client.Client, cpInformer kyvernoinformer.ClusterPolicyInformer, pInformer kyvernoinformer.PolicyInformer, log logr.Logger) *Generator { gen := Generator{ - client: client, - cpLister: cpInformer.Lister(), - cpSynced: cpInformer.Informer().HasSynced, - pLister: pInformer.Lister(), - pSynced: pInformer.Informer().HasSynced, - queue: workqueue.NewNamedRateLimitingQueue(rateLimiter(), eventWorkQueueName), - policyCtrRecorder: initRecorder(client, PolicyController, log), - admissionCtrRecorder: initRecorder(client, AdmissionController, log), - genPolicyRecorder: initRecorder(client, GeneratePolicyController, log), - log: log, + client: client, + cpLister: cpInformer.Lister(), + cpSynced: cpInformer.Informer().HasSynced, + pLister: pInformer.Lister(), + pSynced: pInformer.Informer().HasSynced, + queue: workqueue.NewNamedRateLimitingQueue(rateLimiter(), eventWorkQueueName), + policyCtrRecorder: initRecorder(client, PolicyController, log), + admissionCtrRecorder: initRecorder(client, AdmissionController, log), + genPolicyRecorder: initRecorder(client, GeneratePolicyController, log), + mutateExistingRecorder: initRecorder(client, MutateExistingController, log), + log: log, } return &gen } @@ -223,6 +227,8 @@ func (gen *Generator) syncHandler(key Info) error { gen.policyCtrRecorder.Event(robj, eventType, key.Reason, key.Message) case GeneratePolicyController: gen.genPolicyRecorder.Event(robj, eventType, key.Reason, key.Message) + case MutateExistingController: + gen.mutateExistingRecorder.Event(robj, eventType, key.Reason, key.Message) default: logger.Info("info.source not defined for the request") } diff --git a/pkg/event/source.go b/pkg/event/source.go index 7ee1bac38b..be335c96a9 100644 --- a/pkg/event/source.go +++ b/pkg/event/source.go @@ -10,6 +10,8 @@ const ( PolicyController // GeneratePolicyController : event generated in generate policyController GeneratePolicyController + // MutateExistingController : event generated for mutateExisting policies + MutateExistingController ) func (s Source) String() string { @@ -17,5 +19,6 @@ func (s Source) String() string { "admission-controller", "policy-controller", "generate-policy-controller", + "mutate-existing-controller", }[s] } diff --git a/pkg/openapi/validation.go b/pkg/openapi/validation.go index 4cd9b1f158..a2f302f016 100644 --- a/pkg/openapi/validation.go +++ b/pkg/openapi/validation.go @@ -184,7 +184,7 @@ func (o *Controller) useOpenAPIDocument(doc *openapiv2.Document) error { gvk, preferredGVK, err := o.getGVKByDefinitionName(definitionName) if err != nil { - log.Log.V(3).Info("unable to cache OpenAPISchema", "definitionName", definitionName, "reason", err.Error()) + log.Log.V(5).Info("unable to cache OpenAPISchema", "definitionName", definitionName, "reason", err.Error()) continue } diff --git a/pkg/policy/background.go b/pkg/policy/background.go index a96fe9ead1..306c378c1b 100644 --- a/pkg/policy/background.go +++ b/pkg/policy/background.go @@ -15,6 +15,12 @@ import ( //ContainsUserVariables returns error if variable that does not start from request.object func containsUserVariables(policy kyverno.PolicyInterface, vars [][]string) error { + for _, rule := range policy.GetSpec().Rules { + if rule.IsMutateExisting() { + return nil + } + } + for _, s := range vars { if strings.Contains(s[0], "userInfo") { return fmt.Errorf("variable %s is not allowed", s[0]) diff --git a/pkg/policy/policy_controller.go b/pkg/policy/policy_controller.go index aa8b28dca9..21995c3167 100644 --- a/pkg/policy/policy_controller.go +++ b/pkg/policy/policy_controller.go @@ -11,12 +11,15 @@ import ( "github.com/go-logr/logr" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" "github.com/kyverno/kyverno/pkg/autogen" kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" "github.com/kyverno/kyverno/pkg/client/clientset/versioned/scheme" kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1" + urkyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1beta1" kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1" + urkyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1" pkgCommon "github.com/kyverno/kyverno/pkg/common" "github.com/kyverno/kyverno/pkg/config" client "github.com/kyverno/kyverno/pkg/dclient" @@ -24,6 +27,7 @@ import ( "github.com/kyverno/kyverno/pkg/metrics" "github.com/kyverno/kyverno/pkg/policyreport" "github.com/kyverno/kyverno/pkg/utils" + kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -71,6 +75,9 @@ type PolicyController struct { // grLister can list/get generate request from the shared informer's store grLister kyvernolister.GenerateRequestLister + // urLister can list/get update request from the shared informer's store + urLister urkyvernolister.UpdateRequestLister + // nsLister can list/get namespaces from the shared informer's store nsLister listerv1.NamespaceLister @@ -86,6 +93,9 @@ type PolicyController struct { // grListerSynced returns true if the generate request store has been synced at least once grListerSynced cache.InformerSynced + // urListerSynced returns true if the update request store has been synced at least once + urListerSynced cache.InformerSynced + // Resource manager, manages the mapping for already processed resource rm resourceManager @@ -112,6 +122,7 @@ func NewPolicyController( pInformer kyvernoinformer.ClusterPolicyInformer, npInformer kyvernoinformer.PolicyInformer, grInformer kyvernoinformer.GenerateRequestInformer, + urInformer urkyvernoinformer.UpdateRequestInformer, configHandler config.Interface, eventGen event.Interface, prGenerator policyreport.GeneratorInterface, @@ -152,12 +163,14 @@ func NewPolicyController( pc.nsLister = namespaces.Lister() pc.grLister = grInformer.Lister() + pc.urLister = urInformer.Lister() pc.pListerSynced = pInformer.Informer().HasSynced pc.npListerSynced = npInformer.Informer().HasSynced pc.nsListerSynced = namespaces.Informer().HasSynced pc.grListerSynced = grInformer.Informer().HasSynced + pc.urListerSynced = urInformer.Informer().HasSynced // resource manager // rebuild after 300 seconds/ 5 mins @@ -415,7 +428,7 @@ func (pc *PolicyController) Run(workers int, reconcileCh <-chan bool, stopCh <-c logger.Info("starting") defer logger.Info("shutting down") - if !cache.WaitForCacheSync(stopCh, pc.pListerSynced, pc.npListerSynced, pc.nsListerSynced, pc.grListerSynced) { + if !cache.WaitForCacheSync(stopCh, pc.pListerSynced, pc.npListerSynced, pc.nsListerSynced, pc.grListerSynced, pc.urListerSynced) { logger.Info("failed to sync informer cache") return } @@ -486,22 +499,22 @@ func (pc *PolicyController) syncPolicy(key string) error { logger.V(4).Info("finished syncing policy", "key", key, "processingTime", time.Since(startTime).String()) }() - grList, err := pc.grLister.List(labels.Everything()) + urList, err := pc.urLister.List(labels.Everything()) if err != nil { - logger.Error(err, "failed to list generate request") + logger.Error(err, "failed to list update request") } policy, err := pc.getPolicy(key) if err != nil { if errors.IsNotFound(err) { - deleteGR(pc.kyvernoClient, key, grList, logger) + deleteGR(pc.kyvernoClient, key, urList, logger) return nil } return err } - updateGR(pc.kyvernoClient, policy.GetName(), grList, logger) + pc.updateUR(policy, urList) pc.processExistingResources(policy) return nil } @@ -520,18 +533,113 @@ func (pc *PolicyController) getPolicy(key string) (policy kyverno.PolicyInterfac return } -func deleteGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList []*kyverno.GenerateRequest, logger logr.Logger) { +func (pc *PolicyController) updateUR(policy kyverno.PolicyInterface, urList []*urkyverno.UpdateRequest) { + if urList == nil { + for _, rule := range policy.GetSpec().Rules { + if !rule.IsMutateExisting() { + continue + } + + triggers := getTriggers(rule) + for _, trigger := range triggers { + ur := newUR(policy, trigger) + new, err := pc.kyvernoClient.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).Create(context.TODO(), ur, metav1.CreateOptions{}) + if err != nil { + pc.log.Error(err, "failed to create new UR for mutateExisting rule on policy update", "policy", policy.GetName(), "rule", rule.Name, + "target", fmt.Sprintf("%s/%s/%s/%s", trigger.APIVersion, trigger.Kind, trigger.Namespace, trigger.Name)) + } else { + pc.log.V(4).Info("successfully created UR for mutateExisting on policy update", "policy", policy.GetName(), "rule", rule.Name, + "target", fmt.Sprintf("%s/%s/%s/%s", trigger.APIVersion, trigger.Kind, trigger.Namespace, trigger.Name)) + } + + new.Status.State = urkyverno.Pending + if _, err := pc.kyvernoClient.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).UpdateStatus(context.TODO(), new, metav1.UpdateOptions{}); err != nil { + pc.log.Error(err, "failed to set UpdateRequest state to Pending") + } + } + } + return + } + + updateUR(pc.kyvernoClient, policy.GetName(), urList, pc.log.WithName("updateUR")) + +} + +func getTriggers(rule kyverno.Rule) []*kyverno.ResourceSpec { + var specs []*kyverno.ResourceSpec + + triggers := getTrigger(rule.MatchResources.ResourceDescription) + specs = append(specs, triggers...) + + for _, any := range rule.MatchResources.Any { + triggers := getTrigger(any.ResourceDescription) + specs = append(specs, triggers...) + } + + triggers = []*kyverno.ResourceSpec{} + for _, all := range rule.MatchResources.All { + triggers = getTrigger(all.ResourceDescription) + } + + subset := make(map[*kyverno.ResourceSpec]int, len(triggers)) + for _, trigger := range triggers { + c := subset[trigger] + subset[trigger] = c + 1 + } + + for k, v := range subset { + if v == len(rule.MatchResources.All) { + specs = append(specs, k) + } + } + return specs +} + +func getTrigger(rd kyverno.ResourceDescription) []*kyverno.ResourceSpec { + if len(rd.Names) == 0 { + return nil + } + + var specs []*kyverno.ResourceSpec + + for _, k := range rd.Kinds { + apiVersion, kind := kubeutils.GetKindFromGVK(k) + if kind == "" { + continue + } + + for _, ns := range rd.Namespaces { + for _, name := range rd.Names { + if name == "" { + continue + } + + spec := &kyverno.ResourceSpec{ + APIVersion: apiVersion, + Kind: kind, + Namespace: ns, + Name: name, + } + specs = append(specs, spec) + } + } + } + + return specs +} + +func deleteGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList []*urkyverno.UpdateRequest, logger logr.Logger) { for _, v := range grList { if policyKey == v.Spec.Policy { - err := kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Delete(context.TODO(), v.GetName(), metav1.DeleteOptions{}) + err := kyvernoClient.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).Delete(context.TODO(), v.GetName(), metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { - logger.Error(err, "failed to delete gr", "name", v.GetName()) + logger.Error(err, "failed to delete ur", "name", v.GetName()) } } } } -func updateGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList []*kyverno.GenerateRequest, logger logr.Logger) { +func updateUR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList []*urkyverno.UpdateRequest, logger logr.Logger) { for _, gr := range grList { if policyKey == gr.Spec.Policy { grLabels := gr.Labels @@ -546,7 +654,7 @@ func updateGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList [ grLabels["policy-update"] = fmt.Sprintf("revision-count-%d", nBig.Int64()) gr.SetLabels(grLabels) - _, err = kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), gr, metav1.UpdateOptions{}) + _, err = kyvernoClient.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).Update(context.TODO(), gr, metav1.UpdateOptions{}) if err != nil { logger.Error(err, "failed to update gr", "name", gr.GetName()) } @@ -592,3 +700,42 @@ func missingAutoGenRules(policy kyverno.PolicyInterface, log logr.Logger) bool { } return false } + +func newUR(policy kyverno.PolicyInterface, target *kyverno.ResourceSpec) *urkyverno.UpdateRequest { + var policyNameNamespaceKey string + + if policy.GetKind() == "Policy" { + policyNameNamespaceKey = policy.GetNamespace() + "/" + policy.GetName() + } else { + policyNameNamespaceKey = policy.GetName() + } + + label := map[string]string{ + "mutate.updaterequest.kyverno.io/policy-name": policyNameNamespaceKey, + "mutate.updaterequest.kyverno.io/trigger-name": target.Name, + "mutate.updaterequest.kyverno.io/trigger-namespace": target.Namespace, + "mutate.updaterequest.kyverno.io/trigger-kind": target.Kind, + } + + if target.APIVersion != "" { + label["mutate.updaterequest.kyverno.io/trigger-apiversion"] = target.APIVersion + } + + return &urkyverno.UpdateRequest{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "ur-", + Namespace: config.KyvernoNamespace, + Labels: label, + }, + Spec: urkyverno.UpdateRequestSpec{ + Type: urkyverno.Mutate, + Policy: policyNameNamespaceKey, + Resource: kyverno.ResourceSpec{ + Kind: target.Kind, + Namespace: target.Namespace, + Name: target.Name, + APIVersion: target.APIVersion, + }, + }, + } +} diff --git a/pkg/policycache/policy_cache.go b/pkg/policycache/policy_cache.go index 9d7d8126a2..e47fa6c375 100644 --- a/pkg/policycache/policy_cache.go +++ b/pkg/policycache/policy_cache.go @@ -13,7 +13,7 @@ import ( type Interface interface { // GetPolicies returns all policies that apply to a namespace, including cluster-wide policies // If the namespace is empty, only cluster-wide policies are returned - GetPolicies(PolicyType, string, string) []kyverno.PolicyInterface + GetPolicies(pType PolicyType, kind string, namespace string) []kyverno.PolicyInterface // add adds a policy to the cache add(kyverno.PolicyInterface) diff --git a/pkg/testrunner/scenario.go b/pkg/testrunner/scenario.go index 432cc9ae4b..18edaf4e13 100644 --- a/pkg/testrunner/scenario.go +++ b/pkg/testrunner/scenario.go @@ -191,7 +191,7 @@ func runTestCase(t *testing.T, tc TestCase) bool { JSONContext: context.NewContext(), } - er = engine.Generate(policyContext) + er = engine.ApplyBackgroundChecks(policyContext) t.Log(("---Generation---")) validateResponse(t, er.PolicyResponse, tc.Expected.Generation.PolicyResponse) // Expected generate resource will be in same namespaces as resource diff --git a/pkg/utils/annotations.go b/pkg/utils/annotations.go new file mode 100644 index 0000000000..e89eda1224 --- /dev/null +++ b/pkg/utils/annotations.go @@ -0,0 +1,142 @@ +package utils + +import ( + "encoding/json" + "strings" + + "github.com/go-logr/logr" + "github.com/kyverno/kyverno/pkg/engine/response" + jsonutils "github.com/kyverno/kyverno/pkg/utils/json" + yamlv2 "gopkg.in/yaml.v2" +) + +const ( + PolicyAnnotation = "policies.kyverno.io/last-applied-patches" + policyAnnotation = "policies.kyverno.io~1last-applied-patches" + oldAnnotation = "policies.kyverno.io~1patches" +) + +type RulePatch struct { + RuleName string `json:"rulename"` + Op string `json:"op"` + Path string `json:"path"` +} + +var OperationToPastTense = map[string]string{ + "add": "added", + "remove": "removed", + "replace": "replaced", + "move": "moved", + "copy": "copied", + "test": "tested", +} + +func GenerateAnnotationPatches(engineResponses []*response.EngineResponse, log logr.Logger) [][]byte { + var annotations map[string]string + var patchBytes [][]byte + for _, er := range engineResponses { + if ann := er.PatchedResource.GetAnnotations(); ann != nil { + annotations = ann + break + } + } + if annotations == nil { + annotations = make(map[string]string) + } + var patchResponse jsonutils.Patch + value := annotationFromEngineResponses(engineResponses, log) + if value == nil { + // no patches or error while processing patches + return nil + } + if _, ok := annotations[strings.ReplaceAll(policyAnnotation, "~1", "/")]; ok { + // create update patch string + if _, ok := annotations["policies.kyverno.io/patches"]; ok { + patchResponse = jsonutils.NewPatch("/metadata/annotations/"+oldAnnotation, "remove", nil) + delete(annotations, "policies.kyverno.io/patches") + patchByte, _ := json.Marshal(patchResponse) + patchBytes = append(patchBytes, patchByte) + } + patchResponse = jsonutils.NewPatch("/metadata/annotations/"+policyAnnotation, "replace", string(value)) + patchByte, _ := json.Marshal(patchResponse) + patchBytes = append(patchBytes, patchByte) + } else { + // mutate rule has annotation patches + if len(annotations) > 0 { + if _, ok := annotations["policies.kyverno.io/patches"]; ok { + patchResponse = jsonutils.NewPatch("/metadata/annotations/"+oldAnnotation, "remove", nil) + delete(annotations, "policies.kyverno.io/patches") + patchByte, _ := json.Marshal(patchResponse) + patchBytes = append(patchBytes, patchByte) + } + patchResponse = jsonutils.NewPatch("/metadata/annotations/"+policyAnnotation, "add", string(value)) + patchByte, _ := json.Marshal(patchResponse) + patchBytes = append(patchBytes, patchByte) + } else { + // insert 'policies.kyverno.patches' entry in annotation map + annotations[strings.ReplaceAll(policyAnnotation, "~1", "/")] = string(value) + patchResponse = jsonutils.NewPatch("/metadata/annotations", "add", annotations) + patchByte, _ := json.Marshal(patchResponse) + patchBytes = append(patchBytes, patchByte) + } + } + for _, patchByte := range patchBytes { + err := jsonutils.CheckPatch(patchByte) + if err != nil { + log.Error(err, "failed to build JSON patch for annotation", "patch", string(patchByte)) + } + } + return patchBytes +} + +func annotationFromEngineResponses(engineResponses []*response.EngineResponse, log logr.Logger) []byte { + var annotationContent = make(map[string]string) + for _, engineResponse := range engineResponses { + if !engineResponse.IsSuccessful() { + log.V(3).Info("skip building annotation; policy failed to apply", "policy", engineResponse.PolicyResponse.Policy.Name) + continue + } + rulePatches := annotationFromPolicyResponse(engineResponse.PolicyResponse, log) + if rulePatches == nil { + continue + } + policyName := engineResponse.PolicyResponse.Policy.Name + for _, rulePatch := range rulePatches { + annotationContent[rulePatch.RuleName+"."+policyName+".kyverno.io"] = OperationToPastTense[rulePatch.Op] + " " + rulePatch.Path + } + } + + // return nil if there's no patches + // otherwise result = null, len(result) = 4 + if len(annotationContent) == 0 { + return nil + } + + result, _ := yamlv2.Marshal(annotationContent) + + return result +} + +func annotationFromPolicyResponse(policyResponse response.PolicyResponse, log logr.Logger) []RulePatch { + var RulePatches []RulePatch + for _, ruleInfo := range policyResponse.Rules { + for _, patch := range ruleInfo.Patches { + var patchmap map[string]interface{} + if err := json.Unmarshal(patch, &patchmap); err != nil { + log.Error(err, "Failed to parse JSON patch bytes") + continue + } + rp := RulePatch{ + RuleName: ruleInfo.Name, + Op: patchmap["op"].(string), + Path: patchmap["path"].(string), + } + RulePatches = append(RulePatches, rp) + log.V(4).Info("annotation value prepared", "patches", RulePatches) + } + } + if len(RulePatches) == 0 { + return nil + } + return RulePatches +} diff --git a/pkg/webhooks/annotations_test.go b/pkg/utils/annotations_test.go similarity index 91% rename from pkg/webhooks/annotations_test.go rename to pkg/utils/annotations_test.go index 6a5371c831..8246e50b80 100644 --- a/pkg/webhooks/annotations_test.go +++ b/pkg/utils/annotations_test.go @@ -1,4 +1,4 @@ -package webhooks +package utils import ( "testing" @@ -43,7 +43,7 @@ func newEngineResponse(policy, rule string, patchesStr []string, status response func Test_empty_annotation(t *testing.T) { patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }` engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, response.RuleStatusPass, nil) - annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) + annPatches := GenerateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) expectedPatches := `{"path":"/metadata/annotations","op":"add","value":{"policies.kyverno.io/last-applied-patches":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}}` assert.Equal(t, string(annPatches[0]), expectedPatches) } @@ -54,7 +54,7 @@ func Test_exist_annotation(t *testing.T) { } patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }` engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, response.RuleStatusPass, annotation) - annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) + annPatches := GenerateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) expectedPatches := `{"path":"/metadata/annotations/policies.kyverno.io~1last-applied-patches","op":"add","value":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}` assert.Equal(t, string(annPatches[0]), expectedPatches) } @@ -65,7 +65,7 @@ func Test_exist_kyverno_annotation(t *testing.T) { } patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }` engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, response.RuleStatusPass, annotation) - annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) + annPatches := GenerateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) expectedPatches := `{"path":"/metadata/annotations/policies.kyverno.io~1last-applied-patches","op":"add","value":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}` assert.Equal(t, string(annPatches[0]), expectedPatches) } @@ -75,10 +75,10 @@ func Test_annotation_nil_patch(t *testing.T) { "policies.kyverno.patches": "old-annotation", } engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, response.RuleStatusPass, annotation) - annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) + annPatches := GenerateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) assert.Assert(t, annPatches == nil) engineResponseNew := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{""}, response.RuleStatusPass, annotation) - annPatchesNew := generateAnnotationPatches([]*response.EngineResponse{engineResponseNew}, log.Log) + annPatchesNew := GenerateAnnotationPatches([]*response.EngineResponse{engineResponseNew}, log.Log) assert.Assert(t, annPatchesNew == nil) } @@ -87,7 +87,7 @@ func Test_annotation_failed_Patch(t *testing.T) { "policies.kyverno.patches": "old-annotation", } engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, response.RuleStatusFail, annotation) - annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) + annPatches := GenerateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) assert.Assert(t, annPatches == nil) } @@ -97,7 +97,7 @@ func Test_exist_patches(t *testing.T) { } patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }` engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, response.RuleStatusPass, annotation) - annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) + annPatches := GenerateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log) expectedPatches1 := `{"path":"/metadata/annotations/policies.kyverno.io~1patches","op":"remove"}` expectedPatches2 := `{"path":"/metadata/annotations/policies.kyverno.io~1last-applied-patches","op":"add","value":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}` assert.Equal(t, string(annPatches[0]), expectedPatches1) diff --git a/pkg/webhookconfig/certmanager.go b/pkg/webhookconfig/certmanager.go index 7ce19ecf8e..4de6303469 100644 --- a/pkg/webhookconfig/certmanager.go +++ b/pkg/webhookconfig/certmanager.go @@ -108,7 +108,8 @@ func (m *certManager) GetTLSPemPair() (*ktls.PemPair, error) { return nil } - f := common.RetryFunc(time.Second, time.Minute, retryReadTLS, m.log.WithName("GetTLSPemPair/Retry")) + msg := "failed to read TLS pair" + f := common.RetryFunc(time.Second, time.Minute, retryReadTLS, msg, m.log.WithName("GetTLSPemPair/Retry")) err = f() return tls, err diff --git a/pkg/webhookconfig/configmanager.go b/pkg/webhookconfig/configmanager.go index 91f3043326..68263beac9 100644 --- a/pkg/webhookconfig/configmanager.go +++ b/pkg/webhookconfig/configmanager.go @@ -434,7 +434,7 @@ func (m *webhookConfigManager) buildWebhooks(namespace string) (res []*webhook, for _, p := range policies { spec := p.GetSpec() - if spec.HasValidate() || spec.HasGenerate() { + if spec.HasValidate() || spec.HasGenerate() || spec.HasMutate() { if spec.GetFailurePolicy() == kyverno.Ignore { m.mergeWebhook(validateIgnore, p, true) } else { @@ -525,7 +525,8 @@ func (m *webhookConfigManager) getWebhook(webhookKind, webhookName string) (reso return err } - retryGetWebhook := common.RetryFunc(time.Second, 10*time.Second, get, m.log) + msg := "getWebhook: unable to get webhook configuration" + retryGetWebhook := common.RetryFunc(time.Second, 10*time.Second, get, msg, m.log) if err := retryGetWebhook(); err != nil { return nil, err } @@ -740,7 +741,8 @@ func (m *webhookConfigManager) mergeWebhook(dst *webhook, policy kyverno.PolicyI } if (updateValidate && rule.HasValidate()) || - (!updateValidate && rule.HasMutate()) || + (updateValidate && rule.HasMutate() && rule.IsMutateExisting()) || + (!updateValidate && rule.HasMutate()) && !rule.IsMutateExisting() || (!updateValidate && rule.HasVerifyImages()) { matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...) } @@ -809,9 +811,15 @@ func (m *webhookConfigManager) mergeWebhook(dst *webhook, policy kyverno.PolicyI rsrcs = append(rsrcs, "pods/ephemeralcontainers") } - dst.rule[apiGroups] = removeDuplicates(groups) - dst.rule[apiVersions] = removeDuplicates(versions) - dst.rule[resources] = removeDuplicates(rsrcs) + if len(groups) > 0 { + dst.rule[apiGroups] = removeDuplicates(groups) + } + if len(versions) > 0 { + dst.rule[apiVersions] = removeDuplicates(versions) + } + if len(rsrcs) > 0 { + dst.rule[resources] = removeDuplicates(rsrcs) + } spec := policy.GetSpec() if spec.WebhookTimeoutSeconds != nil { diff --git a/pkg/webhooks/common.go b/pkg/webhooks/common.go index b2198bf665..227ca9a497 100644 --- a/pkg/webhooks/common.go +++ b/pkg/webhooks/common.go @@ -6,6 +6,7 @@ import ( "github.com/go-logr/logr" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/autogen" enginectx "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" @@ -157,7 +158,7 @@ func excludeKyvernoResources(kind string) bool { } } -func newVariablesContext(request *admissionv1.AdmissionRequest, userRequestInfo *kyverno.RequestInfo) (enginectx.Interface, error) { +func newVariablesContext(request *admissionv1.AdmissionRequest, userRequestInfo *urkyverno.RequestInfo) (enginectx.Interface, error) { ctx := enginectx.NewContext() if err := ctx.AddRequest(request); err != nil { return nil, errors.Wrap(err, "failed to load incoming request in context") diff --git a/pkg/webhooks/generation.go b/pkg/webhooks/generation.go index f9913cbd39..539cb10364 100644 --- a/pkg/webhooks/generation.go +++ b/pkg/webhooks/generation.go @@ -10,7 +10,7 @@ import ( "github.com/gardener/controller-manager-library/pkg/logger" "github.com/go-logr/logr" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" - + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/autogen" gencommon "github.com/kyverno/kyverno/pkg/background/common" gen "github.com/kyverno/kyverno/pkg/background/generate" @@ -18,38 +18,24 @@ import ( "github.com/kyverno/kyverno/pkg/config" client "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/engine" - "github.com/kyverno/kyverno/pkg/engine/context" enginectx "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" enginutils "github.com/kyverno/kyverno/pkg/engine/utils" "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/event" - kyvernoutils "github.com/kyverno/kyverno/pkg/utils" jsonutils "github.com/kyverno/kyverno/pkg/utils/json" - "github.com/kyverno/kyverno/pkg/webhooks/generate" + "github.com/kyverno/kyverno/pkg/webhooks/updaterequest" admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" ) -func (ws *WebhookServer) applyGeneratePolicies(request *admissionv1.AdmissionRequest, policyContext *engine.PolicyContext, policies []kyverno.PolicyInterface, ts int64, logger logr.Logger) { - admissionReviewCompletionLatencyChannel := make(chan int64, 1) - generateEngineResponsesSenderForAdmissionReviewDurationMetric := make(chan []*response.EngineResponse, 1) - generateEngineResponsesSenderForAdmissionRequestsCountMetric := make(chan []*response.EngineResponse, 1) - - go ws.handleGenerate(request, policies, policyContext.JSONContext, policyContext.AdmissionInfo, ws.configHandler, ts, &admissionReviewCompletionLatencyChannel, &generateEngineResponsesSenderForAdmissionReviewDurationMetric, &generateEngineResponsesSenderForAdmissionRequestsCountMetric) - go ws.registerAdmissionReviewDurationMetricGenerate(logger, string(request.Operation), &admissionReviewCompletionLatencyChannel, &generateEngineResponsesSenderForAdmissionReviewDurationMetric) - go ws.registerAdmissionRequestsMetricGenerate(logger, string(request.Operation), &generateEngineResponsesSenderForAdmissionRequestsCountMetric) -} - //handleGenerate handles admission-requests for policies with generate rules func (ws *WebhookServer) handleGenerate( request *admissionv1.AdmissionRequest, policies []kyverno.PolicyInterface, - ctx context.Interface, - userRequestInfo kyverno.RequestInfo, - dynamicConfig config.Interface, + policyContext *engine.PolicyContext, admissionRequestTimestamp int64, latencySender *chan int64, generateEngineResponsesSenderForAdmissionReviewDurationMetric *chan []*response.EngineResponse, @@ -57,33 +43,17 @@ func (ws *WebhookServer) handleGenerate( ) { logger := ws.log.WithValues("action", "generation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation, "gvk", request.Kind.String()) - logger.V(6).Info("generate request") + logger.V(6).Info("update request") var engineResponses []*response.EngineResponse if (request.Operation == admissionv1.Create || request.Operation == admissionv1.Update) && len(policies) != 0 { - // convert RAW to unstructured - new, old, err := kyvernoutils.ExtractResources(nil, request) - if err != nil { - logger.Error(err, "failed to extract resource") - } - - policyContext := &engine.PolicyContext{ - NewResource: new, - OldResource: old, - AdmissionInfo: userRequestInfo, - ExcludeGroupRole: dynamicConfig.GetExcludeGroupRole(), - ExcludeResourceFunc: ws.configHandler.ToFilter, - JSONContext: ctx, - Client: ws.client, - } - for _, policy := range policies { var rules []response.RuleResponse policyContext.Policy = policy if request.Kind.Kind != "Namespace" && request.Namespace != "" { policyContext.NamespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, ws.nsLister, logger) } - engineResponse := engine.Generate(policyContext) + engineResponse := engine.ApplyBackgroundChecks(policyContext) for _, rule := range engineResponse.PolicyResponse.Rules { if rule.Status != response.RuleStatusPass { ws.deleteGR(logger, engineResponse) @@ -105,11 +75,10 @@ func (ws *WebhookServer) handleGenerate( go ws.registerPolicyExecutionDurationMetricGenerate(logger, string(request.Operation), policy, *engineResponse) } - // Adds Generate Request to a channel(queue size 1000) to generators - if failedResponse := applyGenerateRequest(request, ws.grGenerator, userRequestInfo, request.Operation, engineResponses...); err != nil { + if failedResponse := applyGenerateRequest(request, urkyverno.Generate, ws.grGenerator, policyContext.AdmissionInfo, request.Operation, engineResponses...); failedResponse != nil { // report failure event for _, failedGR := range failedResponse { - events := failedEvents(fmt.Errorf("failed to create Generate Request: %v", failedGR.err), failedGR.gr, new) + events := failedEvents(fmt.Errorf("failed to create Generate Request: %v", failedGR.err), failedGR.gr, policyContext.NewResource) ws.eventGen.Add(events...) } } @@ -166,7 +135,7 @@ func (ws *WebhookServer) handleUpdateGenerateSourceResource(resLabels map[string "generate.kyverno.io/policy-name": policyName, })) - grList, err := ws.grLister.List(selector) + grList, err := ws.urLister.List(selector) if err != nil { logger.Error(err, "failed to get generate request for the resource", "label", "generate.kyverno.io/policy-name") return @@ -182,7 +151,7 @@ func (ws *WebhookServer) handleUpdateGenerateSourceResource(resLabels map[string // updateAnnotationInGR - function used to update GR annotation // updating GR will trigger reprocessing of GR and recreation/updation of generated resource -func (ws *WebhookServer) updateAnnotationInGR(gr *kyverno.GenerateRequest, logger logr.Logger) { +func (ws *WebhookServer) updateAnnotationInGR(gr *urkyverno.UpdateRequest, logger logr.Logger) { grAnnotations := gr.Annotations if len(grAnnotations) == 0 { grAnnotations = make(map[string]string) @@ -258,7 +227,7 @@ func (ws *WebhookServer) handleUpdateGenerateTargetResource(request *admissionv1 if enqueueBool { grName := resLabels["policy.kyverno.io/gr-name"] - gr, err := ws.grLister.Get(grName) + gr, err := ws.urLister.Get(grName) if err != nil { logger.Error(err, "failed to get generate request", "name", grName) return @@ -363,7 +332,7 @@ func stripNonPolicyFields(obj, newRes map[string]interface{}, logger logr.Logger return obj, newRes } -//HandleDelete handles admission-requests for delete +//HandleDelete handles DELETE admission-requests for generate policies func (ws *WebhookServer) handleDelete(request *admissionv1.AdmissionRequest) { logger := ws.log.WithValues("action", "generation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation, "gvk", request.Kind.String()) resource, err := enginutils.ConvertToUnstructured(request.OldObject.Raw) @@ -374,11 +343,15 @@ func (ws *WebhookServer) handleDelete(request *admissionv1.AdmissionRequest) { resLabels := resource.GetLabels() if resLabels["app.kubernetes.io/managed-by"] == "kyverno" && request.Operation == admissionv1.Delete { grName := resLabels["policy.kyverno.io/gr-name"] - gr, err := ws.grLister.Get(grName) + gr, err := ws.urLister.Get(grName) if err != nil { logger.Error(err, "failed to get generate request", "name", grName) return } + + if gr.Spec.Type == urkyverno.Mutate { + return + } ws.updateAnnotationInGR(gr, logger) } } @@ -406,20 +379,20 @@ func (ws *WebhookServer) deleteGR(logger logr.Logger, engineResponse *response.E } } -func applyGenerateRequest(request *admissionv1.AdmissionRequest, gnGenerator generate.GenerateRequests, userRequestInfo kyverno.RequestInfo, +func applyGenerateRequest(request *admissionv1.AdmissionRequest, ruleType urkyverno.RequestType, gnGenerator updaterequest.Interface, userRequestInfo urkyverno.RequestInfo, action admissionv1.Operation, engineResponses ...*response.EngineResponse) (failedGenerateRequest []generateRequestResponse) { requestBytes, err := json.Marshal(request) if err != nil { logger.Error(err, "error loading request into context") } - admissionRequestInfo := kyverno.AdmissionRequestInfoObject{ + admissionRequestInfo := urkyverno.AdmissionRequestInfoObject{ AdmissionRequest: string(requestBytes), Operation: action, } for _, er := range engineResponses { - gr := transform(admissionRequestInfo, userRequestInfo, er) + gr := transform(admissionRequestInfo, userRequestInfo, er, ruleType) if err := gnGenerator.Apply(gr, action); err != nil { failedGenerateRequest = append(failedGenerateRequest, generateRequestResponse{gr: gr, err: err}) } @@ -428,15 +401,16 @@ func applyGenerateRequest(request *admissionv1.AdmissionRequest, gnGenerator gen return } -func transform(admissionRequestInfo kyverno.AdmissionRequestInfoObject, userRequestInfo kyverno.RequestInfo, er *response.EngineResponse) kyverno.GenerateRequestSpec { +func transform(admissionRequestInfo urkyverno.AdmissionRequestInfoObject, userRequestInfo urkyverno.RequestInfo, er *response.EngineResponse, ruleType urkyverno.RequestType) urkyverno.UpdateRequestSpec { var PolicyNameNamespaceKey string if er.PolicyResponse.Policy.Namespace != "" { - PolicyNameNamespaceKey = fmt.Sprintf("%s", er.PolicyResponse.Policy.Namespace+"/"+er.PolicyResponse.Policy.Name) + PolicyNameNamespaceKey = er.PolicyResponse.Policy.Namespace + "/" + er.PolicyResponse.Policy.Name } else { PolicyNameNamespaceKey = er.PolicyResponse.Policy.Name } - gr := kyverno.GenerateRequestSpec{ + gr := urkyverno.UpdateRequestSpec{ + Type: ruleType, Policy: PolicyNameNamespaceKey, Resource: kyverno.ResourceSpec{ Kind: er.PolicyResponse.Resource.Kind, @@ -444,7 +418,7 @@ func transform(admissionRequestInfo kyverno.AdmissionRequestInfoObject, userRequ Name: er.PolicyResponse.Resource.Name, APIVersion: er.PolicyResponse.Resource.APIVersion, }, - Context: kyverno.GenerateRequestContext{ + Context: urkyverno.UpdateRequestSpecContext{ UserRequestInfo: userRequestInfo, AdmissionRequestInfo: admissionRequestInfo, }, @@ -454,7 +428,7 @@ func transform(admissionRequestInfo kyverno.AdmissionRequestInfoObject, userRequ } type generateRequestResponse struct { - gr kyverno.GenerateRequestSpec + gr urkyverno.UpdateRequestSpec err error } @@ -466,7 +440,7 @@ func (resp generateRequestResponse) error() string { return resp.err.Error() } -func failedEvents(err error, gr kyverno.GenerateRequestSpec, resource unstructured.Unstructured) []event.Info { +func failedEvents(err error, gr urkyverno.UpdateRequestSpec, resource unstructured.Unstructured) []event.Info { re := event.Info{} re.Kind = resource.GetKind() re.Namespace = resource.GetNamespace() diff --git a/pkg/webhooks/handlers.go b/pkg/webhooks/handlers.go index 412513c7be..847bf90053 100644 --- a/pkg/webhooks/handlers.go +++ b/pkg/webhooks/handlers.go @@ -9,6 +9,7 @@ import ( "github.com/go-logr/logr" kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/common" "github.com/kyverno/kyverno/pkg/engine" enginectx "github.com/kyverno/kyverno/pkg/engine/context" @@ -56,27 +57,32 @@ func (ws *WebhookServer) admissionHandler(filter bool, inner handlers.AdmissionH if filter { inner = handlers.Filter(ws.configHandler, inner) } - return handlers.Monitor(ws.webhookMonitor, handlers.Admission(ws.log, inner)) + return handlers.Monitor(ws.webhookMonitor, handlers.Admission(ws.log.WithName("handlerFunc"), inner)) } func (ws *WebhookServer) policyMutation(request *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse { logger := setupLogger(ws.log, "policy mutation", request) + policy, oldPolicy, err := admissionutils.GetPolicies(request) if err != nil { logger.Error(err, "failed to unmarshal policies from admission request") return admissionutils.ResponseWithMessage(true, fmt.Sprintf("failed to default value, check kyverno controller logs for details: %v", err)) } + if oldPolicy != nil && isStatusUpdate(oldPolicy, policy) { logger.V(4).Info("skip policy mutation on status update") return admissionutils.Response(true) } + startTime := time.Now() logger.V(3).Info("start policy change mutation") defer logger.V(3).Info("finished policy change mutation", "time", time.Since(startTime).String()) + // Generate JSON Patches for defaults if patches, updateMsgs := policymutation.GenerateJSONPatchesForDefaults(policy, logger); len(patches) != 0 { return admissionutils.ResponseWithMessageAndPatch(true, strings.Join(updateMsgs, "'"), patches) } + return admissionutils.Response(true) } @@ -88,21 +94,26 @@ func (ws *WebhookServer) policyValidation(request *admissionv1.AdmissionRequest) logger.Error(err, "failed to unmarshal policies from admission request") return admissionutils.ResponseWithMessage(true, fmt.Sprintf("failed to validate policy, check kyverno controller logs for details: %v", err)) } + if oldPolicy != nil && isStatusUpdate(oldPolicy, policy) { logger.V(4).Info("skip policy validation on status update") return admissionutils.Response(true) } + startTime := time.Now() logger.V(3).Info("start policy change validation") defer logger.V(3).Info("finished policy change validation", "time", time.Since(startTime).String()) + response, err := policyvalidate.Validate(policy, ws.client, false, ws.openAPIController) if err != nil { logger.Error(err, "policy validation errors") return admissionutils.ResponseWithMessage(true, err.Error()) } + if response != nil && len(response.Warnings) != 0 { return response } + return admissionutils.Response(true) } @@ -112,6 +123,7 @@ func (ws *WebhookServer) resourceMutation(request *admissionv1.AdmissionRequest) if excludeKyvernoResources(request.Kind.Kind) { return admissionutils.ResponseSuccess(true, "") } + if request.Operation == admissionv1.Delete { resource, err := utils.ConvertResource(request.OldObject.Raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace) if err == nil { @@ -120,25 +132,32 @@ func (ws *WebhookServer) resourceMutation(request *admissionv1.AdmissionRequest) logger.Info(fmt.Sprintf("Converting oldObject failed: %v", err)) } return admissionutils.ResponseSuccess(true, "") + } + logger.V(4).Info("received an admission request in mutating webhook") + requestTime := time.Now().Unix() mutatePolicies := ws.pCache.GetPolicies(policycache.Mutate, request.Kind.Kind, request.Namespace) verifyImagesPolicies := ws.pCache.GetPolicies(policycache.VerifyImages, request.Kind.Kind, request.Namespace) + if len(mutatePolicies) == 0 && len(verifyImagesPolicies) == 0 { logger.V(4).Info("no policies matched admission request") return admissionutils.ResponseSuccess(true, "") } + addRoles := containsRBACInfo(mutatePolicies) policyContext, err := ws.buildPolicyContext(request, addRoles) if err != nil { logger.Error(err, "failed to build policy context") return admissionutils.ResponseFailure(false, err.Error()) } + // update container images to a canonical form if err := enginectx.MutateResourceWithImageInfo(request.Object.Raw, policyContext.JSONContext); err != nil { ws.log.Error(err, "failed to patch images info to resource, policies that mutate images may be impacted") } + mutatePatches := ws.applyMutatePolicies(request, policyContext, mutatePolicies, requestTime, logger) newRequest := patchRequest(mutatePatches, request, logger) imagePatches, err := ws.applyImageVerifyPolicies(newRequest, policyContext, verifyImagesPolicies, logger) @@ -146,7 +165,9 @@ func (ws *WebhookServer) resourceMutation(request *admissionv1.AdmissionRequest) logger.Error(err, "image verification failed") return admissionutils.ResponseFailure(false, err.Error()) } + var patches = append(mutatePatches, imagePatches...) + return admissionutils.ResponseSuccessWithPatch(true, "", patches) } @@ -155,21 +176,23 @@ func (ws *WebhookServer) resourceValidation(request *admissionv1.AdmissionReques if request.Operation == admissionv1.Delete { ws.handleDelete(request) } + if excludeKyvernoResources(request.Kind.Kind) { return admissionutils.ResponseSuccess(true, "") } + logger.V(6).Info("received an admission request in validating webhook") + // timestamp at which this admission request got triggered requestTime := time.Now().Unix() - policies := ws.pCache.GetPolicies(policycache.ValidateEnforce, request.Kind.Kind, "") - // Get namespace policies from the cache for the requested resource namespace - nsPolicies := ws.pCache.GetPolicies(policycache.ValidateEnforce, request.Kind.Kind, request.Namespace) - policies = append(policies, nsPolicies...) + policies := ws.pCache.GetPolicies(policycache.ValidateEnforce, request.Kind.Kind, request.Namespace) + mutatePolicies := ws.pCache.GetPolicies(policycache.Mutate, request.Kind.Kind, request.Namespace) generatePolicies := ws.pCache.GetPolicies(policycache.Generate, request.Kind.Kind, request.Namespace) if len(generatePolicies) == 0 && request.Operation == admissionv1.Update { // handle generate source resource updates go ws.handleUpdatesForGenerateRules(request, []kyverno.PolicyInterface{}) } + var roles, clusterRoles []string if containsRBACInfo(policies, generatePolicies) { var err error @@ -178,26 +201,32 @@ func (ws *WebhookServer) resourceValidation(request *admissionv1.AdmissionReques return errorResponse(logger, err, "failed to fetch RBAC data") } } - userRequestInfo := kyverno.RequestInfo{ + + userRequestInfo := urkyverno.RequestInfo{ Roles: roles, ClusterRoles: clusterRoles, AdmissionUserInfo: *request.UserInfo.DeepCopy(), } + ctx, err := newVariablesContext(request, &userRequestInfo) if err != nil { return errorResponse(logger, err, "failed create policy rule context") } + namespaceLabels := make(map[string]string) if request.Kind.Kind != "Namespace" && request.Namespace != "" { namespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, ws.nsLister, logger) } + newResource, oldResource, err := utils.ExtractResources(nil, request) if err != nil { return errorResponse(logger, err, "failed create parse resource") } + if err := ctx.AddImageInfos(&newResource); err != nil { return errorResponse(logger, err, "failed add image information to policy rule context") } + policyContext := &engine.PolicyContext{ NewResource: newResource, OldResource: oldResource, @@ -206,20 +235,25 @@ func (ws *WebhookServer) resourceValidation(request *admissionv1.AdmissionReques ExcludeResourceFunc: ws.configHandler.ToFilter, JSONContext: ctx, Client: ws.client, + AdmissionOperation: true, } + vh := &validationHandler{ log: ws.log, eventGen: ws.eventGen, prGenerator: ws.prGenerator, } + ok, msg := vh.handleValidation(ws.promConfig, request, policies, policyContext, namespaceLabels, requestTime) if !ok { logger.Info("admission request denied") return admissionutils.ResponseFailure(false, msg) } + // push admission request to audit handler, this won't block the admission request ws.auditHandler.Add(request.DeepCopy()) - // process generate policies - ws.applyGeneratePolicies(request, policyContext, generatePolicies, requestTime, logger) + + go ws.createUpdateRequests(request, policyContext, generatePolicies, mutatePolicies, requestTime, logger) + return admissionutils.ResponseSuccess(true, "") } diff --git a/pkg/webhooks/handlers/admission.go b/pkg/webhooks/handlers/admission.go index 46d2390b35..5074f0b788 100644 --- a/pkg/webhooks/handlers/admission.go +++ b/pkg/webhooks/handlers/admission.go @@ -43,7 +43,7 @@ func Admission(logger logr.Logger, inner AdmissionHandler) http.HandlerFunc { http.Error(writer, "Can't decode body as AdmissionReview", http.StatusExpectationFailed) return } - logger = logger.WithName("handlerFunc").WithValues( + logger = logger.WithValues( "kind", admissionReview.Request.Kind, "namespace", admissionReview.Request.Namespace, "name", admissionReview.Request.Name, @@ -67,7 +67,12 @@ func Admission(logger logr.Logger, inner AdmissionHandler) http.HandlerFunc { if _, err := writer.Write(responseJSON); err != nil { http.Error(writer, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError) } - logger.V(4).Info("admission review request processed", "time", time.Since(startTime).String()) + + if admissionReview.Request.Kind.Kind == "Lease" { + logger.V(6).Info("admission review request processed", "time", time.Since(startTime).String()) + } else { + logger.V(4).Info("admission review request processed", "time", time.Since(startTime).String()) + } } } @@ -82,7 +87,7 @@ func Filter(c config.Interface, inner AdmissionHandler) AdmissionHandler { func Verify(m *webhookconfig.Monitor, logger logr.Logger) AdmissionHandler { return func(request *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse { - logger = logger.WithName("verifyHandler").WithValues( + logger = logger.WithValues( "action", "verify", "kind", request.Kind, "namespace", request.Namespace, @@ -90,7 +95,7 @@ func Verify(m *webhookconfig.Monitor, logger logr.Logger) AdmissionHandler { "operation", request.Operation, "gvk", request.Kind.String(), ) - logger.V(3).Info("incoming request", "last admission request timestamp", m.Time()) + logger.V(6).Info("incoming request", "last admission request timestamp", m.Time()) return admissionutils.Response(true) } } diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index 956f898f69..6730a73fe9 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -103,7 +103,7 @@ func (ws *WebhookServer) handleMutation( } // generate annotations - if annPatches := generateAnnotationPatches(engineResponses, logger); annPatches != nil { + if annPatches := utils.GenerateAnnotationPatches(engineResponses, logger); annPatches != nil { patches = append(patches, annPatches...) } diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index db8ca876e6..363b42ced1 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -9,11 +9,13 @@ import ( "github.com/go-logr/logr" "github.com/julienschmidt/httprouter" - v1 "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/background" kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1" + urinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1beta1" kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1" + urlister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/config" client "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/engine" @@ -26,8 +28,8 @@ import ( "github.com/kyverno/kyverno/pkg/userinfo" "github.com/kyverno/kyverno/pkg/utils" "github.com/kyverno/kyverno/pkg/webhookconfig" - webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/generate" "github.com/kyverno/kyverno/pkg/webhooks/handlers" + webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/updaterequest" "github.com/pkg/errors" admissionv1 "k8s.io/api/admission/v1" informers "k8s.io/client-go/informers/core/v1" @@ -49,6 +51,12 @@ type WebhookServer struct { // grSynced returns true if the Generate Request store has been synced at least once grSynced cache.InformerSynced + // urLister can list/get update requests from the shared informer's store + urLister urlister.UpdateRequestNamespaceLister + + // urSynced returns true if the Update Request store has been synced at least once + urSynced cache.InformerSynced + // list/get cluster policy resource pLister kyvernolister.ClusterPolicyLister @@ -101,7 +109,7 @@ type WebhookServer struct { prGenerator policyreport.GeneratorInterface // generate request generator - grGenerator *webhookgenerate.Generator + grGenerator webhookgenerate.Interface nsLister listerv1.NamespaceLister @@ -128,6 +136,7 @@ func NewWebhookServer( client *client.Client, tlsPair *tlsutils.PemPair, grInformer kyvernoinformer.GenerateRequestInformer, + urInformer urinformer.UpdateRequestInformer, pInformer kyvernoinformer.ClusterPolicyInformer, rbInformer rbacinformer.RoleBindingInformer, crbInformer rbacinformer.ClusterRoleBindingInformer, @@ -140,7 +149,7 @@ func NewWebhookServer( webhookMonitor *webhookconfig.Monitor, configHandler config.Interface, prGenerator policyreport.GeneratorInterface, - grGenerator *webhookgenerate.Generator, + grGenerator webhookgenerate.Interface, auditHandler AuditHandler, cleanUp chan<- struct{}, log logr.Logger, @@ -160,6 +169,8 @@ func NewWebhookServer( kyvernoClient: kyvernoClient, grLister: grInformer.Lister().GenerateRequests(config.KyvernoNamespace), grSynced: grInformer.Informer().HasSynced, + urLister: urInformer.Lister().UpdateRequests(config.KyvernoNamespace), + urSynced: urInformer.Informer().HasSynced, pLister: pInformer.Lister(), pSynced: pInformer.Informer().HasSynced, rbLister: rbInformer.Lister(), @@ -191,7 +202,7 @@ func NewWebhookServer( mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, ws.admissionHandler(true, ws.resourceValidation)) mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, ws.admissionHandler(true, ws.policyMutation)) mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, ws.admissionHandler(true, ws.policyValidation)) - mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, ws.admissionHandler(false, handlers.Verify(ws.webhookMonitor, ws.log))) + mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, ws.admissionHandler(false, handlers.Verify(ws.webhookMonitor, ws.log.WithName("verifyHandler")))) mux.HandlerFunc("GET", config.LivenessServicePath, handlers.Probe(ws.webhookRegister.Check)) mux.HandlerFunc("GET", config.ReadinessServicePath, handlers.Probe(nil)) ws.server = &http.Server{ @@ -205,7 +216,7 @@ func NewWebhookServer( } func (ws *WebhookServer) buildPolicyContext(request *admissionv1.AdmissionRequest, addRoles bool) (*engine.PolicyContext, error) { - userRequestInfo := v1.RequestInfo{ + userRequestInfo := v1beta1.RequestInfo{ AdmissionUserInfo: *request.UserInfo.DeepCopy(), } @@ -246,6 +257,7 @@ func (ws *WebhookServer) buildPolicyContext(request *admissionv1.AdmissionReques ExcludeResourceFunc: ws.configHandler.ToFilter, JSONContext: ctx, Client: ws.client, + AdmissionOperation: true, } if request.Operation == admissionv1.Update { @@ -257,7 +269,7 @@ func (ws *WebhookServer) buildPolicyContext(request *admissionv1.AdmissionReques // RunAsync TLS server in separate thread and returns control immediately func (ws *WebhookServer) RunAsync(stopCh <-chan struct{}) { - if !cache.WaitForCacheSync(stopCh, ws.grSynced, ws.pSynced, ws.rbSynced, ws.crbSynced, ws.rSynced, ws.crSynced) { + if !cache.WaitForCacheSync(stopCh, ws.grSynced, ws.urSynced, ws.pSynced, ws.rbSynced, ws.crbSynced, ws.rSynced, ws.crSynced) { ws.log.Info("failed to sync informer cache") } go func() { diff --git a/pkg/webhooks/updaterequest.go b/pkg/webhooks/updaterequest.go new file mode 100644 index 0000000000..ba4394fbc8 --- /dev/null +++ b/pkg/webhooks/updaterequest.go @@ -0,0 +1,71 @@ +package webhooks + +import ( + "fmt" + "time" + + "github.com/go-logr/logr" + kyverno "github.com/kyverno/kyverno/api/kyverno/v1" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/engine" + "github.com/kyverno/kyverno/pkg/engine/response" + admissionv1 "k8s.io/api/admission/v1" +) + +// createUpdateRequests applies generate and mutateExisting policies, and creates update requests for background reconcile +func (ws *WebhookServer) createUpdateRequests(request *admissionv1.AdmissionRequest, policyContext *engine.PolicyContext, generatePolicies, mutatePolicies []kyverno.PolicyInterface, ts int64, logger logr.Logger) { + admissionReviewCompletionLatencyChannel := make(chan int64, 1) + generateEngineResponsesSenderForAdmissionReviewDurationMetric := make(chan []*response.EngineResponse, 1) + generateEngineResponsesSenderForAdmissionRequestsCountMetric := make(chan []*response.EngineResponse, 1) + + go ws.handleMutateExisting(request, mutatePolicies, policyContext, ts) + + go ws.handleGenerate(request, generatePolicies, policyContext, ts, &admissionReviewCompletionLatencyChannel, &generateEngineResponsesSenderForAdmissionReviewDurationMetric, &generateEngineResponsesSenderForAdmissionRequestsCountMetric) + go ws.registerAdmissionReviewDurationMetricGenerate(logger, string(request.Operation), &admissionReviewCompletionLatencyChannel, &generateEngineResponsesSenderForAdmissionReviewDurationMetric) + go ws.registerAdmissionRequestsMetricGenerate(logger, string(request.Operation), &generateEngineResponsesSenderForAdmissionRequestsCountMetric) +} + +func (ws *WebhookServer) handleMutateExisting(request *admissionv1.AdmissionRequest, policies []kyverno.PolicyInterface, policyContext *engine.PolicyContext, admissionRequestTimestamp int64) { + + logger := ws.log.WithValues("action", "mutateExisting", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation, "gvk", request.Kind.String()) + logger.V(4).Info("update request") + + if request.Operation == admissionv1.Delete { + policyContext.NewResource = policyContext.OldResource + } + + var engineResponses []*response.EngineResponse + for _, policy := range policies { + var rules []response.RuleResponse + policyContext.Policy = policy + engineResponse := engine.ApplyBackgroundChecks(policyContext) + + for _, rule := range engineResponse.PolicyResponse.Rules { + if rule.Status == response.RuleStatusPass { + rules = append(rules, rule) + } + } + + if len(rules) > 0 { + engineResponse.PolicyResponse.Rules = rules + engineResponses = append(engineResponses, engineResponse) + } + + // registering the kyverno_policy_results_total metric concurrently + go ws.registerPolicyResultsMetricMutation(logger, string(request.Operation), policy, *engineResponse) + + // registering the kyverno_policy_execution_duration_seconds metric concurrently + go ws.registerPolicyExecutionDurationMetricMutate(logger, string(request.Operation), policy, *engineResponse) + } + + if failedResponse := applyGenerateRequest(request, urkyverno.Mutate, ws.grGenerator, policyContext.AdmissionInfo, request.Operation, engineResponses...); failedResponse != nil { + for _, failedGR := range failedResponse { + events := failedEvents(fmt.Errorf("failed to create update request: %v", failedGR.err), failedGR.gr, policyContext.NewResource) + ws.eventGen.Add(events...) + } + } + + admissionReviewLatencyDuration := int64(time.Since(time.Unix(admissionRequestTimestamp, 0))) + go ws.registerAdmissionReviewDurationMetricMutate(logger, string(request.Operation), engineResponses, admissionReviewLatencyDuration) + go ws.registerAdmissionRequestsMetricMutate(logger, string(request.Operation), engineResponses) +} diff --git a/pkg/webhooks/updaterequest/generator.go b/pkg/webhooks/updaterequest/generator.go new file mode 100644 index 0000000000..4f9766bba5 --- /dev/null +++ b/pkg/webhooks/updaterequest/generator.go @@ -0,0 +1,224 @@ +package updaterequest + +import ( + "context" + "time" + + backoff "github.com/cenkalti/backoff" + "github.com/gardener/controller-manager-library/pkg/logger" + "github.com/go-logr/logr" + urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" + kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" + kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1" + urkyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1beta1" + kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1" + urkyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/config" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/cache" +) + +// GenerateRequests provides interface to manage update requests +type Interface interface { + Apply(gr urkyverno.UpdateRequestSpec, action admissionv1.Operation) error +} + +// info object stores message data to create update request +type info struct { + spec urkyverno.UpdateRequestSpec + action admissionv1.Operation +} + +// Generator defines the implementation to mange update request resource +type Generator struct { + client *kyvernoclient.Clientset + stopCh <-chan struct{} + log logr.Logger + // grLister can list/get generate request from the shared informer's store + grLister kyvernolister.GenerateRequestNamespaceLister + grSynced cache.InformerSynced + + urLister urkyvernolister.UpdateRequestNamespaceLister + urSynced cache.InformerSynced +} + +// NewGenerator returns a new instance of UpdateRequest resource generator +func NewGenerator(client *kyvernoclient.Clientset, grInformer kyvernoinformer.GenerateRequestInformer, urInformer urkyvernoinformer.UpdateRequestInformer, stopCh <-chan struct{}, log logr.Logger) *Generator { + gen := &Generator{ + client: client, + stopCh: stopCh, + log: log, + grLister: grInformer.Lister().GenerateRequests(config.KyvernoNamespace), + grSynced: grInformer.Informer().HasSynced, + urLister: urInformer.Lister().UpdateRequests(config.KyvernoNamespace), + urSynced: urInformer.Informer().HasSynced, + } + return gen +} + +// Apply creates update request resource +func (g *Generator) Apply(gr urkyverno.UpdateRequestSpec, action admissionv1.Operation) error { + logger := g.log + logger.V(4).Info("reconcile Update Request", "request", gr) + + message := info{ + action: action, + spec: gr, + } + go g.processApply(message) + return nil +} + +// Run starts the update request spec +func (g *Generator) Run(workers int, stopCh <-chan struct{}) { + logger := g.log + defer utilruntime.HandleCrash() + + logger.V(4).Info("starting") + defer func() { + logger.V(4).Info("shutting down") + }() + + if !cache.WaitForCacheSync(stopCh, g.grSynced, g.urSynced) { + logger.Info("failed to sync informer cache") + return + } + + <-g.stopCh +} + +func (g *Generator) processApply(i info) { + if err := g.generate(i); err != nil { + logger.Error(err, "failed to update request CR") + } +} + +func (g *Generator) generate(i info) error { + if err := retryApplyResource(g.client, i.spec, g.log, i.action, g.urLister); err != nil { + return err + } + return nil +} + +func retryApplyResource(client *kyvernoclient.Clientset, urSpec urkyverno.UpdateRequestSpec, + log logr.Logger, action admissionv1.Operation, urLister urkyvernolister.UpdateRequestNamespaceLister) error { + + if action == admissionv1.Delete && urSpec.Type == urkyverno.Generate { + return nil + } + + var i int + var err error + + _, policyName, err := cache.SplitMetaNamespaceKey(urSpec.Policy) + if err != nil { + return err + } + + applyResource := func() error { + ur := urkyverno.UpdateRequest{ + Spec: urSpec, + Status: urkyverno.UpdateRequestStatus{ + State: urkyverno.Pending, + }, + } + + queryLabels := make(map[string]string) + if ur.Spec.Type == urkyverno.Mutate { + queryLabels := map[string]string{ + "mutate.updaterequest.kyverno.io/policy-name": ur.Spec.Policy, + "mutate.updaterequest.kyverno.io/trigger-name": ur.Spec.Resource.Name, + "mutate.updaterequest.kyverno.io/trigger-namespace": ur.Spec.Resource.Namespace, + "mutate.updaterequest.kyverno.io/trigger-kind": ur.Spec.Resource.Kind, + } + + if ur.Spec.Resource.APIVersion != "" { + queryLabels["mutate.updaterequest.kyverno.io/trigger-apiversion"] = ur.Spec.Resource.APIVersion + } + } else if ur.Spec.Type == urkyverno.Generate { + queryLabels = labels.Set(map[string]string{ + "generate.kyverno.io/policy-name": policyName, + "generate.kyverno.io/resource-name": urSpec.Resource.Name, + "generate.kyverno.io/resource-kind": urSpec.Resource.Kind, + "generate.kyverno.io/resource-namespace": urSpec.Resource.Namespace, + }) + } + + ur.SetNamespace(config.KyvernoNamespace) + isExist := false + log.V(4).Info("apply UpdateRequest", "ruleType", ur.Spec.Type) + + urList, err := urLister.List(labels.SelectorFromSet(queryLabels)) + if err != nil { + logger.Error(err, "failed to get update request for the resource", "kind", urSpec.Resource.Kind, "name", urSpec.Resource.Name, "namespace", urSpec.Resource.Namespace) + return err + } + + for _, v := range urList { + log.V(4).Info("updating existing update request", "name", v.GetName()) + + v.Spec.Context = ur.Spec.Context + v.Spec.Policy = ur.Spec.Policy + v.Spec.Resource = ur.Spec.Resource + v.Status.Message = "" + + new, err := client.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).Update(context.TODO(), v, metav1.UpdateOptions{}) + if err != nil { + log.V(4).Info("failed to update UpdateRequest, retrying", "retryCount", i, "name", ur.GetName(), "namespace", ur.GetNamespace()) + i++ + } else { + log.V(4).Info("successfully updated UpdateRequest", "retryCount", i, "name", ur.GetName(), "namespace", ur.GetNamespace()) + } + + new.Status.State = urkyverno.Pending + if _, err := client.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).UpdateStatus(context.TODO(), new, metav1.UpdateOptions{}); err != nil { + log.Error(err, "failed to set UpdateRequest state to Pending") + } + + isExist = true + } + + if !isExist { + log.V(4).Info("creating new UpdateRequest", "type", ur.Spec.Type) + + ur.SetGenerateName("ur-") + ur.SetLabels(queryLabels) + + new, err := client.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).Create(context.TODO(), &ur, metav1.CreateOptions{}) + if err != nil { + log.V(4).Info("failed to create UpdateRequest, retrying", "retryCount", i, "name", ur.GetGenerateName(), "namespace", ur.GetNamespace()) + i++ + } else { + log.V(4).Info("successfully created UpdateRequest", "retryCount", i, "name", new.GetName(), "namespace", ur.GetNamespace()) + } + + new.Status.State = urkyverno.Pending + if _, err := client.KyvernoV1beta1().UpdateRequests(config.KyvernoNamespace).UpdateStatus(context.TODO(), new, metav1.UpdateOptions{}); err != nil { + log.Error(err, "failed to set UpdateRequest state to Pending") + } + } + + return err + } + + exbackoff := &backoff.ExponentialBackOff{ + InitialInterval: 500 * time.Millisecond, + RandomizationFactor: 0.5, + Multiplier: 1.5, + MaxInterval: time.Second, + MaxElapsedTime: 3 * time.Second, + Clock: backoff.SystemClock, + } + + exbackoff.Reset() + err = backoff.Retry(applyResource, exbackoff) + + if err != nil { + return err + } + + return nil +} diff --git a/pkg/webhooks/validate_audit.go b/pkg/webhooks/validate_audit.go index 33e9aa9eb6..b05ecc8d15 100644 --- a/pkg/webhooks/validate_audit.go +++ b/pkg/webhooks/validate_audit.go @@ -4,22 +4,19 @@ import ( "strings" "time" - v1 "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/engine" - "github.com/kyverno/kyverno/pkg/utils" - - "github.com/pkg/errors" - - "github.com/kyverno/kyverno/pkg/common" - client "github.com/kyverno/kyverno/pkg/dclient" - "github.com/go-logr/logr" + "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/common" "github.com/kyverno/kyverno/pkg/config" + client "github.com/kyverno/kyverno/pkg/dclient" + "github.com/kyverno/kyverno/pkg/engine" "github.com/kyverno/kyverno/pkg/event" "github.com/kyverno/kyverno/pkg/metrics" "github.com/kyverno/kyverno/pkg/policycache" "github.com/kyverno/kyverno/pkg/policyreport" "github.com/kyverno/kyverno/pkg/userinfo" + "github.com/kyverno/kyverno/pkg/utils" + "github.com/pkg/errors" admissionv1 "k8s.io/api/admission/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -161,7 +158,7 @@ func (h *auditHandler) process(request *admissionv1.AdmissionRequest) error { } } - userRequestInfo := v1.RequestInfo{ + userRequestInfo := v1beta1.RequestInfo{ Roles: roles, ClusterRoles: clusterRoles, AdmissionUserInfo: request.UserInfo} @@ -193,6 +190,7 @@ func (h *auditHandler) process(request *admissionv1.AdmissionRequest) error { ExcludeResourceFunc: h.configHandler.ToFilter, JSONContext: ctx, Client: h.client, + AdmissionOperation: true, } vh := &validationHandler{