1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

feat: add context/preconditions support to mutate existing (#6754)

* refactor: engine handlers

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* feat: add context/preconditions support to mutate existing

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* kuttl

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* readme

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix and context kuttl test

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* validation

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* final fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-04-03 21:58:58 +02:00 committed by GitHub
parent 16d57f0940
commit 40ac8eb863
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 4326 additions and 306 deletions

View file

@ -266,7 +266,7 @@ func (r ResourceFilter) IsEmpty() bool {
type Mutation struct {
// Targets defines the target resources to be mutated.
// +optional
Targets []ResourceSpec `json:"targets,omitempty" yaml:"targets,omitempty"`
Targets []TargetResourceSpec `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/

View file

@ -1,6 +1,11 @@
package v1
import "strings"
import (
"strings"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
type ResourceSpec struct {
// APIVersion specifies resource apiVersion.
@ -24,3 +29,25 @@ func (s ResourceSpec) GetAPIVersion() string { return s.APIVersion }
func (s ResourceSpec) String() string {
return strings.Join([]string{s.APIVersion, s.Kind, s.Namespace, s.Name}, "/")
}
// TargetResourceSpec defines targets for mutating existing resources.
type TargetResourceSpec struct {
// ResourceSpec contains the target resources to load when mutating existing resources.
ResourceSpec `json:",omitempty" yaml:",omitempty"`
// Context defines variables and data sources that can be used during rule execution.
// +optional
Context []ContextEntry `json:"context,omitempty" yaml:"context,omitempty"`
// Preconditions are used to determine if a policy rule should be applied by evaluating a
// set of conditions. The declaration can contain nested `any` or `all` statements. A direct list
// of conditions (without `any` or `all` statements is supported for backwards compatibility but
// will be deprecated in the next major release.
// See: https://kyverno.io/docs/writing-policies/preconditions/
// +optional
RawAnyAllConditions *apiextv1.JSON `json:"preconditions,omitempty" yaml:"preconditions,omitempty"`
}
func (r *TargetResourceSpec) GetAnyAllConditions() apiextensions.JSON {
return FromJSON(r.RawAnyAllConditions)
}

View file

@ -775,8 +775,10 @@ func (in *Mutation) DeepCopyInto(out *Mutation) {
*out = *in
if in.Targets != nil {
in, out := &in.Targets, &out.Targets
*out = make([]ResourceSpec, len(*in))
copy(*out, *in)
*out = make([]TargetResourceSpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.RawPatchStrategicMerge != nil {
in, out := &in.RawPatchStrategicMerge, &out.RawPatchStrategicMerge
@ -1267,6 +1269,34 @@ 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 *TargetResourceSpec) DeepCopyInto(out *TargetResourceSpec) {
*out = *in
out.ResourceSpec = in.ResourceSpec
if in.Context != nil {
in, out := &in.Context, &out.Context
*out = make([]ContextEntry, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.RawAnyAllConditions != nil {
in, out := &in.RawAnyAllConditions, &out.RawAnyAllConditions
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetResourceSpec.
func (in *TargetResourceSpec) DeepCopy() *TargetResourceSpec {
if in == nil {
return nil
}
out := new(TargetResourceSpec)
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

File diff suppressed because it is too large Load diff

View file

@ -2060,10 +2060,147 @@ spec:
description: Targets defines the target resources to be
mutated.
items:
description: TargetResourceSpec defines targets for mutating
existing resources.
properties:
apiVersion:
description: APIVersion specifies resource apiVersion.
type: string
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall is an HTTP request to the
Kubernetes API server, or other JSON web service.
The data returned is stored in the context
with the name for the context entry.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the server.
For example a JMESPath of "items | length(@)"
applied to the API server response for
the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
service:
description: Service is an API call to a
JSON web service
properties:
caBundle:
description: CABundle is a PEM encoded
CA bundle which will be used to validate
the server certificate.
type: string
data:
description: Data specifies the POST
data sent to the server.
items:
description: RequestData contains
the HTTP POST data
properties:
key:
description: Key is a unique identifier
for the data value
type: string
value:
description: Value is the data
value
x-kubernetes-preserve-unknown-fields: true
required:
- key
- value
type: object
type: array
requestType:
default: GET
description: Method is the HTTP request
type (GET or POST).
enum:
- GET
- POST
type: string
urlPath:
description: URL is the JSON web service
URL. The typical format is `https://{service}.{namespace}:{port}/{path}`.
type: string
required:
- requestType
- urlPath
type: object
urlPath:
description: URLPath is the URL path to
be used in the HTTP GET request to the
Kubernetes API server (e.g. "/api/v1/namespaces"
or "/apis/apps/v1/deployments"). The
format required is the same format used
by the `kubectl get --raw` command.
type: string
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
imageRegistry:
description: ImageRegistry defines requests
to an OCI/Docker V2 registry to fetch image
details.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the ImageData struct returned as a result
of processing the image reference.
type: string
reference:
description: 'Reference is image reference
to a container image in the registry.
Example: ghcr.io/kyverno/kyverno:latest'
type: string
required:
- reference
type: object
name:
description: Name is the variable name.
type: string
variable:
description: Variable defines an arbitrary JMESPath
context variable that can be defined inline.
properties:
default:
description: Default is an optional arbitrary
JSON object that the variable may take
if the JMESPath expression evaluates to
nil
x-kubernetes-preserve-unknown-fields: true
jmesPath:
description: JMESPath is an optional JMESPath
Expression that can be used to transform
the variable.
type: string
value:
description: Value is any arbitrary JSON
object representable in YAML or JSON form.
x-kubernetes-preserve-unknown-fields: true
type: object
type: object
type: array
kind:
description: Kind specifies resource kind.
type: string
@ -2073,6 +2210,15 @@ spec:
namespace:
description: Namespace specifies resource namespace.
type: string
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. A direct list
of conditions (without `any` or `all` statements
is supported for backwards compatibility but will
be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
@ -5476,10 +5622,155 @@ spec:
description: Targets defines the target resources to
be mutated.
items:
description: TargetResourceSpec defines targets for
mutating existing resources.
properties:
apiVersion:
description: APIVersion specifies resource apiVersion.
type: string
context:
description: Context defines variables and data
sources that can be used during rule execution.
items:
description: ContextEntry adds variables and
data sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall is an HTTP request
to the Kubernetes API server, or other
JSON web service. The data returned is
stored in the context with the name for
the context entry.
properties:
jmesPath:
description: JMESPath is an optional
JSON Match Expression that can be
used to transform the JSON response
returned from the server. For example
a JMESPath of "items | length(@)"
applied to the API server response
for the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
service:
description: Service is an API call
to a JSON web service
properties:
caBundle:
description: CABundle is a PEM encoded
CA bundle which will be used to
validate the server certificate.
type: string
data:
description: Data specifies the
POST data sent to the server.
items:
description: RequestData contains
the HTTP POST data
properties:
key:
description: Key is a unique
identifier for the data
value
type: string
value:
description: Value is the
data value
x-kubernetes-preserve-unknown-fields: true
required:
- key
- value
type: object
type: array
requestType:
default: GET
description: Method is the HTTP
request type (GET or POST).
enum:
- GET
- POST
type: string
urlPath:
description: URL is the JSON web
service URL. The typical format
is `https://{service}.{namespace}:{port}/{path}`.
type: string
required:
- requestType
- urlPath
type: object
urlPath:
description: URLPath is the URL path
to be used in the HTTP GET request
to the Kubernetes API server (e.g.
"/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format
used by the `kubectl get --raw` command.
type: string
type: object
configMap:
description: ConfigMap is the ConfigMap
reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
imageRegistry:
description: ImageRegistry defines requests
to an OCI/Docker V2 registry to fetch
image details.
properties:
jmesPath:
description: JMESPath is an optional
JSON Match Expression that can be
used to transform the ImageData struct
returned as a result of processing
the image reference.
type: string
reference:
description: 'Reference is image reference
to a container image in the registry.
Example: ghcr.io/kyverno/kyverno:latest'
type: string
required:
- reference
type: object
name:
description: Name is the variable name.
type: string
variable:
description: Variable defines an arbitrary
JMESPath context variable that can be
defined inline.
properties:
default:
description: Default is an optional
arbitrary JSON object that the variable
may take if the JMESPath expression
evaluates to nil
x-kubernetes-preserve-unknown-fields: true
jmesPath:
description: JMESPath is an optional
JMESPath Expression that can be used
to transform the variable.
type: string
value:
description: Value is any arbitrary
JSON object representable in YAML
or JSON form.
x-kubernetes-preserve-unknown-fields: true
type: object
type: object
type: array
kind:
description: Kind specifies resource kind.
type: string
@ -5489,6 +5780,16 @@ spec:
namespace:
description: Namespace specifies resource namespace.
type: string
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. A direct list
of conditions (without `any` or `all` statements
is supported for backwards compatibility but
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
@ -8536,10 +8837,147 @@ spec:
description: Targets defines the target resources to be
mutated.
items:
description: TargetResourceSpec defines targets for mutating
existing resources.
properties:
apiVersion:
description: APIVersion specifies resource apiVersion.
type: string
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall is an HTTP request to the
Kubernetes API server, or other JSON web service.
The data returned is stored in the context
with the name for the context entry.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the server.
For example a JMESPath of "items | length(@)"
applied to the API server response for
the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
service:
description: Service is an API call to a
JSON web service
properties:
caBundle:
description: CABundle is a PEM encoded
CA bundle which will be used to validate
the server certificate.
type: string
data:
description: Data specifies the POST
data sent to the server.
items:
description: RequestData contains
the HTTP POST data
properties:
key:
description: Key is a unique identifier
for the data value
type: string
value:
description: Value is the data
value
x-kubernetes-preserve-unknown-fields: true
required:
- key
- value
type: object
type: array
requestType:
default: GET
description: Method is the HTTP request
type (GET or POST).
enum:
- GET
- POST
type: string
urlPath:
description: URL is the JSON web service
URL. The typical format is `https://{service}.{namespace}:{port}/{path}`.
type: string
required:
- requestType
- urlPath
type: object
urlPath:
description: URLPath is the URL path to
be used in the HTTP GET request to the
Kubernetes API server (e.g. "/api/v1/namespaces"
or "/apis/apps/v1/deployments"). The
format required is the same format used
by the `kubectl get --raw` command.
type: string
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
imageRegistry:
description: ImageRegistry defines requests
to an OCI/Docker V2 registry to fetch image
details.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the ImageData struct returned as a result
of processing the image reference.
type: string
reference:
description: 'Reference is image reference
to a container image in the registry.
Example: ghcr.io/kyverno/kyverno:latest'
type: string
required:
- reference
type: object
name:
description: Name is the variable name.
type: string
variable:
description: Variable defines an arbitrary JMESPath
context variable that can be defined inline.
properties:
default:
description: Default is an optional arbitrary
JSON object that the variable may take
if the JMESPath expression evaluates to
nil
x-kubernetes-preserve-unknown-fields: true
jmesPath:
description: JMESPath is an optional JMESPath
Expression that can be used to transform
the variable.
type: string
value:
description: Value is any arbitrary JSON
object representable in YAML or JSON form.
x-kubernetes-preserve-unknown-fields: true
type: object
type: object
type: array
kind:
description: Kind specifies resource kind.
type: string
@ -8549,6 +8987,15 @@ spec:
namespace:
description: Namespace specifies resource namespace.
type: string
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. A direct list
of conditions (without `any` or `all` statements
is supported for backwards compatibility but will
be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
@ -12080,10 +12527,155 @@ spec:
description: Targets defines the target resources to
be mutated.
items:
description: TargetResourceSpec defines targets for
mutating existing resources.
properties:
apiVersion:
description: APIVersion specifies resource apiVersion.
type: string
context:
description: Context defines variables and data
sources that can be used during rule execution.
items:
description: ContextEntry adds variables and
data sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall is an HTTP request
to the Kubernetes API server, or other
JSON web service. The data returned is
stored in the context with the name for
the context entry.
properties:
jmesPath:
description: JMESPath is an optional
JSON Match Expression that can be
used to transform the JSON response
returned from the server. For example
a JMESPath of "items | length(@)"
applied to the API server response
for the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
service:
description: Service is an API call
to a JSON web service
properties:
caBundle:
description: CABundle is a PEM encoded
CA bundle which will be used to
validate the server certificate.
type: string
data:
description: Data specifies the
POST data sent to the server.
items:
description: RequestData contains
the HTTP POST data
properties:
key:
description: Key is a unique
identifier for the data
value
type: string
value:
description: Value is the
data value
x-kubernetes-preserve-unknown-fields: true
required:
- key
- value
type: object
type: array
requestType:
default: GET
description: Method is the HTTP
request type (GET or POST).
enum:
- GET
- POST
type: string
urlPath:
description: URL is the JSON web
service URL. The typical format
is `https://{service}.{namespace}:{port}/{path}`.
type: string
required:
- requestType
- urlPath
type: object
urlPath:
description: URLPath is the URL path
to be used in the HTTP GET request
to the Kubernetes API server (e.g.
"/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format
used by the `kubectl get --raw` command.
type: string
type: object
configMap:
description: ConfigMap is the ConfigMap
reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
imageRegistry:
description: ImageRegistry defines requests
to an OCI/Docker V2 registry to fetch
image details.
properties:
jmesPath:
description: JMESPath is an optional
JSON Match Expression that can be
used to transform the ImageData struct
returned as a result of processing
the image reference.
type: string
reference:
description: 'Reference is image reference
to a container image in the registry.
Example: ghcr.io/kyverno/kyverno:latest'
type: string
required:
- reference
type: object
name:
description: Name is the variable name.
type: string
variable:
description: Variable defines an arbitrary
JMESPath context variable that can be
defined inline.
properties:
default:
description: Default is an optional
arbitrary JSON object that the variable
may take if the JMESPath expression
evaluates to nil
x-kubernetes-preserve-unknown-fields: true
jmesPath:
description: JMESPath is an optional
JMESPath Expression that can be used
to transform the variable.
type: string
value:
description: Value is any arbitrary
JSON object representable in YAML
or JSON form.
x-kubernetes-preserve-unknown-fields: true
type: object
type: object
type: array
kind:
description: Kind specifies resource kind.
type: string
@ -12093,6 +12685,16 @@ spec:
namespace:
description: Namespace specifies resource namespace.
type: string
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. A direct list
of conditions (without `any` or `all` statements
is supported for backwards compatibility but
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object

View file

@ -2061,10 +2061,147 @@ spec:
description: Targets defines the target resources to be
mutated.
items:
description: TargetResourceSpec defines targets for mutating
existing resources.
properties:
apiVersion:
description: APIVersion specifies resource apiVersion.
type: string
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall is an HTTP request to the
Kubernetes API server, or other JSON web service.
The data returned is stored in the context
with the name for the context entry.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the server.
For example a JMESPath of "items | length(@)"
applied to the API server response for
the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
service:
description: Service is an API call to a
JSON web service
properties:
caBundle:
description: CABundle is a PEM encoded
CA bundle which will be used to validate
the server certificate.
type: string
data:
description: Data specifies the POST
data sent to the server.
items:
description: RequestData contains
the HTTP POST data
properties:
key:
description: Key is a unique identifier
for the data value
type: string
value:
description: Value is the data
value
x-kubernetes-preserve-unknown-fields: true
required:
- key
- value
type: object
type: array
requestType:
default: GET
description: Method is the HTTP request
type (GET or POST).
enum:
- GET
- POST
type: string
urlPath:
description: URL is the JSON web service
URL. The typical format is `https://{service}.{namespace}:{port}/{path}`.
type: string
required:
- requestType
- urlPath
type: object
urlPath:
description: URLPath is the URL path to
be used in the HTTP GET request to the
Kubernetes API server (e.g. "/api/v1/namespaces"
or "/apis/apps/v1/deployments"). The
format required is the same format used
by the `kubectl get --raw` command.
type: string
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
imageRegistry:
description: ImageRegistry defines requests
to an OCI/Docker V2 registry to fetch image
details.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the ImageData struct returned as a result
of processing the image reference.
type: string
reference:
description: 'Reference is image reference
to a container image in the registry.
Example: ghcr.io/kyverno/kyverno:latest'
type: string
required:
- reference
type: object
name:
description: Name is the variable name.
type: string
variable:
description: Variable defines an arbitrary JMESPath
context variable that can be defined inline.
properties:
default:
description: Default is an optional arbitrary
JSON object that the variable may take
if the JMESPath expression evaluates to
nil
x-kubernetes-preserve-unknown-fields: true
jmesPath:
description: JMESPath is an optional JMESPath
Expression that can be used to transform
the variable.
type: string
value:
description: Value is any arbitrary JSON
object representable in YAML or JSON form.
x-kubernetes-preserve-unknown-fields: true
type: object
type: object
type: array
kind:
description: Kind specifies resource kind.
type: string
@ -2074,6 +2211,15 @@ spec:
namespace:
description: Namespace specifies resource namespace.
type: string
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. A direct list
of conditions (without `any` or `all` statements
is supported for backwards compatibility but will
be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
@ -5478,10 +5624,155 @@ spec:
description: Targets defines the target resources to
be mutated.
items:
description: TargetResourceSpec defines targets for
mutating existing resources.
properties:
apiVersion:
description: APIVersion specifies resource apiVersion.
type: string
context:
description: Context defines variables and data
sources that can be used during rule execution.
items:
description: ContextEntry adds variables and
data sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall is an HTTP request
to the Kubernetes API server, or other
JSON web service. The data returned is
stored in the context with the name for
the context entry.
properties:
jmesPath:
description: JMESPath is an optional
JSON Match Expression that can be
used to transform the JSON response
returned from the server. For example
a JMESPath of "items | length(@)"
applied to the API server response
for the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
service:
description: Service is an API call
to a JSON web service
properties:
caBundle:
description: CABundle is a PEM encoded
CA bundle which will be used to
validate the server certificate.
type: string
data:
description: Data specifies the
POST data sent to the server.
items:
description: RequestData contains
the HTTP POST data
properties:
key:
description: Key is a unique
identifier for the data
value
type: string
value:
description: Value is the
data value
x-kubernetes-preserve-unknown-fields: true
required:
- key
- value
type: object
type: array
requestType:
default: GET
description: Method is the HTTP
request type (GET or POST).
enum:
- GET
- POST
type: string
urlPath:
description: URL is the JSON web
service URL. The typical format
is `https://{service}.{namespace}:{port}/{path}`.
type: string
required:
- requestType
- urlPath
type: object
urlPath:
description: URLPath is the URL path
to be used in the HTTP GET request
to the Kubernetes API server (e.g.
"/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format
used by the `kubectl get --raw` command.
type: string
type: object
configMap:
description: ConfigMap is the ConfigMap
reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
imageRegistry:
description: ImageRegistry defines requests
to an OCI/Docker V2 registry to fetch
image details.
properties:
jmesPath:
description: JMESPath is an optional
JSON Match Expression that can be
used to transform the ImageData struct
returned as a result of processing
the image reference.
type: string
reference:
description: 'Reference is image reference
to a container image in the registry.
Example: ghcr.io/kyverno/kyverno:latest'
type: string
required:
- reference
type: object
name:
description: Name is the variable name.
type: string
variable:
description: Variable defines an arbitrary
JMESPath context variable that can be
defined inline.
properties:
default:
description: Default is an optional
arbitrary JSON object that the variable
may take if the JMESPath expression
evaluates to nil
x-kubernetes-preserve-unknown-fields: true
jmesPath:
description: JMESPath is an optional
JMESPath Expression that can be used
to transform the variable.
type: string
value:
description: Value is any arbitrary
JSON object representable in YAML
or JSON form.
x-kubernetes-preserve-unknown-fields: true
type: object
type: object
type: array
kind:
description: Kind specifies resource kind.
type: string
@ -5491,6 +5782,16 @@ spec:
namespace:
description: Namespace specifies resource namespace.
type: string
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. A direct list
of conditions (without `any` or `all` statements
is supported for backwards compatibility but
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
@ -8539,10 +8840,147 @@ spec:
description: Targets defines the target resources to be
mutated.
items:
description: TargetResourceSpec defines targets for mutating
existing resources.
properties:
apiVersion:
description: APIVersion specifies resource apiVersion.
type: string
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall is an HTTP request to the
Kubernetes API server, or other JSON web service.
The data returned is stored in the context
with the name for the context entry.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the server.
For example a JMESPath of "items | length(@)"
applied to the API server response for
the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
service:
description: Service is an API call to a
JSON web service
properties:
caBundle:
description: CABundle is a PEM encoded
CA bundle which will be used to validate
the server certificate.
type: string
data:
description: Data specifies the POST
data sent to the server.
items:
description: RequestData contains
the HTTP POST data
properties:
key:
description: Key is a unique identifier
for the data value
type: string
value:
description: Value is the data
value
x-kubernetes-preserve-unknown-fields: true
required:
- key
- value
type: object
type: array
requestType:
default: GET
description: Method is the HTTP request
type (GET or POST).
enum:
- GET
- POST
type: string
urlPath:
description: URL is the JSON web service
URL. The typical format is `https://{service}.{namespace}:{port}/{path}`.
type: string
required:
- requestType
- urlPath
type: object
urlPath:
description: URLPath is the URL path to
be used in the HTTP GET request to the
Kubernetes API server (e.g. "/api/v1/namespaces"
or "/apis/apps/v1/deployments"). The
format required is the same format used
by the `kubectl get --raw` command.
type: string
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
imageRegistry:
description: ImageRegistry defines requests
to an OCI/Docker V2 registry to fetch image
details.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the ImageData struct returned as a result
of processing the image reference.
type: string
reference:
description: 'Reference is image reference
to a container image in the registry.
Example: ghcr.io/kyverno/kyverno:latest'
type: string
required:
- reference
type: object
name:
description: Name is the variable name.
type: string
variable:
description: Variable defines an arbitrary JMESPath
context variable that can be defined inline.
properties:
default:
description: Default is an optional arbitrary
JSON object that the variable may take
if the JMESPath expression evaluates to
nil
x-kubernetes-preserve-unknown-fields: true
jmesPath:
description: JMESPath is an optional JMESPath
Expression that can be used to transform
the variable.
type: string
value:
description: Value is any arbitrary JSON
object representable in YAML or JSON form.
x-kubernetes-preserve-unknown-fields: true
type: object
type: object
type: array
kind:
description: Kind specifies resource kind.
type: string
@ -8552,6 +8990,15 @@ spec:
namespace:
description: Namespace specifies resource namespace.
type: string
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. A direct list
of conditions (without `any` or `all` statements
is supported for backwards compatibility but will
be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
@ -12083,10 +12530,155 @@ spec:
description: Targets defines the target resources to
be mutated.
items:
description: TargetResourceSpec defines targets for
mutating existing resources.
properties:
apiVersion:
description: APIVersion specifies resource apiVersion.
type: string
context:
description: Context defines variables and data
sources that can be used during rule execution.
items:
description: ContextEntry adds variables and
data sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall is an HTTP request
to the Kubernetes API server, or other
JSON web service. The data returned is
stored in the context with the name for
the context entry.
properties:
jmesPath:
description: JMESPath is an optional
JSON Match Expression that can be
used to transform the JSON response
returned from the server. For example
a JMESPath of "items | length(@)"
applied to the API server response
for the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
service:
description: Service is an API call
to a JSON web service
properties:
caBundle:
description: CABundle is a PEM encoded
CA bundle which will be used to
validate the server certificate.
type: string
data:
description: Data specifies the
POST data sent to the server.
items:
description: RequestData contains
the HTTP POST data
properties:
key:
description: Key is a unique
identifier for the data
value
type: string
value:
description: Value is the
data value
x-kubernetes-preserve-unknown-fields: true
required:
- key
- value
type: object
type: array
requestType:
default: GET
description: Method is the HTTP
request type (GET or POST).
enum:
- GET
- POST
type: string
urlPath:
description: URL is the JSON web
service URL. The typical format
is `https://{service}.{namespace}:{port}/{path}`.
type: string
required:
- requestType
- urlPath
type: object
urlPath:
description: URLPath is the URL path
to be used in the HTTP GET request
to the Kubernetes API server (e.g.
"/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format
used by the `kubectl get --raw` command.
type: string
type: object
configMap:
description: ConfigMap is the ConfigMap
reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
imageRegistry:
description: ImageRegistry defines requests
to an OCI/Docker V2 registry to fetch
image details.
properties:
jmesPath:
description: JMESPath is an optional
JSON Match Expression that can be
used to transform the ImageData struct
returned as a result of processing
the image reference.
type: string
reference:
description: 'Reference is image reference
to a container image in the registry.
Example: ghcr.io/kyverno/kyverno:latest'
type: string
required:
- reference
type: object
name:
description: Name is the variable name.
type: string
variable:
description: Variable defines an arbitrary
JMESPath context variable that can be
defined inline.
properties:
default:
description: Default is an optional
arbitrary JSON object that the variable
may take if the JMESPath expression
evaluates to nil
x-kubernetes-preserve-unknown-fields: true
jmesPath:
description: JMESPath is an optional
JMESPath Expression that can be used
to transform the variable.
type: string
value:
description: Value is any arbitrary
JSON object representable in YAML
or JSON form.
x-kubernetes-preserve-unknown-fields: true
type: object
type: object
type: array
kind:
description: Kind specifies resource kind.
type: string
@ -12096,6 +12688,16 @@ spec:
namespace:
description: Namespace specifies resource namespace.
type: string
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. A direct list
of conditions (without `any` or `all` statements
is supported for backwards compatibility but
will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/preconditions/'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object

File diff suppressed because it is too large Load diff

View file

@ -1153,6 +1153,7 @@ string
<a href="#kyverno.io/v1.ForEachMutation">ForEachMutation</a>,
<a href="#kyverno.io/v1.ForEachValidation">ForEachValidation</a>,
<a href="#kyverno.io/v1.Rule">Rule</a>,
<a href="#kyverno.io/v1.TargetResourceSpec">TargetResourceSpec</a>,
<a href="#kyverno.io/v2beta1.Rule">Rule</a>)
</p>
<p>
@ -2299,8 +2300,8 @@ Please specify under &ldquo;any&rdquo; or &ldquo;all&rdquo; instead.</p>
<td>
<code>targets</code><br/>
<em>
<a href="#kyverno.io/v1.ResourceSpec">
[]ResourceSpec
<a href="#kyverno.io/v1.TargetResourceSpec">
[]TargetResourceSpec
</a>
</em>
</td>
@ -2819,7 +2820,7 @@ ResourceDescription
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Generation">Generation</a>,
<a href="#kyverno.io/v1.Mutation">Mutation</a>,
<a href="#kyverno.io/v1.TargetResourceSpec">TargetResourceSpec</a>,
<a href="#kyverno.io/v1beta1.UpdateRequestSpec">UpdateRequestSpec</a>,
<a href="#kyverno.io/v1beta1.UpdateRequestStatus">UpdateRequestStatus</a>)
</p>
@ -3495,6 +3496,71 @@ Rekor (<a href="https://rekor.sigstore.dev">https://rekor.sigstore.dev</a>) is u
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.TargetResourceSpec">TargetResourceSpec
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Mutation">Mutation</a>)
</p>
<p>
<p>TargetResourceSpec defines targets for mutating existing resources.</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>ResourceSpec</code><br/>
<em>
<a href="#kyverno.io/v1.ResourceSpec">
ResourceSpec
</a>
</em>
</td>
<td>
<p>ResourceSpec contains the target resources to load when mutating existing resources.</p>
</td>
</tr>
<tr>
<td>
<code>context</code><br/>
<em>
<a href="#kyverno.io/v1.ContextEntry">
[]ContextEntry
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Context defines variables and data sources that can be used during rule execution.</p>
</td>
</tr>
<tr>
<td>
<code>preconditions</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#json-v1-apiextensions">
Kubernetes apiextensions/v1.JSON
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Preconditions are used to determine if a policy rule should be applied by evaluating a
set of conditions. The declaration can contain nested <code>any</code> or <code>all</code> statements. A direct list
of conditions (without <code>any</code> or <code>all</code> statements is supported for backwards compatibility but
will be deprecated in the next major release.
See: <a href="https://kyverno.io/docs/writing-policies/preconditions/">https://kyverno.io/docs/writing-policies/preconditions/</a></p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.UserInfo">UserInfo
</h3>
<p>

View file

@ -6,14 +6,13 @@ import (
"time"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/handlers/mutation"
"github.com/kyverno/kyverno/pkg/engine/handlers/validation"
"github.com/kyverno/kyverno/pkg/engine/internal"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/logging"
@ -24,19 +23,15 @@ import (
)
type engine struct {
configuration config.Configuration
client dclient.Interface
rclient registryclient.Client
engineContextLoaderFactory engineapi.EngineContextLoaderFactory
exceptionSelector engineapi.PolicyExceptionSelector
validateResourceHandler handlers.Handler
validateManifestHandler handlers.Handler
validatePssHandler handlers.Handler
validateImageHandler handlers.Handler
mutateResourceHandler handlers.Handler
mutateExistingHandler handlers.Handler
configuration config.Configuration
client dclient.Interface
rclient registryclient.Client
contextLoader engineapi.ContextLoaderFactory
exceptionSelector engineapi.PolicyExceptionSelector
}
type handlerFactory = func() (handlers.Handler, error)
func NewEngine(
configuration config.Configuration,
client dclient.Interface,
@ -44,30 +39,12 @@ func NewEngine(
contextLoader engineapi.ContextLoaderFactory,
exceptionSelector engineapi.PolicyExceptionSelector,
) engineapi.Engine {
engineContextLoaderFactory := func(policy kyvernov1.PolicyInterface, rule kyvernov1.Rule) engineapi.EngineContextLoader {
loader := contextLoader(policy, rule)
return func(ctx context.Context, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface) error {
return loader.Load(
ctx,
client,
rclient,
contextEntries,
jsonContext,
)
}
}
return &engine{
configuration: configuration,
client: client,
rclient: rclient,
engineContextLoaderFactory: engineContextLoaderFactory,
exceptionSelector: exceptionSelector,
validateResourceHandler: validation.NewValidateResourceHandler(),
validateManifestHandler: validation.NewValidateManifestHandler(client),
validatePssHandler: validation.NewValidatePssHandler(),
validateImageHandler: validation.NewValidateImageHandler(configuration),
mutateResourceHandler: mutation.NewMutateResourceHandler(),
mutateExistingHandler: mutation.NewMutateExistingHandler(client),
configuration: configuration,
client: client,
rclient: rclient,
contextLoader: contextLoader,
exceptionSelector: exceptionSelector,
}
}
@ -143,7 +120,16 @@ func (e *engine) ContextLoader(
policy kyvernov1.PolicyInterface,
rule kyvernov1.Rule,
) engineapi.EngineContextLoader {
return e.engineContextLoaderFactory(policy, rule)
loader := e.contextLoader(policy, rule)
return func(ctx context.Context, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface) error {
return loader.Load(
ctx,
e.client,
e.rclient,
contextEntries,
jsonContext,
)
}
}
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
@ -188,7 +174,7 @@ func matches(
func (e *engine) invokeRuleHandler(
ctx context.Context,
logger logr.Logger,
handler handlers.Handler,
handlerFactory handlerFactory,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
@ -204,12 +190,37 @@ func (e *engine) invokeRuleHandler(
logger.V(4).Info("rule not matched", "reason", err.Error())
return resource, nil
}
// check if there's an exception
if ruleResp := e.hasPolicyExceptions(logger, ruleType, policyContext, rule); ruleResp != nil {
return resource, handlers.RuleResponses(ruleResp)
if handlerFactory == nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, ruleType, "failed to instantiate handler", nil))
} else if handler, err := handlerFactory(); err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, ruleType, "failed to instantiate handler", err))
} else if handler != nil {
// check if there's an exception
if ruleResp := e.hasPolicyExceptions(logger, ruleType, policyContext, rule); ruleResp != nil {
return resource, handlers.RuleResponses(ruleResp)
}
// load rule context
contextLoader := e.ContextLoader(policyContext.Policy(), rule)
if err := contextLoader(ctx, rule.Context, policyContext.JSONContext()); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
logger.V(3).Info("failed to load context", "reason", err.Error())
} else {
logger.Error(err, "failed to load context")
}
return resource, handlers.RuleResponses(internal.RuleError(rule, ruleType, "failed to load context", err))
}
// check preconditions
preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), rule.GetAnyAllConditions())
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, ruleType, "failed to evaluate preconditions", err))
}
if !preconditionsPassed {
return resource, handlers.RuleResponses(internal.RuleSkip(rule, ruleType, "preconditions not met"))
}
// process handler
return handler.Process(ctx, logger, policyContext, resource, rule, contextLoader)
}
// process handler
return handler.Process(ctx, logger, policyContext, resource, rule, e.ContextLoader(policyContext.Policy(), rule))
return resource, nil
},
)
}

View file

@ -14,36 +14,14 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func mutateResource(
ctx context.Context,
contextLoader engineapi.EngineContextLoader,
rule kyvernov1.Rule,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
logger logr.Logger,
) *mutate.Response {
if err := contextLoader(ctx, rule.Context, policyContext.JSONContext()); err != nil {
logger.Error(err, "failed to load context")
return mutate.NewErrorResponse("failed to load context", err)
}
preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), rule.GetAnyAllConditions())
if err != nil {
return mutate.NewErrorResponse("failed to evaluate preconditions", err)
}
if !preconditionsPassed {
return mutate.NewResponse(engineapi.RuleStatusSkip, resource, nil, "preconditions not met")
}
return mutate.Mutate(&rule, policyContext.JSONContext(), resource, logger)
}
type forEachMutator struct {
rule *kyvernov1.Rule
logger logr.Logger
rule kyvernov1.Rule
policyContext engineapi.PolicyContext
foreach []kyvernov1.ForEachMutation
resource resourceInfo
nesting int
contextLoader engineapi.EngineContextLoader
log logr.Logger
}
func (f *forEachMutator) mutateForEach(ctx context.Context) *mutate.Response {
@ -51,20 +29,6 @@ func (f *forEachMutator) mutateForEach(ctx context.Context) *mutate.Response {
allPatches := make([][]byte, 0)
for _, foreach := range f.foreach {
if err := f.contextLoader(ctx, f.rule.Context, f.policyContext.JSONContext()); err != nil {
f.log.Error(err, "failed to load context")
return mutate.NewErrorResponse("failed to load context", err)
}
preconditionsPassed, err := internal.CheckPreconditions(f.log, f.policyContext.JSONContext(), f.rule.GetAnyAllConditions())
if err != nil {
return mutate.NewErrorResponse("failed to evaluate preconditions", err)
}
if !preconditionsPassed {
return mutate.NewResponse(engineapi.RuleStatusSkip, f.resource.unstructured, nil, "preconditions not met")
}
elements, err := engineutils.EvaluateList(foreach.List, f.policyContext.JSONContext())
if err != nil {
msg := fmt.Sprintf("failed to evaluate list %s: %v", foreach.List, err)
@ -82,9 +46,9 @@ func (f *forEachMutator) mutateForEach(ctx context.Context) *mutate.Response {
f.resource.unstructured = mutateResp.PatchedResource
allPatches = append(allPatches, mutateResp.Patches...)
}
f.log.Info("mutateResp.PatchedResource", "resource", mutateResp.PatchedResource)
f.logger.Info("mutateResp.PatchedResource", "resource", mutateResp.PatchedResource)
if err := f.policyContext.JSONContext().AddResource(mutateResp.PatchedResource.Object); err != nil {
f.log.Error(err, "failed to update resource in context")
f.logger.Error(err, "failed to update resource in context")
}
}
}
@ -124,13 +88,13 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
return mutate.NewErrorResponse(fmt.Sprintf("failed to load to mutate.foreach[%d].context", index), err)
}
preconditionsPassed, err := internal.CheckPreconditions(f.log, policyContext.JSONContext(), foreach.AnyAllConditions)
preconditionsPassed, err := internal.CheckPreconditions(f.logger, policyContext.JSONContext(), foreach.AnyAllConditions)
if err != nil {
return mutate.NewErrorResponse(fmt.Sprintf("failed to evaluate mutate.foreach[%d].preconditions", index), err)
}
if !preconditionsPassed {
f.log.Info("mutate.foreach.preconditions not met", "elementIndex", index)
f.logger.Info("mutate.foreach.preconditions not met", "elementIndex", index)
continue
}
@ -145,7 +109,7 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
rule: f.rule,
policyContext: f.policyContext,
resource: patchedResource,
log: f.log,
logger: f.logger,
foreach: nestedForEach,
nesting: f.nesting + 1,
contextLoader: f.contextLoader,
@ -153,7 +117,7 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
mutateResp = m.mutateForEach(ctx)
} else {
mutateResp = mutate.ForEach(f.rule.Name, foreach, policyContext, patchedResource.unstructured, element, f.log)
mutateResp = mutate.ForEach(f.rule.Name, foreach, policyContext, patchedResource.unstructured, element, f.logger)
}
if mutateResp.Status == engineapi.RuleStatusFail || mutateResp.Status == engineapi.RuleStatusError {

View file

@ -12,6 +12,7 @@ import (
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"github.com/kyverno/kyverno/pkg/utils/wildcard"
"go.uber.org/multierr"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -24,10 +25,17 @@ type resourceInfo struct {
parentResourceGVR metav1.GroupVersionResource
}
func loadTargets(client dclient.Interface, targets []kyvernov1.ResourceSpec, ctx engineapi.PolicyContext, logger logr.Logger) ([]resourceInfo, error) {
var targetObjects []resourceInfo
type target struct {
resourceInfo
context []kyvernov1.ContextEntry
preconditions apiextensions.JSON
}
func loadTargets(client dclient.Interface, targets []kyvernov1.TargetResourceSpec, ctx engineapi.PolicyContext, logger logr.Logger) ([]target, error) {
var targetObjects []target
var errors []error
for i := range targets {
preconditions := targets[i].GetAnyAllConditions()
spec, err := resolveSpec(i, targets[i], ctx, logger)
if err != nil {
errors = append(errors, err)
@ -38,12 +46,18 @@ func loadTargets(client dclient.Interface, targets []kyvernov1.ResourceSpec, ctx
errors = append(errors, err)
continue
}
targetObjects = append(targetObjects, objs...)
for _, obj := range objs {
targetObjects = append(targetObjects, target{
resourceInfo: obj,
context: targets[i].Context,
preconditions: preconditions,
})
}
}
return targetObjects, multierr.Combine(errors...)
}
func resolveSpec(i int, target kyvernov1.ResourceSpec, ctx engineapi.PolicyContext, logger logr.Logger) (kyvernov1.ResourceSpec, error) {
func resolveSpec(i int, target kyvernov1.TargetResourceSpec, ctx engineapi.PolicyContext, logger logr.Logger) (kyvernov1.ResourceSpec, error) {
kind, err := variables.SubstituteAll(logger, ctx.JSONContext(), target.Kind)
if err != nil {
return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Kind %s: %v", i, target.Kind, err)

View file

@ -19,10 +19,10 @@ type mutateExistingHandler struct {
func NewMutateExistingHandler(
client dclient.Interface,
) handlers.Handler {
) (handlers.Handler, error) {
return mutateExistingHandler{
client: client,
}
}, nil
}
func (h mutateExistingHandler) Process(
@ -35,42 +35,57 @@ func (h mutateExistingHandler) Process(
) (unstructured.Unstructured, []engineapi.RuleResponse) {
var responses []engineapi.RuleResponse
logger.V(3).Info("processing mutate rule")
var patchedResources []resourceInfo
targets, err := loadTargets(h.client, rule.Mutation.Targets, policyContext, logger)
if err != nil {
rr := internal.RuleError(rule, engineapi.Mutation, "", err)
responses = append(responses, *rr)
} else {
patchedResources = append(patchedResources, targets...)
}
for _, patchedResource := range patchedResources {
if patchedResource.unstructured.Object == nil {
for _, target := range targets {
if target.unstructured.Object == nil {
continue
}
policyContext := policyContext.Copy()
if err := policyContext.JSONContext().AddTargetResource(patchedResource.unstructured.Object); err != nil {
if err := policyContext.JSONContext().AddTargetResource(target.unstructured.Object); err != nil {
logger.Error(err, "failed to add target resource to the context")
continue
}
// load target specific context
if err := contextLoader(ctx, target.context, policyContext.JSONContext()); err != nil {
rr := internal.RuleError(rule, engineapi.Mutation, "failed to load context", err)
responses = append(responses, *rr)
continue
}
// load target specific preconditions
preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), target.preconditions)
if err != nil {
rr := internal.RuleError(rule, engineapi.Mutation, "failed to evaluate preconditions", err)
responses = append(responses, *rr)
continue
}
if !preconditionsPassed {
rr := internal.RuleSkip(rule, engineapi.Mutation, "preconditions not met")
responses = append(responses, *rr)
continue
}
// logger.V(4).Info("apply rule to resource", "resource namespace", patchedResource.unstructured.GetNamespace(), "resource name", patchedResource.unstructured.GetName())
var mutateResp *mutate.Response
if rule.Mutation.ForEachMutation != nil {
m := &forEachMutator{
rule: &rule,
rule: rule,
foreach: rule.Mutation.ForEachMutation,
policyContext: policyContext,
resource: patchedResource,
log: logger,
resource: target.resourceInfo,
logger: logger,
contextLoader: contextLoader,
nesting: 0,
}
mutateResp = m.mutateForEach(ctx)
} else {
mutateResp = mutateResource(ctx, contextLoader, rule, policyContext, patchedResource.unstructured, logger)
mutateResp = mutate.Mutate(&rule, policyContext.JSONContext(), target.unstructured, logger)
}
if ruleResponse := buildRuleResponse(&rule, mutateResp, patchedResource); ruleResponse != nil {
if ruleResponse := buildRuleResponse(&rule, mutateResp, target.resourceInfo); ruleResponse != nil {
responses = append(responses, *ruleResponse)
}
}

View file

@ -4,7 +4,6 @@ import (
"context"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -14,6 +13,7 @@ import (
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/registryclient"
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -21,18 +21,33 @@ type mutateImageHandler struct {
configuration config.Configuration
rclient registryclient.Client
ivm *engineapi.ImageVerificationMetadata
images []apiutils.ImageInfo
}
func NewMutateImageHandler(
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
configuration config.Configuration,
rclient registryclient.Client,
ivm *engineapi.ImageVerificationMetadata,
) handlers.Handler {
) (handlers.Handler, error) {
if len(rule.VerifyImages) == 0 {
return nil, nil
}
ruleImages, _, err := engineutils.ExtractMatchingImages(resource, policyContext.JSONContext(), rule, configuration)
if err != nil {
return nil, err
}
if len(ruleImages) == 0 {
return nil, nil
}
return mutateImageHandler{
configuration: configuration,
rclient: rclient,
ivm: ivm,
}
images: ruleImages,
}, nil
}
func (h mutateImageHandler) Process(
@ -43,37 +58,7 @@ func (h mutateImageHandler) Process(
rule kyvernov1.Rule,
contextLoader engineapi.EngineContextLoader,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
if engineutils.IsDeleteRequest(policyContext) {
return resource, nil
}
if len(rule.VerifyImages) == 0 {
return resource, nil
}
ruleImages, _, err := engineutils.ExtractMatchingImages(resource, policyContext.JSONContext(), rule, h.configuration)
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.ImageVerify, "failed to extract images", err))
}
if len(ruleImages) == 0 {
return resource, nil
}
jsonContext := policyContext.JSONContext()
// load context
if err := contextLoader(ctx, rule.Context, jsonContext); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
logger.V(3).Info("failed to load context", "reason", err.Error())
} else {
logger.Error(err, "failed to load context")
}
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.ImageVerify, "failed to load context", err))
}
// check preconditions
preconditionsPassed, err := internal.CheckPreconditions(logger, jsonContext, rule.GetAnyAllConditions())
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.ImageVerify, "failed to evaluate preconditions", err))
}
if !preconditionsPassed {
return resource, handlers.RuleResponses(internal.RuleSkip(rule, engineapi.ImageVerify, "preconditions not met"))
}
ruleCopy, err := substituteVariables(rule, jsonContext, logger)
if err != nil {
return resource, handlers.RuleResponses(
@ -83,7 +68,7 @@ func (h mutateImageHandler) Process(
iv := internal.NewImageVerifier(logger, h.rclient, policyContext, *ruleCopy, h.ivm)
var engineResponses []*engineapi.RuleResponse
for _, imageVerify := range ruleCopy.VerifyImages {
engineResponses = append(engineResponses, iv.Verify(ctx, imageVerify, ruleImages, h.configuration)...)
engineResponses = append(engineResponses, iv.Verify(ctx, imageVerify, h.images, h.configuration)...)
}
return resource, handlers.RuleResponses(engineResponses...)
}

View file

@ -14,8 +14,8 @@ import (
type mutateResourceHandler struct{}
func NewMutateResourceHandler() handlers.Handler {
return mutateResourceHandler{}
func NewMutateResourceHandler() (handlers.Handler, error) {
return mutateResourceHandler{}, nil
}
func (h mutateResourceHandler) Process(
@ -41,17 +41,17 @@ func (h mutateResourceHandler) Process(
var mutateResp *mutate.Response
if rule.Mutation.ForEachMutation != nil {
m := &forEachMutator{
rule: &rule,
rule: rule,
foreach: rule.Mutation.ForEachMutation,
policyContext: policyContext,
resource: resourceInfo,
log: logger,
logger: logger,
contextLoader: contextLoader,
nesting: 0,
}
mutateResp = m.mutateForEach(ctx)
} else {
mutateResp = mutateResource(ctx, contextLoader, rule, policyContext, resourceInfo.unstructured, logger)
mutateResp = mutate.Mutate(&rule, policyContext.JSONContext(), resource, logger)
}
if mutateResp == nil {
return resource, nil

View file

@ -5,7 +5,6 @@ import (
"fmt"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -16,16 +15,25 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type validateImageHandler struct {
configuration config.Configuration
}
type validateImageHandler struct{}
func NewValidateImageHandler(
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
configuration config.Configuration,
) handlers.Handler {
return validateImageHandler{
configuration: configuration,
) (handlers.Handler, error) {
if engineutils.IsDeleteRequest(policyContext) {
return nil, nil
}
ruleImages, _, err := engineutils.ExtractMatchingImages(resource, policyContext.JSONContext(), rule, configuration)
if err != nil {
return nil, err
}
if len(ruleImages) == 0 {
return nil, nil
}
return validateImageHandler{}, nil
}
func (h validateImageHandler) Process(
@ -34,41 +42,8 @@ func (h validateImageHandler) Process(
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
contextLoader engineapi.EngineContextLoader,
_ engineapi.EngineContextLoader,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
if engineutils.IsDeleteRequest(policyContext) {
return resource, nil
}
if len(rule.VerifyImages) == 0 {
return resource, nil
}
ruleImages, _, err := engineutils.ExtractMatchingImages(resource, policyContext.JSONContext(), rule, h.configuration)
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "failed to extract images", err))
}
if len(ruleImages) == 0 {
return resource, nil
}
// load context
if err := contextLoader(ctx, rule.Context, policyContext.JSONContext()); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
logger.V(3).Info("failed to load context", "reason", err.Error())
} else {
logger.Error(err, "failed to load context")
}
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "failed to load context", err))
}
// check preconditions
preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), rule.GetAnyAllConditions())
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "failed to evaluate preconditions", err))
}
if !preconditionsPassed {
if policyContext.Policy().GetSpec().ValidationFailureAction.Audit() {
return resource, nil
}
return resource, handlers.RuleResponses(internal.RuleSkip(rule, engineapi.Validation, "preconditions not met"))
}
for _, v := range rule.VerifyImages {
imageVerify := v.Convert()
for _, infoMap := range policyContext.JSONContext().ImageInfo() {

View file

@ -14,7 +14,6 @@ import (
"github.com/ghodss/yaml"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/auth"
"github.com/kyverno/kyverno/pkg/clients/dclient"
@ -39,10 +38,16 @@ type validateManifestHandler struct {
client dclient.Interface
}
func NewValidateManifestHandler(client dclient.Interface) handlers.Handler {
func NewValidateManifestHandler(
policyContext engineapi.PolicyContext,
client dclient.Interface,
) (handlers.Handler, error) {
if engineutils.IsDeleteRequest(policyContext) {
return nil, nil
}
return validateManifestHandler{
client: client,
}
}, nil
}
func (h validateManifestHandler) Process(
@ -51,28 +56,8 @@ func (h validateManifestHandler) Process(
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
contextLoader engineapi.EngineContextLoader,
_ engineapi.EngineContextLoader,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
if engineutils.IsDeleteRequest(policyContext) {
return resource, nil
}
// load context
if err := contextLoader(ctx, rule.Context, policyContext.JSONContext()); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
logger.V(3).Info("failed to load context", "reason", err.Error())
} else {
logger.Error(err, "failed to load context")
}
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "failed to load context", err))
}
// check preconditions
preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), rule.GetAnyAllConditions())
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "failed to evaluate preconditions", err))
}
if !preconditionsPassed {
return resource, handlers.RuleResponses(internal.RuleSkip(rule, engineapi.Validation, "preconditions not met"))
}
// verify manifest
verified, reason, err := h.verifyManifest(ctx, logger, policyContext, *rule.Validation.Manifests)
if err != nil {

View file

@ -6,7 +6,6 @@ import (
"fmt"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
@ -21,8 +20,8 @@ import (
type validatePssHandler struct{}
func NewValidatePssHandler() handlers.Handler {
return validatePssHandler{}
func NewValidatePssHandler() (handlers.Handler, error) {
return validatePssHandler{}, nil
}
func (h validatePssHandler) Process(
@ -31,25 +30,8 @@ func (h validatePssHandler) Process(
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
contextLoader engineapi.EngineContextLoader,
_ engineapi.EngineContextLoader,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
// load context
if err := contextLoader(ctx, rule.Context, policyContext.JSONContext()); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
logger.V(3).Info("failed to load context", "reason", err.Error())
} else {
logger.Error(err, "failed to load context")
}
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "failed to load context", err))
}
// check preconditions
preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), rule.GetAnyAllConditions())
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "failed to evaluate preconditions", err))
}
if !preconditionsPassed {
return resource, handlers.RuleResponses(internal.RuleSkip(rule, engineapi.Validation, "preconditions not met"))
}
// Marshal pod metadata and spec
podSecurity := rule.Validation.PodSecurity
podSpec, metadata, err := getSpec(resource)

