mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-06 16:06:56 +00:00
feat: add validating policy compiler (#11906)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
parent
e7c372a398
commit
616cb93bc4
4 changed files with 210 additions and 0 deletions
|
@ -17,6 +17,13 @@ type ValidatingPolicy struct {
|
|||
Spec admissionregistrationv1.ValidatingAdmissionPolicySpec `json:"spec"`
|
||||
}
|
||||
|
||||
func (s *ValidatingPolicy) GetFailurePolicy() admissionregistrationv1.FailurePolicyType {
|
||||
if s.Spec.FailurePolicy == nil {
|
||||
return admissionregistrationv1.Fail
|
||||
}
|
||||
return *s.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
|
|
123
pkg/cel/policy/compiler.go
Normal file
123
pkg/cel/policy/compiler.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
|
||||
engine "github.com/kyverno/kyverno/pkg/cel"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/context"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
const (
|
||||
ContextKey = "context"
|
||||
ObjectKey = "object"
|
||||
VariablesKey = "variables"
|
||||
)
|
||||
|
||||
type Compiler interface {
|
||||
Compile(*kyvernov2alpha1.ValidatingPolicy) (*CompiledPolicy, field.ErrorList)
|
||||
}
|
||||
|
||||
func NewCompiler() Compiler {
|
||||
return &compiler{}
|
||||
}
|
||||
|
||||
type compiler struct{}
|
||||
|
||||
func (c *compiler) Compile(policy *kyvernov2alpha1.ValidatingPolicy) (*CompiledPolicy, field.ErrorList) {
|
||||
var allErrs field.ErrorList
|
||||
base, err := engine.NewEnv()
|
||||
if err != nil {
|
||||
return nil, append(allErrs, field.InternalError(nil, err))
|
||||
}
|
||||
provider := NewVariablesProvider(base.CELTypeProvider())
|
||||
env, err := base.Extend(
|
||||
cel.Variable(ContextKey, context.ContextType),
|
||||
cel.Variable(ObjectKey, cel.DynType),
|
||||
cel.Variable(VariablesKey, VariablesType),
|
||||
cel.CustomTypeProvider(provider),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, append(allErrs, field.InternalError(nil, err))
|
||||
}
|
||||
path := field.NewPath("spec")
|
||||
matchConditions := make([]cel.Program, 0, len(policy.Spec.MatchConditions))
|
||||
{
|
||||
path := path.Child("matchConditions")
|
||||
for i, matchCondition := range policy.Spec.MatchConditions {
|
||||
path := path.Index(i).Child("expression")
|
||||
ast, issues := env.Compile(matchCondition.Expression)
|
||||
if err := issues.Err(); err != nil {
|
||||
return nil, append(allErrs, field.Invalid(path, matchCondition.Expression, err.Error()))
|
||||
}
|
||||
if !ast.OutputType().IsExactType(types.BoolType) {
|
||||
msg := fmt.Sprintf("output is expected to be of type %s", types.BoolType.TypeName())
|
||||
return nil, append(allErrs, field.Invalid(path, matchCondition.Expression, msg))
|
||||
}
|
||||
prog, err := env.Program(ast)
|
||||
if err != nil {
|
||||
return nil, append(allErrs, field.Invalid(path, matchCondition.Expression, err.Error()))
|
||||
}
|
||||
matchConditions = append(matchConditions, prog)
|
||||
}
|
||||
}
|
||||
variables := map[string]cel.Program{}
|
||||
{
|
||||
path := path.Child("variables")
|
||||
for i, variable := range policy.Spec.Variables {
|
||||
path := path.Index(i).Child("expression")
|
||||
ast, issues := env.Compile(variable.Expression)
|
||||
if err := issues.Err(); err != nil {
|
||||
return nil, append(allErrs, field.Invalid(path, variable.Expression, err.Error()))
|
||||
}
|
||||
provider.RegisterField(variable.Name, ast.OutputType())
|
||||
prog, err := env.Program(ast)
|
||||
if err != nil {
|
||||
return nil, append(allErrs, field.Invalid(path, variable.Expression, err.Error()))
|
||||
}
|
||||
variables[variable.Name] = prog
|
||||
}
|
||||
}
|
||||
validations := make([]cel.Program, 0, len(policy.Spec.Validations))
|
||||
{
|
||||
path := path.Child("validations")
|
||||
for i, rule := range policy.Spec.Validations {
|
||||
path := path.Index(i)
|
||||
program, errs := compileValidation(path, rule, env)
|
||||
if errs != nil {
|
||||
return nil, append(allErrs, errs...)
|
||||
}
|
||||
validations = append(validations, program)
|
||||
}
|
||||
}
|
||||
return &CompiledPolicy{
|
||||
failurePolicy: policy.GetFailurePolicy(),
|
||||
matchConditions: matchConditions,
|
||||
variables: variables,
|
||||
validations: validations,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func compileValidation(path *field.Path, rule admissionregistrationv1.Validation, env *cel.Env) (cel.Program, field.ErrorList) {
|
||||
var allErrs field.ErrorList
|
||||
{
|
||||
path := path.Child("expression")
|
||||
ast, issues := env.Compile(rule.Expression)
|
||||
if err := issues.Err(); err != nil {
|
||||
return nil, append(allErrs, field.Invalid(path, rule.Expression, err.Error()))
|
||||
}
|
||||
if !ast.OutputType().IsExactType(types.BoolType) {
|
||||
msg := fmt.Sprintf("output is expected to be of type %s", types.BoolType.TypeName())
|
||||
return nil, append(allErrs, field.Invalid(path, rule.Expression, msg))
|
||||
}
|
||||
program, err := env.Program(ast)
|
||||
if err != nil {
|
||||
return nil, append(allErrs, field.Invalid(path, rule.Expression, err.Error()))
|
||||
}
|
||||
return program, nil
|
||||
}
|
||||
}
|
13
pkg/cel/policy/policy.go
Normal file
13
pkg/cel/policy/policy.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
)
|
||||
|
||||
type CompiledPolicy struct {
|
||||
failurePolicy admissionregistrationv1.FailurePolicyType
|
||||
matchConditions []cel.Program
|
||||
variables map[string]cel.Program
|
||||
validations []cel.Program
|
||||
}
|
67
pkg/cel/policy/variables.go
Normal file
67
pkg/cel/policy/variables.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
)
|
||||
|
||||
var (
|
||||
VariablesType = types.NewObjectType("kyverno.variables")
|
||||
variablesTypeType = types.NewTypeTypeWithParam(VariablesType)
|
||||
)
|
||||
|
||||
type variablesProvider struct {
|
||||
inner types.Provider
|
||||
fields map[string]*types.Type
|
||||
names []string
|
||||
}
|
||||
|
||||
func NewVariablesProvider(inner types.Provider) *variablesProvider {
|
||||
return &variablesProvider{
|
||||
inner: inner,
|
||||
fields: make(map[string]*types.Type),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *variablesProvider) RegisterField(name string, t *types.Type) {
|
||||
p.fields[name] = t
|
||||
p.names = append(p.names, name)
|
||||
}
|
||||
|
||||
func (p *variablesProvider) EnumValue(enumName string) ref.Val {
|
||||
return p.inner.EnumValue(enumName)
|
||||
}
|
||||
|
||||
func (p *variablesProvider) FindIdent(identName string) (ref.Val, bool) {
|
||||
return p.inner.FindIdent(identName)
|
||||
}
|
||||
|
||||
func (p *variablesProvider) FindStructType(structType string) (*types.Type, bool) {
|
||||
if structType == VariablesType.DeclaredTypeName() {
|
||||
return variablesTypeType, true
|
||||
}
|
||||
return p.inner.FindStructType(structType)
|
||||
}
|
||||
|
||||
func (p *variablesProvider) FindStructFieldNames(structType string) ([]string, bool) {
|
||||
if structType == VariablesType.DeclaredTypeName() {
|
||||
return p.names, true
|
||||
}
|
||||
return p.inner.FindStructFieldNames(structType)
|
||||
}
|
||||
|
||||
func (p *variablesProvider) FindStructFieldType(structType, fieldName string) (*types.FieldType, bool) {
|
||||
if structType == VariablesType.DeclaredTypeName() {
|
||||
if t, ok := p.fields[fieldName]; ok {
|
||||
return &types.FieldType{
|
||||
Type: t,
|
||||
}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
return p.inner.FindStructFieldType(structType, fieldName)
|
||||
}
|
||||
|
||||
func (p *variablesProvider) NewValue(structType string, fields map[string]ref.Val) ref.Val {
|
||||
return p.inner.NewValue(structType, fields)
|
||||
}
|
Loading…
Add table
Reference in a new issue