diff --git a/cmd/cli/kubectl-kyverno/commands/apply/command.go b/cmd/cli/kubectl-kyverno/commands/apply/command.go index 9bf84260f5..c277b8ea4b 100644 --- a/cmd/cli/kubectl-kyverno/commands/apply/command.go +++ b/cmd/cli/kubectl-kyverno/commands/apply/command.go @@ -321,22 +321,18 @@ func (c *ApplyCommandConfig) applyValidatingPolicies( ) ([]engineapi.EngineResponse, error) { ctx := context.TODO() compiler := celpolicy.NewCompiler() - policies := make([]celpolicy.CompiledPolicy, 0, len(vps)) - for _, vp := range vps { - policy, err := compiler.Compile(&vp) - if err != nil { - return nil, fmt.Errorf("failed to compile policy %s (%w)", vp.GetName(), err.ToAggregate()) - } - policies = append(policies, *policy) + provider, err := engine.NewProvider(compiler, vps...) + if err != nil { + return nil, err } - eng := engine.NewEngine() - var responses []engineapi.EngineResponse + eng := engine.NewEngine(provider) + responses := make([]engineapi.EngineResponse, 0) for _, resource := range resources { request := engine.EngineRequest{ Resource: resource, NamespaceLabels: namespaceSelectorMap, } - _, err := eng.Handle(ctx, request, policies...) + response, err := eng.Handle(ctx, request) if err != nil { if c.ContinueOnFail { fmt.Printf("failed to apply validating policies on resource %s (%v)\n", resource.GetName(), err) @@ -344,13 +340,15 @@ func (c *ApplyCommandConfig) applyValidatingPolicies( } return responses, fmt.Errorf("failed to apply validating policies on resource %s (%w)", resource.GetName(), err) } - // TODO - // processor := processor.ValidatingAdmissionPolicyProcessor{ - // PolicyReport: c.PolicyReport, - // Rc: rc, - // Client: dClient, - // } - // responses = append(responses, ers...) + // transform response into legacy engine responses + for _, r := range response.Policies { + responses = append(responses, engineapi.EngineResponse{ + Resource: *response.Resource, + PolicyResponse: engineapi.PolicyResponse{ + Rules: r.Rules, + }, + }.WithPolicy(engine.NewValidatingPolicy(r.Policy))) + } } return responses, nil } diff --git a/cmd/cli/kubectl-kyverno/commands/apply/command_test.go b/cmd/cli/kubectl-kyverno/commands/apply/command_test.go index 8b1fe453b7..2bd2e84404 100644 --- a/cmd/cli/kubectl-kyverno/commands/apply/command_test.go +++ b/cmd/cli/kubectl-kyverno/commands/apply/command_test.go @@ -389,6 +389,22 @@ func Test_Apply(t *testing.T) { }, }}, }, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/cli/test-validating-policy/check-deployment-labels/policy.yaml"}, + ResourcePaths: []string{"../../../../../test/cli/test-validating-policy/check-deployment-labels/deployment1.yaml"}, + PolicyReport: true, + }, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 1, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, + }, } compareSummary := func(expected policyreportv1alpha2.PolicyReportSummary, actual policyreportv1alpha2.PolicyReportSummary, desc string) { diff --git a/pkg/cel/engine/engine.go b/pkg/cel/engine/engine.go index 72965066c8..c94894589a 100644 --- a/pkg/cel/engine/engine.go +++ b/pkg/cel/engine/engine.go @@ -3,7 +3,13 @@ package engine import ( "context" + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1" "github.com/kyverno/kyverno/pkg/cel/policy" + engineapi "github.com/kyverno/kyverno/pkg/engine/api" + "github.com/kyverno/kyverno/pkg/engine/handlers" + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -12,26 +18,112 @@ type EngineRequest struct { NamespaceLabels map[string]map[string]string } -type EngineResponse struct{} +type EngineResponse struct { + Resource *unstructured.Unstructured + Policies []PolicyResponse +} + +type PolicyResponse struct { + Policy kyvernov2alpha1.ValidatingPolicy + Rules []engineapi.RuleResponse +} + +type ValidatingPolicy struct { + policy kyvernov2alpha1.ValidatingPolicy +} + +func (p *ValidatingPolicy) AsKyvernoPolicy() kyvernov1.PolicyInterface { + return nil +} + +func (p *ValidatingPolicy) AsValidatingAdmissionPolicy() *admissionregistrationv1beta1.ValidatingAdmissionPolicy { + return nil +} + +func (p *ValidatingPolicy) GetType() engineapi.PolicyType { + return engineapi.ValidatingAdmissionPolicyType +} + +func (p *ValidatingPolicy) GetAPIVersion() string { + return "admissionregistration.k8s.io/v1beta1" +} + +func (p *ValidatingPolicy) GetName() string { + return p.policy.GetName() +} + +func (p *ValidatingPolicy) GetNamespace() string { + return p.policy.GetNamespace() +} + +func (p *ValidatingPolicy) GetKind() string { + return "ValidatingAdmissionPolicy" +} + +func (p *ValidatingPolicy) GetResourceVersion() string { + return p.policy.GetResourceVersion() +} + +func (p *ValidatingPolicy) GetAnnotations() map[string]string { + return p.policy.GetAnnotations() +} + +func (p *ValidatingPolicy) IsNamespaced() bool { + return false +} + +func (p *ValidatingPolicy) MetaObject() metav1.Object { + return &p.policy +} + +func NewValidatingPolicy(pol kyvernov2alpha1.ValidatingPolicy) engineapi.GenericPolicy { + return &ValidatingPolicy{ + policy: pol, + } +} type Engine interface { Handle(context.Context, EngineRequest, ...policy.CompiledPolicy) (EngineResponse, error) } -type engine struct{} - -func NewEngine() *engine { - return &engine{} +type engine struct { + provider Provider } -func (e *engine) Handle(ctx context.Context, request EngineRequest, policies ...policy.CompiledPolicy) (EngineResponse, error) { - var response EngineResponse +func NewEngine(provider Provider) *engine { + return &engine{ + provider: provider, + } +} + +func (e *engine) Handle(ctx context.Context, request EngineRequest) (EngineResponse, error) { + response := EngineResponse{ + Resource: request.Resource, + } + policies, err := e.provider.CompiledPolicies(ctx) + if err != nil { + return response, err + } for _, policy := range policies { - // TODO - _, err := policy.Evaluate(ctx, request.Resource, request.NamespaceLabels) - if err != nil { - return response, nil - } + response.Policies = append(response.Policies, e.handlePolicy(ctx, policy, request)) } return response, nil } + +func (e *engine) handlePolicy(ctx context.Context, policy policy.CompiledPolicy, request EngineRequest) PolicyResponse { + var rules []engineapi.RuleResponse + ok, err := policy.Evaluate(ctx, request.Resource, request.NamespaceLabels) + // TODO: evaluation should be per rule + if err != nil { + rules = handlers.WithResponses(engineapi.RuleError("todo", engineapi.Validation, "failed to load context", err, nil)) + } else if ok { + rules = handlers.WithResponses(engineapi.RulePass("todo", engineapi.Validation, "success", nil)) + } else { + rules = handlers.WithResponses(engineapi.RuleFail("todo", engineapi.Validation, "failure", nil)) + } + return PolicyResponse{ + // TODO + Policy: kyvernov2alpha1.ValidatingPolicy{}, + Rules: rules, + } +} diff --git a/pkg/cel/engine/provider.go b/pkg/cel/engine/provider.go index 07a20133e3..d49afe245c 100644 --- a/pkg/cel/engine/provider.go +++ b/pkg/cel/engine/provider.go @@ -2,10 +2,33 @@ package engine import ( "context" + "fmt" + kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1" "github.com/kyverno/kyverno/pkg/cel/policy" ) type Provider interface { CompiledPolicies(context.Context) ([]policy.CompiledPolicy, error) } + +type ProviderFunc func(context.Context) ([]policy.CompiledPolicy, error) + +func (f ProviderFunc) CompiledPolicies(ctx context.Context) ([]policy.CompiledPolicy, error) { + return f(ctx) +} + +func NewProvider(compiler policy.Compiler, policies ...kyvernov2alpha1.ValidatingPolicy) (ProviderFunc, error) { + compiled := make([]policy.CompiledPolicy, 0, len(policies)) + for _, vp := range policies { + policy, err := compiler.Compile(&vp) + if err != nil { + return nil, fmt.Errorf("failed to compile policy %s (%w)", vp.GetName(), err.ToAggregate()) + } + compiled = append(compiled, *policy) + } + provider := func(context.Context) ([]policy.CompiledPolicy, error) { + return compiled, nil + } + return provider, nil +} diff --git a/test/cli/test-validating-policy/check-deployment-labels/deployment1.yaml b/test/cli/test-validating-policy/check-deployment-labels/deployment1.yaml new file mode 100644 index 0000000000..432cfa9dda --- /dev/null +++ b/test/cli/test-validating-policy/check-deployment-labels/deployment1.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx + env: prod +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:latest diff --git a/test/cli/test-validating-policy/check-deployment-labels/deployment2.yaml b/test/cli/test-validating-policy/check-deployment-labels/deployment2.yaml new file mode 100644 index 0000000000..2ec8788465 --- /dev/null +++ b/test/cli/test-validating-policy/check-deployment-labels/deployment2.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx + env: testing +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:latest diff --git a/test/cli/test-validating-policy/check-deployment-labels/policy.yaml b/test/cli/test-validating-policy/check-deployment-labels/policy.yaml new file mode 100644 index 0000000000..6d269a64ae --- /dev/null +++ b/test/cli/test-validating-policy/check-deployment-labels/policy.yaml @@ -0,0 +1,17 @@ +apiVersion: kyverno.io/v2alpha1 +kind: ValidatingPolicy +metadata: + name: chech-deployment-labels +spec: + matchConstraints: + resourceRules: + - apiGroups: ["apps"] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["deployments"] + variables: + - name: environment + expression: "has(object.metadata.labels) && 'env' in object.metadata.labels && object.metadata.labels['env'] == 'prod'" + validations: + - expression: "variables.environment == true" + message: "Deployment labels must be env=prod"