View file

@ -23,8 +23,8 @@ import (
type validateResourceHandler struct{}
func NewValidateResourceHandler() handlers.Handler {
return validateResourceHandler{}
func NewValidateResourceHandler() (handlers.Handler, error) {
return validateResourceHandler{}, nil
}
func (h validateResourceHandler) Process(
@ -55,16 +55,14 @@ type validator struct {
func newValidator(log logr.Logger, contextLoader engineapi.EngineContextLoader, ctx engineapi.PolicyContext, rule kyvernov1.Rule) *validator {
return &validator{
log: log,
rule: rule,
policyContext: ctx,
contextLoader: contextLoader,
contextEntries: rule.Context,
anyAllConditions: rule.GetAnyAllConditions(),
pattern: rule.Validation.GetPattern(),
anyPattern: rule.Validation.GetAnyPattern(),
deny: rule.Validation.Deny,
forEach: rule.Validation.ForEachValidation,
log: log,
rule: rule,
policyContext: ctx,
contextLoader: contextLoader,
pattern: rule.Validation.GetPattern(),
anyPattern: rule.Validation.GetAnyPattern(),
deny: rule.Validation.Deny,
forEach: rule.Validation.ForEachValidation,
}
}

View file

@ -8,6 +8,7 @@ import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/handlers/mutation"
"github.com/kyverno/kyverno/pkg/engine/internal"
)
@ -29,18 +30,23 @@ func (e *engine) verifyAndPatchImages(
for _, rule := range autogen.ComputeRules(policy) {
startTime := time.Now()
logger := internal.LoggerWithRule(logger, rule)
if !rule.HasVerifyImages() {
continue
handlerFactory := func() (handlers.Handler, error) {
if !rule.HasVerifyImages() {
return nil, nil
}
return mutation.NewMutateImageHandler(
policyContext,
matchedResource,
rule,
e.configuration,
e.rclient,
&ivm,
)
}
handler := mutation.NewMutateImageHandler(
e.configuration,
e.rclient,
&ivm,
)
resource, ruleResp := e.invokeRuleHandler(
ctx,
logger,
handler,
handlerFactory,
policyContext,
matchedResource,
rule,
@ -56,6 +62,6 @@ func (e *engine) verifyAndPatchImages(
break
}
}
// TODO: i doesn't make sense to not return the patched resource here
// TODO: it doesn't make sense to not return the patched resource here
return resp, ivm
}

View file

@ -9,6 +9,7 @@ import (
"github.com/kyverno/kyverno/pkg/autogen"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/handlers/mutation"
"github.com/kyverno/kyverno/pkg/engine/internal"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -30,18 +31,19 @@ func (e *engine) mutate(
for _, rule := range autogen.ComputeRules(policy) {
startTime := time.Now()
logger := internal.LoggerWithRule(logger, rule)
if !rule.HasMutate() {
continue
}
var handler handlers.Handler
handler = e.mutateResourceHandler
if !policyContext.AdmissionOperation() && rule.IsMutateExisting() {
handler = e.mutateExistingHandler
handlerFactory := func() (handlers.Handler, error) {
if !rule.HasMutate() {
return nil, nil
}
if !policyContext.AdmissionOperation() && rule.IsMutateExisting() {
return mutation.NewMutateExistingHandler(e.client)
}
return mutation.NewMutateResourceHandler()
}
resource, ruleResp := e.invokeRuleHandler(
ctx,
logger,
handler,
handlerFactory,
policyContext,
matchedResource,
rule,

View file

@ -9,6 +9,7 @@ import (
"github.com/kyverno/kyverno/pkg/autogen"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/handlers/validation"
"github.com/kyverno/kyverno/pkg/engine/internal"
)
@ -28,29 +29,39 @@ func (e *engine) validate(
for _, rule := range autogen.ComputeRules(policy) {
startTime := time.Now()
logger := internal.LoggerWithRule(logger, rule)
hasValidate := rule.HasValidate()
hasVerifyImageChecks := rule.HasVerifyImageChecks()
if !hasValidate && !hasVerifyImageChecks {
continue
}
var handler handlers.Handler
if hasValidate {
hasVerifyManifest := rule.HasVerifyManifests()
hasValidatePss := rule.HasValidatePodSecurity()
if hasVerifyManifest {
handler = e.validateManifestHandler
} else if hasValidatePss {
handler = e.validatePssHandler
} else {
handler = e.validateResourceHandler
handlerFactory := func() (handlers.Handler, error) {
hasValidate := rule.HasValidate()
hasVerifyImageChecks := rule.HasVerifyImageChecks()
if !hasValidate && !hasVerifyImageChecks {
return nil, nil
}
} else if hasVerifyImageChecks {
handler = e.validateImageHandler
if hasValidate {
hasVerifyManifest := rule.HasVerifyManifests()
hasValidatePss := rule.HasValidatePodSecurity()
if hasVerifyManifest {
return validation.NewValidateManifestHandler(
policyContext,
e.client,
)
} else if hasValidatePss {
return validation.NewValidatePssHandler()
} else {
return validation.NewValidateResourceHandler()
}
} else if hasVerifyImageChecks {
return validation.NewValidateImageHandler(
policyContext,
policyContext.NewResource(),
rule,
e.configuration,
)
}
return nil, nil
}
resource, ruleResp := e.invokeRuleHandler(
ctx,
logger,
handler,
handlerFactory,
policyContext,
matchedResource,
rule,

View file

@ -90,7 +90,7 @@ func (m *Mutate) hasPatchesJSON6902() bool {
return m.mutation.PatchesJSON6902 != ""
}
func (m *Mutate) validateAuth(ctx context.Context, targets []kyvernov1.ResourceSpec) error {
func (m *Mutate) validateAuth(ctx context.Context, targets []kyvernov1.TargetResourceSpec) error {
var errs []error
for _, target := range targets {
if !regex.IsVariable(target.Namespace) {

View file

@ -39,14 +39,15 @@ import (
"k8s.io/client-go/discovery"
)
var allowedVariables = regexp.MustCompile(`request\.|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images\.|image\.|target\.|([a-z_0-9]+\()[^{}]`)
var allowedVariablesBackground = regexp.MustCompile(`request\.|element|elementIndex|@|images\.|image\.|target\.|([a-z_0-9]+\()[^{}]`)
// wildCardAllowedVariables represents regex for the allowed fields in wildcards
var wildCardAllowedVariables = regexp.MustCompile(`\{\{\s*(request\.|serviceAccountName|serviceAccountNamespace)[^{}]*\}\}`)
var errOperationForbidden = errors.New("variables are forbidden in the path of a JSONPatch")
var (
allowedVariables = regexp.MustCompile(`request\.|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images\.|image\.|([a-z_0-9]+\()[^{}]`)
allowedVariablesBackground = regexp.MustCompile(`request\.|element|elementIndex|@|images\.|image\.|([a-z_0-9]+\()[^{}]`)
allowedVariablesInTarget = regexp.MustCompile(`request\.|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images\.|image\.|target\.|([a-z_0-9]+\()[^{}]`)
allowedVariablesBackgroundInTarget = regexp.MustCompile(`request\.|element|elementIndex|@|images\.|image\.|target\.|([a-z_0-9]+\()[^{}]`)
// wildCardAllowedVariables represents regex for the allowed fields in wildcards
wildCardAllowedVariables = regexp.MustCompile(`\{\{\s*(request\.|serviceAccountName|serviceAccountNamespace)[^{}]*\}\}`)
errOperationForbidden = errors.New("variables are forbidden in the path of a JSONPatch")
)
// validateJSONPatchPathForForwardSlash checks for forward slash
func validateJSONPatchPathForForwardSlash(patch string) error {
@ -441,9 +442,22 @@ func hasInvalidVariables(policy kyvernov1.PolicyInterface, background bool) erro
}
}
ctx := buildContext(ruleCopy, background)
if _, err := variables.SubstituteAllInRule(logging.GlobalLogger(), ctx, *ruleCopy); !variables.CheckNotFoundErr(err) {
return fmt.Errorf("variable substitution failed for rule %s: %s", ruleCopy.Name, err.Error())
// skip variable checks on mutate.targets, they will be validated separately
withoutTargets := ruleCopy.DeepCopy()
for i := range withoutTargets.Mutation.Targets {
withoutTargets.Mutation.Targets[i].RawAnyAllConditions = nil
}
ctx := buildContext(withoutTargets, background, false, nil)
if _, err := variables.SubstituteAllInRule(logging.GlobalLogger(), ctx, *withoutTargets); !variables.CheckNotFoundErr(err) {
return fmt.Errorf("variable substitution failed for rule %s: %s", withoutTargets.Name, err.Error())
}
// perform variable checks with mutate.targets
for _, target := range r.Mutation.Targets {
ctx := buildContext(ruleCopy, background, true, target.Context)
if _, err := variables.SubstituteAllInRule(logging.GlobalLogger(), ctx, *ruleCopy); !variables.CheckNotFoundErr(err) {
return fmt.Errorf("variable substitution failed for rule target %s: %s", ruleCopy.Name, err.Error())
}
}
}
@ -554,29 +568,34 @@ func imageRefHasVariables(verifyImages []kyvernov1.ImageVerification) error {
return nil
}
func buildContext(rule *kyvernov1.Rule, background bool) *enginecontext.MockContext {
re := getAllowedVariables(background)
func buildContext(rule *kyvernov1.Rule, background bool, target bool, targetContext []kyvernov1.ContextEntry) *enginecontext.MockContext {
re := getAllowedVariables(background, target)
ctx := enginecontext.NewMockContext(re)
addContextVariables(rule.Context, ctx)
for _, fe := range rule.Validation.ForEachValidation {
addContextVariables(fe.Context, ctx)
}
for _, fe := range rule.Mutation.ForEachMutation {
addContextVariables(fe.Context, ctx)
}
for _, fe := range rule.Mutation.Targets {
addContextVariables(fe.Context, ctx)
}
return ctx
}
func getAllowedVariables(background bool) *regexp.Regexp {
if background {
return allowedVariablesBackground
func getAllowedVariables(background bool, target bool) *regexp.Regexp {
if target {
if background {
return allowedVariablesBackgroundInTarget
}
return allowedVariablesInTarget
} else {
if background {
return allowedVariablesBackground
}
return allowedVariables
}
return allowedVariables
}
func addContextVariables(entries []kyvernov1.ContextEntry, ctx *enginecontext.MockContext) {

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- file: resources.yaml

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- file: trigger.yaml

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- resources-assert.yaml

View file

@ -0,0 +1,21 @@
## Description
This test creates one `ConfigMap` named `target`.
It then creates a `ClusterPolicy` with a mutate existing rule targeting the previously created `ConfigMap`.
The policy rule uses `context` on the trigger resource to create a variable containing the value of `data.content`.
The policy rule uses `context` on the target resource to create a variable containing the value of `data.content`.
The policy mutates target resource, setting `data.content` to the value of the trigger resource level variable and `data.targetContent` to the value of the target resource level variable.
Finally, the test creates the trigger config map.
## Expected Behavior
The target config map should contain:
```yaml
data:
content: trigger
targetContent: target
```

View file

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

View file

@ -0,0 +1,36 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: update-targets
spec:
background: false
rules:
- name: update-targets
match:
any:
- resources:
kinds:
- ConfigMap
context:
- name: triggerContent
variable:
jmesPath: request.object.data.content
preconditions:
all:
- key: "{{ request.object.metadata.name }}"
operator: Equals
value: trigger
mutate:
targets:
- apiVersion: v1
kind: ConfigMap
namespace: "{{ request.object.metadata.namespace }}"
name: target*
context:
- name: targetContent
variable:
jmesPath: target.data.content
patchStrategicMerge:
data:
content: "{{ triggerContent }}"
targetContent: "{{ targetContent }}"

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: target
data:
content: trigger
targetContent: target

View file

@ -0,0 +1,6 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: target
data:
content: target

View file

@ -0,0 +1,6 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: trigger
data:
content: trigger

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- file: resources.yaml

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- file: trigger.yaml

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- resources-assert.yaml

View file

@ -0,0 +1,18 @@
## Description
This test creates three `ConfigMap`s:
- one without labels
- one with label `foo: bar`
- one with label `foo: not_bar`
It then creates a `ClusterPolicy` with a mutate existing rule targeting the previously created `ConfigMap`s.
The policy rule uses preconditions on the trigger resource to match only `ConfigMap`s with the `trigger` name.
The policy rule also uses preconditions on target resources to match only `ConfigMap`s with he label `foo: bar`.
The policy mutates target resources passing preconditions by copying the `data.content` from the trigger `ConfigMap` to the target `ConfigMap`.
Finally, the test creates the trigger config map.
## Expected Behavior
Only the target config map with label `foo: bar` should have its content updated.

View file

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

View file

@ -0,0 +1,31 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: update-targets
spec:
background: false
rules:
- name: update-targets
match:
any:
- resources:
kinds:
- ConfigMap
preconditions:
all:
- key: "{{ request.object.metadata.name }}"
operator: Equals
value: trigger
mutate:
targets:
- apiVersion: v1
kind: ConfigMap
namespace: "{{ request.object.metadata.namespace }}"
preconditions:
all:
- key: "{{ target.metadata.labels.foo || '' }}"
operator: Equals
value: bar
patchStrategicMerge:
data:
content: "{{ request.object.data.content }}"

View file

@ -0,0 +1,20 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: target-1
data:
content: trigger
---
apiVersion: v1
kind: ConfigMap
metadata:
name: target-2
data:
content: abc
---
apiVersion: v1
kind: ConfigMap
metadata:
name: target-3
data:
content: abc

View file

@ -0,0 +1,24 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: target-1
labels:
foo: bar
data:
content: abc
---
apiVersion: v1
kind: ConfigMap
metadata:
name: target-2
labels:
foo: not_bar
data:
content: abc
---
apiVersion: v1
kind: ConfigMap
metadata:
name: target-3
data:
content: abc

View file

@ -0,0 +1,6 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: trigger
data:
content: trigger

View file

@ -0,0 +1,7 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
# - file: policy-1.yaml
# shouldFail: true
- file: policy-2.yaml
shouldFail: true

View file

@ -0,0 +1,8 @@
## Description
This test tries to create policies referencing `target` in the trigger preconditions or context of a mutate existing rule.
## Expected Behavior
Policies shoudl be rejected.
Referencing `target` is only allowed in the target section of a mutate existing rule.

View file

@ -0,0 +1,35 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: update-targets
spec:
background: false
rules:
- name: update-targets
match:
any:
- resources:
kinds:
- ConfigMap
context:
- name: triggerContent
variable:
jmesPath: request.object.data.content
- name: targetContent
variable:
jmesPath: target.data.content
preconditions:
all:
- key: "{{ request.object.metadata.name }}"
operator: Equals
value: trigger
mutate:
targets:
- apiVersion: v1
kind: ConfigMap
namespace: "{{ request.object.metadata.namespace }}"
name: target*
patchStrategicMerge:
data:
content: "{{ triggerContent }}"
targetContent: "{{ targetContent }}"

View file

@ -0,0 +1,39 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: update-targets
spec:
background: false
rules:
- name: update-targets
match:
any:
- resources:
kinds:
- ConfigMap
context:
- name: triggerContent
variable:
jmesPath: request.object.data.content
preconditions:
all:
- key: "{{ request.object.metadata.name }}"
operator: Equals
value: trigger
- key: "{{ target.data.content }}"
operator: Equals
value: target
mutate:
targets:
- apiVersion: v1
kind: ConfigMap
namespace: "{{ request.object.metadata.namespace }}"
name: target*
context:
- name: targetContent
variable:
jmesPath: target.data.content
patchStrategicMerge:
data:
content: "{{ triggerContent }}"
targetContent: "{{ targetContent }}"