package v1
import (
"strings"
"github.com/kyverno/kyverno/api/kyverno"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:path=clusterpolicies,scope="Cluster",shortName=cpol,categories=kyverno
// +kubebuilder:printcolumn:name="ADMISSION",type=boolean,JSONPath=".spec.admission"
// +kubebuilder:printcolumn:name="BACKGROUND",type=boolean,JSONPath=".spec.background"
// +kubebuilder:printcolumn:name="READY",type=string,JSONPath=`.status.conditions[?(@.type == "Ready")].status`
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="FAILURE POLICY",type=string,JSONPath=".spec.failurePolicy",priority=1
// +kubebuilder:printcolumn:name="VALIDATE",type=integer,JSONPath=`.status.rulecount.validate`,priority=1
// +kubebuilder:printcolumn:name="MUTATE",type=integer,JSONPath=`.status.rulecount.mutate`,priority=1
// +kubebuilder:printcolumn:name="GENERATE",type=integer,JSONPath=`.status.rulecount.generate`,priority=1
// +kubebuilder:printcolumn:name="VERIFY IMAGES",type=integer,JSONPath=`.status.rulecount.verifyimages`,priority=1
// +kubebuilder:printcolumn:name="MESSAGE",type=string,JSONPath=`.status.conditions[?(@.type == "Ready")].message`
// +kubebuilder:storageversion
// ClusterPolicy declares validation, mutation, and generation behaviors for matching resources.
type ClusterPolicy struct {
metav1.TypeMeta `json:",inline,omitempty"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec declares policy behaviors.
Spec Spec `json:"spec"`
// Status contains policy runtime data.
// +optional
Status PolicyStatus `json:"status,omitempty"`
}
// HasAutoGenAnnotation checks if a policy has auto-gen annotation
func (p *ClusterPolicy) HasAutoGenAnnotation() bool {
annotations := p.GetAnnotations()
val, ok := annotations[kyverno.AnnotationAutogenControllers]
if ok && strings.ToLower(val) != "none" {
return true
}
return false
}
// HasMutateOrValidateOrGenerate checks for rule types
func (p *ClusterPolicy) HasMutateOrValidateOrGenerate() bool {
for _, rule := range p.Spec.Rules {
if rule.HasMutate() || rule.HasValidate() || rule.HasGenerate() {
return true
}
}
return false
}
// HasMutate checks for mutate rule types
func (p *ClusterPolicy) HasMutate() bool {
return p.Spec.HasMutate()
}
// HasValidate checks for validate rule types
func (p *ClusterPolicy) HasValidate() bool {
return p.Spec.HasValidate()
}
// HasGenerate checks for generate rule types
func (p *ClusterPolicy) HasGenerate() bool {
return p.Spec.HasGenerate()
}
// HasVerifyImages checks for image verification rule types
func (p *ClusterPolicy) HasVerifyImages() bool {
return p.Spec.HasVerifyImages()
}
// AdmissionProcessingEnabled checks if admission is set to true
func (p *ClusterPolicy) AdmissionProcessingEnabled() bool {
return p.Spec.AdmissionProcessingEnabled()
}
// BackgroundProcessingEnabled checks if background is set to true
func (p *ClusterPolicy) BackgroundProcessingEnabled() bool {
return p.Spec.BackgroundProcessingEnabled()
}
// GetSpec returns the policy spec
func (p *ClusterPolicy) GetSpec() *Spec {
return &p.Spec
}
// GetStatus returns the policy status
func (p *ClusterPolicy) GetStatus() *PolicyStatus {
return &p.Status
}
// IsNamespaced indicates if the policy is namespace scoped
func (p *ClusterPolicy) IsNamespaced() bool {
return false
}
// IsReady indicates if the policy is ready to serve the admission request
func (p *ClusterPolicy) IsReady() bool {
return p.Status.IsReady()
}
// Validate implements programmatic validation
// namespaced means that the policy is bound to a namespace and therefore
// should not filter/generate cluster wide resources.
func (p *ClusterPolicy) Validate(clusterResources sets.Set[string]) (warnings []string, errs field.ErrorList) {
errs = append(errs, ValidateAutogenAnnotation(field.NewPath("metadata").Child("annotations"), p.GetAnnotations())...)
errs = append(errs, ValidatePolicyName(field.NewPath("name"), p.Name)...)
warning, errors := p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), p.GetNamespace(), clusterResources)
warnings = append(warnings, warning...)
errs = append(errs, errors...)
return warnings, errs
}
func (p *ClusterPolicy) GetKind() string {
return "ClusterPolicy"
}
func (p *ClusterPolicy) CreateDeepCopy() PolicyInterface {
return p.DeepCopy()
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ClusterPolicyList is a list of ClusterPolicy instances.
type ClusterPolicyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []ClusterPolicy `json:"items"`
}
package v1
import (
"encoding/json"
"fmt"
kjson "github.com/kyverno/kyverno-json/pkg/apis/policy/v1alpha1"
"github.com/kyverno/kyverno/api/kyverno"
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/pss/utils"
"github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest"
admissionv1 "k8s.io/api/admission/v1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/pod-security-admission/api"
)
// AssertionTree defines a kyverno-json assertion tree.
type AssertionTree = kjson.Any
// FailurePolicyType specifies a failure policy that defines how unrecognized errors from the admission endpoint are handled.
// +kubebuilder:validation:Enum=Ignore;Fail
type FailurePolicyType string
const (
// Ignore means that an error calling the webhook is ignored.
Ignore FailurePolicyType = "Ignore"
// Fail means that an error calling the webhook causes the admission to fail.
Fail FailurePolicyType = "Fail"
)
// ApplyRulesType controls whether processing stops after one rule is applied or all rules are applied.
// +kubebuilder:validation:Enum=All;One
type ApplyRulesType string
const (
// ApplyAll applies all rules in a policy that match.
ApplyAll ApplyRulesType = "All"
// ApplyOne applies only the first matching rule in the policy.
ApplyOne ApplyRulesType = "One"
)
// ForeachOrder specifies the iteration order in foreach statements.
// +kubebuilder:validation:Enum=Ascending;Descending
type ForeachOrder string
const (
// Ascending means iterating from first to last element.
Ascending ForeachOrder = "Ascending"
// Descending means iterating from last to first element.
Descending ForeachOrder = "Descending"
)
// WebhookConfiguration specifies the configuration for Kubernetes admission webhookconfiguration.
type WebhookConfiguration struct {
// FailurePolicy defines how unexpected policy errors and webhook response timeout errors are handled.
// Rules within the same policy share the same failure behavior.
// This field should not be accessed directly, instead `GetFailurePolicy()` should be used.
// Allowed values are Ignore or Fail. Defaults to Fail.
// +optional
FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty"`
// TimeoutSeconds specifies the maximum time in seconds allowed to apply this policy.
// After the configured time expires, the admission request may fail, or may simply ignore the policy results,
// based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds.
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
// MatchCondition configures admission webhook matchConditions.
// Requires Kubernetes 1.27 or later.
// +optional
MatchConditions []admissionregistrationv1.MatchCondition `json:"matchConditions,omitempty"`
}
// AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled.
// AnyConditions get fulfilled when at least one of its sub-conditions passes.
// AllConditions get fulfilled only when all of its sub-conditions pass.
type AnyAllConditions struct {
// AnyConditions enable variable-based conditional rule execution. This is useful for
// finer control of when an rule is applied. A condition can reference object data
// using JMESPath notation.
// Here, at least one of the conditions need to pass
// +optional
AnyConditions []Condition `json:"any,omitempty"`
// AllConditions enable variable-based conditional rule execution. This is useful for
// finer control of when an rule is applied. A condition can reference object data
// using JMESPath notation.
// Here, all of the conditions need to pass
// +optional
AllConditions []Condition `json:"all,omitempty"`
}
// ContextEntry adds variables and data sources to a rule Context. Either a
// ConfigMap reference or a APILookup must be provided.
// +kubebuilder:oneOf:={required:{configMap}}
// +kubebuilder:oneOf:={required:{apiCall}}
// +kubebuilder:oneOf:={required:{imageRegistry}}
// +kubebuilder:oneOf:={required:{variable}}
// +kubebuilder:oneOf:={required:{globalReference}}
type ContextEntry struct {
// Name is the variable name.
Name string `json:"name"`
// ConfigMap is the ConfigMap reference.
ConfigMap *ConfigMapReference `json:"configMap,omitempty"`
// 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.
APICall *ContextAPICall `json:"apiCall,omitempty"`
// ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image
// details.
ImageRegistry *ImageRegistry `json:"imageRegistry,omitempty"`
// Variable defines an arbitrary JMESPath context variable that can be defined inline.
Variable *Variable `json:"variable,omitempty"`
// GlobalContextEntryReference is a reference to a cached global context entry.
GlobalReference *GlobalContextEntryReference `json:"globalReference,omitempty"`
}
// Variable defines an arbitrary JMESPath context variable that can be defined inline.
type Variable struct {
// Value is any arbitrary JSON object representable in YAML or JSON form.
// +optional
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
Value *kyverno.Any `json:"value,omitempty"`
// JMESPath is an optional JMESPath Expression that can be used to
// transform the variable.
// +optional
JMESPath string `json:"jmesPath,omitempty"`
// Default is an optional arbitrary JSON object that the variable may take if the JMESPath
// expression evaluates to nil
// +optional
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
Default *kyverno.Any `json:"default,omitempty"`
}
func (v *Variable) GetValue() any {
return kyverno.FromAny(v.Value)
}
func (v *Variable) SetValue(in any) {
v.Value = kyverno.ToAny(in)
}
func (v *Variable) GetDefault() any {
return kyverno.FromAny(v.Default)
}
func (v *Variable) SetDefault(in any) {
v.Default = kyverno.ToAny(in)
}
// ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image
// details.
type ImageRegistry struct {
// Reference is image reference to a container image in the registry.
// Example: ghcr.io/kyverno/kyverno:latest
Reference string `json:"reference"`
// 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.
// +optional
JMESPath string `json:"jmesPath,omitempty"`
// ImageRegistryCredentials provides credentials that will be used for authentication with registry
// +kubebuilder:validation:Optional
ImageRegistryCredentials *ImageRegistryCredentials `json:"imageRegistryCredentials,omitempty"`
}
// ConfigMapReference refers to a ConfigMap
type ConfigMapReference struct {
// Name is the ConfigMap name.
Name string `json:"name"`
// Namespace is the ConfigMap namespace.
Namespace string `json:"namespace,omitempty"`
}
type APICall struct {
// URLPath is the URL path to be used in the HTTP GET or POST 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.
// See https://kyverno.io/docs/writing-policies/external-data-sources/#variables-from-kubernetes-api-server-calls
// for details.
// It's mutually exclusive with the Service field.
// +kubebuilder:validation:Optional
URLPath string `json:"urlPath"`
// Method is the HTTP request type (GET or POST). Defaults to GET.
// +kubebuilder:default=GET
Method Method `json:"method,omitempty"`
// The data object specifies the POST data sent to the server.
// Only applicable when the method field is set to POST.
// +kubebuilder:validation:Optional
Data []RequestData `json:"data,omitempty"`
// Service is an API call to a JSON web service.
// This is used for non-Kubernetes API server calls.
// It's mutually exclusive with the URLPath field.
// +kubebuilder:validation:Optional
Service *ServiceCall `json:"service,omitempty"`
}
type ContextAPICall struct {
APICall `json:",inline"`
// Default is an optional arbitrary JSON object that the context
// value is set to, if the apiCall returns error.
// +optional
Default *apiextv1.JSON `json:"default,omitempty"`
// 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.
// +kubebuilder:validation:Optional
JMESPath string `json:"jmesPath,omitempty"`
}
type GlobalContextEntryReference struct {
// Name of the global context entry
// +kubebuilder:validation:Required
Name string `json:"name"`
// 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.
// +kubebuilder:validation:Optional
JMESPath string `json:"jmesPath,omitempty"`
}
type ServiceCall struct {
// URL is the JSON web service URL. A typical form is
// `https://{service}.{namespace}:{port}/{path}`.
URL string `json:"url"`
// Headers is a list of optional HTTP headers to be included in the request.
Headers []HTTPHeader `json:"headers,omitempty"`
// CABundle is a PEM encoded CA bundle which will be used to validate
// the server certificate.
// +kubebuilder:validation:Optional
CABundle string `json:"caBundle"`
}
// Method is a HTTP request type.
// +kubebuilder:validation:Enum=GET;POST
type Method string
// RequestData contains the HTTP POST data
type RequestData struct {
// Key is a unique identifier for the data value
Key string `json:"key"`
// Value is the data value
Value *apiextv1.JSON `json:"value"`
}
type HTTPHeader struct {
// Key is the header key
Key string `json:"key"`
// Value is the header value
Value string `json:"value"`
}
// Condition defines variable-based conditional criteria for rule execution.
type Condition struct {
// Key is the context entry (using JMESPath) for conditional rule evaluation.
RawKey *apiextv1.JSON `json:"key,omitempty"`
// Operator is the conditional operation to perform. Valid operators are:
// Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals,
// GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan,
// DurationLessThanOrEquals, DurationLessThan
Operator ConditionOperator `json:"operator,omitempty"`
// Value is the conditional value, or set of values. The values can be fixed set
// or can be variables declared using JMESPath.
// +optional
RawValue *apiextv1.JSON `json:"value,omitempty"`
// Message is an optional display message
Message string `json:"message,omitempty"`
}
func (c *Condition) GetKey() apiextensions.JSON {
return FromJSON(c.RawKey)
}
func (c *Condition) SetKey(in apiextensions.JSON) {
c.RawKey = ToJSON(in)
}
func (c *Condition) GetValue() apiextensions.JSON {
return FromJSON(c.RawValue)
}
func (c *Condition) SetValue(in apiextensions.JSON) {
c.RawValue = ToJSON(in)
}
// ConditionOperator is the operation performed on condition key and value.
// +kubebuilder:validation:Enum=Equals;NotEquals;In;AnyIn;AllIn;NotIn;AnyNotIn;AllNotIn;GreaterThanOrEquals;GreaterThan;LessThanOrEquals;LessThan;DurationGreaterThanOrEquals;DurationGreaterThan;DurationLessThanOrEquals;DurationLessThan
type ConditionOperator string
// ConditionOperators stores all the valid ConditionOperator types as key-value pairs.
//
// "Equal" evaluates if the key is equal to the value. (Deprecated; Use Equals instead)
// "Equals" evaluates if the key is equal to the value.
// "NotEqual" evaluates if the key is not equal to the value. (Deprecated; Use NotEquals instead)
// "NotEquals" evaluates if the key is not equal to the value.
// "In" evaluates if the key is contained in the set of values.
// "AnyIn" evaluates if any of the keys are contained in the set of values.
// "AllIn" evaluates if all the keys are contained in the set of values.
// "NotIn" evaluates if the key is not contained in the set of values.
// "AnyNotIn" evaluates if any of the keys are not contained in the set of values.
// "AllNotIn" evaluates if all the keys are not contained in the set of values.
// "GreaterThanOrEquals" evaluates if the key (numeric) is greater than or equal to the value (numeric).
// "GreaterThan" evaluates if the key (numeric) is greater than the value (numeric).
// "LessThanOrEquals" evaluates if the key (numeric) is less than or equal to the value (numeric).
// "LessThan" evaluates if the key (numeric) is less than the value (numeric).
// "DurationGreaterThanOrEquals" evaluates if the key (duration) is greater than or equal to the value (duration)
// "DurationGreaterThan" evaluates if the key (duration) is greater than the value (duration)
// "DurationLessThanOrEquals" evaluates if the key (duration) is less than or equal to the value (duration)
// "DurationLessThan" evaluates if the key (duration) is greater than the value (duration)
var ConditionOperators = map[string]ConditionOperator{
"Equal": ConditionOperator("Equal"),
"Equals": ConditionOperator("Equals"),
"NotEqual": ConditionOperator("NotEqual"),
"NotEquals": ConditionOperator("NotEquals"),
"In": ConditionOperator("In"),
"AnyIn": ConditionOperator("AnyIn"),
"AllIn": ConditionOperator("AllIn"),
"NotIn": ConditionOperator("NotIn"),
"AnyNotIn": ConditionOperator("AnyNotIn"),
"AllNotIn": ConditionOperator("AllNotIn"),
"GreaterThanOrEquals": ConditionOperator("GreaterThanOrEquals"),
"GreaterThan": ConditionOperator("GreaterThan"),
"LessThanOrEquals": ConditionOperator("LessThanOrEquals"),
"LessThan": ConditionOperator("LessThan"),
"DurationGreaterThanOrEquals": ConditionOperator("DurationGreaterThanOrEquals"),
"DurationGreaterThan": ConditionOperator("DurationGreaterThan"),
"DurationLessThanOrEquals": ConditionOperator("DurationLessThanOrEquals"),
"DurationLessThan": ConditionOperator("DurationLessThan"),
}
// ResourceFilters is a slice of ResourceFilter
type ResourceFilters []ResourceFilter
// ResourceFilter allow users to "AND" or "OR" between resources
type ResourceFilter struct {
// UserInfo contains information about the user performing the operation.
// +optional
UserInfo `json:",omitempty"`
// ResourceDescription contains information about the resource being created or modified.
ResourceDescription `json:"resources,omitempty"`
}
func (r ResourceFilter) IsEmpty() bool {
return r.UserInfo.IsEmpty() && r.ResourceDescription.IsEmpty()
}
// Mutation defines how resource are modified.
type Mutation struct {
// MutateExistingOnPolicyUpdate controls if the mutateExisting rule will be applied on policy events.
// +optional
MutateExistingOnPolicyUpdate *bool `json:"mutateExistingOnPolicyUpdate,omitempty"`
// Targets defines the target resources to be mutated.
// +optional
Targets []TargetResourceSpec `json:"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/kustomization/patchesstrategicmerge/.
// +optional
RawPatchStrategicMerge *apiextv1.JSON `json:"patchStrategicMerge,omitempty"`
// 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/kustomization/patchesjson6902/.
// +optional
PatchesJSON6902 string `json:"patchesJson6902,omitempty"`
// ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic.
// +optional
ForEachMutation []ForEachMutation `json:"foreach,omitempty"`
}
func (m *Mutation) GetPatchStrategicMerge() apiextensions.JSON {
return FromJSON(m.RawPatchStrategicMerge)
}
func (m *Mutation) SetPatchStrategicMerge(in apiextensions.JSON) {
m.RawPatchStrategicMerge = ToJSON(in)
}
// ForEachMutation applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic.
type ForEachMutation struct {
// List specifies a JMESPath expression that results in one or more elements
// to which the validation logic is applied.
List string `json:"list,omitempty"`
// Order defines the iteration order on the list.
// Can be Ascending to iterate from first to last element or Descending to iterate in from last to first element.
// +optional
Order *ForeachOrder `json:"order,omitempty"`
// Context defines variables and data sources that can be used during rule execution.
// +optional
Context []ContextEntry `json:"context,omitempty"`
// AnyAllConditions 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.
// See: https://kyverno.io/docs/writing-policies/preconditions/
// +kubebuilder:validation:XPreserveUnknownFields
// +optional
AnyAllConditions *AnyAllConditions `json:"preconditions,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/kustomization/patchesstrategicmerge/.
// +optional
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
RawPatchStrategicMerge *kyverno.Any `json:"patchStrategicMerge,omitempty"`
// 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/kustomization/patchesjson6902/.
// +optional
PatchesJSON6902 string `json:"patchesJson6902,omitempty"`
// Foreach declares a nested foreach iterator
// +optional
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
ForEachMutation *ForEachMutationWrapper `json:"foreach,omitempty"`
}
func (m *ForEachMutation) GetForEachMutation() []ForEachMutation {
if m.ForEachMutation == nil {
return nil
}
return m.ForEachMutation.Items
}
func (m *ForEachMutation) GetPatchStrategicMerge() any {
return kyverno.FromAny(m.RawPatchStrategicMerge)
}
func (m *ForEachMutation) SetPatchStrategicMerge(in any) {
m.RawPatchStrategicMerge = kyverno.ToAny(in)
}
// Validation defines checks to be performed on matching resources.
type Validation struct {
// FailureAction defines if a validation policy rule violation should block
// the admission review request (Enforce), or allow (Audit) the admission review request
// and report an error in a policy report. Optional.
// Allowed values are Audit or Enforce.
// +optional
// +kubebuilder:validation:Enum=Audit;Enforce
FailureAction *ValidationFailureAction `json:"failureAction,omitempty"`
// FailureActionOverrides is a Cluster Policy attribute that specifies FailureAction
// namespace-wise. It overrides FailureAction for the specified namespaces.
// +optional
FailureActionOverrides []ValidationFailureActionOverride `json:"failureActionOverrides,omitempty"`
// AllowExistingViolations allows prexisting violating resources to continue violating a policy.
// +kubebuilder:validation:Optional
// +kubebuilder:default=true
AllowExistingViolations *bool `json:"allowExistingViolations,omitempty"`
// Message specifies a custom message to be displayed on failure.
// +optional
Message string `json:"message,omitempty"`
// Manifest specifies conditions for manifest verification
// +optional
Manifests *Manifests `json:"manifests,omitempty"`
// ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic.
// +optional
ForEachValidation []ForEachValidation `json:"foreach,omitempty"`
// Pattern specifies an overlay-style pattern used to check resources.
// +optional
RawPattern *apiextv1.JSON `json:"pattern,omitempty"`
// AnyPattern specifies list of validation patterns. At least one of the patterns
// must be satisfied for the validation rule to succeed.
// +optional
RawAnyPattern *apiextv1.JSON `json:"anyPattern,omitempty"`
// Deny defines conditions used to pass or fail a validation rule.
// +optional
Deny *Deny `json:"deny,omitempty"`
// PodSecurity applies exemptions for Kubernetes Pod Security admission
// by specifying exclusions for Pod Security Standards controls.
// +optional
PodSecurity *PodSecurity `json:"podSecurity,omitempty"`
// CEL allows validation checks using the Common Expression Language (https://kubernetes.io/docs/reference/using-api/cel/).
// +optional
CEL *CEL `json:"cel,omitempty"`
// Assert defines a kyverno-json assertion tree.
// +optional
Assert AssertionTree `json:"assert"`
}
// PodSecurity applies exemptions for Kubernetes Pod Security admission
// by specifying exclusions for Pod Security Standards controls.
type PodSecurity struct {
// Level defines the Pod Security Standard level to be applied to workloads.
// Allowed values are privileged, baseline, and restricted.
// +kubebuilder:validation:Enum=privileged;baseline;restricted
Level api.Level `json:"level,omitempty"`
// Version defines the Pod Security Standard versions that Kubernetes supports.
// Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, v1.26, v1.27, v1.28, v1.29, latest. Defaults to latest.
// +kubebuilder:validation:Enum=v1.19;v1.20;v1.21;v1.22;v1.23;v1.24;v1.25;v1.26;v1.27;v1.28;v1.29;latest
// +optional
Version string `json:"version,omitempty"`
// Exclude specifies the Pod Security Standard controls to be excluded.
Exclude []PodSecurityStandard `json:"exclude,omitempty"`
}
// PodSecurityStandard specifies the Pod Security Standard controls to be excluded.
type PodSecurityStandard struct {
// ControlName specifies the name of the Pod Security Standard control.
// See: https://kubernetes.io/docs/concepts/security/pod-security-standards/
// +kubebuilder:validation:Enum=HostProcess;Host Namespaces;Privileged Containers;Capabilities;HostPath Volumes;Host Ports;AppArmor;SELinux;/proc Mount Type;Seccomp;Sysctls;Volume Types;Privilege Escalation;Running as Non-root;Running as Non-root user
ControlName string `json:"controlName"`
// Images selects matching containers and applies the container level PSS.
// Each image is the image name consisting of the registry address, repository, image, and tag.
// Empty list matches no containers, PSS checks are applied at the pod level only.
// Wildcards ('*' and '?') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.
// +optional
Images []string `json:"images,omitempty"`
// RestrictedField selects the field for the given Pod Security Standard control.
// When not set, all restricted fields for the control are selected.
// +optional
RestrictedField string `json:"restrictedField,omitempty"`
// Values defines the allowed values that can be excluded.
// +optional
Values []string `json:"values,omitempty"`
}
func (pss *PodSecurityStandard) Validate(path *field.Path) (errs field.ErrorList) {
// container level control must specify images
if containsString(utils.PSS_container_level_control, pss.ControlName) {
if len(pss.Images) == 0 {
errs = append(errs, field.Invalid(path.Child("controlName"), pss.ControlName, "exclude.images must be specified for the container level control"))
}
} else if containsString(utils.PSS_pod_level_control, pss.ControlName) {
if len(pss.Images) != 0 {
errs = append(errs, field.Invalid(path.Child("controlName"), pss.ControlName, "exclude.images must not be specified for the pod level control"))
}
}
if pss.RestrictedField != "" && len(pss.Values) == 0 {
errs = append(errs, field.Forbidden(path.Child("values"), "values is required"))
}
if pss.RestrictedField == "" && len(pss.Values) != 0 {
errs = append(errs, field.Forbidden(path.Child("restrictedField"), "restrictedField is required"))
}
return errs
}
// CEL allows validation checks using the Common Expression Language (https://kubernetes.io/docs/reference/using-api/cel/).
type CEL struct {
// Generate specifies whether to generate a Kubernetes ValidatingAdmissionPolicy from the rule.
// Optional. Defaults to "false" if not specified.
// +optional
// +kubebuilder:default=false
Generate *bool `json:"generate,omitempty"`
// Expressions is a list of CELExpression types.
Expressions []admissionregistrationv1.Validation `json:"expressions,omitempty"`
// ParamKind is a tuple of Group Kind and Version.
// +optional
ParamKind *admissionregistrationv1.ParamKind `json:"paramKind,omitempty"`
// ParamRef references a parameter resource.
// +optional
ParamRef *admissionregistrationv1.ParamRef `json:"paramRef,omitempty"`
// AuditAnnotations contains CEL expressions which are used to produce audit annotations for the audit event of the API request.
// +optional
AuditAnnotations []admissionregistrationv1.AuditAnnotation `json:"auditAnnotations,omitempty"`
// Variables contain definitions of variables that can be used in composition of other expressions.
// Each variable is defined as a named CEL expression.
// The variables defined here will be available under `variables` in other expressions of the policy.
// +optional
Variables []admissionregistrationv1.Variable `json:"variables,omitempty"`
}
func (c *CEL) GenerateVAP() bool {
return c.Generate != nil && *c.Generate
}
func (c *CEL) HasParam() bool {
return c.ParamKind != nil && c.ParamRef != nil
}
func (c *CEL) GetParamKind() admissionregistrationv1.ParamKind {
return *c.ParamKind
}
func (c *CEL) GetParamRef() admissionregistrationv1.ParamRef {
return *c.ParamRef
}
// DeserializeAnyPattern deserialize apiextensions.JSON to []interface{}
func (in *Validation) DeserializeAnyPattern() ([]interface{}, error) {
anyPattern := in.GetAnyPattern()
if anyPattern == nil {
return nil, nil
}
res, nil := deserializePattern(anyPattern)
return res, nil
}
func deserializePattern(pattern apiextensions.JSON) ([]interface{}, error) {
anyPattern, err := json.Marshal(pattern)
if err != nil {
return nil, err
}
var res []interface{}
if err := json.Unmarshal(anyPattern, &res); err != nil {
return nil, err
}
return res, nil
}
func (v *Validation) GetPattern() apiextensions.JSON {
return FromJSON(v.RawPattern)
}
func (v *Validation) SetPattern(in apiextensions.JSON) {
v.RawPattern = ToJSON(in)
}
func (v *Validation) GetAnyPattern() apiextensions.JSON {
return FromJSON(v.RawAnyPattern)
}
func (v *Validation) SetAnyPattern(in apiextensions.JSON) {
v.RawAnyPattern = ToJSON(in)
}
func (v *Validation) GetForeach() apiextensions.JSON {
return FromJSON(v.RawPattern)
}
func (v *Validation) SetForeach(in apiextensions.JSON) {
v.RawPattern = ToJSON(in)
}
// Deny specifies a list of conditions used to pass or fail a validation rule.
type Deny struct {
// Multiple conditions can be declared under an `any` or `all` statement. A direct list
// of conditions (without `any` or `all` statements) is also supported for backwards compatibility
// but will be deprecated in the next major release.
// See: https://kyverno.io/docs/writing-policies/validate/#deny-rules
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
RawAnyAllConditions *ConditionsWrapper `json:"conditions,omitempty"`
}
func (d *Deny) GetAnyAllConditions() any {
if d.RawAnyAllConditions == nil {
return nil
}
return d.RawAnyAllConditions.Conditions
}
func (d *Deny) SetAnyAllConditions(in any) {
var new *ConditionsWrapper
if in != nil {
new = &ConditionsWrapper{in}
}
d.RawAnyAllConditions = new
}
// ForEachValidation applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic.
type ForEachValidation struct {
// List specifies a JMESPath expression that results in one or more elements
// to which the validation logic is applied.
List string `json:"list,omitempty"`
// ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified.
// When set to "false", "request.object" is used as the validation scope within the foreach
// block to allow referencing other elements in the subtree.
// +optional
ElementScope *bool `json:"elementScope,omitempty"`
// Context defines variables and data sources that can be used during rule execution.
// +optional
Context []ContextEntry `json:"context,omitempty"`
// AnyAllConditions 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.
// See: https://kyverno.io/docs/writing-policies/preconditions/
// +kubebuilder:validation:XPreserveUnknownFields
// +optional
AnyAllConditions *AnyAllConditions `json:"preconditions,omitempty"`
// Pattern specifies an overlay-style pattern used to check resources.
// +optional
RawPattern *apiextv1.JSON `json:"pattern,omitempty"`
// AnyPattern specifies list of validation patterns. At least one of the patterns
// must be satisfied for the validation rule to succeed.
// +optional
RawAnyPattern *apiextv1.JSON `json:"anyPattern,omitempty"`
// Deny defines conditions used to pass or fail a validation rule.
// +optional
Deny *Deny `json:"deny,omitempty"`
// Foreach declares a nested foreach iterator
// +optional
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
ForEachValidation *ForEachValidationWrapper `json:"foreach,omitempty"`
}
func (v *ForEachValidation) GetForEachValidation() []ForEachValidation {
if v.ForEachValidation == nil {
return nil
}
return v.ForEachValidation.Items
}
func (v *ForEachValidation) GetPattern() apiextensions.JSON {
return FromJSON(v.RawPattern)
}
func (v *ForEachValidation) SetPattern(in apiextensions.JSON) {
v.RawPattern = ToJSON(in)
}
func (v *ForEachValidation) GetAnyPattern() apiextensions.JSON {
return FromJSON(v.RawAnyPattern)
}
func (v *ForEachValidation) SetAnyPattern(in apiextensions.JSON) {
v.RawAnyPattern = ToJSON(in)
}
// Generation defines how new resources should be created and managed.
type Generation struct {
// GenerateExisting controls whether to trigger the rule in existing resources
// If is set to "true" the rule will be triggered and applied to existing matched resources.
// +optional
GenerateExisting *bool `json:"generateExisting,omitempty"`
// Synchronize controls if generated resources should be kept in-sync with their source resource.
// If Synchronize is set to "true" changes to generated resources will be overwritten with resource
// data from Data or the resource specified in the Clone declaration.
// Optional. Defaults to "false" if not specified.
// +optional
Synchronize bool `json:"synchronize,omitempty"`
// OrphanDownstreamOnPolicyDelete controls whether generated resources should be deleted when the rule that generated
// them is deleted with synchronization enabled. This option is only applicable to generate rules of the data type.
// See https://kyverno.io/docs/writing-policies/generate/#data-examples.
// Defaults to "false" if not specified.
// +optional
OrphanDownstreamOnPolicyDelete bool `json:"orphanDownstreamOnPolicyDelete,omitempty"`
// +optional
GeneratePattern `json:",omitempty"`
// ForEach applies generate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic.
// +optional
ForEachGeneration []ForEachGeneration `json:"foreach,omitempty"`
}
type GeneratePattern struct {
// ResourceSpec contains information to select the resource.
// +kubebuilder:validation:Optional
ResourceSpec `json:",omitempty"`
// Data provides the resource declaration used to populate each generated resource.
// At most one of Data or Clone must be specified. If neither are provided, the generated
// resource will be created with default data only.
// +optional
RawData *apiextv1.JSON `json:"data,omitempty"`
// Clone specifies the source resource used to populate each generated resource.
// At most one of Data or Clone can be specified. If neither are provided, the generated
// resource will be created with default data only.
// +optional
Clone CloneFrom `json:"clone,omitempty"`
// CloneList specifies the list of source resource used to populate each generated resource.
// +optional
CloneList CloneList `json:"cloneList,omitempty"`
}
type ForEachGeneration struct {
// List specifies a JMESPath expression that results in one or more elements
// to which the validation logic is applied.
List string `json:"list,omitempty"`
// Context defines variables and data sources that can be used during rule execution.
// +optional
Context []ContextEntry `json:"context,omitempty"`
// AnyAllConditions 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.
// See: https://kyverno.io/docs/writing-policies/preconditions/
// +kubebuilder:validation:XPreserveUnknownFields
// +optional
AnyAllConditions *AnyAllConditions `json:"preconditions,omitempty"`
GeneratePattern `json:",omitempty"`
}
type CloneList struct {
// Namespace specifies source resource namespace.
Namespace string `json:"namespace,omitempty"`
// Kinds is a list of resource kinds.
Kinds []string `json:"kinds,omitempty"`
// Selector is a label selector. Label keys and values in `matchLabels`.
// wildcard characters are not supported.
// +optional
Selector *metav1.LabelSelector `json:"selector,omitempty"`
}
func (g *Generation) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (warnings []string, errs field.ErrorList) {
count := 0
if g.GetData() != nil {
count++
}
if g.Clone != (CloneFrom{}) {
count++
}
if g.CloneList.Kinds != nil {
count++
}
if g.ForEachGeneration != nil {
count++
}
if count > 1 {
errs = append(errs, field.Forbidden(path, "only one of generate patterns(data, clone, cloneList and foreach) can be specified"))
return warnings, errs
}
if g.ForEachGeneration != nil {
for i, foreach := range g.ForEachGeneration {
warning, err := foreach.GeneratePattern.Validate(path.Child("foreach").Index(i), namespaced, policyNamespace, clusterResources)
warnings = append(warnings, warning...)
errs = append(errs, err...)
}
return warnings, errs
} else {
return g.GeneratePattern.Validate(path, namespaced, policyNamespace, clusterResources)
}
}
func (g *GeneratePattern) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (warnings []string, errs field.ErrorList) {
if namespaced {
if err := g.validateNamespacedTargetsScope(clusterResources, policyNamespace); err != nil {
errs = append(errs, field.Forbidden(path.Child("namespace"), fmt.Sprintf("target resource scope mismatched: %v ", err)))
}
}
if g.GetKind() != "" {
if !clusterResources.Has(g.GetAPIVersion() + "/" + g.GetKind()) {
if g.GetNamespace() == "" {
errs = append(errs, field.Forbidden(path.Child("namespace"), "target namespace must be set for a namespaced resource"))
}
} else {
if g.GetNamespace() != "" {
errs = append(errs, field.Forbidden(path.Child("namespace"), "target namespace must not be set for a cluster-wide resource"))
}
}
}
newGeneration := GeneratePattern{
ResourceSpec: ResourceSpec{
Kind: g.ResourceSpec.GetKind(),
APIVersion: g.ResourceSpec.GetAPIVersion(),
},
Clone: g.Clone,
CloneList: g.CloneList,
}
if err := regex.ObjectHasVariables(newGeneration); err != nil {
warnings = append(warnings, fmt.Sprintf("variables in %s would lead to incorrect synchronization behavior", path.Child("clone/cloneList").String()))
}
if len(g.CloneList.Kinds) == 0 {
if g.Kind == "" {
errs = append(errs, field.Forbidden(path.Child("kind"), "kind can not be empty"))
}
if g.Name == "" {
errs = append(errs, field.Forbidden(path.Child("name"), "name can not be empty"))
}
if g.APIVersion == "" {
errs = append(errs, field.Forbidden(path.Child("apiVersion"), "apiVersion can not be empty"))
}
}
return warnings, append(errs, g.ValidateCloneList(path, namespaced, policyNamespace, clusterResources)...)
}
func (g *GeneratePattern) ValidateCloneList(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
if len(g.CloneList.Kinds) == 0 {
return nil
}
if namespaced {
for _, kind := range g.CloneList.Kinds {
if clusterResources.Has(kind) {
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("kinds"), fmt.Sprintf("the source in cloneList must be a namespaced resource: %v", kind)))
}
if g.CloneList.Namespace != policyNamespace {
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("namespace"), fmt.Sprintf("a namespaced policy cannot clone resources from other namespace, expected: %v, received: %v", policyNamespace, g.CloneList.Namespace)))
}
}
}
clusterScope := clusterResources.Has(g.CloneList.Kinds[0])
for _, gvk := range g.CloneList.Kinds[1:] {
if clusterScope != clusterResources.Has(gvk) {
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("kinds"), "mixed scope of target resources is forbidden"))
break
}
clusterScope = clusterScope && clusterResources.Has(gvk)
}
if !clusterScope {
if g.CloneList.Namespace == "" {
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("namespace"), "namespace is required for namespaced target resources"))
}
} else if clusterScope && !namespaced {
if g.CloneList.Namespace != "" {
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("namespace"), "namespace is forbidden for cluster-wide target resources"))
}
}
return errs
}
func (g *GeneratePattern) GetType() GenerateType {
if g.RawData != nil {
return Data
}
return Clone
}
func (g *GeneratePattern) GetData() apiextensions.JSON {
return FromJSON(g.RawData)
}
func (g *GeneratePattern) SetData(in apiextensions.JSON) {
g.RawData = ToJSON(in)
}
func (g *GeneratePattern) validateNamespacedTargetsScope(clusterResources sets.Set[string], policyNamespace string) error {
target := g.ResourceSpec
if clusterResources.Has(target.GetAPIVersion() + "/" + target.GetKind()) {
return fmt.Errorf("the target must be a namespaced resource: %v/%v", target.GetAPIVersion(), target.GetKind())
}
if g.GetNamespace() != policyNamespace {
return fmt.Errorf("a namespaced policy cannot generate resources in other namespaces, expected: %v, received: %v", policyNamespace, g.GetNamespace())
}
if g.Clone.Name != "" {
if g.Clone.Namespace != policyNamespace {
return fmt.Errorf("a namespaced policy cannot clone resources from other namespaces, expected: %v, received: %v", policyNamespace, g.Clone.Namespace)
}
}
return nil
}
type GenerateType string
const (
Data GenerateType = "Data"
Clone GenerateType = "Clone"
)
// CloneFrom provides the location of the source resource used to generate target resources.
// The resource kind is derived from the match criteria.
type CloneFrom struct {
// Namespace specifies source resource namespace.
// +optional
Namespace string `json:"namespace,omitempty"`
// Name specifies name of the resource.
Name string `json:"name,omitempty"`
}
type Manifests struct {
// Attestors specified the required attestors (i.e. authorities)
// +kubebuilder:validation:Optional
Attestors []AttestorSet `json:"attestors,omitempty"`
// AnnotationDomain is custom domain of annotation for message and signature. Default is "cosign.sigstore.dev".
// +optional
AnnotationDomain string `json:"annotationDomain,omitempty"`
// Fields which will be ignored while comparing manifests.
// +optional
IgnoreFields IgnoreFieldList `json:"ignoreFields,omitempty"`
// DryRun configuration
// +optional
DryRunOption DryRunOption `json:"dryRun,omitempty"`
// Repository is an optional alternate OCI repository to use for resource bundle reference.
// The repository can be overridden per Attestor or Attestation.
Repository string `json:"repository,omitempty"`
}
// DryRunOption is a configuration for dryrun.
// If enable is set to "true", manifest verification performs "dryrun & compare"
// which provides robust matching against changes by defaults and other admission controllers.
// Dryrun requires additional permissions. See config/dryrun/dryrun_rbac.yaml
type DryRunOption struct {
Enable bool `json:"enable,omitempty"`
Namespace string `json:"namespace,omitempty"`
}
type IgnoreFieldList []ObjectFieldBinding
type ObjectFieldBinding k8smanifest.ObjectFieldBinding
// AdmissionOperation can have one of the values CREATE, UPDATE, CONNECT, DELETE, which are used to match a specific action.
// +kubebuilder:validation:Enum=CREATE;CONNECT;UPDATE;DELETE
type AdmissionOperation admissionv1.Operation
const (
Create AdmissionOperation = AdmissionOperation(admissionv1.Create)
Update AdmissionOperation = AdmissionOperation(admissionv1.Update)
Delete AdmissionOperation = AdmissionOperation(admissionv1.Delete)
Connect AdmissionOperation = AdmissionOperation(admissionv1.Connect)
)
package v1
import (
"encoding/json"
"fmt"
"github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ImageVerificationType selects the type of verification algorithm
// +kubebuilder:validation:Enum=Cosign;SigstoreBundle;Notary
// +kubebuilder:default=Cosign
type ImageVerificationType string
// ImageRegistryCredentialsProvidersType provides the list of credential providers required.
type ImageRegistryCredentialsProvidersType v1alpha1.CredentialsProvidersType
const (
Cosign ImageVerificationType = "Cosign"
SigstoreBundle ImageVerificationType = "SigstoreBundle"
Notary ImageVerificationType = "Notary"
DEFAULT ImageRegistryCredentialsProvidersType = "default"
AWS ImageRegistryCredentialsProvidersType = "amazon"
ACR ImageRegistryCredentialsProvidersType = "azure"
GCP ImageRegistryCredentialsProvidersType = "google"
GHCR ImageRegistryCredentialsProvidersType = "github"
)
var signatureAlgorithmMap = map[string]bool{
"": true,
"sha224": true,
"sha256": true,
"sha384": true,
"sha512": true,
}
// ImageVerification validates that images that match the specified pattern
// are signed with the supplied public key. Once the image is verified it is
// mutated to include the SHA digest retrieved during the registration.
type ImageVerification struct {
// Allowed values are Audit or Enforce.
// +optional
// +kubebuilder:validation:Enum=Audit;Enforce
FailureAction *ValidationFailureAction `json:"failureAction,omitempty"`
// Type specifies the method of signature validation. The allowed options
// are Cosign, Sigstore Bundle and Notary. By default Cosign is used if a type is not specified.
// +kubebuilder:validation:Optional
Type ImageVerificationType `json:"type,omitempty"`
// Deprecated. Use ImageReferences instead.
// +kubebuilder:validation:Optional
Image string `json:"image,omitempty"`
// ImageReferences is a list of matching image reference patterns. At least one pattern in the
// list must match the image for the rule to apply. Each image reference consists of a registry
// address (defaults to docker.io), repository, image, and tag (defaults to latest).
// Wildcards ('*' and '?') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.
// +kubebuilder:validation:Optional
ImageReferences []string `json:"imageReferences,omitempty"`
// SkipImageReferences is a list of matching image reference patterns that should be skipped.
// At least one pattern in the list must match the image for the rule to be skipped. Each image reference
// consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest).
// Wildcards ('*' and '?') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.
// +kubebuilder:validation:Optional
SkipImageReferences []string `json:"skipImageReferences,omitempty"`
// Deprecated. Use StaticKeyAttestor instead.
Key string `json:"key,omitempty"`
// Deprecated. Use KeylessAttestor instead.
Roots string `json:"roots,omitempty"`
// Deprecated. Use KeylessAttestor instead.
Subject string `json:"subject,omitempty"`
// Deprecated. Use KeylessAttestor instead.
Issuer string `json:"issuer,omitempty"`
// Deprecated.
AdditionalExtensions map[string]string `json:"additionalExtensions,omitempty"`
// Attestors specified the required attestors (i.e. authorities)
// +kubebuilder:validation:Optional
Attestors []AttestorSet `json:"attestors,omitempty"`
// Attestations are optional checks for signed in-toto Statements used to verify the image.
// See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the
// OCI registry and decodes them into a list of Statement declarations.
Attestations []Attestation `json:"attestations,omitempty"`
// Deprecated. Use annotations per Attestor instead.
Annotations map[string]string `json:"annotations,omitempty"`
// Repository is an optional alternate OCI repository to use for image signatures and attestations that match this rule.
// If specified Repository will override the default OCI image repository configured for the installation.
// The repository can also be overridden per Attestor or Attestation.
Repository string `json:"repository,omitempty"`
// CosignOCI11 enables the experimental OCI 1.1 behaviour in cosign image verification.
// Defaults to false.
// +optional
CosignOCI11 bool `json:"cosignOCI11,omitempty"`
// MutateDigest enables replacement of image tags with digests.
// Defaults to true.
// +kubebuilder:default=true
// +kubebuilder:validation:Optional
MutateDigest bool `json:"mutateDigest"`
// VerifyDigest validates that images have a digest.
// +kubebuilder:default=true
// +kubebuilder:validation:Optional
VerifyDigest bool `json:"verifyDigest"`
// Validation checks conditions across multiple image
// verification attestations or context entries
Validation ValidateImageVerification `json:"validate,omitempty"`
// Required validates that images are verified i.e. have matched passed a signature or attestation check.
// +kubebuilder:default=true
// +kubebuilder:validation:Optional
Required bool `json:"required"`
// ImageRegistryCredentials provides credentials that will be used for authentication with registry.
// +kubebuilder:validation:Optional
ImageRegistryCredentials *ImageRegistryCredentials `json:"imageRegistryCredentials,omitempty"`
// UseCache enables caching of image verify responses for this rule.
// +kubebuilder:default=true
// +kubebuilder:validation:Optional
UseCache bool `json:"useCache"`
}
type AttestorSet struct {
// Count specifies the required number of entries that must match. If the count is null, all entries must match
// (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a
// value N, then N must be less than or equal to the size of entries, and at least N entries must match.
// +kubebuilder:validation:Optional
// +kubebuilder:validation:Minimum:=1
Count *int `json:"count,omitempty"`
// Entries contains the available attestors. An attestor can be a static key,
// attributes for keyless verification, or a nested attestor declaration.
// +kubebuilder:validation:Optional
Entries []Attestor `json:"entries,omitempty"`
}
func (as AttestorSet) RequiredCount() int {
if as.Count == nil || *as.Count == 0 {
return len(as.Entries)
}
return *as.Count
}
type Attestor struct {
// Keys specifies one or more public keys.
// +kubebuilder:validation:Optional
Keys *StaticKeyAttestor `json:"keys,omitempty"`
// Certificates specifies one or more certificates.
// +kubebuilder:validation:Optional
Certificates *CertificateAttestor `json:"certificates,omitempty"`
// Keyless is a set of attribute used to verify a Sigstore keyless attestor.
// See https://github.com/sigstore/cosign/blob/main/KEYLESS.md.
// +kubebuilder:validation:Optional
Keyless *KeylessAttestor `json:"keyless,omitempty"`
// Attestor is a nested set of Attestor used to specify a more complex set of match authorities.
// +kubebuilder:validation:Optional
Attestor *apiextv1.JSON `json:"attestor,omitempty"`
// Annotations are used for image verification.
// Every specified key-value pair must exist and match in the verified payload.
// The payload may contain other key-value pairs.
Annotations map[string]string `json:"annotations,omitempty"`
// Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule.
// If specified Repository will override other OCI image repository locations for this Attestor.
Repository string `json:"repository,omitempty"`
// Specify signature algorithm for public keys. Supported values are sha224, sha256, sha384 and sha512.
// +kubebuilder:default=sha256
SignatureAlgorithm string `json:"signatureAlgorithm,omitempty"`
}
type StaticKeyAttestor struct {
// Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly
// specified or can be a variable reference to a key specified in a ConfigMap (see
// https://kyverno.io/docs/writing-policies/variables/), or reference a standard Kubernetes Secret
// elsewhere in the cluster by specifying it in the format "k8s://<namespace>/<secret_name>".
// The named Secret must specify a key `cosign.pub` containing the public key used for
// verification, (see https://github.com/sigstore/cosign/blob/main/KMS.md#kubernetes-secret).
// When multiple keys are specified each key is processed as a separate staticKey entry
// (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys.
PublicKeys string `json:"publicKeys,omitempty"`
// Deprecated. Use attestor.signatureAlgorithm instead.
// +kubebuilder:default=sha256
SignatureAlgorithm string `json:"signatureAlgorithm,omitempty"`
// KMS provides the URI to the public key stored in a Key Management System. See:
// https://github.com/sigstore/cosign/blob/main/KMS.md
KMS string `json:"kms,omitempty"`
// Reference to a Secret resource that contains a public key
Secret *SecretReference `json:"secret,omitempty"`
// Rekor provides configuration for the Rekor transparency log service. If an empty object
// is provided the public instance of Rekor (https://rekor.sigstore.dev) is used.
// +kubebuilder:validation:Optional
Rekor *Rekor `json:"rekor,omitempty"`
// CTLog (certificate timestamp log) provides a configuration for validation of Signed Certificate
// Timestamps (SCTs). If the value is unset, the default behavior by Cosign is used.
// +kubebuilder:validation:Optional
CTLog *CTLog `json:"ctlog,omitempty"`
}
type SecretReference struct {
// Name of the secret. The provided secret must contain a key named cosign.pub.
Name string `json:"name"`
// Namespace name where the Secret exists.
Namespace string `json:"namespace"`
}
type CertificateAttestor struct {
// Cert is an optional PEM-encoded public certificate.
// +kubebuilder:validation:Optional
Certificate string `json:"cert,omitempty"`
// CertChain is an optional PEM encoded set of certificates used to verify.
// +kubebuilder:validation:Optional
CertificateChain string `json:"certChain,omitempty"`
// Rekor provides configuration for the Rekor transparency log service. If an empty object
// is provided the public instance of Rekor (https://rekor.sigstore.dev) is used.
// +kubebuilder:validation:Optional
Rekor *Rekor `json:"rekor,omitempty"`
// CTLog (certificate timestamp log) provides a configuration for validation of Signed Certificate
// Timestamps (SCTs). If the value is unset, the default behavior by Cosign is used.
// +kubebuilder:validation:Optional
CTLog *CTLog `json:"ctlog,omitempty"`
}
type KeylessAttestor struct {
// Rekor provides configuration for the Rekor transparency log service. If an empty object
// is provided the public instance of Rekor (https://rekor.sigstore.dev) is used.
// +kubebuilder:validation:Optional
Rekor *Rekor `json:"rekor,omitempty"`
// CTLog (certificate timestamp log) provides a configuration for validation of Signed Certificate
// Timestamps (SCTs). If the value is unset, the default behavior by Cosign is used.
// +kubebuilder:validation:Optional
CTLog *CTLog `json:"ctlog,omitempty"`
// Issuer is the certificate issuer used for keyless signing.
// +kubebuilder:validation:Optional
Issuer string `json:"issuer,omitempty"`
// IssuerRegExp is the regular expression to match certificate issuer used for keyless signing.
// +kubebuilder:validation:Optional
IssuerRegExp string `json:"issuerRegExp,omitempty"`
// Subject is the verified identity used for keyless signing, for example the email address.
// +kubebuilder:validation:Optional
Subject string `json:"subject,omitempty"`
// SubjectRegExp is the regular expression to match identity used for keyless signing, for example the email address.
// +kubebuilder:validation:Optional
SubjectRegExp string `json:"subjectRegExp,omitempty"`
// Roots is an optional set of PEM encoded trusted root certificates.
// If not provided, the system roots are used.
// +kubebuilder:validation:Optional
Roots string `json:"roots,omitempty"`
// AdditionalExtensions are certificate-extensions used for keyless signing.
// +kubebuilder:validation:Optional
AdditionalExtensions map[string]string `json:"additionalExtensions,omitempty"`
}
type Rekor struct {
// URL is the address of the transparency log. Defaults to the public Rekor log instance https://rekor.sigstore.dev.
// +kubebuilder:validation:Optional
// +kubebuilder:Default:=https://rekor.sigstore.dev
URL string `json:"url"`
// RekorPubKey is an optional PEM-encoded public key to use for a custom Rekor.
// If set, this will be used to validate transparency log signatures from a custom Rekor.
// +kubebuilder:validation:Optional
RekorPubKey string `json:"pubkey,omitempty"`
// IgnoreTlog skips transparency log verification.
// +kubebuilder:validation:Optional
IgnoreTlog bool `json:"ignoreTlog,omitempty"`
}
type CTLog struct {
// IgnoreSCT defines whether to use the Signed Certificate Timestamp (SCT) log to check for a certificate
// timestamp. Default is false. Set to true if this was opted out during signing.
// +kubebuilder:validation:Optional
IgnoreSCT bool `json:"ignoreSCT,omitempty"`
// PubKey, if set, is used to validate SCTs against a custom source.
// +kubebuilder:validation:Optional
CTLogPubKey string `json:"pubkey,omitempty"`
// TSACertChain, if set, is the PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must
// contain the root CA certificate. Optionally may contain intermediate CA certificates, and
// may contain the leaf TSA certificate if not present in the timestamurce.
// +kubebuilder:validation:Optional
TSACertChain string `json:"tsaCertChain,omitempty"`
}
// Attestation are checks for signed in-toto Statements that are used to verify the image.
// See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the
// OCI registry and decodes them into a list of Statements.
type Attestation struct {
// Name is the variable name.
Name string `json:"name,omitempty"`
// Deprecated in favour of 'Type', to be removed soon
// +kubebuilder:validation:Optional
PredicateType string `json:"predicateType"`
// Type defines the type of attestation contained within the Statement.
// +kubebuilder:validation:Optional
Type string `json:"type"`
// Attestors specify the required attestors (i.e. authorities).
// +kubebuilder:validation:Optional
Attestors []AttestorSet `json:"attestors"`
// Conditions are used to verify attributes within a Predicate. If no Conditions are specified
// the attestation check is satisfied as long there are predicates that match the predicate type.
// +kubebuilder:validation:Optional
Conditions []AnyAllConditions `json:"conditions,omitempty"`
}
type ImageRegistryCredentials struct {
// AllowInsecureRegistry allows insecure access to a registry.
// +kubebuilder:validation:Optional
AllowInsecureRegistry bool `json:"allowInsecureRegistry,omitempty"`
// Providers specifies a list of OCI Registry names, whose authentication providers are provided.
// It can be of one of these values: default,google,azure,amazon,github.
// +kubebuilder:validation:Optional
Providers []ImageRegistryCredentialsProvidersType `json:"providers,omitempty"`
// Secrets specifies a list of secrets that are provided for credentials.
// Secrets must live in the Kyverno namespace.
// +kubebuilder:validation:Optional
Secrets []string `json:"secrets,omitempty"`
}
// ValidateImageVerification checks conditions across multiple image
// verification attestations or context entries
type ValidateImageVerification struct {
// Message specifies a custom message to be displayed on failure.
// +optional
Message string `json:"message,omitempty"`
// Deny defines conditions used to pass or fail a validation rule.
// +optional
Deny *Deny `json:"deny,omitempty"`
}
func (iv *ImageVerification) GetType() ImageVerificationType {
if iv.Type != "" {
return iv.Type
}
return Cosign
}
// Validate implements programmatic validation
func (iv *ImageVerification) Validate(isAuditFailureAction bool, path *field.Path) (errs field.ErrorList) {
copy := iv.Convert()
if isAuditFailureAction && iv.MutateDigest {
errs = append(errs, field.Invalid(path.Child("mutateDigest"), iv.MutateDigest, "mutateDigest must be set to false for ‘Audit’ failure action"))
}
if len(copy.ImageReferences) == 0 {
errs = append(errs, field.Invalid(path, iv, "An image reference is required"))
}
asPath := path.Child("attestations")
for i, attestation := range copy.Attestations {
attestationErrors := attestation.Validate(asPath.Index(i))
errs = append(errs, attestationErrors...)
}
attestorsPath := path.Child("attestors")
for i, as := range copy.Attestors {
attestorErrors := as.Validate(attestorsPath.Index(i))
errs = append(errs, attestorErrors...)
}
if iv.Type == Notary {
for _, attestorSet := range iv.Attestors {
for _, attestor := range attestorSet.Entries {
if attestor.Keyless != nil {
errs = append(errs, field.Invalid(attestorsPath, iv, "Keyless field is not allowed for type notary"))
}
if attestor.Keys != nil {
errs = append(errs, field.Invalid(attestorsPath, iv, "Keys field is not allowed for type notary"))
}
}
}
}
return errs
}
func (a *Attestation) Validate(path *field.Path) (errs field.ErrorList) {
if len(a.Attestors) == 0 {
return
}
attestorsPath := path.Child("attestors")
for i, as := range a.Attestors {
attestorErrors := as.Validate(attestorsPath.Index(i))
errs = append(errs, attestorErrors...)
}
return errs
}
func (as *AttestorSet) Validate(path *field.Path) (errs field.ErrorList) {
return validateAttestorSet(as, path)
}
func validateAttestorSet(as *AttestorSet, path *field.Path) (errs field.ErrorList) {
if as.Count != nil {
if *as.Count > len(as.Entries) {
errs = append(errs, field.Invalid(path, as, "Count cannot exceed length of entries"))
}
}
if len(as.Entries) == 0 {
errs = append(errs, field.Invalid(path, as, "An entry is required"))
}
entriesPath := path.Child("entries")
for i, e := range as.Entries {
attestorErrors := e.Validate(entriesPath.Index(i))
errs = append(errs, attestorErrors...)
}
return errs
}
func (a *Attestor) Validate(path *field.Path) (errs field.ErrorList) {
if (a.Keys != nil && (a.Certificates != nil || a.Keyless != nil || a.Attestor != nil)) ||
(a.Certificates != nil && (a.Keys != nil || a.Keyless != nil || a.Attestor != nil)) ||
(a.Keyless != nil && (a.Certificates != nil || a.Keys != nil || a.Attestor != nil)) ||
(a.Attestor != nil && (a.Certificates != nil || a.Keys != nil || a.Keyless != nil)) ||
(a.Keys == nil && a.Certificates == nil && a.Keyless == nil && a.Attestor == nil) {
errs = append(errs, field.Invalid(path, a, "keys, certificates, keyless, or a nested attestor is required"))
}
if a.Keys != nil {
staticKeyPath := path.Child("keys")
staticKeyErrors := a.Keys.Validate(staticKeyPath)
errs = append(errs, staticKeyErrors...)
}
if a.Certificates != nil {
certificatesPath := path.Child("certificates")
certificatesErrors := a.Certificates.Validate(certificatesPath)
errs = append(errs, certificatesErrors...)
}
if a.Keyless != nil {
keylessPath := path.Child("keyless")
keylessErrors := a.Keyless.Validate(keylessPath)
errs = append(errs, keylessErrors...)
}
if a.Attestor != nil {
attestorPath := path.Child("attestor")
attestorSet, err := AttestorSetUnmarshal(a.Attestor)
if err != nil {
fieldErr := field.Invalid(attestorPath, a.Attestor, err.Error())
errs = append(errs, fieldErr)
} else {
attestorErrors := validateAttestorSet(attestorSet, attestorPath)
errs = append(errs, attestorErrors...)
}
}
return errs
}
func AttestorSetUnmarshal(o *apiextv1.JSON) (*AttestorSet, error) {
var as AttestorSet
if err := json.Unmarshal(o.Raw, &as); err != nil {
return nil, fmt.Errorf("failed to unmarshal attestor set %s: %w", string(o.Raw), err)
}
return &as, nil
}
func (ska *StaticKeyAttestor) Validate(path *field.Path) (errs field.ErrorList) {
if ska.PublicKeys == "" && ska.KMS == "" && ska.Secret == nil {
errs = append(errs, field.Invalid(path, ska, "A public key, kms key or secret is required"))
}
if ska.PublicKeys != "" {
if _, ok := signatureAlgorithmMap[ska.SignatureAlgorithm]; !ok {
errs = append(errs, field.Invalid(path, ska, "Invalid signature algorithm provided"))
}
}
return errs
}
func (ca *CertificateAttestor) Validate(path *field.Path) (errs field.ErrorList) {
if ca.Certificate == "" && ca.CertificateChain == "" {
errs = append(errs, field.Invalid(path, ca, "cert or certChain required"))
}
return errs
}
func (ka *KeylessAttestor) Validate(path *field.Path) (errs field.ErrorList) {
if ka.Rekor == nil && ka.Roots == "" {
errs = append(errs, field.Invalid(path, ka, "Either Rekor URL or roots are required"))
}
if ka.Rekor != nil && ka.Rekor.URL == "" {
errs = append(errs, field.Invalid(path, ka, "An URL is required"))
}
return errs
}
func (iv *ImageVerification) Convert() *ImageVerification {
if iv.Image == "" && iv.Key == "" && iv.Issuer == "" {
return iv
}
copy := iv.DeepCopy()
copy.Image = ""
copy.Issuer = ""
copy.Subject = ""
copy.Roots = ""
if iv.Image != "" {
copy.ImageReferences = append(copy.ImageReferences, iv.Image)
}
attestorSet := AttestorSet{}
if len(iv.Annotations) > 0 || iv.Key != "" || iv.Issuer != "" {
attestor := Attestor{
Annotations: iv.Annotations,
}
if iv.Key != "" {
attestor.Keys = &StaticKeyAttestor{
PublicKeys: iv.Key,
}
} else if iv.Issuer != "" {
attestor.Keyless = &KeylessAttestor{
Issuer: iv.Issuer,
Subject: iv.Subject,
Roots: iv.Roots,
}
}
attestorSet.Entries = append(attestorSet.Entries, attestor)
if len(iv.Attestations) > 0 {
for i := range iv.Attestations {
copy.Attestations[i].Attestors = append(copy.Attestations[i].Attestors, attestorSet)
}
} else {
copy.Attestors = append(copy.Attestors, attestorSet)
}
}
copy.Attestations = iv.Attestations
return copy
}
package v1
import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// MatchResources is used to specify resource and admission review request data for
// which a policy rule is applicable.
// +kubebuilder:not:={required:{any,all}}
type MatchResources struct {
// Any allows specifying resources which will be ORed
// +optional
Any ResourceFilters `json:"any,omitempty"`
// All allows specifying resources which will be ANDed
// +optional
All ResourceFilters `json:"all,omitempty"`
// UserInfo contains information about the user performing the operation.
// Specifying UserInfo directly under match is being deprecated.
// Please specify under "any" or "all" instead.
// +optional
UserInfo `json:",omitempty"`
// ResourceDescription contains information about the resource being created or modified.
// Requires at least one tag to be specified when under MatchResources.
// Specifying ResourceDescription directly under match is being deprecated.
// Please specify under "any" or "all" instead.
// +optional
ResourceDescription `json:"resources,omitempty"`
}
// GetKinds returns all kinds
func (m *MatchResources) GetKinds() []string {
var kinds []string
kinds = append(kinds, m.ResourceDescription.Kinds...)
for _, value := range m.All {
kinds = append(kinds, value.ResourceDescription.Kinds...)
}
for _, value := range m.Any {
kinds = append(kinds, value.ResourceDescription.Kinds...)
}
return kinds
}
// Validate implements programmatic validation
func (m *MatchResources) Validate(path *field.Path, namespaced bool, clusterResources sets.Set[string]) (errs field.ErrorList) {
if m == nil {
return errs
}
if len(m.Any) > 0 && len(m.All) > 0 {
errs = append(errs, field.Invalid(path, m, "Can't specify any and all together"))
}
anyPath := path.Child("any")
for i, filter := range m.Any {
errs = append(errs, filter.UserInfo.Validate(anyPath.Index(i))...)
errs = append(errs, filter.ResourceDescription.Validate(anyPath.Index(i), namespaced, clusterResources)...)
}
allPath := path.Child("all")
for i, filter := range m.All {
errs = append(errs, filter.UserInfo.Validate(anyPath.Index(i))...)
errs = append(errs, filter.ResourceDescription.Validate(allPath.Index(i), namespaced, clusterResources)...)
}
errs = append(errs, m.UserInfo.Validate(path)...)
errs = append(errs, m.ResourceDescription.Validate(path, namespaced, clusterResources)...)
return errs
}
package v1
import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
// PolicyConditionReady means that the policy is ready
PolicyConditionReady = "Ready"
)
const (
// PolicyReasonSucceeded is the reason set when the policy is ready
PolicyReasonSucceeded = "Succeeded"
// PolicyReasonSucceeded is the reason set when the policy is not ready
PolicyReasonFailed = "Failed"
)
// Deprecated. Policy metrics are now available via the "/metrics" endpoint.
// See: https://kyverno.io/docs/monitoring-kyverno-with-prometheus-metrics/
type PolicyStatus struct {
// Deprecated in favor of Conditions
Ready *bool `json:"ready,omitempty"`
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// +optional
Autogen AutogenStatus `json:"autogen"`
// +optional
RuleCount RuleCountStatus `json:"rulecount"`
// ValidatingAdmissionPolicy contains status information
// +optional
ValidatingAdmissionPolicy ValidatingAdmissionPolicyStatus `json:"validatingadmissionpolicy"`
}
// RuleCountStatus contains four variables which describes counts for
// validate, generate, mutate and verify images rules
type RuleCountStatus struct {
// Count for validate rules in policy
Validate int `json:"validate"`
// Count for generate rules in policy
Generate int `json:"generate"`
// Count for mutate rules in policy
Mutate int `json:"mutate"`
// Count for verify image rules in policy
VerifyImages int `json:"verifyimages"`
}
func (status *PolicyStatus) SetReady(ready bool, message string) {
condition := metav1.Condition{
Type: PolicyConditionReady,
Message: message,
}
if ready {
condition.Status = metav1.ConditionTrue
condition.Reason = PolicyReasonSucceeded
} else {
condition.Status = metav1.ConditionFalse
condition.Reason = PolicyReasonFailed
}
status.Ready = nil
meta.SetStatusCondition(&status.Conditions, condition)
}
// IsReady indicates if the policy is ready to serve the admission request
func (status *PolicyStatus) IsReady() bool {
condition := meta.FindStatusCondition(status.Conditions, PolicyConditionReady)
return condition != nil && condition.Status == metav1.ConditionTrue
}
// AutogenStatus contains autogen status information.
type AutogenStatus struct {
// Rules is a list of Rule instances. It contains auto generated rules added for pod controllers
Rules []Rule `json:"rules,omitempty"`
}
// ValidatingAdmissionPolicy contains status information
type ValidatingAdmissionPolicyStatus struct {
// Generated indicates whether a validating admission policy is generated from the policy or not
Generated bool `json:"generated"`
// Message is a human readable message indicating details about the generation of validating admission policy
// It is an empty string when validating admission policy is successfully generated.
Message string `json:"message"`
}
package v1
import (
"strings"
"github.com/kyverno/kyverno/api/kyverno"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="ADMISSION",type=boolean,JSONPath=".spec.admission"
// +kubebuilder:printcolumn:name="BACKGROUND",type=boolean,JSONPath=".spec.background"
// +kubebuilder:printcolumn:name="READY",type=string,JSONPath=`.status.conditions[?(@.type == "Ready")].status`
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="FAILURE POLICY",type=string,JSONPath=".spec.failurePolicy",priority=1
// +kubebuilder:printcolumn:name="VALIDATE",type=integer,JSONPath=`.status.rulecount.validate`,priority=1
// +kubebuilder:printcolumn:name="MUTATE",type=integer,JSONPath=`.status.rulecount.mutate`,priority=1
// +kubebuilder:printcolumn:name="GENERATE",type=integer,JSONPath=`.status.rulecount.generate`,priority=1
// +kubebuilder:printcolumn:name="VERIFY IMAGES",type=integer,JSONPath=`.status.rulecount.verifyimages`,priority=1
// +kubebuilder:printcolumn:name="MESSAGE",type=string,JSONPath=`.status.conditions[?(@.type == "Ready")].message`
// +kubebuilder:resource:shortName=pol,categories=kyverno
// +kubebuilder:storageversion
// Policy declares validation, mutation, and generation behaviors for matching resources.
// See: https://kyverno.io/docs/writing-policies/ for more information.
type Policy struct {
metav1.TypeMeta `json:",inline,omitempty"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec defines policy behaviors and contains one or more rules.
Spec Spec `json:"spec"`
// +optional
// Deprecated. Policy metrics are available via the metrics endpoint
Status PolicyStatus `json:"status,omitempty"`
}
// HasAutoGenAnnotation checks if a policy has auto-gen annotation
func (p *Policy) HasAutoGenAnnotation() bool {
annotations := p.GetAnnotations()
val, ok := annotations[kyverno.AnnotationAutogenControllers]
if ok && strings.ToLower(val) != "none" {
return true
}
return false
}
// HasMutateOrValidateOrGenerate checks for rule types
func (p *Policy) HasMutateOrValidateOrGenerate() bool {
for _, rule := range p.Spec.Rules {
if rule.HasMutateStandard() || rule.HasValidate() || rule.HasGenerate() {
return true
}
}
return false
}
// HasMutate checks for mutate rule types
func (p *Policy) HasMutate() bool {
return p.Spec.HasMutate()
}
// HasValidate checks for validate rule types
func (p *Policy) HasValidate() bool {
return p.Spec.HasValidate()
}
// HasGenerate checks for generate rule types
func (p *Policy) HasGenerate() bool {
return p.Spec.HasGenerate()
}
// HasVerifyImages checks for image verification rule types
func (p *Policy) HasVerifyImages() bool {
return p.Spec.HasVerifyImages()
}
// AdmissionProcessingEnabled checks if admission is set to true
func (p *Policy) AdmissionProcessingEnabled() bool {
return p.Spec.AdmissionProcessingEnabled()
}
// BackgroundProcessingEnabled checks if background is set to true
func (p *Policy) BackgroundProcessingEnabled() bool {
return p.Spec.BackgroundProcessingEnabled()
}
// GetSpec returns the policy spec
func (p *Policy) GetSpec() *Spec {
return &p.Spec
}
// GetStatus returns the policy status
func (p *Policy) GetStatus() *PolicyStatus {
return &p.Status
}
// IsNamespaced indicates if the policy is namespace scoped
func (p *Policy) IsNamespaced() bool {
return true
}
// IsReady indicates if the policy is ready to serve the admission request
func (p *Policy) IsReady() bool {
return p.Status.IsReady()
}
// Validate implements programmatic validation.
// namespaced means that the policy is bound to a namespace and therefore
// should not filter/generate cluster wide resources.
func (p *Policy) Validate(clusterResources sets.Set[string]) (warnings []string, errs field.ErrorList) {
errs = append(errs, ValidateAutogenAnnotation(field.NewPath("metadata").Child("annotations"), p.GetAnnotations())...)
errs = append(errs, ValidatePolicyName(field.NewPath("name"), p.Name)...)
warning, errors := p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), p.GetNamespace(), clusterResources)
warnings = append(warnings, warning...)
errs = append(errs, errors...)
return warnings, errs
}
func (p *Policy) GetKind() string {
return "Policy"
}
func (p *Policy) CreateDeepCopy() PolicyInterface {
return p.DeepCopy()
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// PolicyList is a list of Policy instances.
type PolicyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Policy `json:"items"`
}
package v1
import (
"fmt"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ResourceDescription contains criteria used to match resources.
// +kubebuilder:not:={required:{name,names}}
type ResourceDescription struct {
// Kinds is a list of resource kinds.
// +optional
Kinds []string `json:"kinds,omitempty"`
// 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"`
// Names are the names of the resources. Each name supports wildcard characters
// "*" (matches zero or many characters) and "?" (at least one character).
// +optional
Names []string `json:"names,omitempty"`
// Namespaces is a list of namespaces names. Each name supports wildcard characters
// "*" (matches zero or many characters) and "?" (at least one character).
// +optional
Namespaces []string `json:"namespaces,omitempty"`
// Annotations is a map of annotations (key-value pairs of type string). Annotation keys
// and values support the wildcard characters "*" (matches zero or many characters) and
// "?" (matches at least one character).
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
// Selector is a label selector. Label keys and values in `matchLabels` support the wildcard
// characters `*` (matches zero or many characters) and `?` (matches one character).
// Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that
// using ["*" : "*"] matches any key and value but does not match an empty label set.
// +optional
Selector *metav1.LabelSelector `json:"selector,omitempty"`
// NamespaceSelector is a label selector for the resource namespace. Label keys and values
// in `matchLabels` support the wildcard characters `*` (matches zero or many characters)
// and `?` (matches one character).Wildcards allows writing label selectors like
// ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but
// does not match an empty label set.
// +optional
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
// Operations can contain values ["CREATE, "UPDATE", "CONNECT", "DELETE"], which are used to match a specific action.
// +optional
Operations []AdmissionOperation `json:"operations,omitempty"`
}
func (r ResourceDescription) IsEmpty() bool {
return len(r.Kinds) == 0 &&
r.Name == "" &&
len(r.Names) == 0 &&
len(r.Namespaces) == 0 &&
len(r.Annotations) == 0 &&
r.Selector == nil &&
r.NamespaceSelector == nil
}
func (r ResourceDescription) GetOperations() []string {
ops := []string{}
for _, op := range r.Operations {
ops = append(ops, string(op))
}
return ops
}
// Validate implements programmatic validation
func (r *ResourceDescription) Validate(path *field.Path, namespaced bool, clusterResources sets.Set[string]) (errs field.ErrorList) {
if r.Name != "" && len(r.Names) > 0 {
errs = append(errs, field.Invalid(path, r, "Both name and names can not be specified together"))
}
if r.Selector != nil && !kubeutils.LabelSelectorContainsWildcard(r.Selector) {
if selector, err := metav1.LabelSelectorAsSelector(r.Selector); err != nil {
errs = append(errs, field.Invalid(path.Child("selector"), r.Selector, err.Error()))
} else {
requirements, _ := selector.Requirements()
if len(requirements) == 0 {
errs = append(errs, field.Invalid(path.Child("selector"), r.Selector, "The requirements are not specified in selector"))
}
}
}
if namespaced {
if len(r.Namespaces) > 0 {
errs = append(errs, field.Forbidden(path.Child("namespaces"), "Filtering namespaces not allowed in namespaced policies"))
}
kindsChild := path.Child("kinds")
for i, kind := range r.Kinds {
if clusterResources.Has(kind) {
errs = append(errs, field.Forbidden(kindsChild.Index(i), fmt.Sprintf("Cluster wide resource '%s' not allowed in namespaced policy", kind)))
}
}
}
return errs
}
package v1
import (
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
)
type ResourceSpec struct {
// APIVersion specifies resource apiVersion.
// +optional
APIVersion string `json:"apiVersion,omitempty"`
// Kind specifies resource kind.
Kind string `json:"kind,omitempty"`
// Namespace specifies resource namespace.
// +optional
Namespace string `json:"namespace,omitempty"`
// Name specifies the resource name.
// +optional
Name string `json:"name,omitempty"`
// UID specifies the resource uid.
// +optional
UID types.UID `json:"uid,omitempty"`
}
func (s ResourceSpec) GetName() string { return s.Name }
func (s ResourceSpec) GetNamespace() string { return s.Namespace }
func (s ResourceSpec) GetKind() string { return s.Kind }
func (s ResourceSpec) GetAPIVersion() string { return s.APIVersion }
func (s ResourceSpec) GetUID() types.UID { return s.UID }
func (s ResourceSpec) GetGroupVersion() (schema.GroupVersion, error) {
return schema.ParseGroupVersion(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 {
// TargetSelector contains the ResourceSpec and a label selector to support selecting with labels.
TargetSelector `json:",omitempty"`
// Context defines variables and data sources that can be used during rule execution.
// +optional
Context []ContextEntry `json:"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
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
RawAnyAllConditions *ConditionsWrapper `json:"preconditions,omitempty"`
}
type TargetSelector struct {
// ResourceSpec contains the target resources to load when mutating existing resources.
ResourceSpec `json:",omitempty"`
// Selector allows you to select target resources with their labels.
// +optional
Selector *metav1.LabelSelector `json:"selector,omitempty"`
}
func (r *TargetResourceSpec) GetAnyAllConditions() any {
if r.RawAnyAllConditions == nil {
return nil
}
return r.RawAnyAllConditions.Conditions
}
func (r *TargetResourceSpec) GetSelector() *metav1.LabelSelector { return r.Selector }
package v1
import (
"encoding/json"
"fmt"
"github.com/kyverno/kyverno/ext/wildcard"
"github.com/kyverno/kyverno/pkg/pss/utils"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
type ImageExtractorConfigs map[string][]ImageExtractorConfig
type ImageExtractorConfig struct {
// Path is the path to the object containing the image field in a custom resource.
// It should be slash-separated. Each slash-separated key must be a valid YAML key or a wildcard '*'.
// Wildcard keys are expanded in case of arrays or objects.
Path string `json:"path"`
// Value is an optional name of the field within 'path' that points to the image URI.
// This is useful when a custom 'key' is also defined.
// +optional
Value string `json:"value,omitempty"`
// Name is the entry the image will be available under 'images.<name>' in the context.
// If this field is not defined, image entries will appear under 'images.custom'.
// +optional
Name string `json:"name,omitempty"`
// Key is an optional name of the field within 'path' that will be used to uniquely identify an image.
// Note - this field MUST be unique.
// +optional
Key string `json:"key,omitempty"`
// JMESPath is an optional JMESPath expression to apply to the image value.
// This is useful when the extracted image begins with a prefix like 'docker://'.
// The 'trim_prefix' function may be used to trim the prefix: trim_prefix(@, 'docker://').
// Note - Image digest mutation may not be used when applying a JMESPAth to an image.
// +optional
JMESPath string `json:"jmesPath,omitempty"`
}
// Rule defines a validation, mutation, or generation control for matching resources.
// Each rules contains a match declaration to select resources, and an optional exclude
// declaration to specify which resources to exclude.
type Rule struct {
// Name is a label to identify the rule, It must be unique within the policy.
// +kubebuilder:validation:MaxLength=63
Name string `json:"name"`
// Context defines variables and data sources that can be used during rule execution.
// +optional
Context []ContextEntry `json:"context,omitempty"`
// ReportProperties are the additional properties from the rule that will be added to the policy report result
// +optional
ReportProperties map[string]string `json:"reportProperties,omitempty"`
// MatchResources defines when this policy rule should be applied. The match
// criteria can include resource information (e.g. kind, name, namespace, labels)
// and admission review request information like the user name or role.
// At least one kind is required.
MatchResources MatchResources `json:"match"`
// ExcludeResources defines when this policy rule should not be applied. The exclude
// criteria can include resource information (e.g. kind, name, namespace, labels)
// and admission review request information like the name or role.
// +optional
ExcludeResources *MatchResources `json:"exclude,omitempty"`
// ImageExtractors defines a mapping from kinds to ImageExtractorConfigs.
// This config is only valid for verifyImages rules.
// +optional
ImageExtractors ImageExtractorConfigs `json:"imageExtractors,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
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
RawAnyAllConditions *ConditionsWrapper `json:"preconditions,omitempty"`
// CELPreconditions are used to determine if a policy rule should be applied by evaluating a
// set of CEL conditions. It can only be used with the validate.cel subrule
// +optional
CELPreconditions []admissionregistrationv1.MatchCondition `json:"celPreconditions,omitempty"`
// Mutation is used to modify matching resources.
// +optional
Mutation *Mutation `json:"mutate,omitempty"`
// Validation is used to validate matching resources.
// +optional
Validation *Validation `json:"validate,omitempty"`
// Generation is used to create new resources.
// +optional
Generation *Generation `json:"generate,omitempty"`
// VerifyImages is used to verify image signatures and mutate them to add a digest
// +optional
VerifyImages []ImageVerification `json:"verifyImages,omitempty"`
// SkipBackgroundRequests bypasses admission requests that are sent by the background controller.
// The default value is set to "true", it must be set to "false" to apply
// generate and mutateExisting rules to those requests.
// +kubebuilder:default=true
// +kubebuilder:validation:Optional
SkipBackgroundRequests *bool `json:"skipBackgroundRequests,omitempty"`
}
// HasMutate checks for mutate rule
func (r *Rule) HasMutate() bool {
return r.Mutation != nil && !datautils.DeepEqual(*r.Mutation, Mutation{})
}
// HasMutateStandard checks for standard admission mutate rule
func (r *Rule) HasMutateStandard() bool {
if r.HasMutateExisting() {
return false
}
return r.HasMutate()
}
// HasMutateExisting checks if the mutate rule applies to existing resources
func (r *Rule) HasMutateExisting() bool {
return r.Mutation != nil && r.Mutation.Targets != nil
}
// HasVerifyImages checks for verifyImages rule
func (r *Rule) HasVerifyImages() bool {
for _, verifyImage := range r.VerifyImages {
if !datautils.DeepEqual(verifyImage, ImageVerification{}) {
return true
}
}
return false
}
// HasValidateImageVerification checks for verifyImages rule has Validation
func (r *Rule) HasValidateImageVerification() bool {
if !r.HasVerifyImages() {
return false
}
for _, verifyImage := range r.VerifyImages {
if !datautils.DeepEqual(verifyImage.Validation, ValidateImageVerification{}) {
return true
}
}
return false
}
// HasVerifyImageChecks checks whether the verifyImages rule has validation checks
func (r *Rule) HasVerifyImageChecks() bool {
for _, verifyImage := range r.VerifyImages {
if verifyImage.VerifyDigest || verifyImage.Required {
return true
}
}
return false
}
// HasVerifyManifests checks for validate.manifests rule
func (r Rule) HasVerifyManifests() bool {
return r.Validation != nil && r.Validation.Manifests != nil && len(r.Validation.Manifests.Attestors) != 0
}
// HasValidatePodSecurity checks for validate.podSecurity rule
func (r Rule) HasValidatePodSecurity() bool {
return r.Validation != nil && r.Validation.PodSecurity != nil && !datautils.DeepEqual(*r.Validation.PodSecurity, PodSecurity{})
}
// HasValidateCEL checks for validate.cel rule
func (r *Rule) HasValidateCEL() bool {
return r.Validation != nil && r.Validation.CEL != nil && !datautils.DeepEqual(*r.Validation.CEL, CEL{})
}
// HasValidateAssert checks for validate.assert rule
func (r *Rule) HasValidateAssert() bool {
return r.Validation != nil && !datautils.DeepEqual(r.Validation.Assert, AssertionTree{})
}
// HasValidate checks for validate rule
func (r *Rule) HasValidate() bool {
return r.Validation != nil && !datautils.DeepEqual(*r.Validation, Validation{})
}
// HasValidateAllowExistingViolations() checks for allowExisitingViolations under validate rule
func (r *Rule) HasValidateAllowExistingViolations() bool {
allowExisitingViolations := true
if r.Validation != nil && r.Validation.AllowExistingViolations != nil {
allowExisitingViolations = *r.Validation.AllowExistingViolations
}
return allowExisitingViolations
}
// HasGenerate checks for generate rule
func (r *Rule) HasGenerate() bool {
return r.Generation != nil && !datautils.DeepEqual(*r.Generation, Generation{})
}
func (r *Rule) IsPodSecurity() bool {
return r.Validation != nil && r.Validation.PodSecurity != nil
}
func (r *Rule) GetSyncAndOrphanDownstream() (sync bool, orphanDownstream bool) {
if !r.HasGenerate() {
return
}
return r.Generation.Synchronize, r.Generation.OrphanDownstreamOnPolicyDelete
}
func (r *Rule) GetAnyAllConditions() any {
if r.RawAnyAllConditions == nil {
return nil
}
return r.RawAnyAllConditions.Conditions
}
func (r *Rule) SetAnyAllConditions(in any) {
var new *ConditionsWrapper
if in != nil {
new = &ConditionsWrapper{in}
}
r.RawAnyAllConditions = new
}
// ValidateRuleType checks only one type of rule is defined per rule
func (r *Rule) ValidateRuleType(path *field.Path) (errs field.ErrorList) {
ruleTypes := []bool{r.HasMutate(), r.HasValidate(), r.HasGenerate(), r.HasVerifyImages()}
count := 0
for _, v := range ruleTypes {
if v {
count++
}
}
if count == 0 {
errs = append(errs, field.Invalid(path, r, fmt.Sprintf("No operation defined in the rule '%s'.(supported operations: mutate,validate,generate,verifyImages)", r.Name)))
} else if count != 1 {
errs = append(errs, field.Invalid(path, r, fmt.Sprintf("Multiple operations defined in the rule '%s', only one operation (mutate,validate,generate,verifyImages) is allowed per rule", r.Name)))
}
if r.ImageExtractors != nil && !r.HasVerifyImages() {
errs = append(errs, field.Invalid(path.Child("imageExtractors"), r, fmt.Sprintf("Invalid rule spec for rule '%s', imageExtractors can only be defined for verifyImages rule", r.Name)))
}
return errs
}
// ValidateMatchExcludeConflict checks if the resultant of match and exclude block is not an empty set
func (r *Rule) ValidateMatchExcludeConflict(path *field.Path) (errs field.ErrorList) {
if r.ExcludeResources == nil {
return errs
}
if len(r.ExcludeResources.All) > 0 || len(r.MatchResources.All) > 0 {
return errs
}
// if both have any then no resource should be common
if len(r.MatchResources.Any) > 0 && len(r.ExcludeResources.Any) > 0 {
for _, rmr := range r.MatchResources.Any {
for _, rer := range r.ExcludeResources.Any {
if datautils.DeepEqual(rmr, rer) {
return append(errs, field.Invalid(path, r, "Rule is matching an empty set"))
}
}
}
return errs
}
if datautils.DeepEqual(*r.ExcludeResources, MatchResources{}) {
return errs
}
excludeRoles := sets.New(r.ExcludeResources.Roles...)
excludeClusterRoles := sets.New(r.ExcludeResources.ClusterRoles...)
excludeKinds := sets.New(r.ExcludeResources.Kinds...)
excludeNamespaces := sets.New(r.ExcludeResources.Namespaces...)
excludeSubjects := sets.New[string]()
for _, subject := range r.ExcludeResources.Subjects {
subjectRaw, _ := json.Marshal(subject)
excludeSubjects.Insert(string(subjectRaw))
}
excludeSelectorMatchExpressions := sets.New[string]()
if r.ExcludeResources.Selector != nil {
for _, matchExpression := range r.ExcludeResources.Selector.MatchExpressions {
matchExpressionRaw, _ := json.Marshal(matchExpression)
excludeSelectorMatchExpressions.Insert(string(matchExpressionRaw))
}
}
excludeNamespaceSelectorMatchExpressions := sets.New[string]()
if r.ExcludeResources.NamespaceSelector != nil {
for _, matchExpression := range r.ExcludeResources.NamespaceSelector.MatchExpressions {
matchExpressionRaw, _ := json.Marshal(matchExpression)
excludeNamespaceSelectorMatchExpressions.Insert(string(matchExpressionRaw))
}
}
if len(excludeRoles) > 0 {
if len(r.MatchResources.Roles) == 0 || !excludeRoles.HasAll(r.MatchResources.Roles...) {
return errs
}
}
if len(excludeClusterRoles) > 0 {
if len(r.MatchResources.ClusterRoles) == 0 || !excludeClusterRoles.HasAll(r.MatchResources.ClusterRoles...) {
return errs
}
}
if len(excludeSubjects) > 0 {
if len(r.MatchResources.Subjects) == 0 {
return errs
}
for _, subject := range r.MatchResources.UserInfo.Subjects {
subjectRaw, _ := json.Marshal(subject)
if !excludeSubjects.Has(string(subjectRaw)) {
return errs
}
}
}
if r.ExcludeResources.Name != "" {
if !wildcard.Match(r.ExcludeResources.Name, r.MatchResources.Name) {
return errs
}
}
if len(r.ExcludeResources.Names) > 0 {
excludeSlice := r.ExcludeResources.Names
matchSlice := r.MatchResources.Names
// if exclude block has something and match doesn't it means we
// have a non empty set
if len(r.MatchResources.Names) == 0 {
return errs
}
// if *any* name in match and exclude conflicts
// we want user to fix that
for _, matchName := range matchSlice {
for _, excludeName := range excludeSlice {
if wildcard.Match(excludeName, matchName) {
return append(errs, field.Invalid(path, r, "Rule is matching an empty set"))
}
}
}
return errs
}
if len(excludeNamespaces) > 0 {
if len(r.MatchResources.Namespaces) == 0 || !excludeNamespaces.HasAll(r.MatchResources.Namespaces...) {
return errs
}
}
if len(excludeKinds) > 0 {
if len(r.MatchResources.Kinds) == 0 || !excludeKinds.HasAll(r.MatchResources.Kinds...) {
return errs
}
}
if r.MatchResources.Selector != nil && r.ExcludeResources.Selector != nil {
if len(excludeSelectorMatchExpressions) > 0 {
if len(r.MatchResources.Selector.MatchExpressions) == 0 {
return errs
}
for _, matchExpression := range r.MatchResources.Selector.MatchExpressions {
matchExpressionRaw, _ := json.Marshal(matchExpression)
if !excludeSelectorMatchExpressions.Has(string(matchExpressionRaw)) {
return errs
}
}
}
if len(r.ExcludeResources.Selector.MatchLabels) > 0 {
if len(r.MatchResources.Selector.MatchLabels) == 0 {
return errs
}
for label, value := range r.MatchResources.Selector.MatchLabels {
if r.ExcludeResources.Selector.MatchLabels[label] != value {
return errs
}
}
}
}
if r.MatchResources.NamespaceSelector != nil && r.ExcludeResources.NamespaceSelector != nil {
if len(excludeNamespaceSelectorMatchExpressions) > 0 {
if len(r.MatchResources.NamespaceSelector.MatchExpressions) == 0 {
return errs
}
for _, matchExpression := range r.MatchResources.NamespaceSelector.MatchExpressions {
matchExpressionRaw, _ := json.Marshal(matchExpression)
if !excludeNamespaceSelectorMatchExpressions.Has(string(matchExpressionRaw)) {
return errs
}
}
}
if len(r.ExcludeResources.NamespaceSelector.MatchLabels) > 0 {
if len(r.MatchResources.NamespaceSelector.MatchLabels) == 0 {
return errs
}
for label, value := range r.MatchResources.NamespaceSelector.MatchLabels {
if r.ExcludeResources.NamespaceSelector.MatchLabels[label] != value {
return errs
}
}
}
}
if (r.MatchResources.Selector == nil && r.ExcludeResources.Selector != nil) ||
(r.MatchResources.Selector != nil && r.ExcludeResources.Selector == nil) {
return errs
}
if (r.MatchResources.NamespaceSelector == nil && r.ExcludeResources.NamespaceSelector != nil) ||
(r.MatchResources.NamespaceSelector != nil && r.ExcludeResources.NamespaceSelector == nil) {
return errs
}
if r.MatchResources.Annotations != nil && r.ExcludeResources.Annotations != nil {
if !datautils.DeepEqual(r.MatchResources.Annotations, r.ExcludeResources.Annotations) {
return errs
}
}
if (r.MatchResources.Annotations == nil && r.ExcludeResources.Annotations != nil) ||
(r.MatchResources.Annotations != nil && r.ExcludeResources.Annotations == nil) {
return errs
}
return append(errs, field.Invalid(path, r, "Rule is matching an empty set"))
}
// ValidateMutationRuleTargetNamespace checks if the targets are scoped to the policy's namespace
func (r *Rule) ValidateMutationRuleTargetNamespace(path *field.Path, namespaced bool, policyNamespace string) (errs field.ErrorList) {
if r.HasMutateExisting() && namespaced {
for idx, target := range r.Mutation.Targets {
if target.Namespace != "" && target.Namespace != policyNamespace {
errs = append(errs, field.Invalid(path.Child("targets").Index(idx).Child("namespace"), target.Namespace, "This field can be ignored or should have value of the namespace where the policy is being created"))
}
}
}
return errs
}
func (r *Rule) ValidatePSaControlNames(path *field.Path) (errs field.ErrorList) {
if r.IsPodSecurity() {
podSecurity := r.Validation.PodSecurity
forbiddenControls := []string{}
if podSecurity.Level == "baseline" {
forbiddenControls = utils.PSS_restricted_control_names
}
for idx, exclude := range podSecurity.Exclude {
errs = append(errs, exclude.Validate(path.Child("podSecurity").Child("exclude").Index(idx))...)
if containsString([]string{"Seccomp", "Capabilities"}, exclude.ControlName) {
continue
}
if containsString(forbiddenControls, exclude.ControlName) {
errs = append(errs, field.Invalid(path.Child("podSecurity").Child("exclude").Index(idx).Child("controlName"), exclude.ControlName, "Invalid control name defined at the given level"))
}
}
}
return errs
}
func (r *Rule) ValidateGenerate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (warnings []string, errs field.ErrorList) {
if !r.HasGenerate() {
return nil, nil
}
return r.Generation.Validate(path, namespaced, policyNamespace, clusterResources)
}
// Validate implements programmatic validation
func (r *Rule) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (warnings []string, errs field.ErrorList) {
errs = append(errs, r.ValidateRuleType(path)...)
errs = append(errs, r.ValidateMatchExcludeConflict(path)...)
errs = append(errs, r.MatchResources.Validate(path.Child("match"), namespaced, clusterResources)...)
errs = append(errs, r.ExcludeResources.Validate(path.Child("exclude"), namespaced, clusterResources)...)
errs = append(errs, r.ValidateMutationRuleTargetNamespace(path, namespaced, policyNamespace)...)
errs = append(errs, r.ValidatePSaControlNames(path)...)
warning, errors := r.ValidateGenerate(path, namespaced, policyNamespace, clusterResources)
warnings = append(warnings, warning...)
errs = append(errs, errors...)
return warnings, errs
}
package v1
import (
"context"
"fmt"
"github.com/kyverno/kyverno/pkg/toggle"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ValidationFailureAction defines the policy validation failure action
type ValidationFailureAction string
// Policy Reporting Modes
const (
// auditOld doesn't block the request on failure
// DEPRECATED: use Audit instead
auditOld ValidationFailureAction = "audit"
// enforceOld blocks the request on failure
// DEPRECATED: use Enforce instead
enforceOld ValidationFailureAction = "enforce"
// Enforce blocks the request on failure
Enforce ValidationFailureAction = "Enforce"
// Audit doesn't block the request on failure
Audit ValidationFailureAction = "Audit"
)
func (a ValidationFailureAction) Enforce() bool {
return a == Enforce || a == enforceOld
}
func (a ValidationFailureAction) Audit() bool {
return !a.Enforce()
}
func (a ValidationFailureAction) IsValid() bool {
return a == enforceOld || a == auditOld || a == Enforce || a == Audit
}
type ValidationFailureActionOverride struct {
// +kubebuilder:validation:Enum=audit;enforce;Audit;Enforce
Action ValidationFailureAction `json:"action,omitempty"`
Namespaces []string `json:"namespaces,omitempty"`
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
}
// Spec contains a list of Rule instances and other policy controls.
type Spec struct {
// Rules is a list of Rule instances. A Policy contains multiple rules and
// each rule can validate, mutate, or generate resources.
Rules []Rule `json:"rules,omitempty"`
// ApplyRules controls how rules in a policy are applied. Rule are processed in
// the order of declaration. When set to `One` processing stops after a rule has
// been applied i.e. the rule matches and results in a pass, fail, or error. When
// set to `All` all rules in the policy are processed. The default is `All`.
// +optional
ApplyRules *ApplyRulesType `json:"applyRules,omitempty"`
// Deprecated, use failurePolicy under the webhookConfiguration instead.
FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty"`
// Deprecated, use validationFailureAction under the validate rule instead.
// +kubebuilder:validation:Enum=audit;enforce;Audit;Enforce
// +kubebuilder:default=Audit
ValidationFailureAction ValidationFailureAction `json:"validationFailureAction,omitempty"`
// Deprecated, use validationFailureActionOverrides under the validate rule instead.
ValidationFailureActionOverrides []ValidationFailureActionOverride `json:"validationFailureActionOverrides,omitempty"`
// EmitWarning enables API response warnings for mutate policy rules or validate policy rules with validationFailureAction set to Audit.
// Enabling this option will extend admission request processing times. The default value is "false".
// +optional
// +kubebuilder:default=false
EmitWarning *bool `json:"emitWarning,omitempty"`
// Admission controls if rules are applied during admission.
// Optional. Default value is "true".
// +optional
// +kubebuilder:default=true
Admission *bool `json:"admission,omitempty"`
// Background controls if rules are applied to existing resources during a background scan.
// Optional. Default value is "true". The value must be set to "false" if the policy rule
// uses variables that are only available in the admission review request (e.g. user name).
// +optional
// +kubebuilder:default=true
Background *bool `json:"background,omitempty"`
// Deprecated.
SchemaValidation *bool `json:"schemaValidation,omitempty"`
// Deprecated, use webhookTimeoutSeconds under webhookConfiguration instead.
WebhookTimeoutSeconds *int32 `json:"webhookTimeoutSeconds,omitempty"`
// Deprecated, use mutateExistingOnPolicyUpdate under the mutate rule instead
// +optional
MutateExistingOnPolicyUpdate bool `json:"mutateExistingOnPolicyUpdate,omitempty"`
// Deprecated, use generateExisting instead
// +optional
GenerateExistingOnPolicyUpdate *bool `json:"generateExistingOnPolicyUpdate,omitempty"`
// Deprecated, use generateExisting under the generate rule instead
// +optional
GenerateExisting bool `json:"generateExisting,omitempty"`
// UseServerSideApply controls whether to use server-side apply for generate rules
// If is set to "true" create & update for generate rules will use apply instead of create/update.
// Defaults to "false" if not specified.
// +optional
UseServerSideApply bool `json:"useServerSideApply,omitempty"`
// WebhookConfiguration specifies the custom configuration for Kubernetes admission webhookconfiguration.
// +optional
WebhookConfiguration *WebhookConfiguration `json:"webhookConfiguration,omitempty"`
}
func (s *Spec) CustomWebhookMatchConditions() bool {
return s.WebhookConfiguration != nil && len(s.WebhookConfiguration.MatchConditions) != 0
}
func (s *Spec) SetRules(rules []Rule) {
s.Rules = rules
}
// HasMutateOrValidateOrGenerate checks for rule types
func (s *Spec) HasMutateOrValidateOrGenerate() bool {
for _, rule := range s.Rules {
if rule.HasMutate() || rule.HasValidate() || rule.HasGenerate() {
return true
}
}
return false
}
// HasMutate checks for mutate rule types
func (s *Spec) HasMutate() bool {
for _, rule := range s.Rules {
if rule.HasMutate() {
return true
}
}
return false
}
// HasMutateStandard checks for standard admission mutate rule
func (s *Spec) HasMutateStandard() bool {
for _, rule := range s.Rules {
if rule.HasMutateStandard() {
return true
}
}
return false
}
// HasMutateExisting checks for mutate existing rule types
func (s *Spec) HasMutateExisting() bool {
for _, rule := range s.Rules {
if rule.HasMutateExisting() {
return true
}
}
return false
}
// HasValidate checks for validate rule types
func (s *Spec) HasValidate() bool {
for _, rule := range s.Rules {
if rule.HasValidate() {
return true
}
}
return false
}
// HasValidateEnforce checks if the policy has any validate rules with enforce action
func (s *Spec) HasValidateEnforce() bool {
for _, rule := range s.Rules {
if rule.HasValidate() {
action := rule.Validation.FailureAction
if action != nil && action.Enforce() {
return true
}
}
}
return s.ValidationFailureAction.Enforce()
}
// HasGenerate checks for generate rule types
func (s *Spec) HasGenerate() bool {
for _, rule := range s.Rules {
if rule.HasGenerate() {
return true
}
}
return false
}
// HasVerifyImageChecks checks for image verification rules invoked during resource validation
func (s *Spec) HasVerifyImageChecks() bool {
for _, rule := range s.Rules {
if rule.HasVerifyImageChecks() {
return true
}
}
return false
}
// HasVerifyImages checks for image verification rules invoked during resource mutation
func (s *Spec) HasVerifyImages() bool {
for _, rule := range s.Rules {
if rule.HasVerifyImages() {
return true
}
}
return false
}
// HasVerifyManifests checks for image verification rules invoked during resource mutation
func (s *Spec) HasVerifyManifests() bool {
for _, rule := range s.Rules {
if rule.HasVerifyManifests() {
return true
}
}
return false
}
// AdmissionProcessingEnabled checks if admission is set to true
func (s *Spec) AdmissionProcessingEnabled() bool {
if s.Admission == nil {
return true
}
return *s.Admission
}
// BackgroundProcessingEnabled checks if background is set to true
func (s *Spec) BackgroundProcessingEnabled() bool {
if s.Background == nil {
return true
}
return *s.Background
}
// GetMutateExistingOnPolicyUpdate returns true if any of the rules have MutateExistingOnPolicyUpdate set to true
func (s *Spec) GetMutateExistingOnPolicyUpdate() bool {
for _, rule := range s.Rules {
if rule.HasMutate() {
isMutateExisting := rule.Mutation.MutateExistingOnPolicyUpdate
if isMutateExisting != nil && *isMutateExisting {
return true
}
}
}
return s.MutateExistingOnPolicyUpdate
}
// IsGenerateExisting returns true if any of the generate rules has generateExisting set to true
func (s *Spec) IsGenerateExisting() bool {
for _, rule := range s.Rules {
if rule.HasGenerate() {
isGenerateExisting := rule.Generation.GenerateExisting
if isGenerateExisting != nil && *isGenerateExisting {
return true
}
}
}
return s.GenerateExisting
}
// GetFailurePolicy returns the failure policy to be applied
func (s *Spec) GetFailurePolicy(ctx context.Context) FailurePolicyType {
if toggle.FromContext(ctx).ForceFailurePolicyIgnore() {
return Ignore
} else if s.WebhookConfiguration != nil && s.WebhookConfiguration.FailurePolicy != nil {
return *s.WebhookConfiguration.FailurePolicy
} else if s.FailurePolicy != nil {
return *s.FailurePolicy
}
return Fail
}
func (s *Spec) GetWebhookTimeoutSeconds() *int32 {
if s.WebhookConfiguration != nil && s.WebhookConfiguration.TimeoutSeconds != nil {
return s.WebhookConfiguration.TimeoutSeconds
}
if s.WebhookTimeoutSeconds != nil {
return s.WebhookTimeoutSeconds
}
return nil
}
// GetMatchConditions returns matchConditions in webhookConfiguration
func (s *Spec) GetMatchConditions() []admissionregistrationv1.MatchCondition {
if s.WebhookConfiguration != nil {
return s.WebhookConfiguration.MatchConditions
}
return nil
}
// GetApplyRules returns the apply rules type
func (s *Spec) GetApplyRules() ApplyRulesType {
if s.ApplyRules == nil {
return ApplyAll
}
return *s.ApplyRules
}
func (s *Spec) GetRules() []Rule {
return s.Rules
}
// ValidateRuleNames checks if the rule names are unique across a policy
func (s *Spec) ValidateRuleNames(path *field.Path) (errs field.ErrorList) {
names := sets.New[string]()
for i, rule := range s.Rules {
rulePath := path.Index(i)
if names.Has(rule.Name) {
errs = append(errs, field.Invalid(rulePath.Child("name"), rule, fmt.Sprintf(`Duplicate rule name: '%s'`, rule.Name)))
}
names.Insert(rule.Name)
}
return errs
}
// ValidateRules implements programmatic validation of Rules
func (s *Spec) ValidateRules(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (warnings []string, errs field.ErrorList) {
errs = append(errs, s.ValidateRuleNames(path)...)
for i, rule := range s.Rules {
warning, errors := rule.Validate(path.Index(i), namespaced, policyNamespace, clusterResources)
warnings = append(warnings, warning...)
errs = append(errs, errors...)
}
return warnings, errs
}
func (s *Spec) validateDeprecatedFields(path *field.Path) (errs field.ErrorList) {
if s.WebhookTimeoutSeconds != nil && s.WebhookConfiguration != nil && s.WebhookConfiguration.TimeoutSeconds != nil {
errs = append(errs, field.Forbidden(path.Child("webhookTimeoutSeconds"), "remove the deprecated field and use spec.webhookConfiguration.timeoutSeconds instead"))
}
if s.FailurePolicy != nil && s.WebhookConfiguration != nil && s.WebhookConfiguration.FailurePolicy != nil {
errs = append(errs, field.Forbidden(path.Child("failurePolicy"), "remove the deprecated field and use spec.webhookConfiguration.failurePolicy instead"))
}
if s.GenerateExistingOnPolicyUpdate != nil {
errs = append(errs, field.Forbidden(path.Child("generateExistingOnPolicyUpdate"), "remove the deprecated field and use spec.generate[*].generateExisting instead"))
}
return errs
}
func (s *Spec) validateMutateTargets(path *field.Path) (errs field.ErrorList) {
for i, rule := range s.Rules {
if !rule.HasMutate() {
continue
}
mutateExisting := rule.Mutation.MutateExistingOnPolicyUpdate
if s.MutateExistingOnPolicyUpdate || (mutateExisting != nil && *mutateExisting) {
if len(rule.Mutation.Targets) == 0 {
errs = append(errs, field.Forbidden(path.Child("mutateExistingOnPolicyUpdate"), fmt.Sprintf("rules[%v].mutate.targets has to be specified when mutateExistingOnPolicyUpdate is set", i)))
}
}
}
return errs
}
// Validate implements programmatic validation
func (s *Spec) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (warnings []string, errs field.ErrorList) {
if err := s.validateDeprecatedFields(path); err != nil {
errs = append(errs, err...)
}
if err := s.validateMutateTargets(path); err != nil {
errs = append(errs, err...)
}
if s.WebhookTimeoutSeconds != nil && (*s.WebhookTimeoutSeconds < 1 || *s.WebhookTimeoutSeconds > 30) {
errs = append(errs, field.Invalid(path.Child("webhookTimeoutSeconds"), s.WebhookTimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
}
if s.WebhookConfiguration != nil && s.WebhookConfiguration.TimeoutSeconds != nil && (*s.WebhookConfiguration.TimeoutSeconds < 1 || *s.WebhookConfiguration.TimeoutSeconds > 30) {
errs = append(errs, field.Invalid(path.Child("webhookConfiguration.timeoutSeconds"), s.WebhookConfiguration.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
}
warning, errors := s.ValidateRules(path.Child("rules"), namespaced, policyNamespace, clusterResources)
warnings = append(warnings, warning...)
errs = append(errs, errors...)
if namespaced && len(s.ValidationFailureActionOverrides) > 0 {
errs = append(errs, field.Forbidden(path.Child("validationFailureActionOverrides"), "Use of validationFailureActionOverrides is supported only with ClusterPolicy"))
}
return warnings, errs
}
package v1
import (
"fmt"
"strings"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// UserInfo contains information about the user performing the operation.
type UserInfo struct {
// Roles is the list of namespaced role names for the user.
// +optional
Roles []string `json:"roles,omitempty"`
// ClusterRoles is the list of cluster-wide role names for the user.
// +optional
ClusterRoles []string `json:"clusterRoles,omitempty"`
// Subjects is the list of subject names like users, user groups, and service accounts.
// +optional
Subjects []rbacv1.Subject `json:"subjects,omitempty"`
}
func (r UserInfo) IsEmpty() bool {
return len(r.Roles) == 0 &&
len(r.ClusterRoles) == 0 &&
len(r.Subjects) == 0
}
// ValidateSubjects implements programmatic validation of Subjects
func (u *UserInfo) ValidateSubjects(path *field.Path) (errs field.ErrorList) {
for index, subject := range u.Subjects {
entry := path.Index(index)
if subject.Kind == "" {
errs = append(errs, field.Required(entry.Child("kind"), ""))
} else if subject.Kind != rbacv1.GroupKind && subject.Kind != rbacv1.ServiceAccountKind && subject.Kind != rbacv1.UserKind {
errs = append(errs, field.Invalid(entry.Child("kind"), subject.Kind, "kind must be 'User', 'Group', or 'ServiceAccount'"))
}
if subject.Name == "" {
errs = append(errs, field.Required(entry.Child("name"), ""))
}
if subject.Kind == rbacv1.ServiceAccountKind && subject.Namespace == "" {
errs = append(errs, field.Required(entry.Child("namespace"), fmt.Sprintf("namespace is required when Kind is %s", rbacv1.ServiceAccountKind)))
}
}
return errs
}
// ValidateRoles implements programmatic validation of Roles
func (u *UserInfo) ValidateRoles(path *field.Path) (errs field.ErrorList) {
for i, r := range u.Roles {
role := strings.Split(r, ":")
if len(role) != 2 {
errs = append(errs, field.Invalid(path.Index(i), r, "Role is expected to be in namespace:name format"))
}
}
return errs
}
// ValidateNoUserInfo verifies that no user info is used
func (u *UserInfo) ValidateNoUserInfo(path *field.Path) (errs field.ErrorList) {
if u != nil {
if len(u.Roles) != 0 {
errs = append(errs, field.Forbidden(path.Child("roles"), "Usage of user info is forbidden"))
}
if len(u.ClusterRoles) != 0 {
errs = append(errs, field.Forbidden(path.Child("clusterRoles"), "Usage of user info is forbidden"))
}
if len(u.Subjects) != 0 {
errs = append(errs, field.Forbidden(path.Child("subjects"), "Usage of user info is forbidden"))
}
}
return errs
}
// Validate implements programmatic validation
func (u *UserInfo) Validate(path *field.Path) (errs field.ErrorList) {
errs = append(errs, u.ValidateSubjects(path.Child("subjects"))...)
errs = append(errs, u.ValidateRoles(path.Child("roles"))...)
return errs
}
package v1
import (
"github.com/kyverno/kyverno/api/kyverno"
log "github.com/kyverno/kyverno/pkg/logging"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func FromJSON(in *apiextv1.JSON) apiextensions.JSON {
var out apiextensions.JSON
if err := apiextv1.Convert_v1_JSON_To_apiextensions_JSON(in, &out, nil); err != nil {
log.Error(err, "failed to convert JSON to interface")
}
return out
}
func ToJSON(in apiextensions.JSON) *apiextv1.JSON {
if in == nil {
return nil
}
var out apiextv1.JSON
if err := apiextv1.Convert_apiextensions_JSON_To_v1_JSON(&in, &out, nil); err != nil {
log.Error(err, "failed to convert interface to JSON")
}
return &out
}
// ValidatePolicyName validates policy name
func ValidateAutogenAnnotation(path *field.Path, annotations map[string]string) (errs field.ErrorList) {
value, ok := annotations[kyverno.AnnotationAutogenControllers]
if ok {
if value == "all" {
errs = append(errs, field.Forbidden(path, "Autogen annotation does not support 'all' anymore, remove the annotation or set it to a valid value"))
}
}
return errs
}
// ValidatePolicyName validates policy name
func ValidatePolicyName(path *field.Path, name string) (errs field.ErrorList) {
// policy name is stored in the label of the report change request
if len(name) > 63 {
errs = append(errs, field.TooLong(path, name, 63))
}
return errs
}
func containsString(list []string, key string) bool {
for _, val := range list {
if val == key {
return true
}
}
return false
}
package v1
import (
"encoding/json"
"fmt"
"github.com/jinzhu/copier"
)
// ForEachValidationWrapper contains a list of ForEach descriptors.
// +k8s:deepcopy-gen=false
type ForEachValidationWrapper struct {
// Item is a descriptor on how to iterate over the list of items.
// +optional
Items []ForEachValidation `json:"-"`
}
func (in *ForEachValidationWrapper) DeepCopyInto(out *ForEachValidationWrapper) {
if err := copier.Copy(out, in); err != nil {
panic("deep copy failed")
}
}
func (in *ForEachValidationWrapper) DeepCopy() *ForEachValidationWrapper {
if in == nil {
return nil
}
out := new(ForEachValidationWrapper)
in.DeepCopyInto(out)
return out
}
func (a *ForEachValidationWrapper) MarshalJSON() ([]byte, error) {
return json.Marshal(a.Items)
}
func (a *ForEachValidationWrapper) UnmarshalJSON(data []byte) error {
var res []ForEachValidation
if err := json.Unmarshal(data, &res); err != nil {
return err
}
a.Items = res
return nil
}
// ForEachMutationWrapper contains a list of ForEach descriptors.
// +k8s:deepcopy-gen=false
type ForEachMutationWrapper struct {
// Item is a descriptor on how to iterate over the list of items.
// +optional
Items []ForEachMutation `json:"-"`
}
func (in *ForEachMutationWrapper) DeepCopyInto(out *ForEachMutationWrapper) {
if err := copier.Copy(out, in); err != nil {
panic("deep copy failed")
}
}
func (in *ForEachMutationWrapper) DeepCopy() *ForEachMutationWrapper {
if in == nil {
return nil
}
out := new(ForEachMutationWrapper)
in.DeepCopyInto(out)
return out
}
func (a *ForEachMutationWrapper) MarshalJSON() ([]byte, error) {
return json.Marshal(a.Items)
}
func (a *ForEachMutationWrapper) UnmarshalJSON(data []byte) error {
var res []ForEachMutation
if err := json.Unmarshal(data, &res); err != nil {
return err
}
a.Items = res
return nil
}
// ConditionsWrapper contains either the deprecated list of Conditions or the new AnyAll Conditions.
// +k8s:deepcopy-gen=false
type ConditionsWrapper struct {
// Conditions is a list of conditions that must be satisfied for the rule to be applied.
// +optional
Conditions any `json:"-"`
}
func (in *ConditionsWrapper) DeepCopyInto(out *ConditionsWrapper) {
if err := copier.Copy(out, in); err != nil {
panic("deep copy failed")
}
}
func (in *ConditionsWrapper) DeepCopy() *ConditionsWrapper {
if in == nil {
return nil
}
out := new(ConditionsWrapper)
in.DeepCopyInto(out)
return out
}
func (a *ConditionsWrapper) MarshalJSON() ([]byte, error) {
return json.Marshal(a.Conditions)
}
func (a *ConditionsWrapper) UnmarshalJSON(data []byte) error {
var err error
var kyvernoOldConditions []Condition
if err = json.Unmarshal(data, &kyvernoOldConditions); err == nil {
a.Conditions = kyvernoOldConditions
return nil
}
var kyvernoAnyAllConditions AnyAllConditions
if err = json.Unmarshal(data, &kyvernoAnyAllConditions); err == nil {
a.Conditions = kyvernoAnyAllConditions
return nil
}
return fmt.Errorf("failed to unmarshal Conditions")
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
import (
k8smanifest "github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *APICall) DeepCopyInto(out *APICall) {
*out = *in
if in.Data != nil {
in, out := &in.Data, &out.Data
*out = make([]RequestData, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Service != nil {
in, out := &in.Service, &out.Service
*out = new(ServiceCall)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APICall.
func (in *APICall) DeepCopy() *APICall {
if in == nil {
return nil
}
out := new(APICall)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AnyAllConditions) DeepCopyInto(out *AnyAllConditions) {
*out = *in
if in.AnyConditions != nil {
in, out := &in.AnyConditions, &out.AnyConditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AllConditions != nil {
in, out := &in.AllConditions, &out.AllConditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnyAllConditions.
func (in *AnyAllConditions) DeepCopy() *AnyAllConditions {
if in == nil {
return nil
}
out := new(AnyAllConditions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Attestation) DeepCopyInto(out *Attestation) {
*out = *in
if in.Attestors != nil {
in, out := &in.Attestors, &out.Attestors
*out = make([]AttestorSet, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]AnyAllConditions, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Attestation.
func (in *Attestation) DeepCopy() *Attestation {
if in == nil {
return nil
}
out := new(Attestation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Attestor) DeepCopyInto(out *Attestor) {
*out = *in
if in.Keys != nil {
in, out := &in.Keys, &out.Keys
*out = new(StaticKeyAttestor)
(*in).DeepCopyInto(*out)
}
if in.Certificates != nil {
in, out := &in.Certificates, &out.Certificates
*out = new(CertificateAttestor)
(*in).DeepCopyInto(*out)
}
if in.Keyless != nil {
in, out := &in.Keyless, &out.Keyless
*out = new(KeylessAttestor)
(*in).DeepCopyInto(*out)
}
if in.Attestor != nil {
in, out := &in.Attestor, &out.Attestor
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Attestor.
func (in *Attestor) DeepCopy() *Attestor {
if in == nil {
return nil
}
out := new(Attestor)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AttestorSet) DeepCopyInto(out *AttestorSet) {
*out = *in
if in.Count != nil {
in, out := &in.Count, &out.Count
*out = new(int)
**out = **in
}
if in.Entries != nil {
in, out := &in.Entries, &out.Entries
*out = make([]Attestor, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AttestorSet.
func (in *AttestorSet) DeepCopy() *AttestorSet {
if in == nil {
return nil
}
out := new(AttestorSet)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AutogenStatus) DeepCopyInto(out *AutogenStatus) {
*out = *in
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]Rule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutogenStatus.
func (in *AutogenStatus) DeepCopy() *AutogenStatus {
if in == nil {
return nil
}
out := new(AutogenStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CEL) DeepCopyInto(out *CEL) {
*out = *in
if in.Generate != nil {
in, out := &in.Generate, &out.Generate
*out = new(bool)
**out = **in
}
if in.Expressions != nil {
in, out := &in.Expressions, &out.Expressions
*out = make([]admissionregistrationv1.Validation, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ParamKind != nil {
in, out := &in.ParamKind, &out.ParamKind
*out = new(admissionregistrationv1.ParamKind)
**out = **in
}
if in.ParamRef != nil {
in, out := &in.ParamRef, &out.ParamRef
*out = new(admissionregistrationv1.ParamRef)
(*in).DeepCopyInto(*out)
}
if in.AuditAnnotations != nil {
in, out := &in.AuditAnnotations, &out.AuditAnnotations
*out = make([]admissionregistrationv1.AuditAnnotation, len(*in))
copy(*out, *in)
}
if in.Variables != nil {
in, out := &in.Variables, &out.Variables
*out = make([]admissionregistrationv1.Variable, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CEL.
func (in *CEL) DeepCopy() *CEL {
if in == nil {
return nil
}
out := new(CEL)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CTLog) DeepCopyInto(out *CTLog) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CTLog.
func (in *CTLog) DeepCopy() *CTLog {
if in == nil {
return nil
}
out := new(CTLog)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CertificateAttestor) DeepCopyInto(out *CertificateAttestor) {
*out = *in
if in.Rekor != nil {
in, out := &in.Rekor, &out.Rekor
*out = new(Rekor)
**out = **in
}
if in.CTLog != nil {
in, out := &in.CTLog, &out.CTLog
*out = new(CTLog)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateAttestor.
func (in *CertificateAttestor) DeepCopy() *CertificateAttestor {
if in == nil {
return nil
}
out := new(CertificateAttestor)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CloneFrom) DeepCopyInto(out *CloneFrom) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloneFrom.
func (in *CloneFrom) DeepCopy() *CloneFrom {
if in == nil {
return nil
}
out := new(CloneFrom)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CloneList) DeepCopyInto(out *CloneList) {
*out = *in
if in.Kinds != nil {
in, out := &in.Kinds, &out.Kinds
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(metav1.LabelSelector)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloneList.
func (in *CloneList) DeepCopy() *CloneList {
if in == nil {
return nil
}
out := new(CloneList)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterPolicy) DeepCopyInto(out *ClusterPolicy) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterPolicy.
func (in *ClusterPolicy) DeepCopy() *ClusterPolicy {
if in == nil {
return nil
}
out := new(ClusterPolicy)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ClusterPolicy) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterPolicyList) DeepCopyInto(out *ClusterPolicyList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ClusterPolicy, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterPolicyList.
func (in *ClusterPolicyList) DeepCopy() *ClusterPolicyList {
if in == nil {
return nil
}
out := new(ClusterPolicyList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ClusterPolicyList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Condition) DeepCopyInto(out *Condition) {
*out = *in
if in.RawKey != nil {
in, out := &in.RawKey, &out.RawKey
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
if in.RawValue != nil {
in, out := &in.RawValue, &out.RawValue
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
func (in *Condition) DeepCopy() *Condition {
if in == nil {
return nil
}
out := new(Condition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigMapReference) DeepCopyInto(out *ConfigMapReference) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapReference.
func (in *ConfigMapReference) DeepCopy() *ConfigMapReference {
if in == nil {
return nil
}
out := new(ConfigMapReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ContextAPICall) DeepCopyInto(out *ContextAPICall) {
*out = *in
in.APICall.DeepCopyInto(&out.APICall)
if in.Default != nil {
in, out := &in.Default, &out.Default
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContextAPICall.
func (in *ContextAPICall) DeepCopy() *ContextAPICall {
if in == nil {
return nil
}
out := new(ContextAPICall)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ContextEntry) DeepCopyInto(out *ContextEntry) {
*out = *in
if in.ConfigMap != nil {
in, out := &in.ConfigMap, &out.ConfigMap
*out = new(ConfigMapReference)
**out = **in
}
if in.APICall != nil {
in, out := &in.APICall, &out.APICall
*out = new(ContextAPICall)
(*in).DeepCopyInto(*out)
}
if in.ImageRegistry != nil {
in, out := &in.ImageRegistry, &out.ImageRegistry
*out = new(ImageRegistry)
(*in).DeepCopyInto(*out)
}
if in.Variable != nil {
in, out := &in.Variable, &out.Variable
*out = new(Variable)
(*in).DeepCopyInto(*out)
}
if in.GlobalReference != nil {
in, out := &in.GlobalReference, &out.GlobalReference
*out = new(GlobalContextEntryReference)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContextEntry.
func (in *ContextEntry) DeepCopy() *ContextEntry {
if in == nil {
return nil
}
out := new(ContextEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Deny) DeepCopyInto(out *Deny) {
*out = *in
if in.RawAnyAllConditions != nil {
in, out := &in.RawAnyAllConditions, &out.RawAnyAllConditions
*out = (*in).DeepCopy()
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Deny.
func (in *Deny) DeepCopy() *Deny {
if in == nil {
return nil
}
out := new(Deny)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DryRunOption) DeepCopyInto(out *DryRunOption) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DryRunOption.
func (in *DryRunOption) DeepCopy() *DryRunOption {
if in == nil {
return nil
}
out := new(DryRunOption)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ForEachGeneration) DeepCopyInto(out *ForEachGeneration) {
*out = *in
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.AnyAllConditions != nil {
in, out := &in.AnyAllConditions, &out.AnyAllConditions
*out = new(AnyAllConditions)
(*in).DeepCopyInto(*out)
}
in.GeneratePattern.DeepCopyInto(&out.GeneratePattern)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForEachGeneration.
func (in *ForEachGeneration) DeepCopy() *ForEachGeneration {
if in == nil {
return nil
}
out := new(ForEachGeneration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ForEachMutation) DeepCopyInto(out *ForEachMutation) {
*out = *in
if in.Order != nil {
in, out := &in.Order, &out.Order
*out = new(ForeachOrder)
**out = **in
}
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.AnyAllConditions != nil {
in, out := &in.AnyAllConditions, &out.AnyAllConditions
*out = new(AnyAllConditions)
(*in).DeepCopyInto(*out)
}
if in.RawPatchStrategicMerge != nil {
in, out := &in.RawPatchStrategicMerge, &out.RawPatchStrategicMerge
*out = (*in).DeepCopy()
}
if in.ForEachMutation != nil {
in, out := &in.ForEachMutation, &out.ForEachMutation
*out = (*in).DeepCopy()
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForEachMutation.
func (in *ForEachMutation) DeepCopy() *ForEachMutation {
if in == nil {
return nil
}
out := new(ForEachMutation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ForEachValidation) DeepCopyInto(out *ForEachValidation) {
*out = *in
if in.ElementScope != nil {
in, out := &in.ElementScope, &out.ElementScope
*out = new(bool)
**out = **in
}
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.AnyAllConditions != nil {
in, out := &in.AnyAllConditions, &out.AnyAllConditions
*out = new(AnyAllConditions)
(*in).DeepCopyInto(*out)
}
if in.RawPattern != nil {
in, out := &in.RawPattern, &out.RawPattern
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
if in.RawAnyPattern != nil {
in, out := &in.RawAnyPattern, &out.RawAnyPattern
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
if in.Deny != nil {
in, out := &in.Deny, &out.Deny
*out = new(Deny)
(*in).DeepCopyInto(*out)
}
if in.ForEachValidation != nil {
in, out := &in.ForEachValidation, &out.ForEachValidation
*out = (*in).DeepCopy()
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForEachValidation.
func (in *ForEachValidation) DeepCopy() *ForEachValidation {
if in == nil {
return nil
}
out := new(ForEachValidation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GeneratePattern) DeepCopyInto(out *GeneratePattern) {
*out = *in
out.ResourceSpec = in.ResourceSpec
if in.RawData != nil {
in, out := &in.RawData, &out.RawData
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
out.Clone = in.Clone
in.CloneList.DeepCopyInto(&out.CloneList)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratePattern.
func (in *GeneratePattern) DeepCopy() *GeneratePattern {
if in == nil {
return nil
}
out := new(GeneratePattern)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Generation) DeepCopyInto(out *Generation) {
*out = *in
if in.GenerateExisting != nil {
in, out := &in.GenerateExisting, &out.GenerateExisting
*out = new(bool)
**out = **in
}
in.GeneratePattern.DeepCopyInto(&out.GeneratePattern)
if in.ForEachGeneration != nil {
in, out := &in.ForEachGeneration, &out.ForEachGeneration
*out = make([]ForEachGeneration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Generation.
func (in *Generation) DeepCopy() *Generation {
if in == nil {
return nil
}
out := new(Generation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalContextEntryReference) DeepCopyInto(out *GlobalContextEntryReference) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalContextEntryReference.
func (in *GlobalContextEntryReference) DeepCopy() *GlobalContextEntryReference {
if in == nil {
return nil
}
out := new(GlobalContextEntryReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPHeader) DeepCopyInto(out *HTTPHeader) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeader.
func (in *HTTPHeader) DeepCopy() *HTTPHeader {
if in == nil {
return nil
}
out := new(HTTPHeader)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in IgnoreFieldList) DeepCopyInto(out *IgnoreFieldList) {
{
in := &in
*out = make(IgnoreFieldList, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IgnoreFieldList.
func (in IgnoreFieldList) DeepCopy() IgnoreFieldList {
if in == nil {
return nil
}
out := new(IgnoreFieldList)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImageExtractorConfig) DeepCopyInto(out *ImageExtractorConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageExtractorConfig.
func (in *ImageExtractorConfig) DeepCopy() *ImageExtractorConfig {
if in == nil {
return nil
}
out := new(ImageExtractorConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in ImageExtractorConfigs) DeepCopyInto(out *ImageExtractorConfigs) {
{
in := &in
*out = make(ImageExtractorConfigs, len(*in))
for key, val := range *in {
var outVal []ImageExtractorConfig
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make([]ImageExtractorConfig, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageExtractorConfigs.
func (in ImageExtractorConfigs) DeepCopy() ImageExtractorConfigs {
if in == nil {
return nil
}
out := new(ImageExtractorConfigs)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImageRegistry) DeepCopyInto(out *ImageRegistry) {
*out = *in
if in.ImageRegistryCredentials != nil {
in, out := &in.ImageRegistryCredentials, &out.ImageRegistryCredentials
*out = new(ImageRegistryCredentials)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageRegistry.
func (in *ImageRegistry) DeepCopy() *ImageRegistry {
if in == nil {
return nil
}
out := new(ImageRegistry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImageRegistryCredentials) DeepCopyInto(out *ImageRegistryCredentials) {
*out = *in
if in.Providers != nil {
in, out := &in.Providers, &out.Providers
*out = make([]ImageRegistryCredentialsProvidersType, len(*in))
copy(*out, *in)
}
if in.Secrets != nil {
in, out := &in.Secrets, &out.Secrets
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageRegistryCredentials.
func (in *ImageRegistryCredentials) DeepCopy() *ImageRegistryCredentials {
if in == nil {
return nil
}
out := new(ImageRegistryCredentials)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImageVerification) DeepCopyInto(out *ImageVerification) {
*out = *in
if in.FailureAction != nil {
in, out := &in.FailureAction, &out.FailureAction
*out = new(ValidationFailureAction)
**out = **in
}
if in.ImageReferences != nil {
in, out := &in.ImageReferences, &out.ImageReferences
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.SkipImageReferences != nil {
in, out := &in.SkipImageReferences, &out.SkipImageReferences
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AdditionalExtensions != nil {
in, out := &in.AdditionalExtensions, &out.AdditionalExtensions
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Attestors != nil {
in, out := &in.Attestors, &out.Attestors
*out = make([]AttestorSet, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Attestations != nil {
in, out := &in.Attestations, &out.Attestations
*out = make([]Attestation, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
in.Validation.DeepCopyInto(&out.Validation)
if in.ImageRegistryCredentials != nil {
in, out := &in.ImageRegistryCredentials, &out.ImageRegistryCredentials
*out = new(ImageRegistryCredentials)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageVerification.
func (in *ImageVerification) DeepCopy() *ImageVerification {
if in == nil {
return nil
}
out := new(ImageVerification)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeylessAttestor) DeepCopyInto(out *KeylessAttestor) {
*out = *in
if in.Rekor != nil {
in, out := &in.Rekor, &out.Rekor
*out = new(Rekor)
**out = **in
}
if in.CTLog != nil {
in, out := &in.CTLog, &out.CTLog
*out = new(CTLog)
**out = **in
}
if in.AdditionalExtensions != nil {
in, out := &in.AdditionalExtensions, &out.AdditionalExtensions
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeylessAttestor.
func (in *KeylessAttestor) DeepCopy() *KeylessAttestor {
if in == nil {
return nil
}
out := new(KeylessAttestor)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Manifests) DeepCopyInto(out *Manifests) {
*out = *in
if in.Attestors != nil {
in, out := &in.Attestors, &out.Attestors
*out = make([]AttestorSet, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.IgnoreFields != nil {
in, out := &in.IgnoreFields, &out.IgnoreFields
*out = make(IgnoreFieldList, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.DryRunOption = in.DryRunOption
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Manifests.
func (in *Manifests) DeepCopy() *Manifests {
if in == nil {
return nil
}
out := new(Manifests)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MatchResources) DeepCopyInto(out *MatchResources) {
*out = *in
if in.Any != nil {
in, out := &in.Any, &out.Any
*out = make(ResourceFilters, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.All != nil {
in, out := &in.All, &out.All
*out = make(ResourceFilters, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.UserInfo.DeepCopyInto(&out.UserInfo)
in.ResourceDescription.DeepCopyInto(&out.ResourceDescription)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchResources.
func (in *MatchResources) DeepCopy() *MatchResources {
if in == nil {
return nil
}
out := new(MatchResources)
in.DeepCopyInto(out)
return out
}
// 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.MutateExistingOnPolicyUpdate != nil {
in, out := &in.MutateExistingOnPolicyUpdate, &out.MutateExistingOnPolicyUpdate
*out = new(bool)
**out = **in
}
if in.Targets != nil {
in, out := &in.Targets, &out.Targets
*out = make([]TargetResourceSpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.RawPatchStrategicMerge != nil {
in, out := &in.RawPatchStrategicMerge, &out.RawPatchStrategicMerge
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
if in.ForEachMutation != nil {
in, out := &in.ForEachMutation, &out.ForEachMutation
*out = make([]ForEachMutation, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mutation.
func (in *Mutation) DeepCopy() *Mutation {
if in == nil {
return nil
}
out := new(Mutation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectFieldBinding) DeepCopyInto(out *ObjectFieldBinding) {
*out = *in
if in.Fields != nil {
in, out := &in.Fields, &out.Fields
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Objects != nil {
in, out := &in.Objects, &out.Objects
*out = make(k8smanifest.ObjectReferenceList, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectFieldBinding.
func (in *ObjectFieldBinding) DeepCopy() *ObjectFieldBinding {
if in == nil {
return nil
}
out := new(ObjectFieldBinding)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurity) DeepCopyInto(out *PodSecurity) {
*out = *in
if in.Exclude != nil {
in, out := &in.Exclude, &out.Exclude
*out = make([]PodSecurityStandard, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurity.
func (in *PodSecurity) DeepCopy() *PodSecurity {
if in == nil {
return nil
}
out := new(PodSecurity)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSecurityStandard) DeepCopyInto(out *PodSecurityStandard) {
*out = *in
if in.Images != nil {
in, out := &in.Images, &out.Images
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityStandard.
func (in *PodSecurityStandard) DeepCopy() *PodSecurityStandard {
if in == nil {
return nil
}
out := new(PodSecurityStandard)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Policy) DeepCopyInto(out *Policy) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy.
func (in *Policy) DeepCopy() *Policy {
if in == nil {
return nil
}
out := new(Policy)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Policy) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PolicyList) DeepCopyInto(out *PolicyList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Policy, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyList.
func (in *PolicyList) DeepCopy() *PolicyList {
if in == nil {
return nil
}
out := new(PolicyList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PolicyList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PolicyStatus) DeepCopyInto(out *PolicyStatus) {
*out = *in
if in.Ready != nil {
in, out := &in.Ready, &out.Ready
*out = new(bool)
**out = **in
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.Autogen.DeepCopyInto(&out.Autogen)
out.RuleCount = in.RuleCount
out.ValidatingAdmissionPolicy = in.ValidatingAdmissionPolicy
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyStatus.
func (in *PolicyStatus) DeepCopy() *PolicyStatus {
if in == nil {
return nil
}
out := new(PolicyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Rekor) DeepCopyInto(out *Rekor) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rekor.
func (in *Rekor) DeepCopy() *Rekor {
if in == nil {
return nil
}
out := new(Rekor)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RequestData) DeepCopyInto(out *RequestData) {
*out = *in
if in.Value != nil {
in, out := &in.Value, &out.Value
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestData.
func (in *RequestData) DeepCopy() *RequestData {
if in == nil {
return nil
}
out := new(RequestData)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceDescription) DeepCopyInto(out *ResourceDescription) {
*out = *in
if in.Kinds != nil {
in, out := &in.Kinds, &out.Kinds
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Names != nil {
in, out := &in.Names, &out.Names
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(metav1.LabelSelector)
(*in).DeepCopyInto(*out)
}
if in.NamespaceSelector != nil {
in, out := &in.NamespaceSelector, &out.NamespaceSelector
*out = new(metav1.LabelSelector)
(*in).DeepCopyInto(*out)
}
if in.Operations != nil {
in, out := &in.Operations, &out.Operations
*out = make([]AdmissionOperation, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceDescription.
func (in *ResourceDescription) DeepCopy() *ResourceDescription {
if in == nil {
return nil
}
out := new(ResourceDescription)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceFilter) DeepCopyInto(out *ResourceFilter) {
*out = *in
in.UserInfo.DeepCopyInto(&out.UserInfo)
in.ResourceDescription.DeepCopyInto(&out.ResourceDescription)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceFilter.
func (in *ResourceFilter) DeepCopy() *ResourceFilter {
if in == nil {
return nil
}
out := new(ResourceFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in ResourceFilters) DeepCopyInto(out *ResourceFilters) {
{
in := &in
*out = make(ResourceFilters, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceFilters.
func (in ResourceFilters) DeepCopy() ResourceFilters {
if in == nil {
return nil
}
out := new(ResourceFilters)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSpec.
func (in *ResourceSpec) DeepCopy() *ResourceSpec {
if in == nil {
return nil
}
out := new(ResourceSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Rule) DeepCopyInto(out *Rule) {
*out = *in
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.ReportProperties != nil {
in, out := &in.ReportProperties, &out.ReportProperties
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
in.MatchResources.DeepCopyInto(&out.MatchResources)
if in.ExcludeResources != nil {
in, out := &in.ExcludeResources, &out.ExcludeResources
*out = new(MatchResources)
(*in).DeepCopyInto(*out)
}
if in.ImageExtractors != nil {
in, out := &in.ImageExtractors, &out.ImageExtractors
*out = make(ImageExtractorConfigs, len(*in))
for key, val := range *in {
var outVal []ImageExtractorConfig
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make([]ImageExtractorConfig, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
}
if in.RawAnyAllConditions != nil {
in, out := &in.RawAnyAllConditions, &out.RawAnyAllConditions
*out = (*in).DeepCopy()
}
if in.CELPreconditions != nil {
in, out := &in.CELPreconditions, &out.CELPreconditions
*out = make([]admissionregistrationv1.MatchCondition, len(*in))
copy(*out, *in)
}
if in.Mutation != nil {
in, out := &in.Mutation, &out.Mutation
*out = new(Mutation)
(*in).DeepCopyInto(*out)
}
if in.Validation != nil {
in, out := &in.Validation, &out.Validation
*out = new(Validation)
(*in).DeepCopyInto(*out)
}
if in.Generation != nil {
in, out := &in.Generation, &out.Generation
*out = new(Generation)
(*in).DeepCopyInto(*out)
}
if in.VerifyImages != nil {
in, out := &in.VerifyImages, &out.VerifyImages
*out = make([]ImageVerification, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.SkipBackgroundRequests != nil {
in, out := &in.SkipBackgroundRequests, &out.SkipBackgroundRequests
*out = new(bool)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule.
func (in *Rule) DeepCopy() *Rule {
if in == nil {
return nil
}
out := new(Rule)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RuleCountStatus) DeepCopyInto(out *RuleCountStatus) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleCountStatus.
func (in *RuleCountStatus) DeepCopy() *RuleCountStatus {
if in == nil {
return nil
}
out := new(RuleCountStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretReference) DeepCopyInto(out *SecretReference) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference.
func (in *SecretReference) DeepCopy() *SecretReference {
if in == nil {
return nil
}
out := new(SecretReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceCall) DeepCopyInto(out *ServiceCall) {
*out = *in
if in.Headers != nil {
in, out := &in.Headers, &out.Headers
*out = make([]HTTPHeader, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceCall.
func (in *ServiceCall) DeepCopy() *ServiceCall {
if in == nil {
return nil
}
out := new(ServiceCall)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Spec) DeepCopyInto(out *Spec) {
*out = *in
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]Rule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ApplyRules != nil {
in, out := &in.ApplyRules, &out.ApplyRules
*out = new(ApplyRulesType)
**out = **in
}
if in.FailurePolicy != nil {
in, out := &in.FailurePolicy, &out.FailurePolicy
*out = new(FailurePolicyType)
**out = **in
}
if in.ValidationFailureActionOverrides != nil {
in, out := &in.ValidationFailureActionOverrides, &out.ValidationFailureActionOverrides
*out = make([]ValidationFailureActionOverride, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.EmitWarning != nil {
in, out := &in.EmitWarning, &out.EmitWarning
*out = new(bool)
**out = **in
}
if in.Admission != nil {
in, out := &in.Admission, &out.Admission
*out = new(bool)
**out = **in
}
if in.Background != nil {
in, out := &in.Background, &out.Background
*out = new(bool)
**out = **in
}
if in.SchemaValidation != nil {
in, out := &in.SchemaValidation, &out.SchemaValidation
*out = new(bool)
**out = **in
}
if in.WebhookTimeoutSeconds != nil {
in, out := &in.WebhookTimeoutSeconds, &out.WebhookTimeoutSeconds
*out = new(int32)
**out = **in
}
if in.GenerateExistingOnPolicyUpdate != nil {
in, out := &in.GenerateExistingOnPolicyUpdate, &out.GenerateExistingOnPolicyUpdate
*out = new(bool)
**out = **in
}
if in.WebhookConfiguration != nil {
in, out := &in.WebhookConfiguration, &out.WebhookConfiguration
*out = new(WebhookConfiguration)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Spec.
func (in *Spec) DeepCopy() *Spec {
if in == nil {
return nil
}
out := new(Spec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StaticKeyAttestor) DeepCopyInto(out *StaticKeyAttestor) {
*out = *in
if in.Secret != nil {
in, out := &in.Secret, &out.Secret
*out = new(SecretReference)
**out = **in
}
if in.Rekor != nil {
in, out := &in.Rekor, &out.Rekor
*out = new(Rekor)
**out = **in
}
if in.CTLog != nil {
in, out := &in.CTLog, &out.CTLog
*out = new(CTLog)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticKeyAttestor.
func (in *StaticKeyAttestor) DeepCopy() *StaticKeyAttestor {
if in == nil {
return nil
}
out := new(StaticKeyAttestor)
in.DeepCopyInto(out)
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
in.TargetSelector.DeepCopyInto(&out.TargetSelector)
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 = (*in).DeepCopy()
}
return
}
// 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 *TargetSelector) DeepCopyInto(out *TargetSelector) {
*out = *in
out.ResourceSpec = in.ResourceSpec
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(metav1.LabelSelector)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetSelector.
func (in *TargetSelector) DeepCopy() *TargetSelector {
if in == nil {
return nil
}
out := new(TargetSelector)
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
if in.Roles != nil {
in, out := &in.Roles, &out.Roles
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ClusterRoles != nil {
in, out := &in.ClusterRoles, &out.ClusterRoles
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Subjects != nil {
in, out := &in.Subjects, &out.Subjects
*out = make([]rbacv1.Subject, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserInfo.
func (in *UserInfo) DeepCopy() *UserInfo {
if in == nil {
return nil
}
out := new(UserInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ValidateImageVerification) DeepCopyInto(out *ValidateImageVerification) {
*out = *in
if in.Deny != nil {
in, out := &in.Deny, &out.Deny
*out = new(Deny)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidateImageVerification.
func (in *ValidateImageVerification) DeepCopy() *ValidateImageVerification {
if in == nil {
return nil
}
out := new(ValidateImageVerification)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ValidatingAdmissionPolicyStatus) DeepCopyInto(out *ValidatingAdmissionPolicyStatus) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingAdmissionPolicyStatus.
func (in *ValidatingAdmissionPolicyStatus) DeepCopy() *ValidatingAdmissionPolicyStatus {
if in == nil {
return nil
}
out := new(ValidatingAdmissionPolicyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Validation) DeepCopyInto(out *Validation) {
*out = *in
if in.FailureAction != nil {
in, out := &in.FailureAction, &out.FailureAction
*out = new(ValidationFailureAction)
**out = **in
}
if in.FailureActionOverrides != nil {
in, out := &in.FailureActionOverrides, &out.FailureActionOverrides
*out = make([]ValidationFailureActionOverride, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AllowExistingViolations != nil {
in, out := &in.AllowExistingViolations, &out.AllowExistingViolations
*out = new(bool)
**out = **in
}
if in.Manifests != nil {
in, out := &in.Manifests, &out.Manifests
*out = new(Manifests)
(*in).DeepCopyInto(*out)
}
if in.ForEachValidation != nil {
in, out := &in.ForEachValidation, &out.ForEachValidation
*out = make([]ForEachValidation, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.RawPattern != nil {
in, out := &in.RawPattern, &out.RawPattern
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
if in.RawAnyPattern != nil {
in, out := &in.RawAnyPattern, &out.RawAnyPattern
*out = new(apiextensionsv1.JSON)
(*in).DeepCopyInto(*out)
}
if in.Deny != nil {
in, out := &in.Deny, &out.Deny
*out = new(Deny)
(*in).DeepCopyInto(*out)
}
if in.PodSecurity != nil {
in, out := &in.PodSecurity, &out.PodSecurity
*out = new(PodSecurity)
(*in).DeepCopyInto(*out)
}
if in.CEL != nil {
in, out := &in.CEL, &out.CEL
*out = new(CEL)
(*in).DeepCopyInto(*out)
}
in.Assert.DeepCopyInto(&out.Assert)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Validation.
func (in *Validation) DeepCopy() *Validation {
if in == nil {
return nil
}
out := new(Validation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ValidationFailureActionOverride) DeepCopyInto(out *ValidationFailureActionOverride) {
*out = *in
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.NamespaceSelector != nil {
in, out := &in.NamespaceSelector, &out.NamespaceSelector
*out = new(metav1.LabelSelector)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationFailureActionOverride.
func (in *ValidationFailureActionOverride) DeepCopy() *ValidationFailureActionOverride {
if in == nil {
return nil
}
out := new(ValidationFailureActionOverride)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Variable) DeepCopyInto(out *Variable) {
*out = *in
if in.Value != nil {
in, out := &in.Value, &out.Value
*out = (*in).DeepCopy()
}
if in.Default != nil {
in, out := &in.Default, &out.Default
*out = (*in).DeepCopy()
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Variable.
func (in *Variable) DeepCopy() *Variable {
if in == nil {
return nil
}
out := new(Variable)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) {
*out = *in
if in.FailurePolicy != nil {
in, out := &in.FailurePolicy, &out.FailurePolicy
*out = new(FailurePolicyType)
**out = **in
}
if in.TimeoutSeconds != nil {
in, out := &in.TimeoutSeconds, &out.TimeoutSeconds
*out = new(int32)
**out = **in
}
if in.MatchConditions != nil {
in, out := &in.MatchConditions, &out.MatchConditions
*out = make([]admissionregistrationv1.MatchCondition, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookConfiguration.
func (in *WebhookConfiguration) DeepCopy() *WebhookConfiguration {
if in == nil {
return nil
}
out := new(WebhookConfiguration)
in.DeepCopyInto(out)
return out
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by register-gen. DO NOT EDIT.
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName specifies the group name used to register the objects.
const GroupName = "kyverno.io"
// GroupVersion specifies the group and the version used to register the objects.
var GroupVersion = metav1.GroupVersion{Group: GroupName, Version: "v1"}
// SchemeGroupVersion is group version used to register these objects
// Deprecated: use GroupVersion instead.
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
// Deprecated: use Install instead
AddToScheme = localSchemeBuilder.AddToScheme
Install = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes)
}
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&ClusterPolicy{},
&ClusterPolicyList{},
&Policy{},
&PolicyList{},
)
// AddToGroupVersion allows the serialization of client types like ListOptions.
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
package anchor
import (
"regexp"
"strings"
)
type AnchorType string
const (
Condition AnchorType = ""
Global AnchorType = "<"
Negation AnchorType = "X"
AddIfNotPresent AnchorType = "+"
Equality AnchorType = "="
Existence AnchorType = "^"
)
var regex = regexp.MustCompile(`^(?P<modifier>[+<=X^])?\((?P<key>.+)\)$`)
// Anchor interface
type Anchor interface {
// Type returns the anchor type
Type() AnchorType
// Key returns the anchor key
Key() string
// String returns the anchor string
String() string
}
type anchor struct {
modifier AnchorType
key string
}
// Parse parses a string, returns nil if not an anchor
func Parse(str string) Anchor {
str = strings.TrimSpace(str)
values := regex.FindStringSubmatch(str)
if len(values) == 0 {
return nil
}
return New(AnchorType(values[1]), values[2])
}
// New creates an anchor
func New(modifier AnchorType, key string) Anchor {
if key == "" {
return nil
}
return anchor{
modifier: modifier,
key: key,
}
}
// String returns the anchor string.
// Will return an empty string if key is empty.
func String(modifier AnchorType, key string) string {
if key == "" {
return ""
}
return string(modifier) + "(" + key + ")"
}
func (a anchor) Type() AnchorType {
return a.modifier
}
func (a anchor) Key() string {
return a.key
}
func (a anchor) String() string {
return String(a.modifier, a.key)
}
// IsOneOf returns checks if anchor is one of the given types
func IsOneOf(a Anchor, types ...AnchorType) bool {
if a != nil {
for _, t := range types {
if t == a.Type() {
return true
}
}
}
return false
}
// ContainsCondition returns true if anchor is either condition anchor or global condition anchor
func ContainsCondition(a Anchor) bool {
return IsOneOf(a, Condition, Global)
}
// IsCondition checks for condition anchor
func IsCondition(a Anchor) bool {
return IsOneOf(a, Condition)
}
// IsGlobal checks for global condition anchor
func IsGlobal(a Anchor) bool {
return IsOneOf(a, Global)
}
// IsNegation checks for negation anchor
func IsNegation(a Anchor) bool {
return IsOneOf(a, Negation)
}
// IsAddIfNotPresent checks for addition anchor
func IsAddIfNotPresent(a Anchor) bool {
return IsOneOf(a, AddIfNotPresent)
}
// IsEquality checks for equality anchor
func IsEquality(a Anchor) bool {
return IsOneOf(a, Equality)
}
// IsExistence checks for existence anchor
func IsExistence(a Anchor) bool {
return IsOneOf(a, Existence)
}
package anchor
// AnchorMap - contains map of anchors
type AnchorMap struct {
// anchorMap - for each anchor key in the patterns it will maintain information if the key exists in the resource
// if anchor key of the pattern exists in the resource then (key)=true else (key)=false
anchorMap map[string]bool
// AnchorError - used in validate to break execution of the recursion when if condition fails
AnchorError validateAnchorError
}
// NewAnchorMap -initialize anchorMap
func NewAnchorMap() *AnchorMap {
return &AnchorMap{anchorMap: map[string]bool{}}
}
// KeysAreMissing - if any of the anchor key doesn't exists in the resource then it will return true
// if any of (key)=false then return KeysAreMissing() as true
// if all the keys exists in the pattern exists in resource then return KeysAreMissing() as false
func (ac *AnchorMap) KeysAreMissing() bool {
for k, v := range ac.anchorMap {
if !v {
// Negations should not be present in the resource so they count as missing.
if a := Parse(k); IsNegation(a) {
continue
}
return true
}
}
return false
}
// CheckAnchorInResource checks if condition anchor key has values
func (ac *AnchorMap) CheckAnchorInResource(pattern map[string]interface{}, resource interface{}) {
for key := range pattern {
if a := Parse(key); IsCondition(a) || IsExistence(a) || IsNegation(a) {
val, ok := ac.anchorMap[key]
if !ok {
ac.anchorMap[key] = false
} else if ok && val {
continue
}
if resourceHasValueForKey(resource, a.Key()) {
ac.anchorMap[key] = true
}
}
}
}
package anchor
import (
"fmt"
"strings"
)
// anchorError is the const specification of anchor errors
type anchorError int
const (
// conditionalAnchorErr refers to condition violation
conditionalAnchorErr anchorError = iota
// globalAnchorErr refers to global condition violation
globalAnchorErr
// negationAnchorErr refers to negation violation
negationAnchorErr
)
const (
// negationAnchorErrMsg - the error message for negation anchor error
negationAnchorErrMsg = "negation anchor matched in resource"
// conditionalAnchorErrMsg - the error message for conditional anchor error
conditionalAnchorErrMsg = "conditional anchor mismatch"
// globalAnchorErrMsg - the error message for global anchor error
globalAnchorErrMsg = "global anchor mismatch"
)
// validateAnchorError represents the error type of validation anchors
type validateAnchorError struct {
err anchorError
message string
}
// Error implements error interface
func (e validateAnchorError) Error() string {
return e.message
}
// newNegationAnchorError returns a new instance of validateAnchorError
func newValidateAnchorError(err anchorError, prefix, msg string) validateAnchorError {
return validateAnchorError{
err: err,
message: fmt.Sprintf("%s: %s", prefix, msg),
}
}
// newNegationAnchorError returns a new instance of NegationAnchorError
func newNegationAnchorError(msg string) validateAnchorError {
return newValidateAnchorError(negationAnchorErr, negationAnchorErrMsg, msg)
}
// newConditionalAnchorError returns a new instance of ConditionalAnchorError
func newConditionalAnchorError(msg string) validateAnchorError {
return newValidateAnchorError(conditionalAnchorErr, conditionalAnchorErrMsg, msg)
}
// newGlobalAnchorError returns a new instance of GlobalAnchorError
func newGlobalAnchorError(msg string) validateAnchorError {
return newValidateAnchorError(globalAnchorErr, globalAnchorErrMsg, msg)
}
// isError checks if error matches the given error type
func isError(err error, code anchorError, msg string) bool {
if err != nil {
if t, ok := err.(validateAnchorError); ok {
return t.err == code
} else {
// TODO: we shouldn't need this, error is not properly propagated
return strings.Contains(err.Error(), msg)
}
}
return false
}
// IsNegationAnchorError checks if error is a negation anchor error
func IsNegationAnchorError(err error) bool {
return isError(err, negationAnchorErr, negationAnchorErrMsg)
}
// IsConditionalAnchorError checks if error is a conditional anchor error
func IsConditionalAnchorError(err error) bool {
return isError(err, conditionalAnchorErr, conditionalAnchorErrMsg)
}
// IsGlobalAnchorError checks if error is a global anchor error
func IsGlobalAnchorError(err error) bool {
return isError(err, globalAnchorErr, globalAnchorErrMsg)
}
package anchor
import (
"fmt"
"strconv"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/logging"
)
type resourceElementHandler = func(
log logr.Logger,
resourceElement interface{},
patternElement interface{},
originPattern interface{},
path string,
ac *AnchorMap,
) (string, error)
// ValidationHandler for element processes
type ValidationHandler interface {
Handle(
handler resourceElementHandler,
resourceMap map[string]interface{},
originPattern interface{},
ac *AnchorMap,
) (string, error)
}
// CreateElementHandler factory to process elements
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
if anchor := Parse(element); anchor != nil {
switch {
case IsCondition(anchor):
return newConditionAnchorHandler(anchor, pattern, path)
case IsGlobal(anchor):
return newGlobalAnchorHandler(anchor, pattern, path)
case IsExistence(anchor):
return newExistenceHandler(anchor, pattern, path)
case IsEquality(anchor):
return newEqualityHandler(anchor, pattern, path)
case IsNegation(anchor):
return newNegationHandler(anchor, pattern, path)
}
}
return newDefaultHandler(element, pattern, path)
}
// negationHandler provides handler for check if the tag in anchor is not defined
type negationHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newNegationHandler returns instance of negation handler
func newNegationHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return negationHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle process negation handler
func (nh negationHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := nh.anchor.Key()
currentPath := nh.path + anchorKey + "/"
// if anchor is present in the resource then fail
if _, ok := resourceMap[anchorKey]; ok {
// no need to process elements in value as key cannot be present in resource
ac.AnchorError = newNegationAnchorError(fmt.Sprintf("%s is not allowed", currentPath))
return currentPath, ac.AnchorError
}
// key is not defined in the resource
return "", nil
}
// equalityHandler provides handler for non anchor element
type equalityHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newEqualityHandler returens instance of equality handler
func newEqualityHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return equalityHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processed equality anchor
func (eh equalityHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := eh.anchor.Key()
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, eh.pattern, originPattern, currentPath, ac)
if err != nil {
return returnPath, err
}
return "", nil
}
return "", nil
}
// defaultHandler provides handler for non anchor element
type defaultHandler struct {
element string
pattern interface{}
path string
}
// newDefaultHandler returns handler for non anchor elements
func newDefaultHandler(element string, pattern interface{}, path string) ValidationHandler {
return defaultHandler{
element: element,
pattern: pattern,
path: path,
}
}
// Handle process non anchor element
func (dh defaultHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
currentPath := dh.path + dh.element + "/"
if dh.pattern == "*" && resourceMap[dh.element] != nil {
return "", nil
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
return dh.path, fmt.Errorf("%s/%s not found", dh.path, dh.element)
} else {
path, err := handler(logging.GlobalLogger(), resourceMap[dh.element], dh.pattern, originPattern, currentPath, ac)
if err != nil {
return path, err
}
}
return "", nil
}
// conditionAnchorHandler provides handler for condition anchor
type conditionAnchorHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newConditionAnchorHandler returns an instance of condition acnhor handler
func newConditionAnchorHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return conditionAnchorHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processed condition anchor
func (ch conditionAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := ch.anchor.Key()
currentPath := ch.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, ch.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = newConditionalAnchorError(err.Error())
return returnPath, ac.AnchorError
}
return "", nil
} else {
msg := "conditional anchor key doesn't exist in the resource"
return currentPath, newConditionalAnchorError(msg)
}
}
// globalAnchorHandler provides handler for global condition anchor
type globalAnchorHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newGlobalAnchorHandler returns an instance of condition acnhor handler
func newGlobalAnchorHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return globalAnchorHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processed global condition anchor
func (gh globalAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := gh.anchor.Key()
currentPath := gh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, gh.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = newGlobalAnchorError(err.Error())
return returnPath, ac.AnchorError
}
return "", nil
}
return "", nil
}
// existenceHandler provides handlers to process exitence anchor handler
type existenceHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newExistenceHandler returns existence handler
func newExistenceHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return existenceHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processes the existence anchor handler
func (eh existenceHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
// skip is used by existence anchor to not process further if condition is not satisfied
anchorKey := eh.anchor.Key()
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// Existence anchor can only exist on resource value type of list
switch typedResource := value.(type) {
case []interface{}:
typedPattern, ok := eh.pattern.([]interface{})
if !ok {
return currentPath, fmt.Errorf("invalid pattern type %T: Pattern has to be of list to compare against resource", eh.pattern)
}
// loop all item in the pattern array
errorPath := ""
var err error
for _, patternMap := range typedPattern {
typedPatternMap, ok := patternMap.(map[string]interface{})
if !ok {
return currentPath, fmt.Errorf("invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
}
errorPath, err = validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath, ac)
if err != nil {
return errorPath, err
}
}
return errorPath, err
default:
return currentPath, fmt.Errorf("invalid resource type %T: Existence ^ () anchor can be used only on list/array type resource", value)
}
}
return "", nil
}
func validateExistenceListResource(handler resourceElementHandler, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *AnchorMap) (string, error) {
// the idea is all the element in the pattern array should be present atleast once in the resource list
// if non satisfy then throw an error
for i, resourceElement := range resourceList {
currentPath := path + strconv.Itoa(i) + "/"
_, err := handler(logging.GlobalLogger(), resourceElement, patternMap, originPattern, currentPath, ac)
if err == nil {
// condition is satisfied, dont check further
return "", nil
}
}
// none of the existence checks worked, so thats a failure sceanario
return path, fmt.Errorf("existence anchor validation failed at path %s", path)
}
package anchor
import (
"path"
"strings"
)
// GetAnchorsResourcesFromMap returns maps of anchors and resources
func GetAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := map[string]interface{}{}
resources := map[string]interface{}{}
for key, value := range patternMap {
if a := Parse(key); IsCondition(a) || IsExistence(a) || IsEquality(a) || IsNegation(a) {
anchors[key] = value
} else {
resources[key] = value
}
}
return anchors, resources
}
// RemoveAnchorsFromPath removes all anchor from path string
func RemoveAnchorsFromPath(str string) string {
parts := strings.Split(str, "/")
if parts[0] == "" {
parts = parts[1:]
}
for i, part := range parts {
if a := Parse(part); a != nil {
parts[i] = a.Key()
} else {
parts[i] = part
}
}
newPath := path.Join(parts...)
if path.IsAbs(str) {
newPath = "/" + newPath
}
return newPath
}
// resourceHasValueForKey checks if a resource has value for a given key
func resourceHasValueForKey(resource interface{}, key string) bool {
switch typed := resource.(type) {
case map[string]interface{}:
if _, ok := typed[key]; ok {
return true
}
return false
case []interface{}:
for _, value := range typed {
if resourceHasValueForKey(value, key) {
return true
}
}
return false
default:
return false
}
}
package api
import (
"fmt"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/ext/wildcard"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
utils "github.com/kyverno/kyverno/pkg/utils/match"
"gomodules.xyz/jsonpatch/v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// EngineResponse engine response to the action
type EngineResponse struct {
// Resource is the original resource
Resource unstructured.Unstructured
// Policy is the original policy
policy GenericPolicy
// namespaceLabels given by policy context
namespaceLabels map[string]string
// PatchedResource is the resource patched with the engine action changes
PatchedResource unstructured.Unstructured
// PolicyResponse contains the engine policy response
PolicyResponse PolicyResponse
// stats contains engine statistics
stats ExecutionStats
}
func resource(policyContext PolicyContext) unstructured.Unstructured {
resource := policyContext.NewResource()
if resource.Object == nil {
resource = policyContext.OldResource()
}
return resource
}
func NewEngineResponseFromPolicyContext(policyContext PolicyContext) EngineResponse {
return NewEngineResponse(
resource(policyContext),
NewKyvernoPolicy(policyContext.Policy()),
policyContext.NamespaceLabels(),
)
}
func NewEngineResponse(
resource unstructured.Unstructured,
policy GenericPolicy,
namespaceLabels map[string]string,
) EngineResponse {
return EngineResponse{
Resource: resource,
policy: policy,
namespaceLabels: namespaceLabels,
PatchedResource: resource,
}
}
func (er EngineResponse) WithPolicy(policy GenericPolicy) EngineResponse {
er.policy = policy
return er
}
func (er EngineResponse) WithPolicyResponse(policyResponse PolicyResponse) EngineResponse {
er.PolicyResponse = policyResponse
return er
}
func (r EngineResponse) WithStats(stats ExecutionStats) EngineResponse {
r.stats = stats
return r
}
func (er EngineResponse) WithPatchedResource(patchedResource unstructured.Unstructured) EngineResponse {
er.PatchedResource = patchedResource
return er
}
func (er EngineResponse) WithNamespaceLabels(namespaceLabels map[string]string) EngineResponse {
er.namespaceLabels = namespaceLabels
return er
}
func (er EngineResponse) WithWarning() EngineResponse {
er.PolicyResponse.emitWarning = true
return er
}
func (er *EngineResponse) NamespaceLabels() map[string]string {
return er.namespaceLabels
}
func (er *EngineResponse) Policy() GenericPolicy {
return er.policy
}
// IsOneOf checks if any rule has status in a given list
func (er EngineResponse) IsOneOf(status ...RuleStatus) bool {
for _, r := range er.PolicyResponse.Rules {
if r.HasStatus(status...) {
return true
}
}
return false
}
// IsSuccessful checks if any rule has failed or produced an error during execution
func (er EngineResponse) IsSuccessful() bool {
return !er.IsOneOf(RuleStatusFail, RuleStatusError)
}
// IsSkipped checks if any rule has skipped resource or not.
func (er EngineResponse) IsSkipped() bool {
return er.IsOneOf(RuleStatusSkip)
}
// IsFailed checks if any rule created a policy violation
func (er EngineResponse) IsFailed() bool {
return er.IsOneOf(RuleStatusFail)
}
// IsError checks if any rule resulted in a processing error
func (er EngineResponse) IsError() bool {
return er.IsOneOf(RuleStatusError)
}
// IsEmpty checks if any rule results are present
func (er EngineResponse) IsEmpty() bool {
return len(er.PolicyResponse.Rules) == 0
}
// IsNil checks if rule is an empty rule
func (er EngineResponse) IsNil() bool {
return datautils.DeepEqual(er, EngineResponse{})
}
// EmitsWarning checks if policy emits warnings
func (er EngineResponse) EmitsWarning() bool {
return er.PolicyResponse.emitWarning
}
// GetPatches returns all the patches joined
func (er EngineResponse) GetPatches() []jsonpatch.JsonPatchOperation {
originalBytes, err := er.Resource.MarshalJSON()
if err != nil {
return nil
}
patchedBytes, err := er.PatchedResource.MarshalJSON()
if err != nil {
return nil
}
patches, err := jsonpatch.CreatePatch(originalBytes, patchedBytes)
if err != nil {
return nil
}
return patches
}
// GetFailedRules returns failed rules
func (er EngineResponse) GetFailedRules() []string {
return er.getRules(func(rule RuleResponse) bool { return rule.HasStatus(RuleStatusFail, RuleStatusError) })
}
// GetFailedRulesWithErrors returns failed rules with corresponding error messages
func (er EngineResponse) GetFailedRulesWithErrors() []string {
return er.getRulesWithErrors(func(rule RuleResponse) bool { return rule.HasStatus(RuleStatusFail, RuleStatusError) })
}
// GetSuccessRules returns success rules
func (er EngineResponse) GetSuccessRules() []string {
return er.getRules(func(rule RuleResponse) bool { return rule.HasStatus(RuleStatusPass) })
}
// GetResourceSpec returns resourceSpec of er
func (er EngineResponse) GetResourceSpec() ResourceSpec {
return ResourceSpec{
Kind: er.PatchedResource.GetKind(),
APIVersion: er.PatchedResource.GetAPIVersion(),
Namespace: er.PatchedResource.GetNamespace(),
Name: er.PatchedResource.GetName(),
UID: string(er.PatchedResource.GetUID()),
}
}
func (er EngineResponse) getRules(predicate func(RuleResponse) bool) []string {
var rules []string
for _, r := range er.PolicyResponse.Rules {
if predicate(r) {
rules = append(rules, r.Name())
}
}
return rules
}
func (er EngineResponse) getRulesWithErrors(predicate func(RuleResponse) bool) []string {
var rules []string
for _, r := range er.PolicyResponse.Rules {
if predicate(r) {
rules = append(rules, fmt.Sprintf("%s: %s", r.Name(), r.Message()))
}
}
return rules
}
// If the policy is of type ValidatingAdmissionPolicy, an empty string is returned.
func (er EngineResponse) GetValidationFailureAction() kyvernov1.ValidationFailureAction {
pol := er.Policy()
if pol.AsKyvernoPolicy() == nil {
return ""
}
spec := pol.AsKyvernoPolicy().GetSpec()
for _, r := range spec.Rules {
if r.HasValidate() {
for _, v := range r.Validation.FailureActionOverrides {
if !v.Action.IsValid() {
continue
}
if v.Namespaces == nil {
hasPass, err := utils.CheckSelector(v.NamespaceSelector, er.namespaceLabels)
if err == nil && hasPass {
return v.Action
}
}
for _, ns := range v.Namespaces {
if wildcard.Match(ns, er.PatchedResource.GetNamespace()) {
if v.NamespaceSelector == nil {
return v.Action
}
hasPass, err := utils.CheckSelector(v.NamespaceSelector, er.namespaceLabels)
if err == nil && hasPass {
return v.Action
}
}
}
}
if r.Validation.FailureAction != nil {
return *r.Validation.FailureAction
}
} else if r.HasVerifyImages() {
if r.VerifyImages[0].FailureAction != nil {
return *r.VerifyImages[0].FailureAction
}
}
}
for _, v := range spec.ValidationFailureActionOverrides {
if !v.Action.IsValid() {
continue
}
if v.Namespaces == nil {
hasPass, err := utils.CheckSelector(v.NamespaceSelector, er.namespaceLabels)
if err == nil && hasPass {
return v.Action
}
}
for _, ns := range v.Namespaces {
if wildcard.Match(ns, er.PatchedResource.GetNamespace()) {
if v.NamespaceSelector == nil {
return v.Action
}
hasPass, err := utils.CheckSelector(v.NamespaceSelector, er.namespaceLabels)
if err == nil && hasPass {
return v.Action
}
}
}
}
return spec.ValidationFailureAction
}
package api
import (
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// GenericException abstracts the exception type (PolicyException, PolicyException)
type GenericException interface {
metav1.Object
// GetAPIVersion returns policy API version
GetAPIVersion() string
// GetKind returns policy kind
GetKind() string
// AsException returns the policy exception
AsException() *kyvernov2.PolicyException
// AsCELException returns the CEL policy exception
AsCELException() *policiesv1alpha1.PolicyException
}
type genericException struct {
metav1.Object
PolicyException *kyvernov2.PolicyException
CELPolicyException *policiesv1alpha1.PolicyException
}
func (p *genericException) AsException() *kyvernov2.PolicyException {
return p.PolicyException
}
func (p *genericException) AsCELException() *policiesv1alpha1.PolicyException {
return p.CELPolicyException
}
func (p *genericException) GetAPIVersion() string {
switch {
case p.PolicyException != nil:
return kyvernov2.GroupVersion.String()
case p.CELPolicyException != nil:
return policiesv1alpha1.GroupVersion.String()
}
return ""
}
func (p *genericException) GetKind() string {
switch {
case p.PolicyException != nil:
return "PolicyException"
case p.CELPolicyException != nil:
return "CELPolicyException"
}
return ""
}
func NewPolicyException(polex *kyvernov2.PolicyException) GenericException {
return &genericException{
Object: polex,
PolicyException: polex,
}
}
func NewCELPolicyException(polex *policiesv1alpha1.PolicyException) GenericException {
return &genericException{
Object: polex,
CELPolicyException: polex,
}
}
package api
import (
"encoding/json"
"fmt"
"strings"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/api/kyverno"
"gomodules.xyz/jsonpatch/v2"
)
type ImageVerificationMetadataStatus string
const (
ImageVerificationPass ImageVerificationMetadataStatus = "pass"
ImageVerificationFail ImageVerificationMetadataStatus = "fail"
ImageVerificationSkip ImageVerificationMetadataStatus = "skip"
)
type ImageVerificationMetadata struct {
Data map[string]ImageVerificationMetadataStatus `json:"data"`
}
func (ivm *ImageVerificationMetadata) Add(image string, verified ImageVerificationMetadataStatus) {
if ivm.Data == nil {
ivm.Data = make(map[string]ImageVerificationMetadataStatus)
}
ivm.Data[image] = verified
}
func (ivm *ImageVerificationMetadata) IsVerified(image string) bool {
if ivm.Data == nil {
return false
}
verified, ok := ivm.Data[image]
if !ok {
return false
}
return verified == ImageVerificationPass || verified == ImageVerificationSkip
}
func (ivm *ImageVerificationMetadata) ImageVerificationStatus(image string) ImageVerificationMetadataStatus {
if ivm.Data == nil {
return ImageVerificationFail
}
verified, ok := ivm.Data[image]
if !ok {
return ImageVerificationFail
}
return verified
}
func ParseImageMetadata(jsonData string) (*ImageVerificationMetadata, error) {
var data map[string]ImageVerificationMetadataStatus
if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
return nil, err
}
return &ImageVerificationMetadata{
Data: data,
}, nil
}
func (ivm *ImageVerificationMetadata) Patches(hasAnnotations bool, log logr.Logger) ([]jsonpatch.JsonPatchOperation, error) {
if data, err := json.Marshal(ivm.Data); err != nil {
return nil, fmt.Errorf("failed to marshal metadata value: %v: %w", data, err)
} else {
var patches []jsonpatch.JsonPatchOperation
if !hasAnnotations {
patch := jsonpatch.JsonPatchOperation{
Operation: "add",
Path: "/metadata/annotations",
Value: map[string]string{},
}
log.V(4).Info("adding annotation patch", "patch", patch)
patches = append(patches, patch)
}
patch := jsonpatch.JsonPatchOperation{
Operation: "add",
Path: makeAnnotationKeyForJSONPatch(),
Value: string(data),
}
log.V(4).Info("adding image verification patch", "patch", patch)
patches = append(patches, patch)
return patches, nil
}
}
func (ivm *ImageVerificationMetadata) Merge(other ImageVerificationMetadata) {
for k, v := range other.Data {
ivm.Add(k, v)
}
}
func (ivm *ImageVerificationMetadata) IsEmpty() bool {
return len(ivm.Data) == 0
}
func makeAnnotationKeyForJSONPatch() string {
return "/metadata/annotations/" + strings.ReplaceAll(kyverno.AnnotationImageVerify, "/", "~1")
}
package api
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Everything someone might need to validate a single ValidatingAdmissionPolicy
// against all of its registered bindings.
type ValidatingAdmissionPolicyData struct {
definition *admissionregistrationv1.ValidatingAdmissionPolicy
bindings []admissionregistrationv1.ValidatingAdmissionPolicyBinding
}
func (p *ValidatingAdmissionPolicyData) AddBinding(binding admissionregistrationv1.ValidatingAdmissionPolicyBinding) {
p.bindings = append(p.bindings, binding)
}
func (p *ValidatingAdmissionPolicyData) GetDefinition() *admissionregistrationv1.ValidatingAdmissionPolicy {
return p.definition
}
func (p *ValidatingAdmissionPolicyData) GetBindings() []admissionregistrationv1.ValidatingAdmissionPolicyBinding {
return p.bindings
}
func NewValidatingAdmissionPolicyData(
policy *admissionregistrationv1.ValidatingAdmissionPolicy,
bindings ...admissionregistrationv1.ValidatingAdmissionPolicyBinding,
) *ValidatingAdmissionPolicyData {
return &ValidatingAdmissionPolicyData{
definition: policy,
bindings: bindings,
}
}
// MutatingPolicyData holds a MAP and its associated MAPBs
type MutatingAdmissionPolicyData struct {
definition *admissionregistrationv1alpha1.MutatingAdmissionPolicy
bindings []admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding
}
// AddBinding appends a MAPB to the policy data
func (m *MutatingAdmissionPolicyData) AddBinding(b admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding) {
m.bindings = append(m.bindings, b)
}
func (p *MutatingAdmissionPolicyData) GetDefinition() *admissionregistrationv1alpha1.MutatingAdmissionPolicy {
return p.definition
}
func (p *MutatingAdmissionPolicyData) GetBindings() []admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding {
return p.bindings
}
// NewMutatingPolicyData initializes a MAP wrapper with no bindings
func NewMutatingAdmissionPolicyData(
policy *admissionregistrationv1alpha1.MutatingAdmissionPolicy,
bindings ...admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding,
) *MutatingAdmissionPolicyData {
return &MutatingAdmissionPolicyData{
definition: policy,
bindings: bindings,
}
}
// GenericPolicy abstracts the policy type (ClusterPolicy/Policy, ValidatingPolicy, ValidatingAdmissionPolicy and MutatingAdmissionPolicy)
// It is intended to be used in EngineResponse
type GenericPolicy interface {
metav1.Object
// GetAPIVersion returns policy API version
GetAPIVersion() string
// GetKind returns policy kind
GetKind() string
// IsNamespaced indicates if the policy is namespace scoped
IsNamespaced() bool
// AsObject returns the raw underlying policy
AsObject() any
// AsKyvernoPolicy returns the kyverno policy
AsKyvernoPolicy() kyvernov1.PolicyInterface
// AsValidatingAdmissionPolicy returns the validating admission policy with its bindings
AsValidatingAdmissionPolicy() *ValidatingAdmissionPolicyData
// AsValidatingPolicy returns the validating policy
AsValidatingPolicy() *policiesv1alpha1.ValidatingPolicy
// AsImageValidatingPolicy returns the imageverificationpolicy
AsImageValidatingPolicy() *policiesv1alpha1.ImageValidatingPolicy
// AsMutatingAdmissionPolicy returns the mutatingadmission policy
AsMutatingAdmissionPolicy() *MutatingAdmissionPolicyData
// AsMutatingPolicy returns the mutating policy
AsMutatingPolicy() *policiesv1alpha1.MutatingPolicy
// AsGeneratingPolicy returns the generating policy
AsGeneratingPolicy() *policiesv1alpha1.GeneratingPolicy
// AsDeletingPolicy returns the deleting policy
AsDeletingPolicy() *policiesv1alpha1.DeletingPolicy
}
type genericPolicy struct {
metav1.Object
PolicyInterface kyvernov1.PolicyInterface
ValidatingAdmissionPolicy *ValidatingAdmissionPolicyData
MutatingAdmissionPolicy *MutatingAdmissionPolicyData
ValidatingPolicy *policiesv1alpha1.ValidatingPolicy
ImageValidatingPolicy *policiesv1alpha1.ImageValidatingPolicy
MutatingPolicy *policiesv1alpha1.MutatingPolicy
GeneratingPolicy *policiesv1alpha1.GeneratingPolicy
DeletingPolicy *policiesv1alpha1.DeletingPolicy
}
func (p *genericPolicy) AsObject() any {
return p.Object
}
func (p *genericPolicy) AsKyvernoPolicy() kyvernov1.PolicyInterface {
return p.PolicyInterface
}
func (p *genericPolicy) AsValidatingAdmissionPolicy() *ValidatingAdmissionPolicyData {
return p.ValidatingAdmissionPolicy
}
func (p *genericPolicy) AsMutatingAdmissionPolicy() *MutatingAdmissionPolicyData {
return p.MutatingAdmissionPolicy
}
func (p *genericPolicy) AsValidatingPolicy() *policiesv1alpha1.ValidatingPolicy {
return p.ValidatingPolicy
}
func (p *genericPolicy) AsImageValidatingPolicy() *policiesv1alpha1.ImageValidatingPolicy {
return p.ImageValidatingPolicy
}
func (p *genericPolicy) AsMutatingPolicy() *policiesv1alpha1.MutatingPolicy {
return p.MutatingPolicy
}
func (p *genericPolicy) AsGeneratingPolicy() *policiesv1alpha1.GeneratingPolicy {
return p.GeneratingPolicy
}
func (p *genericPolicy) AsDeletingPolicy() *policiesv1alpha1.DeletingPolicy {
return p.DeletingPolicy
}
func (p *genericPolicy) GetAPIVersion() string {
switch {
case p.PolicyInterface != nil:
return kyvernov1.GroupVersion.String()
case p.ValidatingAdmissionPolicy != nil:
return admissionregistrationv1.SchemeGroupVersion.String()
case p.MutatingAdmissionPolicy != nil:
return admissionregistrationv1alpha1.SchemeGroupVersion.String()
case p.ValidatingPolicy != nil:
return policiesv1alpha1.GroupVersion.String()
case p.ImageValidatingPolicy != nil:
return policiesv1alpha1.GroupVersion.String()
case p.MutatingPolicy != nil:
return policiesv1alpha1.GroupVersion.String()
case p.GeneratingPolicy != nil:
return policiesv1alpha1.GroupVersion.String()
case p.DeletingPolicy != nil:
return policiesv1alpha1.GroupVersion.String()
}
return ""
}
func (p *genericPolicy) GetKind() string {
switch {
case p.PolicyInterface != nil:
return p.PolicyInterface.GetKind()
case p.ValidatingAdmissionPolicy != nil:
return "ValidatingAdmissionPolicy"
case p.MutatingAdmissionPolicy != nil:
return "MutatingAdmissionPolicy"
case p.ValidatingPolicy != nil:
return "ValidatingPolicy"
case p.ImageValidatingPolicy != nil:
return "ImageValidatingPolicy"
case p.MutatingPolicy != nil:
return "MutatingPolicy"
case p.GeneratingPolicy != nil:
return "GeneratingPolicy"
case p.DeletingPolicy != nil:
return "DeletingPolicy"
}
return ""
}
func (p *genericPolicy) IsNamespaced() bool {
switch {
case p.PolicyInterface != nil:
return p.PolicyInterface.IsNamespaced()
}
return false
}
func NewKyvernoPolicy(pol kyvernov1.PolicyInterface) GenericPolicy {
return &genericPolicy{
Object: pol,
PolicyInterface: pol,
}
}
func NewValidatingAdmissionPolicy(pol *admissionregistrationv1.ValidatingAdmissionPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
ValidatingAdmissionPolicy: NewValidatingAdmissionPolicyData(pol),
}
}
func NewValidatingAdmissionPolicyWithBindings(pol *admissionregistrationv1.ValidatingAdmissionPolicy, bindings ...admissionregistrationv1.ValidatingAdmissionPolicyBinding) GenericPolicy {
return &genericPolicy{
Object: pol,
ValidatingAdmissionPolicy: NewValidatingAdmissionPolicyData(pol, bindings...),
}
}
func NewMutatingAdmissionPolicy(pol *admissionregistrationv1alpha1.MutatingAdmissionPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
MutatingAdmissionPolicy: NewMutatingAdmissionPolicyData(pol),
}
}
func NewMutatingAdmissionPolicyWithBindings(pol *admissionregistrationv1alpha1.MutatingAdmissionPolicy, bindings ...admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding) GenericPolicy {
return &genericPolicy{
Object: pol,
MutatingAdmissionPolicy: NewMutatingAdmissionPolicyData(pol, bindings...),
}
}
func NewValidatingPolicy(pol *policiesv1alpha1.ValidatingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
ValidatingPolicy: pol,
}
}
func NewImageValidatingPolicy(pol *policiesv1alpha1.ImageValidatingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
ImageValidatingPolicy: pol,
}
}
func NewMutatingPolicy(pol *policiesv1alpha1.MutatingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
MutatingPolicy: pol,
}
}
func NewGeneratingPolicy(pol *policiesv1alpha1.GeneratingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
GeneratingPolicy: pol,
}
}
func NewDeletingPolicy(pol *policiesv1alpha1.DeletingPolicy) GenericPolicy {
return &genericPolicy{
Object: pol,
DeletingPolicy: pol,
}
}
package api
// PolicyResponse policy application response
type PolicyResponse struct {
// stats contains policy statistics
stats PolicyStats
// Rules contains policy rules responses
Rules []RuleResponse
// emitWarning controls if rule responses are returned in warning header
emitWarning bool
}
func (pr *PolicyResponse) Add(stats ExecutionStats, responses ...RuleResponse) {
for _, response := range responses {
pr.Rules = append(pr.Rules, response.WithStats(stats))
status := response.Status()
if status == RuleStatusPass || status == RuleStatusFail {
pr.stats.rulesAppliedCount++
} else if status == RuleStatusError {
pr.stats.rulesErrorCount++
}
}
}
func NewPolicyResponse() PolicyResponse {
return PolicyResponse{}
}
func (pr *PolicyResponse) Stats() PolicyStats {
return pr.stats
}
func (pr *PolicyResponse) RulesAppliedCount() int {
return pr.stats.RulesAppliedCount()
}
func (pr *PolicyResponse) RulesErrorCount() int {
return pr.stats.RulesErrorCount()
}
package api
import (
"context"
"errors"
corev1 "k8s.io/api/core/v1"
)
// NamespacedResourceResolver is an abstract interface used to resolve namespaced resources
// Any implementation might exist, cache based, file based, client based etc...
type NamespacedResourceResolver[T any] interface {
// Get is used to resolve a resource given a namespace and name
Get(
ctx context.Context,
namespace string,
name string,
) (T, error)
}
// ConfigmapResolver is an abstract interface used to resolve configmaps
type ConfigmapResolver = NamespacedResourceResolver[*corev1.ConfigMap]
// namespacedResourceResolverChain represents a chain of NamespacedResourceResolver
type namespacedResourceResolverChain[T any] []NamespacedResourceResolver[T]
// NewNamespacedResourceResolver creates a NamespacedResourceResolver from a NamespacedResourceResolver chain
// It will try to resolve resources by iterating over individual resolvers until one finds the requested resource
func NewNamespacedResourceResolver[T any](resolvers ...NamespacedResourceResolver[T]) (NamespacedResourceResolver[T], error) {
if len(resolvers) == 0 {
return nil, errors.New("no resolvers")
}
for _, resolver := range resolvers {
if resolver == nil {
return nil, errors.New("at least one resolver is nil")
}
}
return namespacedResourceResolverChain[T](resolvers), nil
}
func (chain namespacedResourceResolverChain[T]) Get(ctx context.Context, namespace, name string) (T, error) {
var lastErr error
for _, resolver := range chain {
r, err := resolver.Get(ctx, namespace, name)
if err == nil {
return r, nil
}
lastErr = err
}
var notFound T
return notFound, lastErr
}
package api
// ResourceSpec resource action applied on
type ResourceSpec struct {
Kind string
APIVersion string
Namespace string
Name string
UID string
}
// String implements Stringer interface
func (rs ResourceSpec) String() string {
return rs.Kind + "/" + rs.Namespace + "/" + rs.Name
}
package api
import (
"fmt"
pssutils "github.com/kyverno/kyverno/pkg/pss/utils"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/pod-security-admission/api"
)
// PodSecurityChecks details about pod securty checks
type PodSecurityChecks struct {
// Level is the pod security level
Level api.Level
// Version is the pod security version
Version string
// Checks contains check result details
Checks []pssutils.PSSCheckResult
}
// RuleResponse details for each rule application
type RuleResponse struct {
// name is the rule name specified in policy
name string
// ruleType is the rule type (Mutation,Generation,Validation) for Kyverno Policy
ruleType RuleType
// message is the message response from the rule application
message string
// status rule status
status RuleStatus
// stats contains rule statistics
stats ExecutionStats
// generatedResources is the list of resources generated by the generate rules of a policy
generatedResources []*unstructured.Unstructured
// patchedTarget is the patched resource for mutate.targets
patchedTarget *unstructured.Unstructured
// patchedTargetParentResourceGVR is the GVR of the parent resource of the PatchedTarget. This is only populated when PatchedTarget is a subresource.
patchedTargetParentResourceGVR metav1.GroupVersionResource
// patchedTargetSubresourceName is the name of the subresource which is patched, empty if the resource patched is not a subresource.
patchedTargetSubresourceName string
// podSecurityChecks contains pod security checks (only if this is a pod security rule)
podSecurityChecks *PodSecurityChecks
// exceptions are the exceptions applied (if any)
exceptions []GenericException
// vapbinding is the validatingadmissionpolicybinding (if any)
vapBinding *admissionregistrationv1.ValidatingAdmissionPolicyBinding
// mapbinding is the mutatingadmissionpolicybinding (if any)
mapBinding *admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding
// emitWarning enable passing rule message as warning to api server warning header
emitWarning bool
// properties are the additional properties from the rule that will be added to the policy report result
properties map[string]string
}
func NewRuleResponse(name string, ruleType RuleType, msg string, status RuleStatus, properties map[string]string) *RuleResponse {
emitWarn := false
if status == RuleStatusError || status == RuleStatusFail || status == RuleStatusWarn {
emitWarn = true
}
return &RuleResponse{
name: name,
ruleType: ruleType,
message: msg,
status: status,
emitWarning: emitWarn,
properties: properties,
}
}
func RuleError(name string, ruleType RuleType, msg string, err error, properties map[string]string) *RuleResponse {
if err != nil {
return NewRuleResponse(name, ruleType, fmt.Sprintf("%s: %s", msg, err.Error()), RuleStatusError, properties)
}
return NewRuleResponse(name, ruleType, msg, RuleStatusError, properties)
}
func RuleSkip(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusSkip, properties)
}
func RuleWarn(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusWarn, properties)
}
func RulePass(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusPass, properties)
}
func RuleFail(name string, ruleType RuleType, msg string, properties map[string]string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusFail, properties)
}
func (r RuleResponse) WithExceptions(exceptions []GenericException) *RuleResponse {
r.exceptions = exceptions
return &r
}
func (r RuleResponse) WithVAPBinding(binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding) *RuleResponse {
r.vapBinding = binding
return &r
}
func (r RuleResponse) WithMAPBinding(binding *admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding) *RuleResponse {
r.mapBinding = binding
return &r
}
func (r RuleResponse) WithPodSecurityChecks(checks PodSecurityChecks) *RuleResponse {
r.podSecurityChecks = &checks
return &r
}
func (r RuleResponse) WithPatchedTarget(patchedTarget *unstructured.Unstructured, gvr metav1.GroupVersionResource, subresource string) *RuleResponse {
r.patchedTarget = patchedTarget
r.patchedTargetParentResourceGVR = gvr
r.patchedTargetSubresourceName = subresource
return &r
}
func (r RuleResponse) WithGeneratedResources(resource []*unstructured.Unstructured) *RuleResponse {
r.generatedResources = resource
return &r
}
func (r RuleResponse) WithStats(stats ExecutionStats) RuleResponse {
r.stats = stats
return r
}
func (r RuleResponse) WithEmitWarning(emitWarning bool) *RuleResponse {
r.emitWarning = emitWarning
return &r
}
func (r RuleResponse) WithProperties(m map[string]string) *RuleResponse {
r.properties = m
return &r
}
func (r *RuleResponse) Stats() ExecutionStats {
return r.stats
}
func (r *RuleResponse) Exceptions() []GenericException {
return r.exceptions
}
func (r *RuleResponse) ValidatingAdmissionPolicyBinding() *admissionregistrationv1.ValidatingAdmissionPolicyBinding {
return r.vapBinding
}
func (r *RuleResponse) MutatingAdmissionPolicyBinding() *admissionregistrationv1alpha1.MutatingAdmissionPolicyBinding {
return r.mapBinding
}
func (r *RuleResponse) IsException() bool {
return len(r.exceptions) > 0
}
func (r *RuleResponse) PodSecurityChecks() *PodSecurityChecks {
return r.podSecurityChecks
}
func (r *RuleResponse) PatchedTarget() (*unstructured.Unstructured, metav1.GroupVersionResource, string) {
return r.patchedTarget, r.patchedTargetParentResourceGVR, r.patchedTargetSubresourceName
}
func (r *RuleResponse) GeneratedResources() []*unstructured.Unstructured {
return r.generatedResources
}
func (r *RuleResponse) Message() string {
return r.message
}
func (r *RuleResponse) Name() string {
return r.name
}
func (r *RuleResponse) RuleType() RuleType {
return r.ruleType
}
func (r *RuleResponse) Status() RuleStatus {
return r.status
}
func (r *RuleResponse) EmitWarning() bool {
return r.emitWarning
}
func (r *RuleResponse) Properties() map[string]string {
return r.properties
}
// HasStatus checks if rule status is in a given list
func (r *RuleResponse) HasStatus(status ...RuleStatus) bool {
for _, s := range status {
if r.status == s {
return true
}
}
return false
}
// String implements Stringer interface
func (r *RuleResponse) String() string {
return fmt.Sprintf("rule %s (%s): %v", r.name, r.ruleType, r.message)
}
package api
import (
"time"
)
// ExecutionStats stores the statistics for the single policy/rule application
type ExecutionStats struct {
// processingTime is the time required to apply the policy/rule on the resource
processingTime time.Duration
// timestamp of the instant the policy/rule got triggered
timestamp time.Time
}
func NewExecutionStats(startTime, endTime time.Time) ExecutionStats {
return ExecutionStats{
timestamp: startTime,
processingTime: endTime.Sub(startTime),
}
}
func (s ExecutionStats) Time() time.Time {
return s.timestamp
}
func (s ExecutionStats) Timestamp() int64 {
return s.timestamp.Unix()
}
func (s ExecutionStats) ProcessingTime() time.Duration {
return s.processingTime
}
// PolicyStats stores statistics for the single policy application
type PolicyStats struct {
// rulesAppliedCount is the count of rules that were applied successfully
rulesAppliedCount int
// rulesErrorCount is the count of rules that with execution errors
rulesErrorCount int
}
func (ps *PolicyStats) RulesAppliedCount() int {
return ps.rulesAppliedCount
}
func (ps *PolicyStats) RulesErrorCount() int {
return ps.rulesErrorCount
}
package engine
import (
"context"
"strings"
"github.com/go-logr/logr"
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/internal"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/variables"
"k8s.io/client-go/tools/cache"
)
// 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 (e *engine) applyBackgroundChecks(
logger logr.Logger,
policyContext engineapi.PolicyContext,
) engineapi.PolicyResponse {
return e.filterRules(policyContext, logger)
}
func (e *engine) filterRules(
policyContext engineapi.PolicyContext,
logger logr.Logger,
) engineapi.PolicyResponse {
policy := policyContext.Policy()
resp := engineapi.NewPolicyResponse()
applyRules := policy.GetSpec().GetApplyRules()
for _, rule := range autogen.Default.ComputeRules(policy, "") {
logger := internal.LoggerWithRule(logger, rule)
if ruleResp := e.filterRule(rule, logger, policyContext); ruleResp != nil {
resp.Rules = append(resp.Rules, *ruleResp)
if applyRules == kyvernov1.ApplyOne && ruleResp.Status() != engineapi.RuleStatusSkip {
break
}
}
}
return resp
}
func (e *engine) filterRule(
rule kyvernov1.Rule,
logger logr.Logger,
policyContext engineapi.PolicyContext,
) *engineapi.RuleResponse {
if !rule.HasGenerate() && !rule.HasMutateExisting() {
return nil
}
ruleType := engineapi.Mutation
if rule.HasGenerate() {
ruleType = engineapi.Generation
}
// get policy exceptions that matches both policy and rule name
exceptions, err := e.GetPolicyExceptions(policyContext.Policy(), rule.Name)
if err != nil {
logger.Error(err, "failed to get exceptions")
return nil
}
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
exceptions := make([]engineapi.GenericException, 0, len(matchedExceptions))
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return engineapi.RuleError(rule.Name, ruleType, "failed to compute exception key", err, rule.ReportProperties)
}
keys = append(keys, key)
exceptions = append(exceptions, engineapi.NewPolicyException(&exception))
}
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return engineapi.RuleSkip(rule.Name, ruleType, "rule is skipped due to policy exception "+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(exceptions)
}
newResource := policyContext.NewResource()
oldResource := policyContext.OldResource()
admissionInfo := policyContext.AdmissionInfo()
namespaceLabels := policyContext.NamespaceLabels()
policy := policyContext.Policy()
gvk, subresource := policyContext.ResourceKind()
if err := engineutils.MatchesResourceDescription(newResource, rule, admissionInfo, namespaceLabels, policy.GetNamespace(), gvk, subresource, policyContext.Operation()); err != nil {
logger.V(4).Info("new resource does not match...", "reason", err.Error())
if ruleType == engineapi.Generation {
// if the oldResource matched, return "false" to delete GR for it
if err = engineutils.MatchesResourceDescription(oldResource, rule, admissionInfo, namespaceLabels, policy.GetNamespace(), gvk, subresource, policyContext.Operation()); err == nil {
return engineapi.RuleFail(rule.Name, ruleType, "", rule.ReportProperties)
}
}
logger.V(4).Info("old resource does not match...", "reason", err.Error())
return nil
}
policyContext.JSONContext().Checkpoint()
defer policyContext.JSONContext().Restore()
contextLoader := e.ContextLoader(policyContext.Policy(), rule)
if err := contextLoader(context.TODO(), rule.Context, policyContext.JSONContext()); err != nil {
logger.V(4).Info("cannot add external data to the context", "reason", err.Error())
return nil
}
// operate on the copy of the conditions, as we perform variable substitution
copyConditions, err := engineutils.TransformConditions(rule.GetAnyAllConditions())
if err != nil {
logger.V(4).Info("cannot copy AnyAllConditions", "reason", err.Error())
return engineapi.RuleError(rule.Name, ruleType, "failed to convert AnyAllConditions", err, rule.ReportProperties)
}
// evaluate pre-conditions
pass, msg, err := variables.EvaluateConditions(logger, policyContext.JSONContext(), copyConditions)
if err != nil {
return engineapi.RuleError(rule.Name, ruleType, "failed to evaluate conditions", err, rule.ReportProperties)
}
if pass {
return engineapi.RulePass(rule.Name, ruleType, "", rule.ReportProperties)
}
if policyContext.OldResource().Object != nil {
if err = policyContext.JSONContext().AddResource(policyContext.OldResource().Object); err != nil {
return engineapi.RuleError(rule.Name, ruleType, "failed to update JSON context for old resource", err, rule.ReportProperties)
}
if val, msg, err := variables.EvaluateConditions(logger, policyContext.JSONContext(), copyConditions); err != nil {
return engineapi.RuleError(rule.Name, ruleType, "failed to evaluate conditions for old resource", err, rule.ReportProperties)
} else {
if val {
return engineapi.RuleFail(rule.Name, ruleType, msg, rule.ReportProperties)
}
}
}
logger.V(4).Info("skip rule as preconditions are not met", "rule", rule.Name, "message", msg)
return engineapi.RuleSkip(rule.Name, ruleType, "", rule.ReportProperties)
}
package context
import (
cont "context"
"encoding/csv"
"fmt"
"regexp"
"strings"
jsoniter "github.com/json-iterator/go"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/jsonutils"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/toggle"
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
var (
logger = logging.WithName("context")
json = jsoniter.ConfigCompatibleWithStandardLibrary
ReservedKeys = regexp.MustCompile(`request|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images|image|([a-z_0-9]+\()[^{}]`)
)
// EvalInterface is used to query and inspect context data
// TODO: move to contextapi to prevent circular dependencies
type EvalInterface interface {
// Query accepts a JMESPath expression and returns matching data
Query(query string) (interface{}, error)
// Operation returns the admission operation i.e. "request.operation"
QueryOperation() string
// HasChanged accepts a JMESPath expression and compares matching data in the
// request.object and request.oldObject context fields. If the data has changed
// it return `true`. If the data has not changed it returns false. If either
// request.object or request.oldObject are not found, an error is returned.
HasChanged(jmespath string) (bool, error)
}
// Interface to manage context operations
// TODO: move to contextapi to prevent circular dependencies
type Interface interface {
EvalInterface
// AddRequest marshals and adds the admission request to the context
AddRequest(request admissionv1.AdmissionRequest) error
// AddVariable adds a variable to the context
AddVariable(key string, value interface{}) error
// AddContextEntry adds a context entry to the context
AddContextEntry(name string, dataRaw []byte) error
// ReplaceContextEntry replaces a context entry to the context
ReplaceContextEntry(name string, dataRaw []byte) error
// AddResource merges resource json under request.object
AddResource(data map[string]interface{}) error
// AddOldResource merges resource json under request.oldObject
AddOldResource(data map[string]interface{}) error
// SetTargetResource merges resource json under target
SetTargetResource(data map[string]interface{}) error
// AddOperation merges operation under request.operation
AddOperation(data string) error
// AddUserInfo merges userInfo json under kyverno.userInfo
AddUserInfo(userInfo kyvernov2.RequestInfo) error
// AddServiceAccount merges ServiceAccount types
AddServiceAccount(userName string) error
// AddNamespace merges resource json under request.namespace
AddNamespace(namespace string) error
// AddElement adds element info to the context
AddElement(data interface{}, index, nesting int) error
// AddImageInfo adds image info to the context
AddImageInfo(info apiutils.ImageInfo, cfg config.Configuration) error
// AddImageInfos adds image infos to the context
AddImageInfos(resource *unstructured.Unstructured, cfg config.Configuration) error
// AddDeferredLoader adds a loader that is executed on first use (query)
// If deferred loading is disabled the loader is immediately executed.
AddDeferredLoader(loader DeferredLoader) error
// ImageInfo returns image infos present in the context
ImageInfo() map[string]map[string]apiutils.ImageInfo
// GenerateCustomImageInfo returns image infos as defined by a custom image extraction config
// and updates the context
GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kyvernov1.ImageExtractorConfigs, cfg config.Configuration) (map[string]map[string]apiutils.ImageInfo, error)
// Checkpoint creates a copy of the current internal state and pushes it into a stack of stored states.
Checkpoint()
// Restore sets the internal state to the last checkpoint, and removes the checkpoint.
Restore()
// Reset sets the internal state to the last checkpoint, but does not remove the checkpoint.
Reset()
// AddJSON merges the json map with context
addJSON(dataMap map[string]interface{}, overwriteMaps bool) error
}
// Context stores the data resources as JSON
type context struct {
jp jmespath.Interface
jsonRaw map[string]interface{}
jsonRawCheckpoints []map[string]interface{}
images map[string]map[string]apiutils.ImageInfo
operation kyvernov1.AdmissionOperation
deferred DeferredLoaders
}
// NewContext returns a new context
func NewContext(jp jmespath.Interface) Interface {
return NewContextFromRaw(jp, map[string]interface{}{})
}
// NewContextFromRaw returns a new context initialized with raw data
func NewContextFromRaw(jp jmespath.Interface, raw map[string]interface{}) Interface {
return &context{
jp: jp,
jsonRaw: raw,
jsonRawCheckpoints: make([]map[string]interface{}, 0),
deferred: NewDeferredLoaders(),
}
}
// addJSON merges json data
func (ctx *context) addJSON(dataMap map[string]interface{}, overwriteMaps bool) error {
mergeMaps(dataMap, ctx.jsonRaw, overwriteMaps)
return nil
}
func (ctx *context) QueryOperation() string {
if ctx.operation != "" {
return string(ctx.operation)
}
if requestMap, val := ctx.jsonRaw["request"].(map[string]interface{}); val {
if op, val := requestMap["operation"].(string); val {
return op
}
}
return ""
}
// AddRequest adds an admission request to context
func (ctx *context) AddRequest(request admissionv1.AdmissionRequest) error {
// an AdmissionRequest needs to be marshaled / unmarshaled as
// JSON to properly convert types of runtime.RawExtension
mapObj, err := jsonutils.DocumentToUntyped(request)
if err != nil {
return err
}
if err := addToContext(ctx, mapObj, false, "request"); err != nil {
return err
}
ctx.operation = kyvernov1.AdmissionOperation(request.Operation)
return nil
}
func (ctx *context) AddVariable(key string, value interface{}) error {
reader := csv.NewReader(strings.NewReader(key))
reader.Comma = '.'
if fields, err := reader.Read(); err != nil {
return err
} else {
return addToContext(ctx, value, false, fields...)
}
}
func (ctx *context) AddContextEntry(name string, dataRaw []byte) error {
var data interface{}
if err := json.Unmarshal(dataRaw, &data); err != nil {
logger.Error(err, "failed to unmarshal the resource")
return err
}
return addToContext(ctx, data, false, name)
}
func (ctx *context) ReplaceContextEntry(name string, dataRaw []byte) error {
var data interface{}
if err := json.Unmarshal(dataRaw, &data); err != nil {
logger.Error(err, "failed to unmarshal the resource")
return err
}
// Adding a nil entry to clean out any existing data in the context with the entry name
if err := addToContext(ctx, nil, false, name); err != nil {
logger.Error(err, "unable to replace context entry", "context entry name", name)
return err
}
return addToContext(ctx, data, false, name)
}
// AddResource data at path: request.object
func (ctx *context) AddResource(data map[string]interface{}) error {
clearLeafValue(ctx.jsonRaw, "request", "object")
return addToContext(ctx, data, false, "request", "object")
}
// AddOldResource data at path: request.oldObject
func (ctx *context) AddOldResource(data map[string]interface{}) error {
clearLeafValue(ctx.jsonRaw, "request", "oldObject")
return addToContext(ctx, data, false, "request", "oldObject")
}
// AddTargetResource adds data at path: target
func (ctx *context) SetTargetResource(data map[string]interface{}) error {
clearLeafValue(ctx.jsonRaw, "target")
return addToContext(ctx, data, false, "target")
}
// AddOperation data at path: request.operation
func (ctx *context) AddOperation(data string) error {
if err := addToContext(ctx, data, false, "request", "operation"); err != nil {
return err
}
ctx.operation = kyvernov1.AdmissionOperation(data)
return nil
}
// AddUserInfo adds userInfo at path request.userInfo
func (ctx *context) AddUserInfo(userRequestInfo kyvernov2.RequestInfo) error {
if data, err := toUnstructured(&userRequestInfo); err == nil {
return addToContext(ctx, data, false, "request")
} else {
return err
}
}
// AddServiceAccount removes prefix 'system:serviceaccount:' and namespace, then loads only SA name and SA namespace
func (ctx *context) AddServiceAccount(userName string) error {
saPrefix := "system:serviceaccount:"
var sa string
saName := ""
saNamespace := ""
if len(userName) <= len(saPrefix) {
sa = ""
} else {
sa = userName[len(saPrefix):]
}
// filter namespace
groups := strings.Split(sa, ":")
if len(groups) >= 2 {
saName = groups[1]
saNamespace = groups[0]
}
data := map[string]interface{}{
"serviceAccountName": saName,
"serviceAccountNamespace": saNamespace,
}
if err := ctx.addJSON(data, false); err != nil {
return err
}
logger.V(4).Info("Adding service account", "service account name", saName, "service account namespace", saNamespace)
return nil
}
// AddNamespace merges resource json under request.namespace
func (ctx *context) AddNamespace(namespace string) error {
return addToContext(ctx, namespace, false, "request", "namespace")
}
func (ctx *context) AddElement(data interface{}, index, nesting int) error {
nestedElement := fmt.Sprintf("element%d", nesting)
nestedElementIndex := fmt.Sprintf("elementIndex%d", nesting)
data = map[string]interface{}{
"element": data,
nestedElement: data,
"elementIndex": int64(index),
nestedElementIndex: int64(index),
}
return addToContext(ctx, data, true)
}
func (ctx *context) AddImageInfo(info apiutils.ImageInfo, cfg config.Configuration) error {
data := map[string]interface{}{
"reference": info.Reference,
"referenceWithTag": info.ReferenceWithTag,
"registry": info.Registry,
"path": info.Path,
"name": info.Name,
"tag": info.Tag,
"digest": info.Digest,
}
return addToContext(ctx, data, false, "image")
}
func (ctx *context) AddImageInfos(resource *unstructured.Unstructured, cfg config.Configuration) error {
imageInfoLoader := &ImageInfoLoader{
resource: resource,
eCtx: ctx,
cfg: cfg,
}
dl, err := NewDeferredLoader("images", imageInfoLoader, logger)
if err != nil {
return err
}
if toggle.FromContext(cont.Background()).EnableDeferredLoading() {
if err := ctx.AddDeferredLoader(dl); err != nil {
return err
}
} else {
if err := imageInfoLoader.LoadData(); err != nil {
return err
}
}
return nil
}
func (ctx *context) addImageInfos(images map[string]map[string]apiutils.ImageInfo) error {
if len(images) == 0 {
return nil
}
ctx.images = images
utm, err := convertImagesToUnstructured(images)
if err != nil {
return err
}
logging.V(4).Info("updated image info", "images", utm)
return addToContext(ctx, utm, false, "images")
}
type ImageInfoLoader struct {
resource *unstructured.Unstructured
hasLoaded bool
eCtx *context
cfg config.Configuration
}
func (l *ImageInfoLoader) HasLoaded() bool {
return l.hasLoaded
}
func (l *ImageInfoLoader) LoadData() error {
images, err := apiutils.ExtractImagesFromResource(*l.resource, nil, l.cfg)
if err != nil {
return err
}
return l.eCtx.addImageInfos(images)
}
func convertImagesToUnstructured(images map[string]map[string]apiutils.ImageInfo) (map[string]interface{}, error) {
results := map[string]interface{}{}
for containerType, v := range images {
imgMap := map[string]interface{}{}
for containerName := range v {
imageInfo := v[containerName]
img, err := toUnstructured(&imageInfo.ImageInfo)
if err != nil {
return nil, err
}
var pointer interface{} = imageInfo.Pointer
img["jsonPointer"] = pointer
imgMap[containerName] = img
}
results[containerType] = imgMap
}
return results, nil
}
func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kyvernov1.ImageExtractorConfigs, cfg config.Configuration) (map[string]map[string]apiutils.ImageInfo, error) {
images, err := apiutils.ExtractImagesFromResource(*resource, imageExtractorConfigs, cfg)
if err != nil {
return nil, fmt.Errorf("failed to extract images: %w", err)
}
if err := ctx.addImageInfos(images); err != nil {
return nil, fmt.Errorf("failed to add images to context: %w", err)
}
return images, nil
}
func (ctx *context) ImageInfo() map[string]map[string]apiutils.ImageInfo {
// force load of image info from deferred loader
if len(ctx.images) == 0 {
if err := ctx.loadDeferred("images"); err != nil {
return map[string]map[string]apiutils.ImageInfo{}
}
}
return ctx.images
}
// Checkpoint creates a copy of the current internal state and
// pushes it into a stack of stored states.
func (ctx *context) Checkpoint() {
jsonRawCheckpoint := ctx.copyContext(ctx.jsonRaw)
ctx.jsonRawCheckpoints = append(ctx.jsonRawCheckpoints, jsonRawCheckpoint)
}
func (ctx *context) copyContext(in map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(in))
for k, v := range in {
if ReservedKeys.MatchString(k) {
out[k] = v
} else {
out[k] = runtime.DeepCopyJSONValue(v)
}
}
return out
}
// Restore sets the internal state to the last checkpoint, and removes the checkpoint.
func (ctx *context) Restore() {
ctx.reset(true)
}
// Reset sets the internal state to the last checkpoint, but does not remove the checkpoint.
func (ctx *context) Reset() {
ctx.reset(false)
}
func (ctx *context) reset(restore bool) {
if ctx.resetCheckpoint(restore) {
ctx.deferred.Reset(restore, len(ctx.jsonRawCheckpoints))
}
}
func (ctx *context) resetCheckpoint(restore bool) bool {
if len(ctx.jsonRawCheckpoints) == 0 {
return false
}
n := len(ctx.jsonRawCheckpoints) - 1
jsonRawCheckpoint := ctx.jsonRawCheckpoints[n]
if restore {
ctx.jsonRawCheckpoints = ctx.jsonRawCheckpoints[:n]
ctx.jsonRaw = jsonRawCheckpoint
} else {
ctx.jsonRaw = ctx.copyContext(jsonRawCheckpoint)
}
return true
}
func (ctx *context) AddDeferredLoader(dl DeferredLoader) error {
ctx.deferred.Add(dl, len(ctx.jsonRawCheckpoints))
return nil
}
package context
import (
"regexp"
"github.com/go-logr/logr"
)
type deferredLoader struct {
name string
matcher regexp.Regexp
loader Loader
logger logr.Logger
}
func NewDeferredLoader(name string, loader Loader, logger logr.Logger) (DeferredLoader, error) {
// match on ASCII word boundaries except do not allow starting with a `.`
// this allows `x` to match `x.y` but not `y.x` or `y.x.z`
matcher, err := regexp.Compile(`(?:\A|\z|\s|[^.0-9A-Za-z])` + name + `\b`)
if err != nil {
return nil, err
}
return &deferredLoader{
name: name,
matcher: *matcher,
loader: loader,
logger: logger,
}, nil
}
func (dl *deferredLoader) Name() string {
return dl.name
}
func (dl *deferredLoader) HasLoaded() bool {
return dl.loader.HasLoaded()
}
func (dl *deferredLoader) LoadData() error {
if err := dl.loader.LoadData(); err != nil {
dl.logger.Error(err, "failed to load data", "name", dl.name)
return err
}
return nil
}
func (d *deferredLoader) Matches(query string) bool {
return d.matcher.MatchString(query)
}
type leveledLoader struct {
level int
matched bool
loader DeferredLoader
}
func (cl *leveledLoader) Level() int {
return cl.level
}
func (cl *leveledLoader) Name() string {
return cl.loader.Name()
}
func (cl *leveledLoader) Matches(query string) bool {
return cl.loader.Matches(query)
}
func (cl *leveledLoader) HasLoaded() bool {
return cl.loader.HasLoaded()
}
func (cl *leveledLoader) LoadData() error {
return cl.loader.LoadData()
}
type deferredLoaders struct {
level int
index int
loaders []*leveledLoader
}
func NewDeferredLoaders() DeferredLoaders {
return &deferredLoaders{
loaders: make([]*leveledLoader, 0),
level: -1,
index: -1,
}
}
func (d *deferredLoaders) Add(dl DeferredLoader, level int) {
d.loaders = append(d.loaders, &leveledLoader{level, false, dl})
}
func (d *deferredLoaders) Reset(restore bool, level int) {
d.clearMatches()
for i := 0; i < len(d.loaders); i++ {
l := d.loaders[i]
if l.level > level {
i = d.removeLoader(i)
} else {
if l.loader.HasLoaded() {
// reload data into the current context for restore, and
// for a reset but only if loader is at a prior level
if restore || (l.level < level) {
if err := d.loadData(l, i); err != nil {
logger.Error(err, "failed to reload context entry", "name", l.loader.Name())
}
}
if l.level == level {
i = d.removeLoader(i)
}
} else if !restore {
if l.level == level {
i = d.removeLoader(i)
}
}
}
}
}
// removeLoader removes loader at the specified index
// and returns the prior index
func (d *deferredLoaders) removeLoader(i int) int {
d.loaders = append(d.loaders[:i], d.loaders[i+1:]...)
return i - 1
}
func (d *deferredLoaders) clearMatches() {
for _, dl := range d.loaders {
dl.matched = false
}
}
func (d *deferredLoaders) LoadMatching(query string, level int) error {
if d.level >= 0 {
level = d.level
}
index := len(d.loaders)
if d.index >= 0 {
index = d.index
}
for l, idx := d.match(query, level, index); l != nil; l, idx = d.match(query, level, index) {
if err := d.loadData(l, idx); err != nil {
return err
}
}
return nil
}
func (d *deferredLoaders) loadData(l *leveledLoader, index int) error {
d.setLevelAndIndex(l.level, index)
defer d.setLevelAndIndex(-1, -1)
if err := l.LoadData(); err != nil {
return err
}
return nil
}
func (d *deferredLoaders) setLevelAndIndex(level, index int) {
d.level = level
d.index = index
}
func (d *deferredLoaders) match(query string, level, index int) (*leveledLoader, int) {
for i := 0; i < index; i++ {
dl := d.loaders[i]
if dl.matched || dl.loader.HasLoaded() {
continue
}
if dl.Matches(query) && dl.level <= level {
idx := i
d.loaders[i].matched = true
return dl, idx
}
}
return nil, -1
}
package context
import (
"fmt"
"strings"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
)
// Query the JSON context with JMESPATH search path
func (ctx *context) Query(query string) (interface{}, error) {
if err := ctx.loadDeferred(query); err != nil {
return nil, err
}
query = strings.TrimSpace(query)
if query == "" {
return nil, fmt.Errorf("invalid query (nil)")
}
// compile the query
queryPath, err := ctx.jp.Query(query)
if err != nil {
logger.Error(err, "incorrect query", "query", query)
return nil, fmt.Errorf("incorrect query %s: %v", query, err)
}
// search
result, err := queryPath.Search(ctx.jsonRaw)
if err != nil {
return nil, fmt.Errorf("JMESPath query failed: %w", err)
}
return result, nil
}
func (ctx *context) loadDeferred(query string) error {
level := len(ctx.jsonRawCheckpoints)
return ctx.deferred.LoadMatching(query, level)
}
func (ctx *context) HasChanged(jmespath string) (bool, error) {
objData, err := ctx.Query("request.object." + jmespath)
if err != nil {
return false, fmt.Errorf("failed to query request.object: %w", err)
}
if objData == nil {
return false, fmt.Errorf("request.object.%s not found", jmespath)
}
oldObjData, err := ctx.Query("request.oldObject." + jmespath)
if err != nil {
return false, fmt.Errorf("failed to query request.object: %w", err)
}
if oldObjData == nil {
return false, fmt.Errorf("request.oldObject.%s not found", jmespath)
}
return !datautils.DeepEqual(objData, oldObjData), nil
}
package context
import (
"fmt"
"regexp"
"strings"
"sync"
"github.com/kyverno/kyverno/ext/wildcard"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
)
// MockContext is used for testing and validation of variables
type MockContext struct {
mutex sync.RWMutex
re *regexp.Regexp
allowedPatterns []string
}
// NewMockContext creates a new MockContext that allows variables matching the supplied list of wildcard patterns
func NewMockContext(re *regexp.Regexp, vars ...string) *MockContext {
return &MockContext{re: re, allowedPatterns: vars}
}
// AddVariable adds given wildcardPattern to the allowed variable patterns
func (ctx *MockContext) AddVariable(wildcardPattern string) {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
builtInVarsCopy := ctx.allowedPatterns
ctx.allowedPatterns = append(builtInVarsCopy, wildcardPattern)
}
// Query the JSON context with JMESPATH search path
func (ctx *MockContext) Query(query string) (interface{}, error) {
query = strings.TrimSpace(query)
if query == "" {
return nil, fmt.Errorf("invalid query (nil)")
}
var emptyResult interface{}
// compile the query
jp := jmespath.New(config.NewDefaultConfiguration(false))
if _, err := jp.Query(query); err != nil {
return emptyResult, fmt.Errorf("invalid JMESPath query %s: %v", query, err)
}
// strip escaped quotes from JMESPath variables with dashes e.g. {{ \"my-map.data\".key }}
query = strings.Replace(query, "\"", "", -1)
if ctx.re != nil && ctx.re.MatchString(query) {
return emptyResult, nil
}
if ctx.isVariableDefined(query) {
return emptyResult, nil
}
return emptyResult, InvalidVariableError{
variable: query,
re: ctx.re,
allowedPatterns: ctx.allowedPatterns,
}
}
func (ctx *MockContext) QueryOperation() string {
if op, err := ctx.Query("request.operation"); err != nil {
if op != nil {
return op.(string)
}
}
return ""
}
func (ctx *MockContext) isVariableDefined(variable string) bool {
for _, pattern := range ctx.getVariables() {
if wildcard.Match(pattern, variable) {
return true
}
}
return false
}
func (ctx *MockContext) getVariables() []string {
ctx.mutex.RLock()
defer ctx.mutex.RUnlock()
vars := ctx.allowedPatterns
return vars
}
// InvalidVariableError represents error for non-white-listed variables
type InvalidVariableError struct {
variable string
re *regexp.Regexp
allowedPatterns []string
}
func (i InvalidVariableError) Error() string {
if i.re == nil {
return fmt.Sprintf("variable %s must match patterns %v", i.variable, i.allowedPatterns)
}
return fmt.Sprintf("variable %s must match regex \"%s\" or patterns %v", i.variable, i.re.String(), i.allowedPatterns)
}
func (ctx *MockContext) HasChanged(_ string) (bool, error) {
return false, nil
}
package context
import "fmt"
type mockLoader struct {
name string
level int
value interface{}
query string
hasLoaded bool
invocations int
eventHandler func(event string)
ctx Interface
}
func (ml *mockLoader) Name() string {
return ml.name
}
func (ml *mockLoader) SetLevel(level int) {
ml.level = level
}
func (ml *mockLoader) GetLevel() int {
return ml.level
}
func (ml *mockLoader) HasLoaded() bool {
return ml.hasLoaded
}
func (ml *mockLoader) LoadData() error {
ml.invocations++
err := ml.ctx.AddVariable(ml.name, ml.value)
if err != nil {
return err
}
// simulate a JMESPath evaluation after loading
if err := ml.executeQuery(); err != nil {
return err
}
ml.hasLoaded = true
if ml.eventHandler != nil {
event := fmt.Sprintf("%s=%v", ml.name, ml.value)
ml.eventHandler(event)
}
return nil
}
func (ml *mockLoader) executeQuery() error {
if ml.query == "" {
return nil
}
results, err := ml.ctx.Query(ml.query)
if err != nil {
return err
}
return ml.ctx.AddVariable(ml.name, results)
}
func (ml *mockLoader) setEventHandler(eventHandler func(string)) {
ml.eventHandler = eventHandler
}
func AddMockDeferredLoader(ctx Interface, name string, value interface{}) (*mockLoader, error) {
return addDeferredWithQuery(ctx, name, value, "")
}
func addDeferredWithQuery(ctx Interface, name string, value interface{}, query string) (*mockLoader, error) {
loader := &mockLoader{
name: name,
value: value,
ctx: ctx,
query: query,
}
d, err := NewDeferredLoader(name, loader, logger)
if err != nil {
return loader, err
}
err = ctx.AddDeferredLoader(d)
if err != nil {
return nil, err
}
return loader, nil
}
package context
import (
"reflect"
"k8s.io/apimachinery/pkg/runtime"
)
// AddJSONObject merges json data
func AddJSONObject(ctx Interface, data map[string]interface{}) error {
return ctx.addJSON(data, false)
}
func AddResource(ctx Interface, dataRaw []byte) error {
var data map[string]interface{}
if err := json.Unmarshal(dataRaw, &data); err != nil {
logger.Error(err, "failed to unmarshal the resource")
return err
}
return ctx.AddResource(data)
}
func AddOldResource(ctx Interface, dataRaw []byte) error {
var data map[string]interface{}
if err := json.Unmarshal(dataRaw, &data); err != nil {
logger.Error(err, "failed to unmarshal the resource")
return err
}
return ctx.AddOldResource(data)
}
func addToContext(ctx *context, data interface{}, overwriteMaps bool, tags ...string) error {
if v, err := convertStructs(data); err != nil {
return err
} else {
dataRaw := push(v, tags...)
return ctx.addJSON(dataRaw, overwriteMaps)
}
}
func clearLeafValue(data map[string]interface{}, tags ...string) bool {
if len(tags) == 0 {
return false
}
for i := 0; i < len(tags); i++ {
k := tags[i]
if i == len(tags)-1 {
delete(data, k)
return true
}
if nextMap, ok := data[k].(map[string]interface{}); ok {
data = nextMap
} else {
return false
}
}
return false
}
// convertStructs converts structs, and pointers-to-structs, to map[string]interface{}
func convertStructs(value interface{}) (interface{}, error) {
if value != nil {
v := reflect.ValueOf(value)
if v.Kind() == reflect.Struct {
return toUnstructured(value)
}
if v.Kind() == reflect.Ptr {
ptrVal := v.Elem()
if ptrVal.Kind() == reflect.Struct {
return toUnstructured(value)
}
}
}
return value, nil
}
func push(data interface{}, tags ...string) map[string]interface{} {
for i := len(tags) - 1; i >= 0; i-- {
data = map[string]interface{}{
tags[i]: data,
}
}
return data.(map[string]interface{})
}
// mergeMaps merges srcMap entries into destMap
func mergeMaps(srcMap, destMap map[string]interface{}, overwriteMaps bool) {
for k, v := range srcMap {
if nextSrcMap, ok := v.(map[string]interface{}); ok && !overwriteMaps {
if nextDestMap, ok := destMap[k].(map[string]interface{}); ok {
mergeMaps(nextSrcMap, nextDestMap, overwriteMaps)
} else {
destMap[k] = nextSrcMap
}
} else {
destMap[k] = v
}
}
}
// toUnstructured converts a struct with JSON tags to a map[string]interface{}
func toUnstructured(typedStruct interface{}) (map[string]interface{}, error) {
converter := runtime.DefaultUnstructuredConverter
u, err := converter.ToUnstructured(typedStruct)
return u, err
}
package engine
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
gojmespath "github.com/kyverno/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"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/internal"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/imageverifycache"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/metrics"
"github.com/kyverno/kyverno/pkg/tracing"
stringutils "github.com/kyverno/kyverno/pkg/utils/strings"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type engine struct {
configuration config.Configuration
metricsConfiguration config.MetricsConfiguration
jp jmespath.Interface
client engineapi.Client
isCluster bool
rclientFactory engineapi.RegistryClientFactory
ivCache imageverifycache.Client
contextLoader engineapi.ContextLoaderFactory
exceptionSelector engineapi.PolicyExceptionSelector
// metrics
resultCounter metric.Int64Counter
durationHistogram metric.Float64Histogram
}
type handlerFactory = func() (handlers.Handler, error)
func NewEngine(
configuration config.Configuration,
metricsConfiguration config.MetricsConfiguration,
jp jmespath.Interface,
client engineapi.Client,
rclientFactory engineapi.RegistryClientFactory,
ivCache imageverifycache.Client,
contextLoader engineapi.ContextLoaderFactory,
exceptionSelector engineapi.PolicyExceptionSelector,
isCluster *bool,
) engineapi.Engine {
if isCluster == nil {
defaultCluster := true
isCluster = &defaultCluster
}
meter := otel.GetMeterProvider().Meter(metrics.MeterName)
resultCounter, err := meter.Int64Counter(
"kyverno_policy_results",
metric.WithDescription("can be used to track the results associated with the policies applied in the user's cluster, at the level from rule to policy to admission requests"),
)
if err != nil {
logging.Error(err, "failed to register metric kyverno_policy_results")
}
durationHistogram, err := meter.Float64Histogram(
"kyverno_policy_execution_duration_seconds",
metric.WithDescription("can be used to track the latencies (in seconds) associated with the execution/processing of the individual rules under Kyverno policies whenever they evaluate incoming resource requests"),
)
if err != nil {
logging.Error(err, "failed to register metric kyverno_policy_execution_duration_seconds")
}
return &engine{
configuration: configuration,
metricsConfiguration: metricsConfiguration,
jp: jp,
client: client,
rclientFactory: rclientFactory,
ivCache: ivCache,
isCluster: *isCluster,
contextLoader: contextLoader,
exceptionSelector: exceptionSelector,
resultCounter: resultCounter,
durationHistogram: durationHistogram,
}
}
func (e *engine) Validate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
startTime := time.Now()
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.validate"), policyContext)
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
policyResponse := e.validate(ctx, logger, policyContext)
response = response.WithPolicyResponse(policyResponse)
}
response = response.WithStats(engineapi.NewExecutionStats(startTime, time.Now()))
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), policyContext.AdmissionInfo(), response)
return response
}
func (e *engine) Mutate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
startTime := time.Now()
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.mutate"), policyContext)
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
policyResponse, patchedResource := e.mutate(ctx, logger, policyContext)
response = response.
WithPatchedResource(patchedResource).
WithPolicyResponse(policyResponse)
}
response = response.WithStats(engineapi.NewExecutionStats(startTime, time.Now()))
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), policyContext.AdmissionInfo(), response)
return response
}
func (e *engine) Generate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
startTime := time.Now()
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.generate"), policyContext)
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
policyResponse := e.generateResponse(logger, policyContext)
response = response.WithPolicyResponse(policyResponse)
}
response = response.WithStats(engineapi.NewExecutionStats(startTime, time.Now()))
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), policyContext.AdmissionInfo(), response)
return response
}
func (e *engine) VerifyAndPatchImages(
ctx context.Context,
policyContext engineapi.PolicyContext,
) (engineapi.EngineResponse, engineapi.ImageVerificationMetadata) {
startTime := time.Now()
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
ivm := engineapi.ImageVerificationMetadata{}
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.verify"), policyContext)
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
policyResponse, patchedResource, innerIvm := e.verifyAndPatchImages(ctx, logger, policyContext)
response, ivm = response.
WithPolicyResponse(policyResponse).
WithPatchedResource(patchedResource), innerIvm
}
response = response.WithStats(engineapi.NewExecutionStats(startTime, time.Now()))
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), policyContext.AdmissionInfo(), response)
return response, ivm
}
func (e *engine) ApplyBackgroundChecks(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
startTime := time.Now()
response := engineapi.NewEngineResponseFromPolicyContext(policyContext)
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.background"), policyContext)
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
policyResponse := e.applyBackgroundChecks(logger, policyContext)
response = response.WithPolicyResponse(policyResponse)
}
response = response.WithStats(engineapi.NewExecutionStats(startTime, time.Now()))
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), policyContext.AdmissionInfo(), response)
return response
}
func (e *engine) ContextLoader(
policy kyvernov1.PolicyInterface,
rule kyvernov1.Rule,
) engineapi.EngineContextLoader {
loader := e.contextLoader(policy, rule)
return func(ctx context.Context, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface) error {
return loader.Load(
ctx,
e.jp,
e.client,
e.rclientFactory,
contextEntries,
jsonContext,
)
}
}
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
func (e *engine) matches(
rule kyvernov1.Rule,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
) error {
if policyContext.AdmissionOperation() {
request := policyContext.AdmissionInfo()
if e.configuration.IsExcluded(request.AdmissionUserInfo.Username, request.AdmissionUserInfo.Groups, request.Roles, request.ClusterRoles) {
return fmt.Errorf("excluded by configuration")
}
}
gvk, subresource := policyContext.ResourceKind()
err := engineutils.MatchesResourceDescription(
resource,
rule,
policyContext.AdmissionInfo(),
policyContext.NamespaceLabels(),
policyContext.Policy().GetNamespace(),
gvk,
subresource,
policyContext.Operation(),
)
if err == nil {
return nil
}
oldResource := policyContext.OldResource()
if resource.Object == nil && oldResource.Object != nil {
err := engineutils.MatchesResourceDescription(
policyContext.OldResource(),
rule,
policyContext.AdmissionInfo(),
policyContext.NamespaceLabels(),
policyContext.Policy().GetNamespace(),
gvk,
subresource,
policyContext.Operation(),
)
if err == nil {
return nil
}
}
return err
}
func (e *engine) invokeRuleHandler(
ctx context.Context,
logger logr.Logger,
handlerFactory handlerFactory,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
ruleType engineapi.RuleType,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
return tracing.ChildSpan2(
ctx,
"pkg/engine",
fmt.Sprintf("RULE %s", rule.Name),
func(ctx context.Context, span trace.Span) (patchedResource unstructured.Unstructured, results []engineapi.RuleResponse) {
// check if resource and rule match
if err := e.matches(rule, policyContext, resource); err != nil {
logger.V(4).Info("rule not matched", "reason", err.Error())
return resource, nil
}
if handlerFactory == nil {
return resource, handlers.WithError(rule, ruleType, "failed to instantiate handler", nil)
} else if handler, err := handlerFactory(); err != nil {
return resource, handlers.WithError(rule, ruleType, "failed to instantiate handler", err)
} else if handler != nil {
policyContext.JSONContext().Checkpoint()
defer func() {
policyContext.JSONContext().Restore()
if patchedResource.Object != nil {
if err := policyContext.JSONContext().AddResource(patchedResource.Object); err != nil {
logger.Error(err, "failed to add resource in the json context")
}
}
}()
// 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.WithError(rule, ruleType, "failed to load context", err)
}
// check preconditions
preconditionsPassed, msg, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), rule.GetAnyAllConditions())
if err != nil {
return resource, handlers.WithError(rule, ruleType, "failed to evaluate preconditions", err)
}
if !preconditionsPassed {
s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ")
return resource, handlers.WithSkip(rule, ruleType, s)
}
// substitute properties
if err := internal.SubstitutePropertiesInRule(logger, &rule, policyContext.JSONContext()); err != nil {
logger.Error(err, "failed to substitute variables in rule properties")
}
// get policy exceptions that matches both policy and rule name
exceptions, err := e.GetPolicyExceptions(policyContext.Policy(), rule.Name)
if err != nil {
logger.Error(err, "failed to get exceptions")
return resource, nil
}
// process handler
resource, ruleResponses := handler.Process(ctx, logger, policyContext, resource, rule, contextLoader, exceptions)
return resource, ruleResponses
}
return resource, nil
},
)
}
package engine
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
"k8s.io/client-go/tools/cache"
)
// GetPolicyExceptions get all exceptions that match both the policy and the rule.
func (e *engine) GetPolicyExceptions(
policy kyvernov1.PolicyInterface,
rule string,
) ([]*kyvernov2.PolicyException, error) {
if e.exceptionSelector == nil {
return nil, nil
}
return e.exceptionSelector.Find(cache.MetaObjectToName(policy).String(), rule)
}
package engine
import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/mutate"
"github.com/kyverno/kyverno/pkg/engine/variables"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// ForceMutate does not check any conditions, it simply mutates the given resource
// It is used to validate mutation logic, and for tests.
func ForceMutate(
ctx context.Interface,
logger logr.Logger,
policy kyvernov1.PolicyInterface,
resource unstructured.Unstructured,
) (unstructured.Unstructured, error) {
logger = internal.LoggerWithPolicy(logger, policy)
logger = internal.LoggerWithResource(logger, "resource", resource)
// logger := logging.WithName("EngineForceMutate").WithValues("policy", policy.GetName(), "kind", resource.GetKind(),
// "namespace", resource.GetNamespace(), "name", resource.GetName())
patchedResource := resource
// TODO: if we apply autogen, tests will fail
spec := policy.GetSpec()
for _, rule := range spec.Rules {
if !rule.HasMutate() {
continue
}
logger := internal.LoggerWithRule(logger, rule)
ruleCopy := rule.DeepCopy()
removeConditions(ruleCopy)
r, err := variables.SubstituteAllForceMutate(logger, ctx, *ruleCopy)
if err != nil {
return resource, err
}
if r.Mutation.ForEachMutation != nil {
patchedResource, err = applyForEachMutate(r.Name, r.Mutation.ForEachMutation, patchedResource, logger)
if err != nil {
return patchedResource, err
}
} else {
m := r.Mutation
patchedResource, err = applyPatches(m.GetPatchStrategicMerge(), m.PatchesJSON6902, patchedResource, logger)
if err != nil {
return patchedResource, err
}
}
}
return patchedResource, nil
}
func applyForEachMutate(name string, foreach []kyvernov1.ForEachMutation, resource unstructured.Unstructured, logger logr.Logger) (patchedResource unstructured.Unstructured, err error) {
patchedResource = resource
for _, fe := range foreach {
fem := fe.GetForEachMutation()
if len(fem) > 0 {
return applyForEachMutate(name, fem, patchedResource, logger)
}
patchedResource, err = applyPatches(fe.GetPatchStrategicMerge(), fe.PatchesJSON6902, patchedResource, logger)
if err != nil {
return resource, err
}
}
return patchedResource, nil
}
func applyPatches(mergePatch apiextensions.JSON, jsonPatch string, resource unstructured.Unstructured, logger logr.Logger) (unstructured.Unstructured, error) {
patcher := mutate.NewPatcher(mergePatch, jsonPatch)
resourceBytes, err := resource.MarshalJSON()
if err != nil {
return resource, err
}
resourceBytes, err = patcher.Patch(logger, resourceBytes)
if err != nil {
return resource, err
}
if err := resource.UnmarshalJSON(resourceBytes); err != nil {
return resource, err
}
return resource, err
}
// removeConditions mutates the rule to remove AnyAllConditions
func removeConditions(rule *kyvernov1.Rule) {
if rule.GetAnyAllConditions() != nil {
rule.SetAnyAllConditions(nil)
}
for i, fem := range rule.Mutation.ForEachMutation {
if fem.AnyAllConditions != nil {
rule.Mutation.ForEachMutation[i].AnyAllConditions = nil
}
}
}
package engine
import (
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/autogen"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/internal"
)
// GenerateResponse checks for validity of generate rule on the resource
func (e *engine) generateResponse(
logger logr.Logger,
policyContext engineapi.PolicyContext,
) engineapi.PolicyResponse {
resp := engineapi.NewPolicyResponse()
for _, rule := range autogen.Default.ComputeRules(policyContext.Policy(), "") {
logger := internal.LoggerWithRule(logger, rule)
if ruleResp := e.filterRule(rule, logger, policyContext); ruleResp != nil {
resp.Rules = append(resp.Rules, *ruleResp)
}
}
return resp
}
package engine
import (
"context"
"time"
"github.com/go-logr/logr"
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"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func (e *engine) verifyAndPatchImages(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
) (engineapi.PolicyResponse, unstructured.Unstructured, engineapi.ImageVerificationMetadata) {
resp := engineapi.NewPolicyResponse()
policy := policyContext.Policy()
matchedResource := policyContext.NewResource()
applyRules := policy.GetSpec().GetApplyRules()
ivm := engineapi.ImageVerificationMetadata{}
policyContext.JSONContext().Checkpoint()
defer policyContext.JSONContext().Restore()
for _, rule := range autogen.Default.ComputeRules(policy, "") {
startTime := time.Now()
logger := internal.LoggerWithRule(logger, rule)
handlerFactory := func() (handlers.Handler, error) {
if !rule.HasVerifyImages() {
return nil, nil
}
return mutation.NewMutateImageHandler(
policyContext,
matchedResource,
rule,
e.configuration,
e.rclientFactory,
e.ivCache,
&ivm,
)
}
resource, ruleResp := e.invokeRuleHandler(
ctx,
logger,
handlerFactory,
policyContext,
matchedResource,
rule,
engineapi.ImageVerify,
)
matchedResource = resource
resp.Add(engineapi.NewExecutionStats(startTime, time.Now()), ruleResp...)
if applyRules == kyvernov1.ApplyOne && resp.RulesAppliedCount() > 0 {
break
}
}
return resp, matchedResource, ivm
}
package engine
import (
"context"
"strconv"
"strings"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/metrics"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
func (e *engine) reportMetrics(
ctx context.Context,
logger logr.Logger,
operation kyvernov1.AdmissionOperation,
admissionOperation bool,
admissionInfo kyvernov2.RequestInfo,
response engineapi.EngineResponse,
) {
if e.resultCounter == nil && e.durationHistogram == nil {
return
}
policy := response.Policy().AsKyvernoPolicy()
if name, namespace, policyType, backgroundMode, validationMode, err := metrics.GetPolicyInfos(policy); err != nil {
logger.Error(err, "failed to get policy infos for metrics reporting")
} else {
if policyType == metrics.Cluster {
namespace = "-"
}
if !e.metricsConfiguration.CheckNamespace(namespace) {
return
}
resourceSpec := response.Resource
resourceKind := resourceSpec.GetKind()
resourceNamespace := resourceSpec.GetNamespace()
for _, rule := range response.PolicyResponse.Rules {
ruleName := rule.Name()
ruleType := metrics.ParseRuleTypeFromEngineRuleResponse(rule)
var ruleResult metrics.RuleResult
switch rule.Status() {
case engineapi.RuleStatusPass:
ruleResult = metrics.Pass
case engineapi.RuleStatusFail:
ruleResult = metrics.Fail
case engineapi.RuleStatusWarn:
ruleResult = metrics.Warn
case engineapi.RuleStatusError:
ruleResult = metrics.Error
case engineapi.RuleStatusSkip:
ruleResult = metrics.Skip
default:
ruleResult = metrics.Fail
}
executionCause := metrics.AdmissionRequest
if !admissionOperation {
executionCause = metrics.BackgroundScan
}
if e.resultCounter != nil {
commonLabels := []attribute.KeyValue{
attribute.String("policy_validation_mode", string(validationMode)),
attribute.String("policy_type", string(policyType)),
attribute.String("policy_background_mode", string(backgroundMode)),
attribute.String("policy_namespace", namespace),
attribute.String("policy_name", name),
attribute.String("resource_kind", resourceKind),
attribute.String("resource_namespace", resourceNamespace),
attribute.String("resource_request_operation", strings.ToLower(string(operation))),
attribute.String("rule_name", ruleName),
attribute.String("rule_result", string(ruleResult)),
attribute.String("rule_type", string(ruleType)),
attribute.String("rule_execution_cause", string(executionCause)),
attribute.String("dry_run", strconv.FormatBool(admissionInfo.DryRun)),
}
e.resultCounter.Add(ctx, 1, metric.WithAttributes(commonLabels...))
}
if e.durationHistogram != nil {
commonLabels := []attribute.KeyValue{
attribute.String("policy_validation_mode", string(validationMode)),
attribute.String("policy_type", string(policyType)),
attribute.String("policy_background_mode", string(backgroundMode)),
attribute.String("policy_namespace", namespace),
attribute.String("policy_name", name),
attribute.String("resource_kind", resourceKind),
attribute.String("resource_namespace", resourceNamespace),
attribute.String("resource_request_operation", strings.ToLower(string(operation))),
attribute.String("rule_name", ruleName),
attribute.String("rule_result", string(ruleResult)),
attribute.String("rule_type", string(ruleType)),
attribute.String("rule_execution_cause", string(executionCause)),
attribute.String("dry_run", strconv.FormatBool(admissionInfo.DryRun)),
}
e.durationHistogram.Record(ctx, rule.Stats().ProcessingTime().Seconds(), metric.WithAttributes(commonLabels...))
}
}
}
}
package engine
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
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"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// mutate performs mutation. Overlay first and then mutation patches
func (e *engine) mutate(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
) (engineapi.PolicyResponse, unstructured.Unstructured) {
resp := engineapi.NewPolicyResponse()
policy := policyContext.Policy()
matchedResource := policyContext.NewResource()
applyRules := policy.GetSpec().GetApplyRules()
policyContext.JSONContext().Checkpoint()
defer policyContext.JSONContext().Restore()
for _, rule := range autogen.Default.ComputeRules(policy, "") {
startTime := time.Now()
logger := internal.LoggerWithRule(logger, rule)
handlerFactory := func() (handlers.Handler, error) {
if !rule.HasMutate() {
return nil, nil
}
if !policyContext.AdmissionOperation() && rule.HasMutateExisting() {
if e.client == nil {
return nil, fmt.Errorf("Handler factory requires a client but a nil client was passed, likely due to a bug or unsupported operation.")
}
return mutation.NewMutateExistingHandler(e.client)
}
return mutation.NewMutateResourceHandler()
}
resource, ruleResp := e.invokeRuleHandler(
ctx,
logger,
handlerFactory,
policyContext,
matchedResource,
rule,
engineapi.Mutation,
)
matchedResource = resource
resp.Add(engineapi.NewExecutionStats(startTime, time.Now()), ruleResp...)
if applyRules == kyvernov1.ApplyOne && resp.RulesAppliedCount() > 0 {
break
}
}
return resp, matchedResource
}
package engine
import (
"context"
"time"
"github.com/go-logr/logr"
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/validation"
"github.com/kyverno/kyverno/pkg/engine/internal"
)
func (e *engine) validate(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
) engineapi.PolicyResponse {
resp := engineapi.NewPolicyResponse()
policy := policyContext.Policy()
matchedResource := policyContext.NewResource()
applyRules := policy.GetSpec().GetApplyRules()
policyContext.JSONContext().Checkpoint()
defer policyContext.JSONContext().Restore()
gvk, _ := policyContext.ResourceKind()
for _, rule := range autogen.Default.ComputeRules(policy, gvk.Kind) {
startTime := time.Now()
logger := internal.LoggerWithRule(logger, rule)
handlerFactory := func() (handlers.Handler, error) {
hasValidate := rule.HasValidate()
hasVerifyImageChecks := rule.HasVerifyImageChecks()
if !hasValidate && !hasVerifyImageChecks {
return nil, nil
}
if hasValidate {
if rule.Validation.Assert.Value != nil {
return validation.NewValidateAssertHandler()
}
hasVerifyManifest := rule.HasVerifyManifests()
hasValidatePss := rule.HasValidatePodSecurity()
hasValidateCEL := rule.HasValidateCEL()
if hasVerifyManifest {
return validation.NewValidateManifestHandler(
policyContext,
e.client,
)
} else if hasValidatePss {
return validation.NewValidatePssHandler()
} else if hasValidateCEL {
return validation.NewValidateCELHandler(e.client, e.isCluster)
} 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,
handlerFactory,
policyContext,
matchedResource,
rule,
engineapi.Validation,
)
matchedResource = resource
resp.Add(engineapi.NewExecutionStats(startTime, time.Now()), ruleResp...)
if applyRules == kyvernov1.ApplyOne && resp.RulesAppliedCount() > 0 {
break
}
}
return resp
}
package variables
import (
"github.com/kyverno/go-jmespath"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
)
func CheckNotFoundErr(err error) bool {
if err != nil {
switch err.(type) {
case jmespath.NotFoundError:
return true
case enginecontext.InvalidVariableError:
return false
default:
return false
}
}
return true
}
package variables
import (
"fmt"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/variables/operator"
stringutils "github.com/kyverno/kyverno/pkg/utils/strings"
)
// Evaluate evaluates the condition
func Evaluate(logger logr.Logger, ctx context.EvalInterface, condition kyvernov1.Condition) (bool, string, error) {
key, err := SubstituteAllInPreconditions(logger, ctx, condition.GetKey())
if err != nil {
return false, "", fmt.Errorf("failed to substitute variables in condition key: %w", err)
}
value, err := SubstituteAllInPreconditions(logger, ctx, condition.GetValue())
if err != nil {
return false, "", fmt.Errorf("failed to substitute variables in condition value: %w", err)
}
handler := operator.CreateOperatorHandler(logger, ctx, condition.Operator)
if handler == nil {
return false, "", fmt.Errorf("failed to create handler for condition operator: %w", err)
}
return handler.Evaluate(key, value), condition.Message, nil
}
// EvaluateConditions evaluates all the conditions present in a slice, in a backwards compatible way
func EvaluateConditions(log logr.Logger, ctx context.EvalInterface, conditions interface{}) (bool, string, error) {
switch typedConditions := conditions.(type) {
case *kyvernov1.AnyAllConditions:
return evaluateAnyAllConditions(log, ctx, *typedConditions)
case kyvernov1.AnyAllConditions:
return evaluateAnyAllConditions(log, ctx, typedConditions)
case []kyvernov1.Condition: // backwards compatibility
return evaluateOldConditions(log, ctx, typedConditions)
}
return false, "", fmt.Errorf("invalid condition")
}
func EvaluateAnyAllConditions(log logr.Logger, ctx context.EvalInterface, conditions []kyvernov1.AnyAllConditions) (bool, string, error) {
var conditionTrueMessages []string
for _, c := range conditions {
if val, msg, err := evaluateAnyAllConditions(log, ctx, c); err != nil {
return false, "", err
} else if !val {
return false, msg, nil
} else {
conditionTrueMessages = append(conditionTrueMessages, msg)
}
}
return true, stringutils.JoinNonEmpty(conditionTrueMessages, ";"), nil
}
// evaluateAnyAllConditions evaluates multiple conditions as a logical AND (all) or OR (any) operation depending on the conditions
func evaluateAnyAllConditions(log logr.Logger, ctx context.EvalInterface, conditions kyvernov1.AnyAllConditions) (bool, string, error) {
anyConditions, allConditions := conditions.AnyConditions, conditions.AllConditions
anyConditionsResult, allConditionsResult := true, true
var conditionFalseMessages []string
var conditionTrueMessages []string
// update the anyConditionsResult if they are present
if anyConditions != nil {
anyConditionsResult = false
for _, condition := range anyConditions {
if val, msg, err := Evaluate(log, ctx, condition); err != nil {
return false, "", err
} else if val {
anyConditionsResult = true
conditionTrueMessages = append(conditionTrueMessages, msg)
break
} else {
conditionFalseMessages = append(conditionFalseMessages, msg)
}
}
if !anyConditionsResult {
log.V(3).Info("no condition passed for 'any' block", "any", anyConditions)
}
}
// update the allConditionsResult if they are present
for _, condition := range allConditions {
if val, msg, err := Evaluate(log, ctx, condition); err != nil {
return false, "", err
} else if !val {
allConditionsResult = false
conditionFalseMessages = append(conditionFalseMessages, msg)
log.V(3).Info("a condition failed in 'all' block", "condition", condition, "message", msg)
break
} else {
conditionTrueMessages = append(conditionTrueMessages, msg)
}
}
finalResult := anyConditionsResult && allConditionsResult
if finalResult {
return finalResult, stringutils.JoinNonEmpty(conditionTrueMessages, "; "), nil
}
return finalResult, stringutils.JoinNonEmpty(conditionFalseMessages, "; "), nil
}
// evaluateOldConditions evaluates multiple conditions when those conditions are provided in the old manner i.e. without 'any' or 'all'
func evaluateOldConditions(log logr.Logger, ctx context.EvalInterface, conditions []kyvernov1.Condition) (bool, string, error) {
var conditionTrueMessages []string
for _, condition := range conditions {
if val, msg, err := Evaluate(log, ctx, condition); err != nil {
return false, "", err
} else if !val {
return false, msg, nil
} else {
conditionTrueMessages = append(conditionTrueMessages, msg)
}
}
return true, stringutils.JoinNonEmpty(conditionTrueMessages, ";"), nil
}
package variables
import (
"errors"
"fmt"
"path"
"strings"
"github.com/go-logr/logr"
jsoniter "github.com/json-iterator/go"
gojmespath "github.com/kyverno/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/anchor"
"github.com/kyverno/kyverno/pkg/engine/context"
jsonUtils "github.com/kyverno/kyverno/pkg/engine/jsonutils"
"github.com/kyverno/kyverno/pkg/engine/operator"
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/utils/jsonpointer"
)
var json = jsoniter.ConfigCompatibleWithStandardLibrary
// ReplaceAllVars replaces all variables with the value defined in the replacement function
// This is used to avoid validation errors
func ReplaceAllVars(src string, repl func(string) string) string {
wrapper := func(s string) string {
initial := len(regex.RegexVariableInit.FindAllString(s, -1)) > 0
prefix := ""
if !initial {
prefix = string(s[0])
s = s[1:]
}
return prefix + repl(s)
}
return regex.RegexVariables.ReplaceAllStringFunc(src, wrapper)
}
func newPreconditionsVariableResolver(log logr.Logger) VariableResolver {
// PreconditionsVariableResolver is used to substitute vars in preconditions.
// It returns an empty string if an error occurs during the substitution.
return func(ctx context.EvalInterface, variable string) (interface{}, error) {
value, err := DefaultVariableResolver(ctx, variable)
if err != nil {
log.V(4).Info(fmt.Sprintf("Variable substitution failed in preconditions, therefore nil value assigned to variable, \"%s\" ", variable))
return value, err
}
return value, nil
}
}
// SubstituteAll substitutes variables and references in the document. The document must be JSON data
// i.e. string, []interface{}, map[string]interface{}
func SubstituteAll(log logr.Logger, ctx context.EvalInterface, document interface{}) (interface{}, error) {
return substituteAll(log, ctx, document, DefaultVariableResolver)
}
func SubstituteAllInPreconditions(log logr.Logger, ctx context.EvalInterface, document interface{}) (interface{}, error) {
untypedDoc, err := jsonUtils.DocumentToUntyped(document)
if err != nil {
return nil, err
}
return substituteAll(log, ctx, untypedDoc, newPreconditionsVariableResolver(log))
}
func SubstituteAllInType[T any](log logr.Logger, ctx context.EvalInterface, t *T) (*T, error) {
untyped, err := jsonUtils.DocumentToUntyped(t)
if err != nil {
return nil, err
}
untypedResults, err := SubstituteAll(log, ctx, untyped)
if err != nil {
return nil, err
}
jsonBytes, err := json.Marshal(untypedResults)
if err != nil {
return nil, err
}
var result T
err = json.Unmarshal(jsonBytes, &result)
if err != nil {
return nil, err
}
return &result, nil
}
func SubstituteAllInRule(log logr.Logger, ctx context.EvalInterface, rule kyvernov1.Rule) (kyvernov1.Rule, error) {
result, err := SubstituteAllInType(log, ctx, &rule)
if err != nil {
return kyvernov1.Rule{}, err
}
return *result, nil
}
func untypedToTyped[T any](untyped interface{}) (*T, error) {
jsonRule, err := json.Marshal(untyped)
if err != nil {
return nil, err
}
var t T
err = json.Unmarshal(jsonRule, &t)
if err != nil {
return nil, err
}
return &t, nil
}
func SubstituteAllInConditions(log logr.Logger, ctx context.EvalInterface, conditions []kyvernov1.AnyAllConditions) ([]kyvernov1.AnyAllConditions, error) {
c, err := ConditionsToJSONObject(conditions)
if err != nil {
return nil, err
}
i, err := SubstituteAll(log, ctx, c)
if err != nil {
return nil, err
}
return JSONObjectToConditions(i)
}
func ConditionsToJSONObject(conditions []kyvernov1.AnyAllConditions) ([]map[string]interface{}, error) {
bytes, err := json.Marshal(conditions)
if err != nil {
return nil, err
}
m := []map[string]interface{}{}
if err := json.Unmarshal(bytes, &m); err != nil {
return nil, err
}
return m, nil
}
func JSONObjectToConditions(data interface{}) ([]kyvernov1.AnyAllConditions, error) {
bytes, err := json.Marshal(data)
if err != nil {
return nil, err
}
var c []kyvernov1.AnyAllConditions
if err := json.Unmarshal(bytes, &c); err != nil {
return nil, err
}
return c, nil
}
func substituteAll(log logr.Logger, ctx context.EvalInterface, document interface{}, resolver VariableResolver) (interface{}, error) {
document, err := substituteReferences(log, document)
if err != nil {
return nil, err
}
return substituteVars(log, ctx, document, resolver)
}
func SubstituteAllForceMutate(log logr.Logger, ctx context.Interface, typedRule kyvernov1.Rule) (_ kyvernov1.Rule, err error) {
var rule interface{}
rule, err = jsonUtils.DocumentToUntyped(typedRule)
if err != nil {
return kyvernov1.Rule{}, err
}
rule, err = substituteReferences(log, rule)
if err != nil {
return kyvernov1.Rule{}, err
}
if ctx == nil {
rule = replaceSubstituteVariables(rule)
} else {
rule, err = substituteVars(log, ctx, rule, DefaultVariableResolver)
if err != nil {
return kyvernov1.Rule{}, err
}
}
result, err := untypedToTyped[kyvernov1.Rule](rule)
if err != nil {
return kyvernov1.Rule{}, err
}
return *result, nil
}
func substituteVars(log logr.Logger, ctx context.EvalInterface, rule interface{}, vr VariableResolver) (interface{}, error) {
return jsonUtils.NewTraversal(rule, substituteVariablesIfAny(log, ctx, vr)).TraverseJSON()
}
func substituteReferences(log logr.Logger, rule interface{}) (interface{}, error) {
return jsonUtils.NewTraversal(rule, substituteReferencesIfAny(log)).TraverseJSON()
}
func ValidateElementInForEach(log logr.Logger, rule interface{}) (interface{}, error) {
return jsonUtils.NewTraversal(rule, validateElementInForEach()).TraverseJSON()
}
func validateElementInForEach() jsonUtils.Action {
return jsonUtils.OnlyForLeafsAndKeys(func(data *jsonUtils.ActionData) (interface{}, error) {
value, ok := data.Element.(string)
if !ok {
return data.Element, nil
}
vars := regex.RegexVariables.FindAllString(value, -1)
for _, v := range vars {
initial := len(regex.RegexVariableInit.FindAllString(v, -1)) > 0
if !initial {
v = v[1:]
}
variable, _ := replaceBracesAndTrimSpaces(v)
isElementVar := strings.HasPrefix(variable, "element") || variable == "elementIndex"
if isElementVar && !strings.Contains(data.Path, "/foreach/") {
return nil, fmt.Errorf("variable '%v' present outside of foreach at path %s", variable, data.Path)
}
}
return nil, nil
})
}
// NotResolvedReferenceError is returned when it is impossible to resolve the variable
type NotResolvedReferenceError struct {
reference string
path string
}
func (n NotResolvedReferenceError) Error() string {
return fmt.Sprintf("NotResolvedReferenceErr,reference %s not resolved at path %s", n.reference, n.path)
}
func substituteReferencesIfAny(log logr.Logger) jsonUtils.Action {
return jsonUtils.OnlyForLeafsAndKeys(func(data *jsonUtils.ActionData) (interface{}, error) {
value, ok := data.Element.(string)
if !ok {
return data.Element, nil
}
for _, v := range regex.RegexReferences.FindAllString(value, -1) {
initial := v[:2] == `$(`
old := v
if !initial {
v = v[1:]
}
resolvedReference, err := resolveReference(data.Document, v, data.Path)
if err != nil {
switch err.(type) {
case context.InvalidVariableError:
return nil, err
default:
return nil, fmt.Errorf("failed to resolve %v at path %s: %v", v, data.Path, err)
}
}
if resolvedReference == nil {
return data.Element, fmt.Errorf("got nil resolved variable %v at path %s: %v", v, data.Path, err)
}
log.V(3).Info("reference resolved", "reference", v, "value", resolvedReference, "path", data.Path)
if val, ok := resolvedReference.(string); ok {
replacement := ""
if !initial {
replacement = string(old[0])
}
replacement += val
value = strings.Replace(value, old, replacement, 1)
continue
}
return data.Element, NotResolvedReferenceError{
reference: v,
path: data.Path,
}
}
for _, v := range regex.RegexEscpReferences.FindAllString(value, -1) {
value = strings.Replace(value, v, v[1:], -1)
}
return value, nil
})
}
// VariableResolver defines the handler function for variable substitution
type VariableResolver = func(ctx context.EvalInterface, variable string) (interface{}, error)
// DefaultVariableResolver is used in all variable substitutions except preconditions
func DefaultVariableResolver(ctx context.EvalInterface, variable string) (interface{}, error) {
return ctx.Query(variable)
}
func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, lookupVar VariableResolver) jsonUtils.Action {
isDeleteRequest := isDeleteRequest(ctx)
return jsonUtils.OnlyForLeafsAndKeys(func(data *jsonUtils.ActionData) (interface{}, error) {
value, ok := data.Element.(string)
if !ok {
return data.Element, nil
}
vars := regex.RegexVariables.FindAllString(value, -1)
for len(vars) > 0 {
originalPattern := value
shallowSubstitution := false
var variable string
for _, v := range vars {
initial := len(regex.RegexVariableInit.FindAllString(v, -1)) > 0
old := v
if !initial {
v = v[1:]
}
variable, shallowSubstitution = replaceBracesAndTrimSpaces(v)
if variable == "@" {
pathPrefix := "target"
if _, err := ctx.Query("target"); err != nil {
pathPrefix = "request.object"
}
// Convert path to JMESPath for current identifier.
// Skip 2 elements (e.g. mutate.overlay | validate.pattern) plus "foreach" if it is part of the pointer.
// Prefix the pointer with pathPrefix.
val := jsonpointer.ParsePath(data.Path).SkipPast("foreach").SkipN(2).Prepend(strings.Split(pathPrefix, ".")...).JMESPath()
variable = strings.Replace(variable, "@", val, -1)
}
if isDeleteRequest {
variable = strings.ReplaceAll(variable, "request.object", "request.oldObject")
}
substitutedVar, err := lookupVar(ctx, variable)
if err != nil {
switch err.(type) {
case context.InvalidVariableError, gojmespath.NotFoundError:
return nil, err
default:
return nil, fmt.Errorf("failed to resolve %v at path %s: %v", variable, data.Path, err)
}
}
log.V(3).Info("variable substituted", "variable", v, "value", substitutedVar, "path", data.Path)
if originalPattern == v {
return substitutedVar, nil
}
prefix := ""
if !initial {
prefix = string(old[0])
}
if shallowSubstitution {
substitutedVar = strings.ReplaceAll(substitutedVar.(string), "{{", "\\{{")
}
if value, err = substituteVarInPattern(prefix, value, v, substitutedVar); err != nil {
return nil, fmt.Errorf("failed to resolve %v at path %s: %s", variable, data.Path, err.Error())
}
continue
}
if shallowSubstitution {
vars = []string{}
} else {
// check for nested variables in strings
vars = regex.RegexVariables.FindAllString(value, -1)
}
}
// Unescape escaped braces
value = strings.ReplaceAll(value, "\\{{", "{{")
return value, nil
})
}
func isDeleteRequest(ctx context.EvalInterface) bool {
if ctx == nil {
return false
}
if op := ctx.QueryOperation(); op != "" {
return op == "DELETE"
}
return false
}
func substituteVarInPattern(prefix, pattern, variable string, value interface{}) (string, error) {
var stringToSubstitute string
if s, ok := value.(string); ok {
stringToSubstitute = s
} else {
buffer, err := json.Marshal(value)
if err != nil {
return "", fmt.Errorf("failed to marshal %T: %v", value, value)
}
stringToSubstitute = string(buffer)
}
stringToSubstitute = prefix + stringToSubstitute
variable = prefix + variable
return strings.Replace(pattern, variable, stringToSubstitute, 1), nil
}
func replaceBracesAndTrimSpaces(v string) (variable string, isShallow bool) {
variable = strings.ReplaceAll(v, "{{", "")
variable = strings.ReplaceAll(variable, "}}", "")
variable = strings.TrimSpace(variable)
if strings.HasPrefix(variable, "-") {
variable = strings.TrimSpace(variable[1:])
return variable, true
}
return variable, false
}
func resolveReference(fullDocument interface{}, reference, absolutePath string) (interface{}, error) {
var foundValue interface{}
path := strings.Trim(reference, "$()")
operation := operator.GetOperatorFromStringPattern(path)
path = path[len(operation):]
if len(path) == 0 {
return nil, errors.New("expected path, found empty reference")
}
path = formAbsolutePath(path, absolutePath)
valFromReference, err := getValueFromReference(fullDocument, path)
if err != nil {
return err, nil
}
if operation == operator.Equal { // if operator does not exist return raw value
return valFromReference, nil
}
foundValue, err = valFromReferenceToString(valFromReference, string(operation))
if err != nil {
return "", err
}
return string(operation) + foundValue.(string), nil
}
// Parse value to string
func valFromReferenceToString(value interface{}, operator string) (string, error) {
switch typed := value.(type) {
case string:
return typed, nil
case int, int64:
return fmt.Sprintf("%d", value), nil
case float64:
return fmt.Sprintf("%f", value), nil
default:
return "", fmt.Errorf("incorrect expression: operator %s does not match with value %v", operator, value)
}
}
func FindAndShiftReferences(log logr.Logger, value, shift, pivot string) string {
for _, reference := range regex.RegexReferences.FindAllString(value, -1) {
initial := reference[:2] == `$(`
oldReference := reference
if !initial {
reference = reference[1:]
}
index := strings.Index(reference, pivot)
if index == -1 {
log.Error(fmt.Errorf(`failed to shift reference: pivot value "%s" was not found`, pivot), "pivot search failed")
}
// try to get rule index from the reference
if pivot == "anyPattern" {
ruleIndex := strings.Split(reference[index+len(pivot)+1:], "/")[0]
pivot = pivot + "/" + ruleIndex
}
shiftedReference := strings.Replace(reference, pivot, pivot+"/"+shift, -1)
replacement := ""
if !initial {
replacement = string(oldReference[0])
}
replacement += shiftedReference
value = strings.Replace(value, oldReference, replacement, 1)
}
return value
}
func formAbsolutePath(referencePath, absolutePath string) string {
if path.IsAbs(referencePath) {
return referencePath
}
return path.Join(absolutePath, referencePath)
}
func getValueFromReference(fullDocument interface{}, path string) (interface{}, error) {
var element interface{}
if _, err := jsonUtils.NewTraversal(fullDocument, jsonUtils.OnlyForLeafsAndKeys(
func(data *jsonUtils.ActionData) (interface{}, error) {
if anchor.RemoveAnchorsFromPath(data.Path) == path {
element = data.Element
}
return data.Element, nil
})).TraverseJSON(); err != nil {
return nil, err
}
return element, nil
}
func replaceSubstituteVariables(document interface{}) interface{} {
rawDocument, err := json.Marshal(document)
if err != nil {
return document
}
for {
if len(regex.RegexElementIndex.FindAllSubmatch(rawDocument, -1)) == 0 {
break
}
rawDocument = regex.RegexElementIndex.ReplaceAll(rawDocument, []byte(`0`))
}
for {
if len(regex.RegexVariables.FindAllSubmatch(rawDocument, -1)) == 0 {
break
}
rawDocument = regex.RegexVariables.ReplaceAll(rawDocument, []byte(`${1}placeholderValue`))
}
var output interface{}
err = json.Unmarshal(rawDocument, &output)
if err != nil {
logging.Error(err, "failed to unmarshall JSON", "document", string(rawDocument))
return document
}
return output
}
package pss
import (
"fmt"
"regexp"
"strconv"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/ext/wildcard"
pssutils "github.com/kyverno/kyverno/pkg/pss/utils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/pod-security-admission/api"
"k8s.io/pod-security-admission/policy"
)
var (
regexIndex = regexp.MustCompile(`\d+`)
regexStr = regexp.MustCompile(`[a-zA-Z]+`)
)
// Evaluate Pod's specified containers only and get PSSCheckResults
func evaluatePSS(level *api.LevelVersion, pod corev1.Pod) (results []pssutils.PSSCheckResult) {
checks := policy.DefaultChecks()
for _, check := range checks {
if level.Level == api.LevelBaseline && check.Level != level.Level {
continue
}
selectedCheck := check.Versions[0]
for i := 1; i < len(check.Versions); i++ {
nextCheck := check.Versions[i]
if !level.Version.Older(nextCheck.MinimumVersion) && selectedCheck.MinimumVersion.Older(nextCheck.MinimumVersion) {
selectedCheck = nextCheck
}
}
checkResult := selectedCheck.CheckPod(&pod.ObjectMeta, &pod.Spec, policy.WithFieldErrors())
if !checkResult.Allowed {
results = append(results, pssutils.PSSCheckResult{
ID: string(check.ID),
CheckResult: checkResult,
RestrictedFields: GetRestrictedFields(check),
})
}
}
return results
}
func exemptExclusions(defaultCheckResults, excludeCheckResults []pssutils.PSSCheckResult, exclude kyvernov1.PodSecurityStandard, pod *corev1.Pod, matching *corev1.Pod, isContainerLevelExclusion bool) ([]pssutils.PSSCheckResult, error) {
defaultCheckResultsMap := make(map[string]pssutils.PSSCheckResult, len(defaultCheckResults))
for _, result := range defaultCheckResults {
defaultCheckResultsMap[result.ID] = result
}
for _, excludeResult := range excludeCheckResults {
for _, checkID := range pssutils.PSS_control_name_to_ids[exclude.ControlName] {
if excludeResult.ID == checkID {
if excludeResult.CheckResult.ErrList != nil {
for _, excludeFieldErr := range *excludeResult.CheckResult.ErrList {
var excludeField, excludeContainerType string
var excludeIndexes []int
var isContainerLevelField bool = false
var excludeContainer corev1.Container
if isContainerLevelExclusion {
excludeField, excludeIndexes, excludeContainerType, isContainerLevelField = parseField(excludeFieldErr.Field)
} else {
excludeField = regexIndex.ReplaceAllString(excludeFieldErr.Field, "*")
}
if isContainerLevelField {
excludeContainer = getContainerInfo(matching, excludeIndexes[0], excludeContainerType)
}
excludeBadValues := extractBadValues(excludeFieldErr)
if excludeField == exclude.RestrictedField || len(exclude.RestrictedField) == 0 {
flag := true
if len(exclude.Values) != 0 {
for _, badValue := range excludeBadValues {
if !wildcard.CheckPatterns(exclude.Values, badValue) {
flag = false
break
}
}
}
if flag {
defaultCheckResult := defaultCheckResultsMap[checkID]
if defaultCheckResult.CheckResult.ErrList != nil {
for idx, defaultFieldErr := range *defaultCheckResult.CheckResult.ErrList {
var defaultField, defaultContainerType string
var defaultIndexes []int
var isContainerLevelField bool = false
var defaultContainer corev1.Container
if isContainerLevelExclusion {
defaultField, defaultIndexes, defaultContainerType, isContainerLevelField = parseField(defaultFieldErr.Field)
} else {
defaultField = regexIndex.ReplaceAllString(defaultFieldErr.Field, "*")
}
if isContainerLevelField {
defaultContainer = getContainerInfo(pod, defaultIndexes[0], defaultContainerType)
if excludeField == defaultField && excludeContainer.Name == defaultContainer.Name {
remove(defaultCheckResult.CheckResult.ErrList, idx)
break
}
} else {
if excludeField == defaultField {
remove(defaultCheckResult.CheckResult.ErrList, idx)
break
}
}
}
if len(*defaultCheckResult.CheckResult.ErrList) == 0 {
delete(defaultCheckResultsMap, checkID)
} else {
defaultCheckResultsMap[checkID] = defaultCheckResult
}
}
}
}
}
}
}
}
}
newDefaultCheckResults := make([]pssutils.PSSCheckResult, 0, len(defaultCheckResultsMap))
for _, result := range defaultCheckResultsMap {
newDefaultCheckResults = append(newDefaultCheckResults, result)
}
return newDefaultCheckResults, nil
}
func extractBadValues(excludeFieldErr *field.Error) []string {
var excludeBadValues []string
switch excludeFieldErr.BadValue.(type) {
case string:
badValue := excludeFieldErr.BadValue.(string)
if badValue == "" {
break
}
excludeBadValues = append(excludeBadValues, badValue)
case bool:
excludeBadValues = append(excludeBadValues, strconv.FormatBool(excludeFieldErr.BadValue.(bool)))
case int:
excludeBadValues = append(excludeBadValues, strconv.Itoa(excludeFieldErr.BadValue.(int)))
case []string:
excludeBadValues = append(excludeBadValues, excludeFieldErr.BadValue.([]string)...)
}
return excludeBadValues
}
func remove(s *field.ErrorList, i int) {
(*s)[i] = (*s)[len(*s)-1]
*s = (*s)[:len(*s)-1]
}
func isContainerType(str string) bool {
return str == "containers" || str == "initContainers" || str == "ephemeralContainers"
}
func parseField(field string) (string, []int, string, bool) {
matchesIdx := regexIndex.FindAllStringSubmatch(field, -1)
matchesStr := regexStr.FindAllString(field, -1)
field = regexIndex.ReplaceAllString(field, "*")
indexes := make([]int, 0, len(matchesIdx))
for _, match := range matchesIdx {
index, _ := strconv.Atoi(match[0])
indexes = append(indexes, index)
}
return field, indexes, matchesStr[1], isContainerType(matchesStr[1])
}
func getContainerInfo(pod *corev1.Pod, index int, containerType string) corev1.Container {
var container corev1.Container
switch {
case containerType == "containers":
container = pod.Spec.Containers[index]
case containerType == "initContainers":
container = pod.Spec.InitContainers[index]
case containerType == "ephemeralContainers":
container = (corev1.Container)(pod.Spec.EphemeralContainers[index].EphemeralContainerCommon)
default:
}
return container
}
func ParseVersion(level api.Level, version string) (*api.LevelVersion, error) {
// Get pod security admission version
var apiVersion api.Version
// Version set to "latest" by default
if version == "" || version == "latest" {
apiVersion = api.LatestVersion()
} else {
parsedApiVersion, err := api.ParseVersion(version)
if err != nil {
return nil, err
}
apiVersion = api.MajorMinorVersion(parsedApiVersion.Major(), parsedApiVersion.Minor())
}
return &api.LevelVersion{
Level: level,
Version: apiVersion,
}, nil
}
// EvaluatePod applies PSS checks to the pod and exempts controls specified in the rule
func EvaluatePod(levelVersion *api.LevelVersion, excludes []kyvernov1.PodSecurityStandard, pod *corev1.Pod) (bool, []pssutils.PSSCheckResult) {
var err error
// apply the pod security checks on pods
defaultCheckResults := evaluatePSS(levelVersion, *pod)
// exclude pod security controls if specified
if len(excludes) > 0 {
defaultCheckResults, err = ApplyPodSecurityExclusion(levelVersion, excludes, defaultCheckResults, pod)
}
return (len(defaultCheckResults) == 0 && err == nil), defaultCheckResults
}
// ApplyPodSecurityExclusion excludes pod security controls
func ApplyPodSecurityExclusion(
levelVersion *api.LevelVersion,
excludes []kyvernov1.PodSecurityStandard,
defaultCheckResults []pssutils.PSSCheckResult,
pod *corev1.Pod,
) ([]pssutils.PSSCheckResult, error) {
var err error
for _, exclude := range excludes {
spec, matching := GetPodWithMatchingContainers(exclude, pod)
switch {
// exclude pod level checks
case spec != nil:
excludeCheckResults := evaluatePSS(levelVersion, *spec)
defaultCheckResults, err = exemptExclusions(defaultCheckResults, excludeCheckResults, exclude, pod, matching, false)
// exclude container level checks
default:
excludeCheckResults := evaluatePSS(levelVersion, *matching)
defaultCheckResults, err = exemptExclusions(defaultCheckResults, excludeCheckResults, exclude, pod, matching, true)
}
}
return defaultCheckResults, err
}
// GetPodWithMatchingContainers extracts matching container/pod info by the given exclude rule
// and returns pod manifests containing spec and container info respectively
func GetPodWithMatchingContainers(exclude kyvernov1.PodSecurityStandard, pod *corev1.Pod) (podSpec, matching *corev1.Pod) {
if len(exclude.Images) == 0 {
podSpec = pod.DeepCopy()
podSpec.Spec.Containers = []corev1.Container{{Name: "fake"}}
podSpec.Spec.InitContainers = nil
podSpec.Spec.EphemeralContainers = nil
return podSpec, nil
}
matchingImages := exclude.Images
matching = &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: pod.GetName(),
Namespace: pod.GetNamespace(),
},
}
for _, container := range pod.Spec.Containers {
if wildcard.CheckPatterns(matchingImages, container.Image) {
matching.Spec.Containers = append(matching.Spec.Containers, container)
}
}
for _, container := range pod.Spec.InitContainers {
if wildcard.CheckPatterns(matchingImages, container.Image) {
matching.Spec.InitContainers = append(matching.Spec.InitContainers, container)
}
}
for _, container := range pod.Spec.EphemeralContainers {
if wildcard.CheckPatterns(matchingImages, container.Image) {
matching.Spec.EphemeralContainers = append(matching.Spec.EphemeralContainers, container)
}
}
return nil, matching
}
// Get restrictedFields from Check.ID
func GetRestrictedFields(check policy.Check) []pssutils.RestrictedField {
for _, control := range pssutils.PSS_control_name_to_ids {
for _, checkID := range control {
if string(check.ID) == checkID {
return pssutils.PSS_controls[checkID]
}
}
}
return nil
}
func FormatChecksPrint(checks []pssutils.PSSCheckResult) string {
var str string
for _, check := range checks {
str += fmt.Sprintf("(Forbidden reason: %s, field error list: [", check.CheckResult.ForbiddenReason)
for idx, err := range *check.CheckResult.ErrList {
badValueExist := true
switch err.BadValue.(type) {
case string:
badValue := err.BadValue.(string)
if badValue == "" {
badValueExist = false
}
default:
}
switch err.Type {
case field.ErrorTypeForbidden:
if badValueExist {
str += fmt.Sprintf("%s is forbidden, forbidden values found: %+v", err.Field, err.BadValue)
} else {
str += err.Error()
}
default:
str += err.Error()
}
if idx != len(*check.CheckResult.ErrList)-1 {
str += ", "
}
}
str += "])"
}
return str
}
package api
import (
"fmt"
"strconv"
"strings"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/logging"
imageutils "github.com/kyverno/kyverno/pkg/utils/image"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type ImageInfo struct {
imageutils.ImageInfo
// Pointer is the path to the image object in the resource
Pointer string `json:"jsonPointer"`
}
var (
podExtractors = BuildStandardExtractors("spec")
podControllerExtractors = BuildStandardExtractors("spec", "template", "spec")
cronjobControllerExtractors = BuildStandardExtractors("spec", "jobTemplate", "spec", "template", "spec")
registeredExtractors = map[string][]imageExtractor{
"Pod": podExtractors,
"DaemonSet": podControllerExtractors,
"Deployment": podControllerExtractors,
"ReplicaSet": podControllerExtractors,
"ReplicationController": podControllerExtractors,
"StatefulSet": podControllerExtractors,
"CronJob": cronjobControllerExtractors,
"Job": podControllerExtractors,
}
)
type imageExtractor struct {
Fields []string
Key string
Value string
Name string
JMESPath string
}
func (i *imageExtractor) ExtractFromResource(resource interface{}, cfg config.Configuration) (map[string]ImageInfo, error) {
imageInfo := map[string]ImageInfo{}
if err := extract(resource, []string{}, i.Key, i.Value, i.Fields, i.JMESPath, &imageInfo, cfg); err != nil {
return nil, err
}
return imageInfo, nil
}
func extract(
obj interface{},
path []string,
keyPath string,
valuePath string,
fields []string,
jmesPath string,
imageInfos *map[string]ImageInfo,
cfg config.Configuration,
) error {
if obj == nil {
return nil
}
if len(fields) > 0 && fields[0] == "*" {
switch typedObj := obj.(type) {
case []interface{}:
for i, v := range typedObj {
if err := extract(v, append(path, strconv.Itoa(i)), keyPath, valuePath, fields[1:], jmesPath, imageInfos, cfg); err != nil {
return err
}
}
case map[string]interface{}:
for i, v := range typedObj {
if err := extract(v, append(path, i), keyPath, valuePath, fields[1:], jmesPath, imageInfos, cfg); err != nil {
return err
}
}
case interface{}:
return fmt.Errorf("invalid type")
}
return nil
}
output, ok := obj.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid image config")
}
if len(fields) == 0 {
pointer := fmt.Sprintf("/%s/%s", strings.Join(path, "/"), valuePath)
key := pointer
if keyPath != "" {
key, ok = output[keyPath].(string)
if !ok {
return fmt.Errorf("invalid key")
}
}
value, ok := output[valuePath].(string)
if !ok || strings.TrimSpace(value) == "" {
// the image may not be present
logging.V(4).Info("image information is not present", "pointer", pointer)
return nil
}
if jmesPath != "" {
// TODO: should be injected
jp := jmespath.New(cfg)
q, err := jp.Query(jmesPath)
if err != nil {
return fmt.Errorf("invalid jmespath %s: %v", jmesPath, err)
}
result, err := q.Search(value)
if err != nil {
return fmt.Errorf("failed to apply jmespath %s: %v", jmesPath, err)
}
resultStr, ok := result.(string)
if !ok {
return fmt.Errorf("jmespath %s must produce a string, but produced %v", jmesPath, result)
}
value = resultStr
}
if imageInfo, err := imageutils.GetImageInfo(value, cfg); err != nil {
return fmt.Errorf("invalid image '%s' (%s)", value, err.Error())
} else {
(*imageInfos)[key] = ImageInfo{*imageInfo, pointer}
}
return nil
}
currentPath := fields[0]
return extract(output[currentPath], append(path, currentPath), keyPath, valuePath, fields[1:], jmesPath, imageInfos, cfg)
}
func BuildStandardExtractors(tags ...string) []imageExtractor {
extractors := make([]imageExtractor, 0, 3)
for _, tag := range []string{"initContainers", "containers", "ephemeralContainers"} {
var t []string
t = append(t, tags...)
t = append(t, tag)
t = append(t, "*")
extractors = append(extractors, imageExtractor{Fields: t, Key: "name", Value: "image", Name: tag})
}
return extractors
}
func lookupImageExtractor(kind string, configs kyvernov1.ImageExtractorConfigs) []imageExtractor {
if configs != nil {
if extractorConfigs, ok := configs[kind]; ok {
extractors := []imageExtractor{}
for _, c := range extractorConfigs {
fields := func(input []string) []string {
output := []string{}
for _, i := range input {
o := strings.Trim(i, " ")
if o != "" {
output = append(output, o)
}
}
return output
}(strings.Split(c.Path, "/"))
name := c.Name
if name == "" {
name = "custom"
}
value := c.Value
if value == "" {
value = fields[len(fields)-1]
fields = fields[:len(fields)-1]
}
extractors = append(extractors, imageExtractor{
Fields: fields,
Key: c.Key,
Name: name,
Value: value,
JMESPath: c.JMESPath,
})
}
return extractors
}
}
return registeredExtractors[kind]
}
func ExtractImagesFromResource(resource unstructured.Unstructured, configs kyvernov1.ImageExtractorConfigs, cfg config.Configuration) (map[string]map[string]ImageInfo, error) {
infos := map[string]map[string]ImageInfo{}
extractors := lookupImageExtractor(resource.GetKind(), configs)
if extractors != nil && len(extractors) == 0 {
return nil, fmt.Errorf("no extractors found for %s", resource.GetKind())
}
for _, extractor := range extractors {
if infoMap, err := extractor.ExtractFromResource(resource.Object, cfg); err != nil {
return nil, err
} else if len(infoMap) > 0 {
infos[extractor.Name] = infoMap
}
}
return infos, nil
}
package policy
import (
"context"
"fmt"
"slices"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/admissionpolicy"
authChecker "github.com/kyverno/kyverno/pkg/auth/checker"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/policy/generate"
"github.com/kyverno/kyverno/pkg/policy/mutate"
"github.com/kyverno/kyverno/pkg/policy/validate"
"github.com/kyverno/kyverno/pkg/toggle"
)
// Validation provides methods to validate a rule
type Validation interface {
Validate(ctx context.Context, verbs []string) (warnings []string, path string, err error)
}
// validateAction performs validation on the rule actions
// - Mutate
// - Validation
// - Generate
func validateActions(idx int, rule *kyvernov1.Rule, client dclient.Interface, mock bool, backgroundSA, reportsSA string) (warnings []string, err error) {
if rule == nil {
return nil, nil
}
var checker Validation
// Mutate
if rule.HasMutate() {
checker = mutate.NewMutateFactory(rule, client, mock, backgroundSA, reportsSA)
if w, path, err := checker.Validate(context.TODO(), nil); err != nil {
return nil, fmt.Errorf("path: spec.rules[%d].mutate.%s.: %v", idx, path, err)
} else if w != nil {
warnings = append(warnings, w...)
}
}
// Validate
if rule.HasValidate() {
if reportsSA != "" {
checker = validate.NewValidateFactory(rule, client, mock, reportsSA)
if w, path, err := checker.Validate(context.TODO(), nil); err != nil {
return nil, fmt.Errorf("path: spec.rules[%d].validate.%s.: %v", idx, path, err)
} else if w != nil {
warnings = append(warnings, w...)
}
}
if client != nil && rule.HasValidateCEL() && toggle.FromContext(context.TODO()).GenerateValidatingAdmissionPolicy() {
authCheck := authChecker.NewSelfChecker(client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews())
if !admissionpolicy.HasValidatingAdmissionPolicyPermission(authCheck) {
warnings = append(warnings, "insufficient permissions to generate ValidatingAdmissionPolicies")
}
if !admissionpolicy.HasValidatingAdmissionPolicyBindingPermission(authCheck) {
warnings = append(warnings, "insufficient permissions to generate ValidatingAdmissionPolicies")
}
}
}
// Generate
if rule.HasGenerate() {
// TODO: this check is there to support offline validations
// generate uses selfSubjectReviews to verify actions
// this need to modified to use different implementation for online and offline mode
if mock {
checker = generate.NewFakeGenerate(*rule.Generation)
if w, path, err := checker.Validate(context.TODO(), nil); err != nil {
return nil, fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
} else if warnings != nil {
warnings = append(warnings, w...)
}
} else {
if rule.Generation.Synchronize {
admissionSA := fmt.Sprintf("system:serviceaccount:%s:%s", config.KyvernoNamespace(), config.KyvernoServiceAccountName())
checker = generate.NewGenerateFactory(client, rule, admissionSA, reportsSA, logging.GlobalLogger())
if w, path, err := checker.Validate(context.TODO(), []string{"list", "get"}); err != nil {
return nil, fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
} else if warnings != nil {
warnings = append(warnings, w...)
}
}
checker = generate.NewGenerateFactory(client, rule, backgroundSA, reportsSA, logging.GlobalLogger())
if w, path, err := checker.Validate(context.TODO(), nil); err != nil {
return nil, fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", idx, path, err)
} else if warnings != nil {
warnings = append(warnings, w...)
}
}
if slices.Contains(rule.MatchResources.Kinds, rule.Generation.Kind) {
return nil, fmt.Errorf("generation kind and match resource kind should not be the same")
}
}
return warnings, nil
}
package policy
import (
"fmt"
"regexp"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
)
var ForbiddenUserVariables = []*regexp.Regexp{
regexp.MustCompile(`[^\.](serviceAccountName)\b`),
regexp.MustCompile(`[^\.](serviceAccountNamespace)\b`),
regexp.MustCompile(`[^\.](request.userInfo)\b`),
regexp.MustCompile(`[^\.](request.roles)\b`),
regexp.MustCompile(`[^\.](request.clusterRoles)\b`),
}
// containsUserVariables returns error if variable that does not start from request.object
func containsUserVariables(policy kyvernov1.PolicyInterface, vars [][]string) error {
rules := autogen.Default.ComputeRules(policy, "")
for idx := range rules {
if err := hasUserMatchExclude(idx, &rules[idx]); err != nil {
return err
}
}
for _, rule := range policy.GetSpec().Rules {
if rule.HasMutateExisting() {
return nil
}
}
for _, s := range vars {
for _, banned := range ForbiddenUserVariables {
if banned.Match([]byte(s[2])) {
return fmt.Errorf("variable %s is not allowed", s[2])
}
}
}
return nil
}
func hasUserMatchExclude(idx int, rule *kyvernov1.Rule) error {
if path := userInfoDefined(rule.MatchResources.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/match/%s", idx, path)
}
if len(rule.MatchResources.Any) > 0 {
for i, value := range rule.MatchResources.Any {
if path := userInfoDefined(value.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/match/any[%d]/%s", idx, i, path)
}
}
}
if len(rule.MatchResources.All) > 0 {
for i, value := range rule.MatchResources.All {
if path := userInfoDefined(value.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/match/all[%d]/%s", idx, i, path)
}
}
}
if rule.ExcludeResources != nil {
if path := userInfoDefined(rule.ExcludeResources.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/exclude/%s", idx, path)
}
if len(rule.ExcludeResources.All) > 0 {
for i, value := range rule.ExcludeResources.All {
if path := userInfoDefined(value.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/exclude/all[%d]/%s", idx, i, path)
}
}
}
if len(rule.ExcludeResources.Any) > 0 {
for i, value := range rule.ExcludeResources.Any {
if path := userInfoDefined(value.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/exclude/any[%d]/%s", idx, i, path)
}
}
}
}
return nil
}
func userInfoDefined(ui kyvernov1.UserInfo) string {
if len(ui.Roles) > 0 {
return "roles"
}
if len(ui.ClusterRoles) > 0 {
return "clusterRoles"
}
if len(ui.Subjects) > 0 {
return "subjects"
}
return ""
}
package policy
import (
"crypto/md5" //nolint:gosec
"encoding/hex"
"encoding/json"
"errors"
"fmt"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"k8s.io/apimachinery/pkg/util/sets"
)
func immutableGenerateFields(new, old kyvernov1.PolicyInterface) (string, error) {
if new == nil || old == nil {
return "", nil
}
oldHasSynchronizingRule := hasSynchronizingRule(old.GetSpec().Rules)
oldRuleHashes, oldGenerationHashes, err := buildHashes(old.GetSpec().Rules, oldHasSynchronizingRule)
if err != nil {
return "", err
}
newRuleHashes, newGenerationHashes, err := buildHashes(new.GetSpec().Rules, oldHasSynchronizingRule)
if err != nil {
return "", err
}
if !newGenerationHashes.IsSuperset(oldGenerationHashes) {
return "changes in the generate rule pattern could result in stale targets", nil
}
if !newRuleHashes.IsSuperset(oldRuleHashes) {
return "", errors.New("changes of immutable fields of a rule spec in a generate rule is disallowed")
}
return "", nil
}
func resetMutableFields(rule kyvernov1.Rule, oldHasSynchronizingRule bool) (*kyvernov1.Rule, *kyvernov1.Generation) {
new := new(kyvernov1.Rule)
rule.DeepCopyInto(new)
generation := new.Generation
new.Generation = nil
// we allow changing the matching only if the rule has
// synchronize set to false; otherwise, we risk having
// stale targets and confusing synchronization behavior
if !oldHasSynchronizingRule {
new.MatchResources = kyvernov1.MatchResources{}
}
generation.Synchronize = true
generation.SetData(nil)
generation.ForEachGeneration = nil
generation.OrphanDownstreamOnPolicyDelete = true
generation.GenerateExisting = nil
return new, generation
}
func buildHashes(rules []kyvernov1.Rule, oldHasSynchronizingRule bool) (ruleHashes sets.Set[string], generationHashes sets.Set[string], _ error) {
ruleHashes, generationHashes = sets.New[string](), sets.New[string]()
for _, rule := range rules {
if !rule.HasGenerate() {
continue
}
r, generation := resetMutableFields(rule, oldHasSynchronizingRule)
data, err := json.Marshal(generation)
if err != nil {
return ruleHashes, generationHashes, fmt.Errorf("failed to create hash from the generate rule %v", err)
}
hash := md5.Sum(data) //nolint:gosec
generationHashes.Insert(hex.EncodeToString(hash[:]))
data, err = json.Marshal(r)
if err != nil {
return ruleHashes, generationHashes, fmt.Errorf("failed to create hash from the generate rule %v", err)
}
hash = md5.Sum(data) //nolint:gosec
ruleHashes.Insert(hex.EncodeToString(hash[:]))
}
return ruleHashes, generationHashes, nil
}
func hasSynchronizingRule(rules []kyvernov1.Rule) bool {
for _, r := range rules {
if !r.HasGenerate() {
continue
}
if r.Generation.Synchronize {
return true
}
}
return false
}
package policy
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"regexp"
"slices"
"sort"
"strings"
"github.com/distribution/reference"
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/jmoiron/jsonq"
"github.com/kyverno/go-jmespath"
"github.com/kyverno/kyverno/api/kyverno"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/ext/wildcard"
"github.com/kyverno/kyverno/pkg/admissionpolicy"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/engine/variables/operator"
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/logging"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apiserver/pkg/admission/plugin/cel"
"k8s.io/apiserver/pkg/admission/plugin/policy/validating"
"k8s.io/apiserver/pkg/cel/openapi/resolver"
"k8s.io/client-go/discovery"
"k8s.io/client-go/restmapper"
)
var (
allowedVariables = enginecontext.ReservedKeys
allowedVariablesBackground = regexp.MustCompile(`request\.|element|elementIndex|@|images|images\.|image\.|([a-z_0-9]+\()[^{}]`)
allowedVariablesInTarget = regexp.MustCompile(`request\.|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images|images\.|image\.|target\.|([a-z_0-9]+\()[^{}]`)
allowedVariablesBackgroundInTarget = regexp.MustCompile(`request\.|element|elementIndex|@|images|images\.|image\.|target\.|([a-z_0-9]+\()[^{}]`)
regexVariables = regexp.MustCompile(`\{\{[^{}]*\}\}`)
bindingIdentifier = regexp.MustCompile(`^\w+$`)
// 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")
)
var allowedJsonPatch = regexp.MustCompile("^/")
// validateJSONPatchPathForForwardSlash checks for forward slash
func validateJSONPatchPathForForwardSlash(patch string) error {
// Replace all variables in PatchesJSON6902, all variable checks should have happened already.
// This prevents further checks from failing unexpectedly.
patch = variables.ReplaceAllVars(patch, func(s string) string { return "kyvernojsonpatchvariable" })
jsonPatch, err := yaml.ToJSON([]byte(patch))
if err != nil {
return err
}
decodedPatch, err := jsonpatch.DecodePatch(jsonPatch)
if err != nil {
return err
}
for _, operation := range decodedPatch {
path, err := operation.Path()
if err != nil {
return err
}
val := allowedJsonPatch.MatchString(path)
if !val {
return fmt.Errorf("%s", path)
}
}
return nil
}
func validateJSONPatch(patch string, ruleIdx int) error {
patch = variables.ReplaceAllVars(patch, func(s string) string { return "kyvernojsonpatchvariable" })
jsonPatch, err := yaml.ToJSON([]byte(patch))
if err != nil {
return err
}
decodedPatch, err := jsonpatch.DecodePatch(jsonPatch)
if err != nil {
return err
}
for _, operation := range decodedPatch {
op := operation.Kind()
requiresValue := op != "remove" && op != "move" && op != "copy"
validOperation := op == "add" || op == "remove" || op == "replace" || op == "move" || op == "copy" || op == "test"
if !validOperation {
return fmt.Errorf("unexpected kind: spec.rules[%d]: %s", ruleIdx, op)
}
if requiresValue {
if _, err := operation.ValueInterface(); err != nil {
return fmt.Errorf("invalid value: spec.rules[%d]: %s", ruleIdx, err)
}
}
}
return nil
}
func checkValidationFailureAction(validationFailureAction kyvernov1.ValidationFailureAction, validationFailureActionOverrides []kyvernov1.ValidationFailureActionOverride) []string {
msg := "Validation failure actions enforce/audit are deprecated, use Enforce/Audit instead."
if validationFailureAction == "enforce" || validationFailureAction == "audit" {
return []string{msg}
}
for _, override := range validationFailureActionOverrides {
if override.Action == "enforce" || override.Action == "audit" {
return []string{msg}
}
}
return nil
}
// Validate checks the policy and rules declarations for required configurations
func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interface, mock bool, backgroundSA, reportsSA string) ([]string, error) {
var warnings []string
spec := policy.GetSpec()
background := spec.BackgroundProcessingEnabled()
if policy.GetSpec().CustomWebhookMatchConditions() &&
!kubeutils.HigherThanKubernetesVersion(client.GetKubeClient().Discovery(), logging.GlobalLogger(), 1, 27, 0) {
return warnings, fmt.Errorf("custom webhook configurations are only supported in kubernetes version 1.27.0 and above")
}
warnings = append(warnings, checkValidationFailureAction(spec.ValidationFailureAction, spec.ValidationFailureActionOverrides)...)
for _, rule := range spec.Rules {
if rule.HasValidate() {
if rule.Validation.FailureAction != nil {
warnings = append(warnings, checkValidationFailureAction(*rule.Validation.FailureAction, rule.Validation.FailureActionOverrides)...)
}
}
}
var errs field.ErrorList
specPath := field.NewPath("spec")
mc := spec.GetMatchConditions()
if mc != nil {
if err := ValidateCustomWebhookMatchConditions(spec.GetMatchConditions()); err != nil {
return warnings, err
}
}
err := ValidateVariables(policy, background)
if err != nil {
return warnings, err
}
getClusteredResources := func(invalidate bool) (sets.Set[string], error) {
clusterResources := sets.New[string]()
// Get all the cluster type kind supported by cluster
d := client.Discovery().CachedDiscoveryInterface()
if invalidate {
d.Invalidate()
}
res, err := discovery.ServerPreferredResources(d)
if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) {
err := err.(*discovery.ErrGroupDiscoveryFailed)
for gv, err := range err.Groups {
logging.Error(err, "failed to list api resources", "group", gv)
}
} else {
return nil, err
}
}
for _, resList := range res {
for _, r := range resList.APIResources {
if !r.Namespaced {
clusterResources.Insert(resList.GroupVersion + "/" + r.Kind)
clusterResources.Insert(r.Kind)
}
}
}
return clusterResources, nil
}
clusterResources := sets.New[string]()
// if not using a mock, we first try to validate and if it fails we retry with cache invalidation in between
if !mock {
clusterResources, err = getClusteredResources(false)
if err != nil {
return warnings, err
}
warning, errs := policy.Validate(clusterResources)
warnings = append(warnings, warning...)
if len(errs) != 0 {
clusterResources, err = getClusteredResources(true)
if err != nil {
return warnings, err
}
warning, errs := policy.Validate(clusterResources)
warnings = append(warnings, warning...)
if len(errs) != 0 {
return warnings, errs.ToAggregate()
}
}
} else {
warning, errs := policy.Validate(clusterResources)
warnings = append(warnings, warning...)
if len(errs) != 0 {
return warnings, errs.ToAggregate()
}
}
if !policy.IsNamespaced() {
for i, r := range spec.Rules {
if r.HasValidate() {
err := validateNamespaces(r.Validation.FailureActionOverrides, specPath.Child("rules").Index(i).Child("validate").Child("validationFailureActionOverrides"))
if err != nil {
return warnings, err
}
}
}
err := validateNamespaces(spec.ValidationFailureActionOverrides, specPath.Child("validationFailureActionOverrides"))
if err != nil {
return warnings, err
}
}
if !policy.AdmissionProcessingEnabled() && !policy.BackgroundProcessingEnabled() {
return warnings, fmt.Errorf("disabling both admission and background processing is not allowed")
}
if !policy.AdmissionProcessingEnabled() {
if spec.HasMutate() || spec.HasGenerate() || spec.HasVerifyImages() {
return warnings, fmt.Errorf("disabling admission processing is only allowed with validation policies")
}
}
if warning, err := immutableGenerateFields(policy, oldPolicy); warning != "" || err != nil {
warnings = append(warnings, fmt.Sprintf("no synchronization will be performed to the old target resource upon policy updates: %s", warning))
return warnings, err
}
rules := autogen.Default.ComputeRules(policy, "")
rulesPath := specPath.Child("rules")
for i, rule := range rules {
match := rule.MatchResources
for j, value := range match.Any {
if warning, err := validateKinds(value.ResourceDescription.Kinds, rule, mock, background, client); warning != "" {
warnings = append(warnings, warning)
return warnings, nil
} else if err != nil {
return warnings, fmt.Errorf("path: spec.rules[%d].match.any[%d].kinds: %v", i, j, err)
}
}
for j, value := range match.All {
if warning, err := validateKinds(value.ResourceDescription.Kinds, rule, mock, background, client); warning != "" {
warnings = append(warnings, warning)
return warnings, nil
} else if err != nil {
return warnings, fmt.Errorf("path: spec.rules[%d].match.all[%d].kinds: %v", i, j, err)
}
}
if warning, err := validateKinds(rule.MatchResources.Kinds, rule, mock, background, client); warning != "" {
warnings = append(warnings, warning)
return warnings, nil
} else if err != nil {
return warnings, fmt.Errorf("path: spec.rules[%d].match.kinds: %v", i, err)
}
if exclude := rule.ExcludeResources; exclude != nil {
for j, value := range exclude.Any {
if warning, err := validateKinds(value.ResourceDescription.Kinds, rule, mock, background, client); warning != "" {
warnings = append(warnings, warning)
return warnings, nil
} else if err != nil {
return warnings, fmt.Errorf("path: spec.rules[%d].exclude.any[%d].kinds: %v", i, j, err)
}
}
for j, value := range exclude.All {
if warning, err := validateKinds(value.ResourceDescription.Kinds, rule, mock, background, client); warning != "" {
warnings = append(warnings, warning)
return warnings, nil
} else if err != nil {
return warnings, fmt.Errorf("path: spec.rules[%d].exclude.all[%d].kinds: %v", i, j, err)
}
}
if warning, err := validateKinds(exclude.Kinds, rule, mock, background, client); warning != "" {
warnings = append(warnings, warning)
return warnings, nil
} else if err != nil {
return warnings, fmt.Errorf("path: spec.rules[%d].exclude.kinds: %v", i, err)
}
}
}
for i, rule := range rules {
rulePath := rulesPath.Index(i)
if rule.Mutation != nil {
// check for forward slash
if err := validateJSONPatchPathForForwardSlash(rule.Mutation.PatchesJSON6902); err != nil {
return warnings, fmt.Errorf("path must begin with a forward slash: spec.rules[%d]: %s", i, err)
}
if err := validateJSONPatch(rule.Mutation.PatchesJSON6902, i); err != nil {
return warnings, fmt.Errorf("%s", err)
}
}
if jsonPatchOnPod(rule) {
msg := "Pods managed by workload controllers should not be directly mutated using policies. " +
"Use the autogen feature or write policies that match Pod controllers."
logging.V(1).Info(msg)
warnings = append(warnings, msg)
}
// validate resource description
if path, err := validateResources(rulePath, rule); err != nil {
return warnings, fmt.Errorf("path: spec.rules[%d].%s: %v", i, path, err)
}
err := validateElementInForEach(rule)
if err != nil {
return warnings, err
}
if err := validateRuleContext(rule); err != nil {
return warnings, fmt.Errorf("path: spec.rules[%d]: %v", i, err)
}
if err := validateRuleImageExtractorsJMESPath(rule); err != nil {
return warnings, fmt.Errorf("path: spec.rules[%d]: %v", i, err)
}
// If a rule's match block does not match any kind,
// we should only allow it to have metadata in its overlay
if len(rule.MatchResources.Any) > 0 {
for _, rmr := range rule.MatchResources.Any {
if len(rmr.Kinds) == 0 {
return warnings, validateMatchKindHelper(rule)
}
}
} else if len(rule.MatchResources.All) > 0 {
for _, rmr := range rule.MatchResources.All {
if len(rmr.Kinds) == 0 {
return warnings, validateMatchKindHelper(rule)
}
}
} else {
if len(rule.MatchResources.Kinds) == 0 {
return warnings, validateMatchKindHelper(rule)
}
}
w, err := validateActions(i, &rules[i], client, mock, backgroundSA, reportsSA)
if err != nil {
return warnings, err
} else if len(w) > 0 {
warnings = append(warnings, w...)
}
if rule.HasVerifyImages() {
isAuditFailureAction := false
if spec.ValidationFailureAction.Audit() {
isAuditFailureAction = true
}
verifyImagePath := rulePath.Child("verifyImages")
for index, i := range rule.VerifyImages {
action := i.FailureAction
if action != nil {
if action.Audit() {
isAuditFailureAction = true
} else {
isAuditFailureAction = false
}
}
errs = append(errs, i.Validate(isAuditFailureAction, verifyImagePath.Index(index))...)
}
if len(errs) != 0 {
return warnings, errs.ToAggregate()
}
}
kindsFromRule := rule.MatchResources.GetKinds()
resourceTypesMap := make(map[string]bool)
for _, kind := range kindsFromRule {
_, k := kubeutils.GetKindFromGVK(kind)
k, _ = kubeutils.SplitSubresource(k)
resourceTypesMap[k] = true
}
if len(resourceTypesMap) == 1 {
for k := range resourceTypesMap {
if k == "Pod" && podControllerAutoGenExclusion(policy) {
msg := "Policies that match Pods apply to all Pods including those created and managed by controllers " +
"excluded from autogen. Use preconditions to exclude the Pods managed by controllers which are " +
"excluded from autogen. Refer to https://kyverno.io/docs/writing-policies/autogen/ for details."
warnings = append(warnings, msg)
}
}
}
// Validate string values in labels
if !isLabelAndAnnotationsString(rule) {
return warnings, fmt.Errorf("labels and annotations supports only string values, \"use double quotes around the non string values\"")
}
match := rule.MatchResources
matchKinds := match.GetKinds()
var allKinds []string
allKinds = append(allKinds, matchKinds...)
if exclude := rule.ExcludeResources; exclude != nil {
excludeKinds := exclude.GetKinds()
allKinds = append(allKinds, excludeKinds...)
}
if rule.HasValidate() {
validationElem := rule.Validation.DeepCopy()
if validationElem.Deny != nil {
validationElem.Deny.RawAnyAllConditions = nil
}
validationJson, err := json.Marshal(validationElem)
if err != nil {
return nil, err
}
checkForScaleSubresource(validationJson, allKinds, &warnings)
checkForStatusSubresource(validationJson, allKinds, &warnings)
}
if rule.HasMutate() {
mutationJson, err := json.Marshal(rule.Mutation)
targets := rule.Mutation.Targets
for _, target := range targets {
allKinds = append(allKinds, target.GetKind())
}
if err != nil {
return nil, err
}
checkForScaleSubresource(mutationJson, allKinds, &warnings)
checkForStatusSubresource(mutationJson, allKinds, &warnings)
mutateExisting := rule.Mutation.MutateExistingOnPolicyUpdate
if mutateExisting != nil {
if *mutateExisting {
if err := ValidateOnPolicyUpdate(policy, true); err != nil {
return warnings, err
}
}
} else if spec.MutateExistingOnPolicyUpdate {
if err := ValidateOnPolicyUpdate(policy, true); err != nil {
return warnings, err
}
}
}
if rule.HasVerifyImages() {
checkForDeprecatedFieldsInVerifyImages(rule, &warnings)
if rule.HasValidateImageVerification() {
for _, verifyImage := range rule.VerifyImages {
validationElem := verifyImage.Validation.DeepCopy()
if validationElem.Deny != nil {
validationElem.Deny.RawAnyAllConditions = nil
}
validationJson, err := json.Marshal(validationElem)
if err != nil {
return nil, err
}
checkForScaleSubresource(validationJson, allKinds, &warnings)
checkForStatusSubresource(validationJson, allKinds, &warnings)
}
}
}
checkForDeprecatedOperatorsInRule(rule, &warnings)
}
// check for CEL expression warnings in case of CEL subrules
if ok, _ := admissionpolicy.CanGenerateVAP(spec, nil, true); ok && client != nil {
resolver := &resolver.ClientDiscoveryResolver{
Discovery: client.GetKubeClient().Discovery(),
}
groupResources, err := restmapper.GetAPIGroupResources(client.GetKubeClient().Discovery())
if err != nil {
return nil, err
}
mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
checker := &validating.TypeChecker{
SchemaResolver: resolver,
RestMapper: mapper,
}
// build Kubernetes ValidatingAdmissionPolicy
vap := &admissionregistrationv1.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: policy.GetName(),
},
}
genericPolicy := engineapi.NewKyvernoPolicy(policy)
err = admissionpolicy.BuildValidatingAdmissionPolicy(client.Discovery(), vap, genericPolicy, nil)
if err != nil {
return nil, err
}
// check cel expression warnings
ctx := checker.CreateContext(vap)
fieldRef := field.NewPath("spec", "rules[0]", "validate", "cel", "expressions")
for i, e := range spec.Rules[0].Validation.CEL.Expressions {
results := checker.CheckExpression(ctx, e.Expression)
if len(results) != 0 {
msg := fmt.Sprintf("%s:%s", fieldRef.Index(i).Child("expression").String(), strings.ReplaceAll(results.String(), "\n", ";"))
warnings = append(warnings, msg)
}
if e.MessageExpression == "" {
continue
}
results = checker.CheckExpression(ctx, e.MessageExpression)
if len(results) != 0 {
msg := fmt.Sprintf("%s:%s", fieldRef.Index(i).Child("messageExpression").String(), strings.ReplaceAll(results.String(), "\n", ";"))
warnings = append(warnings, msg)
}
}
}
return warnings, nil
}
func ValidateCustomWebhookMatchConditions(wc []admissionregistrationv1.MatchCondition) error {
c, err := admissionpolicy.NewCompiler(wc, nil)
if err != nil {
return err
}
f := c.CompileMatchConditions(cel.OptionalVariableDeclarations{})
if len(f.CompilationErrors()) > 0 {
return fmt.Errorf("match conditions compilation errors: %v", f.CompilationErrors())
}
return nil
}
func ValidateVariables(p kyvernov1.PolicyInterface, backgroundMode bool) error {
vars, err := hasVariables(p)
if err != nil {
return err
}
if backgroundMode {
if err := containsUserVariables(p, vars); err != nil {
return fmt.Errorf("only select variables are allowed in background mode. Set spec.background=false to disable background mode for this policy rule: %s ", err)
}
}
if err := hasInvalidVariables(p, backgroundMode); err != nil {
return fmt.Errorf("policy contains invalid variables: %s", err.Error())
}
return nil
}
// hasInvalidVariables - checks for unexpected variables in the policy
func hasInvalidVariables(policy kyvernov1.PolicyInterface, background bool) error {
for _, r := range autogen.Default.ComputeRules(policy, "") {
ruleCopy := r.DeepCopy()
if err := ruleForbiddenSectionsHaveVariables(ruleCopy); err != nil {
return err
}
// skip variable checks on verifyImages.attestations, as variables in attestations are dynamic
for i, vi := range ruleCopy.VerifyImages {
for j := range vi.Attestations {
ruleCopy.VerifyImages[i].Attestations[j].Conditions = nil
}
}
mutateTarget := false
if ruleCopy.Mutation != nil && ruleCopy.Mutation.Targets != nil {
mutateTarget = true
withTargetOnly := ruleWithoutPattern(ruleCopy)
for i := range ruleCopy.Mutation.Targets {
withTargetOnly.Mutation.Targets[i].ResourceSpec = ruleCopy.Mutation.Targets[i].ResourceSpec
ctx := buildContext(withTargetOnly, background, false)
if _, err := variables.SubstituteAllInRule(logging.GlobalLogger(), ctx, *withTargetOnly); !variables.CheckNotFoundErr(err) {
return fmt.Errorf("invalid variables defined at mutate.targets[%d]: %s", i, err.Error())
}
}
}
ctx := buildContext(ruleCopy, background, mutateTarget)
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())
}
}
return nil
}
func ValidateOnPolicyUpdate(p kyvernov1.PolicyInterface, onPolicyUpdate bool) error {
vars, err := hasVariables(p)
if err != nil {
return err
}
if len(vars) == 0 {
return nil
}
if err := hasInvalidVariables(p, onPolicyUpdate); err != nil {
return fmt.Errorf("update event, policy contains invalid variables: %s", err.Error())
}
if err := containsUserVariables(p, vars); err != nil {
return fmt.Errorf("only select variables are allowed in on policy update. Set spec.mutateExistingOnPolicyUpdate=false to disable update policy mode for this policy rule: %s ", err)
}
return nil
}
// for now forbidden sections are match, exclude and
func ruleForbiddenSectionsHaveVariables(rule *kyvernov1.Rule) error {
var err error
if rule.Mutation != nil {
err = jsonPatchPathHasVariables(rule.Mutation.PatchesJSON6902)
if err != nil && errors.Is(errOperationForbidden, err) {
return fmt.Errorf("rule \"%s\" should not have variables in patchesJSON6902 path section", rule.Name)
}
}
err = objectHasVariables(rule.ExcludeResources)
if err != nil {
return fmt.Errorf("rule \"%s\" should not have variables in exclude section", rule.Name)
}
err = objectHasVariables(rule.MatchResources)
if err != nil {
return fmt.Errorf("rule \"%s\" should not have variables in match section", rule.Name)
}
err = imageRefHasVariables(rule.VerifyImages)
if err != nil {
return fmt.Errorf("rule \"%s\" should not have variables in image reference section", rule.Name)
}
return nil
}
// hasVariables - check for variables in the policy
func hasVariables(policy kyvernov1.PolicyInterface) ([][]string, error) {
polCopy := cleanup(policy.CreateDeepCopy())
policyRaw, err := json.Marshal(polCopy)
if err != nil {
return nil, fmt.Errorf("failed to serialize the policy: %v", err)
}
matches := regex.RegexVariables.FindAllStringSubmatch(string(policyRaw), -1)
return matches, nil
}
func cleanup(policy kyvernov1.PolicyInterface) kyvernov1.PolicyInterface {
ann := policy.GetAnnotations()
if ann != nil {
ann["kubectl.kubernetes.io/last-applied-configuration"] = ""
policy.SetAnnotations(ann)
}
if policy.GetNamespace() == "" {
var pol *kyvernov1.ClusterPolicy
var ok bool
if pol, ok = policy.(*kyvernov1.ClusterPolicy); !ok {
return policy
}
pol.Status.Autogen.Rules = nil
return pol
} else {
var pol *kyvernov1.Policy
var ok bool
if pol, ok = policy.(*kyvernov1.Policy); !ok {
return policy
}
pol.Status.Autogen.Rules = nil
return pol
}
}
func jsonPatchPathHasVariables(patch string) error {
jsonPatch, err := yaml.ToJSON([]byte(patch))
if err != nil {
return err
}
decodedPatch, err := jsonpatch.DecodePatch(jsonPatch)
if err != nil {
return err
}
for _, operation := range decodedPatch {
path, err := operation.Path()
if err != nil {
return err
}
vars := regex.RegexVariables.FindAllString(path, -1)
if len(vars) > 0 {
return errOperationForbidden
}
}
return nil
}
func objectHasVariables(object any) error {
if object != nil {
objectJSON, err := json.Marshal(object)
if err != nil {
return err
}
if len(regexVariables.FindAllStringSubmatch(string(objectJSON), -1)) > 0 {
return fmt.Errorf("invalid variables")
}
}
return nil
}
func imageRefHasVariables(verifyImages []kyvernov1.ImageVerification) error {
for _, verifyImage := range verifyImages {
verifyImage = *verifyImage.Convert()
for _, imageRef := range verifyImage.ImageReferences {
matches := regex.RegexVariables.FindAllString(imageRef, -1)
if len(matches) > 0 {
return fmt.Errorf("variables are not allowed in image reference")
}
}
}
return nil
}
func ruleWithoutPattern(ruleCopy *kyvernov1.Rule) *kyvernov1.Rule {
withTargetOnly := &kyvernov1.Rule{
Mutation: &kyvernov1.Mutation{},
}
withTargetOnly.Mutation.Targets = make([]kyvernov1.TargetResourceSpec, len(ruleCopy.Mutation.Targets))
withTargetOnly.Context = ruleCopy.Context
withTargetOnly.RawAnyAllConditions = ruleCopy.RawAnyAllConditions
return withTargetOnly
}
func buildContext(rule *kyvernov1.Rule, background bool, target bool) *enginecontext.MockContext {
re := getAllowedVariables(background, target)
ctx := enginecontext.NewMockContext(re)
addContextVariables(rule.Context, ctx)
addImageVerifyVariables(rule, ctx)
if rule.Validation != nil {
for _, fe := range rule.Validation.ForEachValidation {
addContextVariables(fe.Context, ctx)
}
}
if rule.Mutation != nil {
for _, fe := range rule.Mutation.ForEachMutation {
addContextVariables(fe.Context, ctx)
}
for _, fe := range rule.Mutation.Targets {
addContextVariables(fe.Context, ctx)
}
}
if rule.HasGenerate() {
for _, fe := range rule.Generation.ForEachGeneration {
addContextVariables(fe.Context, ctx)
}
}
return ctx
}
func getAllowedVariables(background bool, target bool) *regexp.Regexp {
if target {
if background {
return allowedVariablesBackgroundInTarget
}
return allowedVariablesInTarget
} else {
if background {
return allowedVariablesBackground
}
return allowedVariables
}
}
func addContextVariables(entries []kyvernov1.ContextEntry, ctx *enginecontext.MockContext) {
for _, contextEntry := range entries {
if contextEntry.APICall != nil || contextEntry.GlobalReference != nil || contextEntry.ImageRegistry != nil || contextEntry.Variable != nil {
ctx.AddVariable(contextEntry.Name + "*")
}
if contextEntry.ConfigMap != nil {
ctx.AddVariable(contextEntry.Name + ".data")
ctx.AddVariable(contextEntry.Name + ".metadata")
ctx.AddVariable(contextEntry.Name + ".data.*")
ctx.AddVariable(contextEntry.Name + ".metadata.*")
}
}
}
func addImageVerifyVariables(rule *kyvernov1.Rule, ctx *enginecontext.MockContext) {
if rule.HasValidateImageVerification() {
for _, verifyImage := range rule.VerifyImages {
for _, attestation := range verifyImage.Attestations {
ctx.AddVariable(attestation.Name + "*")
}
}
}
}
func validateElementInForEach(document apiextensions.JSON) error {
jsonByte, err := json.Marshal(document)
if err != nil {
return err
}
var jsonInterface interface{}
err = json.Unmarshal(jsonByte, &jsonInterface)
if err != nil {
return err
}
_, err = variables.ValidateElementInForEach(logging.GlobalLogger(), jsonInterface)
return err
}
func validateMatchKindHelper(rule kyvernov1.Rule) error {
if !ruleOnlyDealsWithResourceMetaData(rule) {
return fmt.Errorf("policy can only deal with the metadata field of the resource if" +
" the rule does not match any kind")
}
return fmt.Errorf("at least one element must be specified in a kind block, the kind attribute is mandatory when working with the resources element")
}
// isMapStringString goes through a map to verify values are string
func isMapStringString(m map[string]interface{}) bool {
// range over labels
for _, val := range m {
if val == nil || reflect.TypeOf(val).String() != "string" {
return false
}
}
return true
}
// isLabelAndAnnotationsString :- Validate if labels and annotations contains only string values
func isLabelAndAnnotationsString(rule kyvernov1.Rule) bool {
checkLabelAnnotation := func(metaKey map[string]interface{}) bool {
for mk := range metaKey {
if mk == "labels" {
labelKey, ok := metaKey[mk].(map[string]interface{})
if ok {
if !isMapStringString(labelKey) {
return false
}
}
} else if mk == "annotations" {
annotationKey, ok := metaKey[mk].(map[string]interface{})
if ok {
if !isMapStringString(annotationKey) {
return false
}
}
}
}
return true
}
// checkMetadata - Verify if the labels and annotations contains string value inside metadata
checkMetadata := func(patternMap map[string]interface{}) bool {
for k := range patternMap {
if k == "metadata" {
metaKey, ok := patternMap[k].(map[string]interface{})
if ok {
// range over metadata
return checkLabelAnnotation(metaKey)
}
}
if k == "spec" {
metadata, _ := jsonq.NewQuery(patternMap).Object("spec", "template", "metadata")
return checkLabelAnnotation(metadata)
}
}
return true
}
if rule.HasValidate() {
if rule.Validation.ForEachValidation != nil {
for _, foreach := range rule.Validation.ForEachValidation {
patternMap, ok := foreach.GetPattern().(map[string]interface{})
if ok {
return checkMetadata(patternMap)
}
}
} else {
patternMap, ok := rule.Validation.GetPattern().(map[string]interface{})
if ok {
return checkMetadata(patternMap)
} else if rule.Validation.GetAnyPattern() != nil {
anyPatterns, err := rule.Validation.DeserializeAnyPattern()
if err != nil {
logging.Error(err, "failed to deserialize anyPattern, expect type array")
return false
}
for _, pattern := range anyPatterns {
patternMap, ok := pattern.(map[string]interface{})
if ok {
ret := checkMetadata(patternMap)
if !ret {
return ret
}
}
}
}
}
}
if rule.HasMutate() {
if rule.Mutation.ForEachMutation != nil {
for _, foreach := range rule.Mutation.ForEachMutation {
forEachStrategicMergeMap, ok := foreach.GetPatchStrategicMerge().(map[string]interface{})
if ok {
return checkMetadata(forEachStrategicMergeMap)
}
}
} else {
strategicMergeMap, ok := rule.Mutation.GetPatchStrategicMerge().(map[string]interface{})
if ok {
return checkMetadata(strategicMergeMap)
}
}
}
return true
}
func ruleOnlyDealsWithResourceMetaData(rule kyvernov1.Rule) bool {
if rule.Mutation != nil {
patches, _ := rule.Mutation.GetPatchStrategicMerge().(map[string]interface{})
for k := range patches {
if k != "metadata" {
return false
}
}
}
if rule.Mutation != nil && rule.Mutation.PatchesJSON6902 != "" {
bytes := []byte(rule.Mutation.PatchesJSON6902)
jp, _ := jsonpatch.DecodePatch(bytes)
for _, o := range jp {
path, _ := o.Path()
if !strings.HasPrefix(path, "/metadata") {
return false
}
}
}
if rule.Validation != nil {
patternMap, _ := rule.Validation.GetPattern().(map[string]interface{})
for k := range patternMap {
if k != "metadata" {
return false
}
}
anyPatterns, err := rule.Validation.DeserializeAnyPattern()
if err != nil {
logging.Error(err, "failed to deserialize anyPattern, expect type array")
return false
}
for _, pattern := range anyPatterns {
patternMap, _ := pattern.(map[string]interface{})
for k := range patternMap {
if k != "metadata" {
return false
}
}
}
}
return true
}
func validateResources(path *field.Path, rule kyvernov1.Rule) (string, error) {
// validate userInfo in match and exclude
if exclude := rule.ExcludeResources; exclude != nil {
if errs := exclude.UserInfo.Validate(path.Child("exclude")); len(errs) != 0 {
return "exclude", errs.ToAggregate()
}
if (len(exclude.Any) > 0 || len(exclude.All) > 0) && !datautils.DeepEqual(exclude.ResourceDescription, kyvernov1.ResourceDescription{}) {
return "exclude.", fmt.Errorf("can't specify any/all together with exclude resources")
}
if len(exclude.Any) > 0 && len(exclude.All) > 0 {
return "match.", fmt.Errorf("can't specify any and all together")
}
}
if (len(rule.MatchResources.Any) > 0 || len(rule.MatchResources.All) > 0) && !datautils.DeepEqual(rule.MatchResources.ResourceDescription, kyvernov1.ResourceDescription{}) {
return "match.", fmt.Errorf("can't specify any/all together with match resources")
}
if len(rule.MatchResources.Any) > 0 {
for _, rmr := range rule.MatchResources.Any {
// matched resources
if path, err := validateMatchedResourceDescription(rmr.ResourceDescription); err != nil {
return fmt.Sprintf("match.resources.%s", path), err
}
}
} else if len(rule.MatchResources.All) > 0 {
for _, rmr := range rule.MatchResources.All {
// matched resources
if path, err := validateMatchedResourceDescription(rmr.ResourceDescription); err != nil {
return fmt.Sprintf("match.resources.%s", path), err
}
}
} else {
// matched resources
if path, err := validateMatchedResourceDescription(rule.MatchResources.ResourceDescription); err != nil {
return fmt.Sprintf("match.resources.%s", path), err
}
}
// validating the values present under validate.preconditions, if they exist
if target := rule.GetAnyAllConditions(); target != nil {
if path, err := validateConditions(target, "preconditions"); err != nil {
return fmt.Sprintf("validate.%s", path), err
}
if path, err := validateRawJSONConditionOperator(target, "preconditions"); err != nil {
return fmt.Sprintf("validate.%s", path), err
}
}
// validating the values present under validate.conditions, if they exist
if rule.Validation != nil {
if rule.Validation.Deny != nil {
if target := rule.Validation.Deny.GetAnyAllConditions(); target != nil {
if path, err := validateConditions(target, "conditions"); err != nil {
return fmt.Sprintf("validate.deny.%s", path), err
}
if path, err := validateRawJSONConditionOperator(target, "conditions"); err != nil {
return fmt.Sprintf("validate.deny.%s", path), err
}
}
}
if len(rule.Validation.ForEachValidation) != 0 {
if path, err := validateValidationForEach(rule.Validation.ForEachValidation, "validate.foreach"); err != nil {
return path, err
}
}
}
if rule.Mutation != nil && len(rule.Mutation.ForEachMutation) != 0 {
if path, err := validateMutationForEach(rule.Mutation.ForEachMutation, "mutate.foreach"); err != nil {
return path, err
}
}
if len(rule.VerifyImages) != 0 {
for _, vi := range rule.VerifyImages {
for _, att := range vi.Attestations {
for _, c := range att.Conditions {
if path, err := validateAnyAllConditionOperator(c, "conditions"); err != nil {
return fmt.Sprintf("verifyImages.attestations.%s", path), err
}
}
}
if rule.HasValidateImageVerification() {
if target := vi.Validation.Deny.GetAnyAllConditions(); target != nil {
if path, err := validateConditions(target, "conditions"); err != nil {
return fmt.Sprintf("imageVerify.validate.deny.%s", path), err
}
if path, err := validateRawJSONConditionOperator(target, "conditions"); err != nil {
return fmt.Sprintf("imageVerify.validate.deny.%s", path), err
}
}
}
}
}
return "", nil
}
func validateValidationForEach(foreach []kyvernov1.ForEachValidation, schemaKey string) (string, error) {
for _, fe := range foreach {
if fe.AnyAllConditions != nil {
if path, err := validateAnyAllConditionOperator(*fe.AnyAllConditions, "conditions"); err != nil {
return fmt.Sprintf("%s.%s", schemaKey, path), err
}
}
if fe.Deny != nil {
if target := fe.Deny.GetAnyAllConditions(); target != nil {
if path, err := validateRawJSONConditionOperator(target, "conditions"); err != nil {
return fmt.Sprintf("%s.deny.%s", schemaKey, path), err
}
}
}
fev := fe.GetForEachValidation()
if len(fev) > 0 {
if path, err := validateValidationForEach(fev, schemaKey); err != nil {
return fmt.Sprintf("%s.%s", schemaKey, path), err
}
}
}
return "", nil
}
func validateMutationForEach(foreach []kyvernov1.ForEachMutation, schemaKey string) (string, error) {
for _, fe := range foreach {
if fe.AnyAllConditions != nil {
if path, err := validateAnyAllConditionOperator(*fe.AnyAllConditions, "conditions"); err != nil {
return fmt.Sprintf("%s.%s", schemaKey, path), err
}
}
fem := fe.GetForEachMutation()
if len(fem) > 0 {
if path, err := validateMutationForEach(fem, schemaKey); err != nil {
return fmt.Sprintf("%s.%s", schemaKey, path), err
}
}
}
return "", nil
}
// validateConditions validates all the 'conditions' or 'preconditions' of a rule depending on the corresponding 'condition.key'.
// As of now, it is validating the 'value' field whether it contains the only allowed set of values or not when 'condition.key' is {{request.operation}}
// this is backwards compatible i.e. conditions can be provided in the old manner as well i.e. without 'any' or 'all'
func validateConditions(conditions any, schemaKey string) (string, error) {
// Conditions can only exist under some specific keys of the policy schema
allowedSchemaKeys := map[string]bool{
"preconditions": true,
"conditions": true,
}
if !allowedSchemaKeys[schemaKey] {
return schemaKey, fmt.Errorf("wrong schema key found for validating the conditions. Conditions can only occur under one of ['preconditions', 'conditions'] keys in the policy schema")
}
switch typedConditions := conditions.(type) {
case kyvernov1.AnyAllConditions:
// validating the conditions under 'any', if there are any
if !datautils.DeepEqual(typedConditions, kyvernov1.AnyAllConditions{}) && typedConditions.AnyConditions != nil {
for i, condition := range typedConditions.AnyConditions {
if path, err := validateConditionValues(condition); err != nil {
return fmt.Sprintf("%s.any[%d].%s", schemaKey, i, path), err
}
}
}
// validating the conditions under 'all', if there are any
if !datautils.DeepEqual(typedConditions, kyvernov1.AnyAllConditions{}) && typedConditions.AllConditions != nil {
for i, condition := range typedConditions.AllConditions {
if path, err := validateConditionValues(condition); err != nil {
return fmt.Sprintf("%s.all[%d].%s", schemaKey, i, path), err
}
}
}
case []kyvernov1.Condition: // backwards compatibility
for i, condition := range typedConditions {
if path, err := validateConditionValues(condition); err != nil {
return fmt.Sprintf("%s[%d].%s", schemaKey, i, path), err
}
}
}
return "", nil
}
// validateConditionValues validates whether all the values under the 'value' field of a 'conditions' field
// are apt with respect to the provided 'condition.key'
func validateConditionValues(c kyvernov1.Condition) (string, error) {
k := c.GetKey()
v := c.GetValue()
if k == nil || v == nil || c.Operator == "" {
return "", fmt.Errorf("entered value of `key`, `value` or `operator` is missing or misspelled")
}
switch reflect.TypeOf(k).Kind() {
case reflect.String:
value, err := validateValuesKeyRequest(c)
return value, err
default:
return "", nil
}
}
func validateOperator(c kyvernov1.ConditionOperator) (string, error) {
if !operator.IsOperatorValid(c) {
return "", fmt.Errorf("entered value of `operator` is invalid. valid values: %+q", operator.GetAllConditionOperators())
}
return "", nil
}
func validateConditionOperator(c []kyvernov1.Condition, schemaKey string) (string, error) {
allowedSchemaKeys := map[string]bool{
"preconditions": true,
"conditions": true,
}
if !allowedSchemaKeys[schemaKey] {
return schemaKey, fmt.Errorf("wrong schema key found for validating the conditions. Conditions can only occur under one of ['preconditions', 'conditions'] keys in the policy schema")
}
for i, condition := range c {
if path, err := validateOperator(condition.Operator); err != nil {
return fmt.Sprintf("%s[%d].%s", schemaKey, i, path), err
}
}
return "", nil
}
func validateAnyAllConditionOperator(c kyvernov1.AnyAllConditions, schemaKey string) (string, error) {
allowedSchemaKeys := map[string]bool{
"preconditions": true,
"conditions": true,
}
if !allowedSchemaKeys[schemaKey] {
return schemaKey, fmt.Errorf("wrong schema key found for validating the conditions. Conditions can only occur under one of ['preconditions', 'conditions'] keys in the policy schema")
}
if !datautils.DeepEqual(c, kyvernov1.AnyAllConditions{}) && c.AnyConditions != nil {
for i, condition := range c.AnyConditions {
if path, err := validateOperator(condition.Operator); err != nil {
return fmt.Sprintf("%s.any[%d].%s", schemaKey, i, path), err
}
}
}
if !datautils.DeepEqual(c, kyvernov1.AnyAllConditions{}) && c.AllConditions != nil {
for i, condition := range c.AllConditions {
if path, err := validateOperator(condition.Operator); err != nil {
return fmt.Sprintf("%s.any[%d].%s", schemaKey, i, path), err
}
}
}
return "", nil
}
func validateRawJSONConditionOperator(c any, schemaKey string) (string, error) {
allowedSchemaKeys := map[string]bool{
"preconditions": true,
"conditions": true,
}
if !allowedSchemaKeys[schemaKey] {
return schemaKey, fmt.Errorf("wrong schema key found for validating the conditions. Conditions can only occur under one of ['preconditions', 'conditions'] keys in the policy schema")
}
switch typedConditions := c.(type) {
case kyvernov1.AnyAllConditions:
if path, err := validateAnyAllConditionOperator(typedConditions, schemaKey); err != nil {
return path, err
}
case []kyvernov1.Condition: // backwards compatibility
if path, err := validateConditionOperator(typedConditions, schemaKey); err != nil {
return path, err
}
}
return "", nil
}
func validateValuesKeyRequest(c kyvernov1.Condition) (string, error) {
k := c.GetKey()
switch strings.ReplaceAll(k.(string), " ", "") {
case "{{request.operation}}":
return validateConditionValuesKeyRequestOperation(c)
default:
return "", nil
}
}
// validateConditionValuesKeyRequestOperation validates whether all the values under the 'value' field of a 'conditions' field
// are one of ["CREATE", "UPDATE", "DELETE", "CONNECT"] when 'condition.key' is {{request.operation}}
func validateConditionValuesKeyRequestOperation(c kyvernov1.Condition) (string, error) {
valuesAllowed := map[string]bool{
"CREATE": true,
"UPDATE": true,
"DELETE": true,
"CONNECT": true,
}
v := c.GetValue()
switch reflect.TypeOf(v).Kind() {
case reflect.String:
valueStr := v.(string)
// allow templatized values like {{ config-map.data.sample-key }}
// because they might be actually pointing to a rightful value in the provided config-map
if len(valueStr) >= 4 && valueStr[:2] == "{{" && valueStr[len(valueStr)-2:] == "}}" {
return "", nil
}
if !valuesAllowed[valueStr] {
return fmt.Sprintf("value: %s", v.(string)), fmt.Errorf("unknown value '%s' found under the 'value' field. Only the following values are allowed: [CREATE, UPDATE, DELETE, CONNECT]", v.(string))
}
case reflect.Slice:
values := reflect.ValueOf(v)
for i := 0; i < values.Len(); i++ {
value := values.Index(i).Interface().(string)
if !valuesAllowed[value] {
return fmt.Sprintf("value[%d]", i), fmt.Errorf("unknown value '%s' found under the 'value' field. Only the following values are allowed: [CREATE, UPDATE, DELETE, CONNECT]", value)
}
}
default:
return "value", fmt.Errorf("'value' field found to be of the type %v. The provided value/values are expected to be either in the form of a string or list", reflect.TypeOf(v).Kind())
}
return "", nil
}
func validateRuleContext(rule kyvernov1.Rule) error {
if len(rule.Context) == 0 {
return nil
}
if rule.HasValidateCEL() {
return fmt.Errorf("context variables are not supported for CEL expressions in validate rules")
}
for _, entry := range rule.Context {
if entry.Name == "" {
return fmt.Errorf("a name is required for context entries")
}
// if it the rule uses kyverno-json we add some constraints on the name of context entries to make
// sure we can create the corresponding bindings
if rule.Validation != nil && rule.Validation.Assert.Value != nil {
if !bindingIdentifier.MatchString(entry.Name) {
return fmt.Errorf("context entry name %s is invalid, it must be a single word when the validation rule uses `assert`", entry.Name)
}
}
for _, v := range []string{"images", "request", "serviceAccountName", "serviceAccountNamespace", "element", "elementIndex"} {
if entry.Name == v || strings.HasPrefix(entry.Name, v+".") {
return fmt.Errorf("entry name %s is invalid as it conflicts with a pre-defined variable %s", entry.Name, v)
}
}
var err error
if entry.ConfigMap != nil && entry.APICall == nil && entry.GlobalReference == nil && entry.ImageRegistry == nil && entry.Variable == nil {
err = validateConfigMap(entry)
} else if entry.ConfigMap == nil && entry.APICall != nil && entry.GlobalReference == nil && entry.ImageRegistry == nil && entry.Variable == nil {
err = validateAPICall(entry)
} else if entry.ConfigMap == nil && entry.APICall == nil && entry.GlobalReference != nil && entry.ImageRegistry == nil && entry.Variable == nil {
err = validateGlobalReference(entry)
} else if entry.ConfigMap == nil && entry.APICall == nil && entry.GlobalReference == nil && entry.ImageRegistry != nil && entry.Variable == nil {
err = validateImageRegistry(entry)
} else if entry.ConfigMap == nil && entry.APICall == nil && entry.GlobalReference == nil && entry.ImageRegistry == nil && entry.Variable != nil {
err = validateVariable(entry)
} else {
return fmt.Errorf("exactly one of configMap or apiCall or imageRegistry or variable is required for context entries")
}
if err != nil {
return err
}
}
return nil
}
// validateRuleImageExtractorsJMESPath ensures that the rule does not
// mutate image digests if it has an image extractor that uses a JMESPath.
func validateRuleImageExtractorsJMESPath(rule kyvernov1.Rule) error {
imageExtractorConfigs := rule.ImageExtractors
imageVerifications := rule.VerifyImages
if imageExtractorConfigs == nil || imageVerifications == nil {
return nil
}
anyMutateDigest := false
for _, imageVerification := range imageVerifications {
if imageVerification.MutateDigest {
anyMutateDigest = true
break
}
}
if !anyMutateDigest {
return nil
}
anyJMESPath := false
for _, imageExtractors := range imageExtractorConfigs {
for _, imageExtractor := range imageExtractors {
if imageExtractor.JMESPath != "" {
anyJMESPath = true
break
}
}
}
if anyJMESPath {
return fmt.Errorf("jmespath may not be used in an image extractor when mutating digests with verify images")
}
return nil
}
func validateVariable(entry kyvernov1.ContextEntry) error {
// If JMESPath contains variables, the validation will fail because it's not possible to infer which value
// will be inserted by the variable
// Skip validation if a variable is detected
jmesPath := variables.ReplaceAllVars(entry.Variable.JMESPath, func(s string) string { return "kyvernojmespathvariable" })
if !strings.Contains(jmesPath, "kyvernojmespathvariable") && entry.Variable.JMESPath != "" {
if _, err := jmespath.NewParser().Parse(entry.Variable.JMESPath); err != nil {
return fmt.Errorf("failed to parse JMESPath %s: %v", entry.Variable.JMESPath, err)
}
}
if entry.Variable.GetValue() == nil && jmesPath == "" {
return fmt.Errorf("a variable must define a value or a jmesPath expression")
}
if entry.Variable.GetDefault() != nil && jmesPath == "" {
return fmt.Errorf("a variable must define a default value only when a jmesPath expression is defined")
}
return nil
}
func validateConfigMap(entry kyvernov1.ContextEntry) error {
if entry.ConfigMap.Name == "" {
return fmt.Errorf("a name is required for configMap context entry")
}
if entry.ConfigMap.Namespace == "" {
return fmt.Errorf("a namespace is required for configMap context entry")
}
return nil
}
func validateAPICall(entry kyvernov1.ContextEntry) error {
if entry.APICall == nil {
return nil
}
if entry.APICall.URLPath != "" {
if entry.APICall.Service != nil {
return fmt.Errorf("a URLPath cannot be used for service API calls")
}
}
// If JMESPath contains variables, the validation will fail because it's not
// possible to infer which value will be inserted by the variable
// Skip validation if a variable is detected
jmesPath := variables.ReplaceAllVars(entry.APICall.JMESPath, func(s string) string { return "kyvernojmespathvariable" })
if !strings.Contains(jmesPath, "kyvernojmespathvariable") && entry.APICall.JMESPath != "" {
if _, err := jmespath.NewParser().Parse(entry.APICall.JMESPath); err != nil {
return fmt.Errorf("failed to parse JMESPath %s: %v", entry.APICall.JMESPath, err)
}
}
return nil
}
func validateGlobalReference(entry kyvernov1.ContextEntry) error {
if entry.GlobalReference == nil {
return nil
}
// If JMESPath contains variables, the validation will fail because it's not
// possible to infer which value will be inserted by the variable
// Skip validation if a variable is detected
jmesPath := variables.ReplaceAllVars(entry.GlobalReference.JMESPath, func(s string) string { return "kyvernojmespathvariable" })
if !strings.Contains(jmesPath, "kyvernojmespathvariable") && entry.GlobalReference.JMESPath != "" {
if _, err := jmespath.NewParser().Parse(entry.GlobalReference.JMESPath); err != nil {
return fmt.Errorf("failed to parse JMESPath %s: %v", entry.GlobalReference.JMESPath, err)
}
}
return nil
}
func validateImageRegistry(entry kyvernov1.ContextEntry) error {
if entry.ImageRegistry.Reference == "" {
return fmt.Errorf("a ref is required for imageRegistry context entry")
}
// Replace all variables to prevent validation failing on variable keys.
ref := variables.ReplaceAllVars(entry.ImageRegistry.Reference, func(s string) string { return "kyvernoimageref" })
// it's no use validating a reference that contains a variable
if !strings.Contains(ref, "kyvernoimageref") {
_, err := reference.Parse(ref)
if err != nil {
return fmt.Errorf("bad image: %s: %w", ref, err)
}
}
// If JMESPath contains variables, the validation will fail because it's not possible to infer which value
// will be inserted by the variable
// Skip validation if a variable is detected
jmesPath := variables.ReplaceAllVars(entry.ImageRegistry.JMESPath, func(s string) string { return "kyvernojmespathvariable" })
if !strings.Contains(jmesPath, "kyvernojmespathvariable") && entry.ImageRegistry.JMESPath != "" {
if _, err := jmespath.NewParser().Parse(entry.ImageRegistry.JMESPath); err != nil {
return fmt.Errorf("failed to parse JMESPath %s: %v", entry.ImageRegistry.JMESPath, err)
}
}
return nil
}
// validateMatchedResourceDescription checks if all necessary fields are present and have values. Also checks a Selector.
// field type is checked through openapi
// Returns error if
// - kinds is empty array in matched resource block, i.e. kinds: []
// - selector is invalid
func validateMatchedResourceDescription(rd kyvernov1.ResourceDescription) (string, error) {
if datautils.DeepEqual(rd, kyvernov1.ResourceDescription{}) {
return "", fmt.Errorf("match resources not specified")
}
return "", nil
}
// jsonPatchOnPod checks if a rule applies JSON patches to Pod
func jsonPatchOnPod(rule kyvernov1.Rule) bool {
if !rule.HasMutate() {
return false
}
if slices.Contains(rule.MatchResources.Kinds, "Pod") && rule.Mutation != nil && rule.Mutation.PatchesJSON6902 != "" {
return true
}
return false
}
func podControllerAutoGenExclusion(policy kyvernov1.PolicyInterface) bool {
annotations := policy.GetAnnotations()
val, ok := annotations[kyverno.AnnotationAutogenControllers]
if !ok || val == "none" {
return false
}
reorderVal := strings.Split(strings.ToLower(val), ",")
sort.Slice(reorderVal, func(i, j int) bool { return reorderVal[i] < reorderVal[j] })
if ok && !datautils.DeepEqual(reorderVal, []string{"cronjob", "daemonset", "deployment", "job", "statefulset"}) {
return true
}
return false
}
func validateKinds(kinds []string, rule kyvernov1.Rule, mock, background bool, client dclient.Interface) (string, error) {
if err := validateWildcard(kinds, background, rule); err != nil {
return "", err
}
if slices.Contains(kinds, "*") {
return "", nil
}
if warning, err := validKinds(kinds, mock, background, rule.HasValidate(), client); warning != "" {
return fmt.Sprintf("the kind defined in the all match resource is invalid: %s", warning), nil
} else if err != nil {
return "", err
}
return "", nil
}
// validateWildcard check for an Match/Exclude block contains "*"
func validateWildcard(kinds []string, background bool, rule kyvernov1.Rule) error {
if slices.Contains(kinds, "*") && background {
return fmt.Errorf("wildcard policy not allowed in background mode. Set spec.background=false to disable background mode for this policy rule ")
}
if slices.Contains(kinds, "*") && len(kinds) > 1 {
return fmt.Errorf("wildcard policy can not deal with more than one kind")
}
if slices.Contains(kinds, "*") {
if rule.HasGenerate() || rule.HasVerifyImages() || (rule.Validation != nil && rule.Validation.ForEachValidation != nil) {
return fmt.Errorf("wildcard policy does not support rule type")
}
if rule.HasValidate() {
if rule.Validation.GetPattern() != nil || rule.Validation.GetAnyPattern() != nil {
if !ruleOnlyDealsWithResourceMetaData(rule) {
return fmt.Errorf("policy can only deal with the metadata field of the resource if" +
" the rule does not match any kind")
}
}
if rule.Validation.Deny != nil {
switch typedConditions := rule.Validation.Deny.GetAnyAllConditions().(type) {
case []kyvernov1.Condition: // backwards compatibility
for _, condition := range typedConditions {
key := condition.GetKey()
if !strings.Contains(key.(string), "request.object.metadata.") && (!wildCardAllowedVariables.MatchString(key.(string)) || strings.Contains(key.(string), "request.object.spec")) {
return fmt.Errorf("policy can only deal with the metadata field of the resource if" +
" the rule does not match any kind")
}
}
}
}
}
if rule.HasMutate() {
if !ruleOnlyDealsWithResourceMetaData(rule) {
return fmt.Errorf("policy can only deal with the metadata field of the resource if" +
" the rule does not match any kind")
}
}
}
return nil
}
// validKinds verifies if an API resource that matches 'kind' is valid kind
// and found in the cache, returns error if not found. It also returns an error if background scanning
// is enabled for a subresource.
func validKinds(kinds []string, mock, backgroundScanningEnabled, isValidationPolicy bool, client dclient.Interface) (string, error) {
if !mock {
for _, k := range kinds {
group, version, kind, subresource := kubeutils.ParseKindSelector(k)
gvrss, err := client.Discovery().FindResources(group, version, kind, subresource)
if err != nil {
return fmt.Sprintf("unable to convert GVK to GVR for kinds %s, err: %v", k, err), nil
}
if len(gvrss) == 0 {
return fmt.Sprintf("unable to convert GVK to GVR for kinds %s", k), nil
}
if isValidationPolicy && backgroundScanningEnabled {
for gvrs := range gvrss {
if gvrs.SubResource != "" {
return "", fmt.Errorf("background scan enabled with subresource %s", k)
}
}
}
}
}
return "", nil
}
func validateWildcardsWithNamespaces(enforce, audit, enforceW, auditW []string) error {
pat, ns, notOk := wildcard.MatchPatterns(auditW, enforce...)
if notOk {
return fmt.Errorf("wildcard pattern '%s' matches with namespace '%s'", pat, ns)
}
pat, ns, notOk = wildcard.MatchPatterns(enforceW, audit...)
if notOk {
return fmt.Errorf("wildcard pattern '%s' matches with namespace '%s'", pat, ns)
}
pat1, pat2, notOk := wildcard.MatchPatterns(auditW, enforceW...)
if notOk {
return fmt.Errorf("wildcard pattern '%s' conflicts with the pattern '%s'", pat1, pat2)
}
pat1, pat2, notOk = wildcard.MatchPatterns(enforceW, auditW...)
if notOk {
return fmt.Errorf("wildcard pattern '%s' conflicts with the pattern '%s'", pat1, pat2)
}
return nil
}
func validateNamespaces(validationFailureActionOverrides []kyvernov1.ValidationFailureActionOverride, path *field.Path) error {
action := map[string]sets.Set[string]{
"enforce": sets.New[string](),
"audit": sets.New[string](),
"enforceW": sets.New[string](),
"auditW": sets.New[string](),
}
for i, vfa := range validationFailureActionOverrides {
if !vfa.Action.IsValid() {
return fmt.Errorf("invalid action")
}
patternList, nsList := wildcard.SeperateWildcards(vfa.Namespaces)
if vfa.Action.Audit() {
if action["enforce"].HasAny(nsList...) {
return fmt.Errorf("conflicting namespaces found in path: %s: %s", path.Index(i).Child("namespaces").String(),
strings.Join(sets.List(action["enforce"].Intersection(sets.New(nsList...))), ", "))
}
action["auditW"].Insert(patternList...)
} else if vfa.Action.Enforce() {
if action["audit"].HasAny(nsList...) {
return fmt.Errorf("conflicting namespaces found in path: %s: %s", path.Index(i).Child("namespaces").String(),
strings.Join(sets.List(action["audit"].Intersection(sets.New(nsList...))), ", "))
}
action["enforceW"].Insert(patternList...)
}
action[strings.ToLower(string(vfa.Action))].Insert(nsList...)
err := validateWildcardsWithNamespaces(
sets.List(action["enforce"]),
sets.List(action["audit"]),
sets.List(action["enforceW"]),
sets.List(action["auditW"]),
)
if err != nil {
return fmt.Errorf("path: %s: %s", path.Index(i).Child("namespaces").String(), err.Error())
}
}
return nil
}
func checkForScaleSubresource(ruleTypeJson []byte, allKinds []string, warnings *[]string) {
if strings.Contains(string(ruleTypeJson), "replicas") {
for _, kind := range allKinds {
if strings.Contains(strings.ToLower(kind), "scale") {
return
}
}
msg := "You are matching on replicas but not including the scale subresource in the policy."
*warnings = append(*warnings, msg)
}
}
func checkForStatusSubresource(ruleTypeJson []byte, allKinds []string, warnings *[]string) {
rule := string(ruleTypeJson)
if strings.Contains(rule, ".status") || strings.Contains(rule, "\"status\":") {
for _, kind := range allKinds {
if strings.Contains(strings.ToLower(kind), "status") {
return
}
}
msg := "You are matching on status but not including the status subresource in the policy."
*warnings = append(*warnings, msg)
}
}
func checkForDeprecatedFieldsInVerifyImages(rule kyvernov1.Rule, warnings *[]string) {
for _, imageVerify := range rule.VerifyImages {
for _, attestation := range imageVerify.Attestations {
if attestation.PredicateType != "" {
msg := fmt.Sprintf("predicateType has been deprecated use 'type: %s' instead of 'predicateType: %s'", attestation.PredicateType, attestation.PredicateType)
*warnings = append(*warnings, msg)
}
}
}
}
func checkDeprecatedOperator(c kyvernov1.ConditionOperator) string {
if operator.IsOperatorDeprecated(c) {
return fmt.Sprintf("Operator %s has been deprecated and will be removed soon. Use these instead: %+q", string(c), operator.GetDeprecatedOperatorAlternative(string(c)))
}
return ""
}
func checkDeprecatedConditionOperator(c []kyvernov1.Condition, warnings *[]string) {
for _, condition := range c {
if warn := checkDeprecatedOperator(condition.Operator); len(warn) > 0 {
*warnings = append(*warnings, warn)
}
}
}
func checkDeprecatedAnyAllConditionOperator(c kyvernov1.AnyAllConditions, warnings *[]string) {
if !datautils.DeepEqual(c, kyvernov1.AnyAllConditions{}) && c.AnyConditions != nil {
for _, condition := range c.AnyConditions {
if warn := checkDeprecatedOperator(condition.Operator); len(warn) > 0 {
*warnings = append(*warnings, warn)
}
}
}
if !datautils.DeepEqual(c, kyvernov1.AnyAllConditions{}) && c.AllConditions != nil {
for _, condition := range c.AllConditions {
if warn := checkDeprecatedOperator(condition.Operator); len(warn) > 0 {
*warnings = append(*warnings, warn)
}
}
}
}
func checkDeprecatedRawJSONConditionOperator(c apiextensions.JSON, warnings *[]string) {
switch typedConditions := c.(type) {
case kyvernov1.AnyAllConditions:
checkDeprecatedAnyAllConditionOperator(typedConditions, warnings)
case []kyvernov1.Condition: // backwards compatibility
checkDeprecatedConditionOperator(typedConditions, warnings)
}
}
func checkForDeprecatedOperatorsInRule(rule kyvernov1.Rule, warnings *[]string) {
if rule.Validation != nil {
if rule.Validation.Deny != nil {
if target := rule.Validation.Deny.GetAnyAllConditions(); target != nil {
checkDeprecatedRawJSONConditionOperator(target, warnings)
}
}
if len(rule.Validation.ForEachValidation) != 0 {
for _, fe := range rule.Validation.ForEachValidation {
if fe.AnyAllConditions != nil {
checkDeprecatedAnyAllConditionOperator(*fe.AnyAllConditions, warnings)
}
if fe.Deny != nil {
if target := fe.Deny.GetAnyAllConditions(); target != nil {
checkDeprecatedRawJSONConditionOperator(target, warnings)
}
}
}
}
}
if len(rule.VerifyImages) != 0 {
for _, vi := range rule.VerifyImages {
for _, att := range vi.Attestations {
for _, c := range att.Conditions {
checkDeprecatedAnyAllConditionOperator(c, warnings)
}
}
}
}
}