mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-28 02:18:15 +00:00
feat: support cel expression in validate rules (#7070)
* feat: support cel expression in validate rules Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * Adding CEL preconditions in kyverno policies Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * Support parameter resources in validate.cel subrule Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * fix Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * Adding CEL preconditions in kyverno policies Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * Add kuttl tests for validate.cel subrule Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * fix Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * Fix disallow-host-path kuttl test Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * Add kuttl test for cel preconditions Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * Fix kuttl tests for validate.cel Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * Use K8S API Validation and AuditAnnotation Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> * Use K8S API ParamKind and ParamRef Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> --------- Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com> Co-authored-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
parent
194bfacc71
commit
7f6fb24057
44 changed files with 6656 additions and 6 deletions
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
|
||||
"github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -387,6 +388,10 @@ type Validation struct {
|
|||
// by specifying exclusions for Pod Security Standards controls.
|
||||
// +optional
|
||||
PodSecurity *PodSecurity `json:"podSecurity,omitempty" yaml:"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" yaml:"cel,omitempty"`
|
||||
}
|
||||
|
||||
// PodSecurity applies exemptions for Kubernetes Pod Security admission
|
||||
|
@ -422,6 +427,36 @@ type PodSecurityStandard struct {
|
|||
Images []string `json:"images,omitempty" yaml:"images,omitempty"`
|
||||
}
|
||||
|
||||
// CEL allows validation checks using the Common Expression Language (https://kubernetes.io/docs/reference/using-api/cel/).
|
||||
type CEL struct {
|
||||
// Expressions is a list of CELExpression types.
|
||||
Expressions []v1alpha1.Validation `json:"expressions,omitempty" yaml:"expressions,omitempty"`
|
||||
|
||||
// ParamKind is a tuple of Group Kind and Version.
|
||||
// +optional
|
||||
ParamKind *v1alpha1.ParamKind `json:"paramKind,omitempty" yaml:"paramKind,omitempty"`
|
||||
|
||||
// ParamRef references a parameter resource.
|
||||
// +optional
|
||||
ParamRef *v1alpha1.ParamRef `json:"paramRef,omitempty" yaml:"paramRef,omitempty"`
|
||||
|
||||
// AuditAnnotations contains CEL expressions which are used to produce audit annotations for the audit event of the API request.
|
||||
// +optional
|
||||
AuditAnnotations []v1alpha1.AuditAnnotation `json:"auditAnnotations,omitempty" yaml:"auditAnnotations,omitempty"`
|
||||
}
|
||||
|
||||
func (c *CEL) HasParam() bool {
|
||||
return c.ParamKind != nil && c.ParamRef != nil
|
||||
}
|
||||
|
||||
func (c *CEL) GetParamKind() v1alpha1.ParamKind {
|
||||
return *c.ParamKind
|
||||
}
|
||||
|
||||
func (c *CEL) GetParamRef() v1alpha1.ParamRef {
|
||||
return *c.ParamRef
|
||||
}
|
||||
|
||||
// DeserializeAnyPattern deserialize apiextensions.JSON to []interface{}
|
||||
func (in *Validation) DeserializeAnyPattern() ([]interface{}, error) {
|
||||
anyPattern := in.GetAnyPattern()
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/pss/utils"
|
||||
datautils "github.com/kyverno/kyverno/pkg/utils/data"
|
||||
wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
@ -77,6 +78,11 @@ type Rule struct {
|
|||
// +optional
|
||||
RawAnyAllConditions *apiextv1.JSON `json:"preconditions,omitempty" yaml:"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" yaml:"celPreconditions,omitempty"`
|
||||
|
||||
// Mutation is used to modify matching resources.
|
||||
// +optional
|
||||
Mutation Mutation `json:"mutate,omitempty" yaml:"mutate,omitempty"`
|
||||
|
@ -129,6 +135,11 @@ func (r Rule) HasValidatePodSecurity() bool {
|
|||
return r.Validation.PodSecurity != nil && !datautils.DeepEqual(r.Validation.PodSecurity, &PodSecurity{})
|
||||
}
|
||||
|
||||
// HasValidateCEL checks for validate.cel rule
|
||||
func (r *Rule) HasValidateCEL() bool {
|
||||
return r.Validation.CEL != nil && !datautils.DeepEqual(r.Validation.CEL, &CEL{})
|
||||
}
|
||||
|
||||
// HasValidate checks for validate rule
|
||||
func (r *Rule) HasValidate() bool {
|
||||
return !datautils.DeepEqual(r.Validation, Validation{})
|
||||
|
|
|
@ -23,6 +23,8 @@ package v1
|
|||
|
||||
import (
|
||||
"github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -205,6 +207,43 @@ func (in *AutogenStatus) DeepCopy() *AutogenStatus {
|
|||
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.Expressions != nil {
|
||||
in, out := &in.Expressions, &out.Expressions
|
||||
*out = make([]v1alpha1.Validation, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ParamKind != nil {
|
||||
in, out := &in.ParamKind, &out.ParamKind
|
||||
*out = new(v1alpha1.ParamKind)
|
||||
**out = **in
|
||||
}
|
||||
if in.ParamRef != nil {
|
||||
in, out := &in.ParamRef, &out.ParamRef
|
||||
*out = new(v1alpha1.ParamRef)
|
||||
**out = **in
|
||||
}
|
||||
if in.AuditAnnotations != nil {
|
||||
in, out := &in.AuditAnnotations, &out.AuditAnnotations
|
||||
*out = make([]v1alpha1.AuditAnnotation, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -1123,6 +1162,11 @@ func (in *Rule) DeepCopyInto(out *Rule) {
|
|||
*out = new(apiextensionsv1.JSON)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.CELPreconditions != nil {
|
||||
in, out := &in.CELPreconditions, &out.CELPreconditions
|
||||
*out = make([]admissionregistrationv1.MatchCondition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.Mutation.DeepCopyInto(&out.Mutation)
|
||||
in.Validation.DeepCopyInto(&out.Validation)
|
||||
in.Generation.DeepCopyInto(&out.Generation)
|
||||
|
@ -1367,6 +1411,11 @@ func (in *Validation) DeepCopyInto(out *Validation) {
|
|||
*out = new(PodSecurity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.CEL != nil {
|
||||
in, out := &in.CEL, &out.CEL
|
||||
*out = new(CEL)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Validation.
|
||||
|
|
|
@ -37,6 +37,10 @@ type Validation struct {
|
|||
// by specifying exclusions for Pod Security Standards controls.
|
||||
// +optional
|
||||
PodSecurity *kyvernov1.PodSecurity `json:"podSecurity,omitempty" yaml:"podSecurity,omitempty"`
|
||||
|
||||
// CEL allows validation checks using the Common Expression Language (https://kubernetes.io/docs/reference/using-api/cel/).
|
||||
// +optional
|
||||
CEL *kyvernov1.CEL `json:"cel,omitempty" yaml:"cel,omitempty"`
|
||||
}
|
||||
|
||||
// ConditionOperator is the operation performed on condition key and value.
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
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"
|
||||
)
|
||||
|
@ -45,6 +46,11 @@ type Rule struct {
|
|||
// +optional
|
||||
RawAnyAllConditions *AnyAllConditions `json:"preconditions,omitempty" yaml:"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" yaml:"celPreconditions,omitempty"`
|
||||
|
||||
// Mutation is used to modify matching resources.
|
||||
// +optional
|
||||
Mutation kyvernov1.Mutation `json:"mutate,omitempty" yaml:"mutate,omitempty"`
|
||||
|
@ -97,6 +103,11 @@ func (r Rule) HasValidatePodSecurity() bool {
|
|||
return r.Validation.PodSecurity != nil && !datautils.DeepEqual(r.Validation.PodSecurity, &kyvernov1.PodSecurity{})
|
||||
}
|
||||
|
||||
// HasValidateCEL checks for validate.cel rule
|
||||
func (r *Rule) HasValidateCEL() bool {
|
||||
return r.Validation.CEL != nil && !datautils.DeepEqual(r.Validation.CEL, &kyvernov1.CEL{})
|
||||
}
|
||||
|
||||
// HasValidate checks for validate rule
|
||||
func (r *Rule) HasValidate() bool {
|
||||
return !datautils.DeepEqual(r.Validation, Validation{})
|
||||
|
|
|
@ -23,6 +23,7 @@ package v2beta1
|
|||
|
||||
import (
|
||||
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -405,6 +406,11 @@ func (in *Rule) DeepCopyInto(out *Rule) {
|
|||
*out = new(AnyAllConditions)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.CELPreconditions != nil {
|
||||
in, out := &in.CELPreconditions, &out.CELPreconditions
|
||||
*out = make([]admissionregistrationv1.MatchCondition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.Mutation.DeepCopyInto(&out.Mutation)
|
||||
in.Validation.DeepCopyInto(&out.Validation)
|
||||
in.Generation.DeepCopyInto(&out.Generation)
|
||||
|
@ -521,6 +527,11 @@ func (in *Validation) DeepCopyInto(out *Validation) {
|
|||
*out = new(v1.PodSecurity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.CEL != nil {
|
||||
in, out := &in.CEL, &out.CEL
|
||||
*out = new(v1.CEL)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Validation.
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -927,6 +927,82 @@ attributes for keyless verification, or a nested attestor declaration.</p>
|
|||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3 id="kyverno.io/v1.CEL">CEL
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#kyverno.io/v1.Validation">Validation</a>,
|
||||
<a href="#kyverno.io/v2beta1.Validation">Validation</a>)
|
||||
</p>
|
||||
<p>
|
||||
<p>CEL allows validation checks using the Common Expression Language (<a href="https://kubernetes.io/docs/reference/using-api/cel/">https://kubernetes.io/docs/reference/using-api/cel/</a>).</p>
|
||||
</p>
|
||||
<table class="table table-striped">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>expressions</code><br/>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#validation-v1alpha1-admissionregistration">
|
||||
[]Kubernetes admissionregistration/v1alpha1.Validation
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Expressions is a list of CELExpression types.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>paramKind</code><br/>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#paramkind-v1alpha1-admissionregistration">
|
||||
Kubernetes admissionregistration/v1alpha1.ParamKind
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ParamKind is a tuple of Group Kind and Version.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>paramRef</code><br/>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#paramref-v1alpha1-admissionregistration">
|
||||
Kubernetes admissionregistration/v1alpha1.ParamRef
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ParamRef references a parameter resource.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>auditAnnotations</code><br/>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#auditannotation-v1alpha1-admissionregistration">
|
||||
[]Kubernetes admissionregistration/v1alpha1.AuditAnnotation
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>AuditAnnotations contains CEL expressions which are used to produce audit annotations for the audit event of the API request.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3 id="kyverno.io/v1.CTLog">CTLog
|
||||
</h3>
|
||||
<p>
|
||||
|
@ -3060,6 +3136,21 @@ See: <a href="https://kyverno.io/docs/writing-policies/preconditions/">https://k
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>celPreconditions</code><br/>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#matchcondition-v1-admissionregistration">
|
||||
[]Kubernetes admissionregistration/v1.MatchCondition
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>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</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>mutate</code><br/>
|
||||
<em>
|
||||
<a href="#kyverno.io/v1.Mutation">
|
||||
|
@ -3774,6 +3865,20 @@ PodSecurity
|
|||
by specifying exclusions for Pod Security Standards controls.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>cel</code><br/>
|
||||
<em>
|
||||
<a href="#kyverno.io/v1.CEL">
|
||||
CEL
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>CEL allows validation checks using the Common Expression Language (<a href="https://kubernetes.io/docs/reference/using-api/cel/">https://kubernetes.io/docs/reference/using-api/cel/</a>).</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
|
@ -6701,6 +6806,21 @@ See: <a href="https://kyverno.io/docs/writing-policies/preconditions/">https://k
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>celPreconditions</code><br/>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#matchcondition-v1-admissionregistration">
|
||||
[]Kubernetes admissionregistration/v1.MatchCondition
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>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</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>mutate</code><br/>
|
||||
<em>
|
||||
<a href="#kyverno.io/v1.Mutation">
|
||||
|
@ -7052,6 +7172,20 @@ PodSecurity
|
|||
by specifying exclusions for Pod Security Standards controls.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>cel</code><br/>
|
||||
<em>
|
||||
<a href="#kyverno.io/v1.CEL">
|
||||
CEL
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>CEL allows validation checks using the Common Expression Language (<a href="https://kubernetes.io/docs/reference/using-api/cel/">https://kubernetes.io/docs/reference/using-api/cel/</a>).</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
|
|
9
go.mod
9
go.mod
|
@ -83,6 +83,12 @@ require (
|
|||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
|
||||
github.com/google/cel-go v0.12.6 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.19.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
|
@ -119,7 +125,6 @@ require (
|
|||
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||
github.com/aliyun/credentials-go v1.2.7 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.18 // indirect
|
||||
|
@ -192,7 +197,6 @@ require (
|
|||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/cel-go v0.12.6 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.4 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-github/v45 v45.2.0 // indirect
|
||||
|
@ -280,7 +284,6 @@ require (
|
|||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.15.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.1.3 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||
|
|
163
pkg/engine/handlers/validation/validate_cel.go
Normal file
163
pkg/engine/handlers/validation/validate_cel.go
Normal file
|
@ -0,0 +1,163 @@
|
|||
package validation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"github.com/kyverno/kyverno/pkg/engine/handlers"
|
||||
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
)
|
||||
|
||||
type validateCELHandler struct {
|
||||
client dclient.Interface
|
||||
}
|
||||
|
||||
func NewValidateCELHandler(client dclient.Interface) (handlers.Handler, error) {
|
||||
return validateCELHandler{
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h validateCELHandler) Process(
|
||||
ctx context.Context,
|
||||
logger logr.Logger,
|
||||
policyContext engineapi.PolicyContext,
|
||||
resource unstructured.Unstructured,
|
||||
rule kyvernov1.Rule,
|
||||
_ engineapi.EngineContextLoader,
|
||||
) (unstructured.Unstructured, []engineapi.RuleResponse) {
|
||||
if engineutils.IsDeleteRequest(policyContext) {
|
||||
logger.V(3).Info("skipping CEL validation on deleted resource")
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
oldResource := policyContext.OldResource()
|
||||
|
||||
var object, oldObject, versionedParams runtime.Object
|
||||
object = resource.DeepCopyObject()
|
||||
if oldResource.Object == nil {
|
||||
oldObject = nil
|
||||
} else {
|
||||
oldObject = oldResource.DeepCopyObject()
|
||||
}
|
||||
|
||||
var expressions, messageExpressions, matchExpressions, auditExpressions []cel.ExpressionAccessor
|
||||
|
||||
validations := rule.Validation.CEL.Expressions
|
||||
auditAnnotations := rule.Validation.CEL.AuditAnnotations
|
||||
|
||||
// Get the parameter resource
|
||||
hasParam := rule.Validation.CEL.HasParam()
|
||||
|
||||
if hasParam {
|
||||
paramKind := rule.Validation.CEL.GetParamKind()
|
||||
paramRef := rule.Validation.CEL.GetParamRef()
|
||||
|
||||
apiVersion := paramKind.APIVersion
|
||||
kind := paramKind.Kind
|
||||
|
||||
name := paramRef.Name
|
||||
namespace := paramRef.Namespace
|
||||
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
|
||||
paramResource, err := h.client.GetResource(ctx, apiVersion, kind, namespace, name, "")
|
||||
if err != nil {
|
||||
return resource, handlers.WithError(rule, engineapi.Validation, "Error while getting the parameterized resource", err)
|
||||
}
|
||||
|
||||
versionedParams = paramResource.DeepCopyObject()
|
||||
}
|
||||
|
||||
for _, cel := range validations {
|
||||
condition := &validatingadmissionpolicy.ValidationCondition{
|
||||
Expression: cel.Expression,
|
||||
Message: cel.Message,
|
||||
}
|
||||
|
||||
messageCondition := &validatingadmissionpolicy.MessageExpressionCondition{
|
||||
MessageExpression: cel.MessageExpression,
|
||||
}
|
||||
|
||||
expressions = append(expressions, condition)
|
||||
messageExpressions = append(messageExpressions, messageCondition)
|
||||
}
|
||||
|
||||
for _, condition := range rule.CELPreconditions {
|
||||
matchCondition := &matchconditions.MatchCondition{
|
||||
Name: condition.Name,
|
||||
Expression: condition.Expression,
|
||||
}
|
||||
|
||||
matchExpressions = append(matchExpressions, matchCondition)
|
||||
}
|
||||
|
||||
for _, auditAnnotation := range auditAnnotations {
|
||||
auditCondition := &validatingadmissionpolicy.AuditAnnotationCondition{
|
||||
Key: auditAnnotation.Key,
|
||||
ValueExpression: auditAnnotation.ValueExpression,
|
||||
}
|
||||
|
||||
auditExpressions = append(auditExpressions, auditCondition)
|
||||
}
|
||||
|
||||
filterCompiler := cel.NewFilterCompiler()
|
||||
filter := filterCompiler.Compile(expressions, cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}, celconfig.PerCallLimit)
|
||||
messageExpressionfilter := filterCompiler.Compile(messageExpressions, cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}, celconfig.PerCallLimit)
|
||||
auditAnnotationFilter := filterCompiler.Compile(auditExpressions, cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}, celconfig.PerCallLimit)
|
||||
matchConditionFilter := filterCompiler.Compile(matchExpressions, cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}, celconfig.PerCallLimit)
|
||||
|
||||
newMatcher := matchconditions.NewMatcher(matchConditionFilter, nil, nil, "", "")
|
||||
|
||||
validator := validatingadmissionpolicy.NewValidator(filter, newMatcher, auditAnnotationFilter, messageExpressionfilter, nil, nil)
|
||||
|
||||
admissionAttributes := admission.NewAttributesRecord(
|
||||
object,
|
||||
oldObject,
|
||||
resource.GroupVersionKind(),
|
||||
resource.GetNamespace(),
|
||||
resource.GetName(),
|
||||
schema.GroupVersionResource{},
|
||||
"",
|
||||
admission.Operation(policyContext.Operation()),
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
versionedAttr, _ := admission.NewVersionedAttributes(admissionAttributes, admissionAttributes.GetKind(), nil)
|
||||
validateResult := validator.Validate(ctx, versionedAttr, versionedParams, celconfig.RuntimeCELCostBudget)
|
||||
|
||||
for _, decision := range validateResult.Decisions {
|
||||
switch decision.Action {
|
||||
case validatingadmissionpolicy.ActionAdmit:
|
||||
if decision.Evaluation == validatingadmissionpolicy.EvalError {
|
||||
return resource, handlers.WithResponses(
|
||||
engineapi.RuleError(rule.Name, engineapi.Validation, decision.Message, nil),
|
||||
)
|
||||
}
|
||||
case validatingadmissionpolicy.ActionDeny:
|
||||
return resource, handlers.WithResponses(
|
||||
engineapi.RuleFail(rule.Name, engineapi.Validation, decision.Message),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name)
|
||||
return resource, handlers.WithResponses(
|
||||
engineapi.RulePass(rule.Name, engineapi.Validation, msg),
|
||||
)
|
||||
}
|
|
@ -129,7 +129,7 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
|
|||
return ruleResponse
|
||||
}
|
||||
|
||||
v.log.V(2).Info("invalid validation rule: podSecurity, patterns, or deny expected")
|
||||
v.log.V(2).Info("invalid validation rule: podSecurity, cel, patterns, or deny expected")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ func (e *engine) validate(
|
|||
if hasValidate {
|
||||
hasVerifyManifest := rule.HasVerifyManifests()
|
||||
hasValidatePss := rule.HasValidatePodSecurity()
|
||||
hasValidateCEL := rule.HasValidateCEL()
|
||||
if hasVerifyManifest {
|
||||
return validation.NewValidateManifestHandler(
|
||||
policyContext,
|
||||
|
@ -45,6 +46,8 @@ func (e *engine) validate(
|
|||
)
|
||||
} else if hasValidatePss {
|
||||
return validation.NewValidatePssHandler()
|
||||
} else if hasValidateCEL {
|
||||
return validation.NewValidateCELHandler(e.client)
|
||||
} else {
|
||||
return validation.NewValidateResourceHandler()
|
||||
}
|
||||
|
|
|
@ -68,17 +68,61 @@ func (v *Validate) Validate(ctx context.Context) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if v.rule.CEL != nil {
|
||||
for _, expression := range v.rule.CEL.Expressions {
|
||||
if expression.Expression == "" {
|
||||
return "", fmt.Errorf("cel.expressions.expression is required")
|
||||
}
|
||||
}
|
||||
|
||||
if v.rule.CEL.ParamKind != nil {
|
||||
if v.rule.CEL.ParamKind.APIVersion == "" {
|
||||
return "", fmt.Errorf("cel.paramKind.apiVersion is required")
|
||||
}
|
||||
|
||||
if v.rule.CEL.ParamKind.Kind == "" {
|
||||
return "", fmt.Errorf("cel.paramKind.kind is required")
|
||||
}
|
||||
|
||||
if v.rule.CEL.ParamRef == nil {
|
||||
return "", fmt.Errorf("cel.paramRef is required")
|
||||
}
|
||||
}
|
||||
|
||||
if v.rule.CEL.ParamRef != nil {
|
||||
if v.rule.CEL.ParamRef.Name == "" {
|
||||
return "", fmt.Errorf("cel.paramRef.name is required")
|
||||
}
|
||||
|
||||
if v.rule.CEL.ParamKind == nil {
|
||||
return "", fmt.Errorf("cel.paramKind is required")
|
||||
}
|
||||
}
|
||||
|
||||
if v.rule.CEL.AuditAnnotations != nil {
|
||||
for _, auditAnnotation := range v.rule.CEL.AuditAnnotations {
|
||||
if auditAnnotation.Key == "" {
|
||||
return "", fmt.Errorf("cel.auditAnnotation.key is required")
|
||||
}
|
||||
|
||||
if auditAnnotation.ValueExpression == "" {
|
||||
return "", fmt.Errorf("cel.auditAnnotation.valueExpression is required")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (v *Validate) validateElements() error {
|
||||
count := validationElemCount(v.rule)
|
||||
if count == 0 {
|
||||
return fmt.Errorf("one of pattern, anyPattern, deny, foreach must be specified")
|
||||
return fmt.Errorf("one of pattern, anyPattern, deny, foreach, cel must be specified")
|
||||
}
|
||||
|
||||
if count > 1 {
|
||||
return fmt.Errorf("only one of pattern, anyPattern, deny, foreach can be specified")
|
||||
return fmt.Errorf("only one of pattern, anyPattern, deny, foreach, cel can be specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -110,6 +154,10 @@ func validationElemCount(v *kyvernov1.Validation) int {
|
|||
count++
|
||||
}
|
||||
|
||||
if v.CEL != nil {
|
||||
count++
|
||||
}
|
||||
|
||||
if v.Manifests != nil && len(v.Manifests.Attestors) != 0 {
|
||||
count++
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- file: pod-pass.yaml
|
||||
shouldFail: false
|
||||
- file: pod-fail.yaml
|
||||
shouldFail: true
|
|
@ -0,0 +1,9 @@
|
|||
## Description
|
||||
|
||||
This test validates the use of `rule.celPreconditions`.
|
||||
The policy will be applied on resources that matches the CEL Preconditions.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
The policy will be applied on `pod-fail` and since it violates the rule, it will be blocked.
|
||||
The policy won't be applied on `pod-pass` because it doesn't match the CEL precondition. Therefore it will be created.
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: webserver
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
hostPort: 80
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
spec:
|
||||
containers:
|
||||
- name: webserver
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
hostPort: 80
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-host-port-range
|
||||
status:
|
||||
conditions:
|
||||
- reason: Succeeded
|
||||
status: "True"
|
||||
type: Ready
|
|
@ -0,0 +1,22 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-host-port-range
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: false
|
||||
rules:
|
||||
- name: host-port-range
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
celPreconditions:
|
||||
- name: "first match condition in CEL"
|
||||
expression: "object.metadata.name.matches('nginx-pod')"
|
||||
validate:
|
||||
cel:
|
||||
expressions:
|
||||
- expression: "object.spec.containers.all(container, !has(container.ports) || container.ports.all(port, !has(port.hostPort) || (port.hostPort >= 5000 && port.hostPort <= 6000)))"
|
||||
message: "The only permitted hostPorts are in the range 5000-6000."
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- ns.yaml
|
||||
assert:
|
||||
- ns.yaml
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- crd.yaml
|
||||
assert:
|
||||
- crd-assert.yaml
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- replicaLimit.yaml
|
||||
assert:
|
||||
- replicaLimit.yaml
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- file: deployment-pass.yaml
|
||||
shouldFail: false
|
||||
- file: deployment-fail.yaml
|
||||
shouldFail: true
|
|
@ -0,0 +1,13 @@
|
|||
## Description
|
||||
|
||||
This test validates the use of parameter resources in validate.cel subrule.
|
||||
|
||||
This test creates the following:
|
||||
1. A namespace `test-params`
|
||||
2. A custom resource definition `ReplicaLimit`
|
||||
3. A policy that checks the deployment replicas using the parameter resource.
|
||||
4. Two deployments.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
The deployment `deployment-fail` is blocked, and the deployment `deployment-pass` is created.
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: replicalimits.rules.example.com
|
|
@ -0,0 +1,26 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: replicalimits.rules.example.com
|
||||
spec:
|
||||
group: rules.example.com
|
||||
names:
|
||||
kind: ReplicaLimit
|
||||
plural: replicalimits
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
maxReplicas:
|
||||
type: integer
|
|
@ -0,0 +1,17 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deployment-fail
|
||||
spec:
|
||||
replicas: 4
|
||||
selector:
|
||||
matchLabels:
|
||||
app: app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: container2
|
||||
image: nginx
|
|
@ -0,0 +1,17 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deployment-pass
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: container2
|
||||
image: nginx
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: test-params
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: check-deployment-replicas
|
||||
status:
|
||||
conditions:
|
||||
- reason: Succeeded
|
||||
status: "True"
|
||||
type: Ready
|
|
@ -0,0 +1,25 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: check-deployment-replicas
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: false
|
||||
rules:
|
||||
- name: deployment-replicas
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Deployment
|
||||
validate:
|
||||
cel:
|
||||
paramKind:
|
||||
apiVersion: rules.example.com/v1
|
||||
kind: ReplicaLimit
|
||||
paramRef:
|
||||
name: "replica-limit-test.example.com"
|
||||
namespace: "test-params"
|
||||
expressions:
|
||||
- expression: "object.spec.replicas <= params.maxReplicas"
|
||||
messageExpression: "'Deployment spec.replicas must be less than ' + string(params.maxReplicas)"
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: rules.example.com/v1
|
||||
kind: ReplicaLimit
|
||||
metadata:
|
||||
name: "replica-limit-test.example.com"
|
||||
namespace: test-params
|
||||
maxReplicas: 3
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- file: pod-pass.yaml
|
||||
shouldFail: false
|
||||
- file: pod-fail.yaml
|
||||
shouldFail: true
|
|
@ -0,0 +1,7 @@
|
|||
## Description
|
||||
|
||||
This test creates a policy that uses CEL expressions to disallow host ports in pods.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
The pod `pod-fail` is blocked, and the pod `pod-pass` is created.
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: webserver
|
||||
spec:
|
||||
containers:
|
||||
- name: webserver
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- hostPort: 80
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: webserver
|
||||
spec:
|
||||
containers:
|
||||
- name: webserver
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-host-port
|
||||
status:
|
||||
conditions:
|
||||
- reason: Succeeded
|
||||
status: "True"
|
||||
type: Ready
|
|
@ -0,0 +1,19 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-host-port
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: false
|
||||
rules:
|
||||
- name: host-port
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
cel:
|
||||
expressions:
|
||||
- expression: "object.spec.containers.all(container, !has(container.ports) || container.ports.all(port, !has(port.hostPort) || port.hostPort == 0))"
|
||||
message: "The fields spec.containers[*].ports[*].hostPort must either be unset or set to `0`"
|
Loading…
Add table
Reference in a new issue