2025-01-17 10:53:17 +01:00
|
|
|
package engine
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2025-01-21 17:41:45 +01:00
|
|
|
"fmt"
|
2025-01-17 10:53:17 +01:00
|
|
|
|
2025-01-20 10:43:05 +01:00
|
|
|
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
|
2025-01-24 16:42:58 +01:00
|
|
|
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
|
2025-01-30 00:07:01 +01:00
|
|
|
"github.com/kyverno/kyverno/pkg/cel/matching"
|
2025-01-21 17:41:45 +01:00
|
|
|
"github.com/kyverno/kyverno/pkg/cel/utils"
|
2025-01-20 10:43:05 +01:00
|
|
|
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
|
|
|
"github.com/kyverno/kyverno/pkg/engine/handlers"
|
2025-01-31 09:03:59 +01:00
|
|
|
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
|
|
|
admissionv1 "k8s.io/api/admission/v1"
|
2025-02-04 05:29:40 +01:00
|
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
2025-01-20 12:43:13 +01:00
|
|
|
corev1 "k8s.io/api/core/v1"
|
2025-01-17 10:53:17 +01:00
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
2025-01-31 00:58:12 +01:00
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
2025-01-30 21:36:41 +01:00
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
2025-02-04 05:29:40 +01:00
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
2025-01-30 21:36:41 +01:00
|
|
|
"k8s.io/apiserver/pkg/admission"
|
2025-01-17 10:53:17 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type EngineRequest struct {
|
2025-01-31 09:03:59 +01:00
|
|
|
Request *admissionv1.AdmissionRequest
|
2025-01-20 12:43:13 +01:00
|
|
|
Resource *unstructured.Unstructured
|
2025-01-24 16:42:58 +01:00
|
|
|
Context contextlib.ContextInterface
|
2025-01-17 10:53:17 +01:00
|
|
|
}
|
|
|
|
|
2025-01-20 10:43:05 +01:00
|
|
|
type EngineResponse struct {
|
|
|
|
Resource *unstructured.Unstructured
|
|
|
|
Policies []PolicyResponse
|
|
|
|
}
|
|
|
|
|
|
|
|
type PolicyResponse struct {
|
2025-02-04 05:29:40 +01:00
|
|
|
Actions sets.Set[admissionregistrationv1.ValidationAction]
|
|
|
|
Policy kyvernov2alpha1.ValidatingPolicy
|
|
|
|
Rules []engineapi.RuleResponse
|
2025-01-20 10:43:05 +01:00
|
|
|
}
|
|
|
|
|
2025-01-17 10:53:17 +01:00
|
|
|
type Engine interface {
|
2025-01-24 16:42:58 +01:00
|
|
|
Handle(context.Context, EngineRequest) (EngineResponse, error)
|
2025-01-17 10:53:17 +01:00
|
|
|
}
|
|
|
|
|
2025-01-20 12:43:13 +01:00
|
|
|
type NamespaceResolver = func(string) *corev1.Namespace
|
|
|
|
|
2025-01-20 10:43:05 +01:00
|
|
|
type engine struct {
|
2025-01-20 12:43:13 +01:00
|
|
|
provider Provider
|
2025-01-30 00:07:01 +01:00
|
|
|
nsResolver NamespaceResolver
|
|
|
|
matcher matching.Matcher
|
2025-01-20 10:43:05 +01:00
|
|
|
}
|
2025-01-17 10:53:17 +01:00
|
|
|
|
2025-01-30 00:07:01 +01:00
|
|
|
func NewEngine(provider Provider, nsResolver NamespaceResolver, matcher matching.Matcher) Engine {
|
2025-01-20 10:43:05 +01:00
|
|
|
return &engine{
|
2025-01-20 12:43:13 +01:00
|
|
|
provider: provider,
|
2025-01-30 00:07:01 +01:00
|
|
|
nsResolver: nsResolver,
|
|
|
|
matcher: matcher,
|
2025-01-20 10:43:05 +01:00
|
|
|
}
|
2025-01-17 10:53:17 +01:00
|
|
|
}
|
|
|
|
|
2025-01-20 10:43:05 +01:00
|
|
|
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
|
|
|
|
}
|
2025-01-20 12:43:13 +01:00
|
|
|
// resolve namespace
|
2025-01-31 00:58:12 +01:00
|
|
|
var namespace runtime.Object
|
2025-01-31 09:03:59 +01:00
|
|
|
var attr admission.Attributes
|
|
|
|
if request.Request != nil {
|
|
|
|
object, oldObject, err := admissionutils.ExtractResources(nil, *request.Request)
|
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
dryRun := false
|
|
|
|
if request.Request.DryRun != nil {
|
|
|
|
dryRun = *request.Request.DryRun
|
|
|
|
}
|
|
|
|
attr = admission.NewAttributesRecord(
|
|
|
|
&object,
|
|
|
|
&oldObject,
|
|
|
|
schema.GroupVersionKind(request.Request.Kind),
|
|
|
|
request.Request.Namespace,
|
|
|
|
request.Request.Name,
|
|
|
|
schema.GroupVersionResource(request.Request.Resource),
|
|
|
|
request.Request.SubResource,
|
|
|
|
admission.Operation(request.Request.Operation),
|
|
|
|
nil,
|
|
|
|
dryRun,
|
|
|
|
// TODO
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
if ns := request.Request.Namespace; ns != "" {
|
|
|
|
namespace = e.nsResolver(ns)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
attr = admission.NewAttributesRecord(
|
|
|
|
request.Resource,
|
|
|
|
nil,
|
|
|
|
request.Resource.GroupVersionKind(),
|
|
|
|
request.Resource.GetNamespace(),
|
|
|
|
request.Resource.GetName(),
|
|
|
|
schema.GroupVersionResource{},
|
|
|
|
"",
|
|
|
|
admission.Create,
|
|
|
|
nil,
|
|
|
|
false,
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
if ns := request.Resource.GetNamespace(); ns != "" {
|
|
|
|
namespace = e.nsResolver(ns)
|
|
|
|
}
|
2025-01-20 12:43:13 +01:00
|
|
|
}
|
2025-01-17 10:53:17 +01:00
|
|
|
for _, policy := range policies {
|
2025-02-03 12:40:05 +01:00
|
|
|
response.Policies = append(response.Policies, e.handlePolicy(ctx, policy, attr, request.Request, namespace, request.Context))
|
2025-01-17 10:53:17 +01:00
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
2025-01-20 10:43:05 +01:00
|
|
|
|
2025-02-03 12:40:05 +01:00
|
|
|
func (e *engine) handlePolicy(ctx context.Context, policy CompiledPolicy, attr admission.Attributes, request *admissionv1.AdmissionRequest, namespace runtime.Object, context contextlib.ContextInterface) PolicyResponse {
|
2025-01-30 00:07:01 +01:00
|
|
|
response := PolicyResponse{
|
2025-02-04 05:29:40 +01:00
|
|
|
Actions: policy.Actions,
|
|
|
|
Policy: policy.Policy,
|
2025-01-30 00:07:01 +01:00
|
|
|
}
|
|
|
|
if e.matcher != nil {
|
|
|
|
criteria := matchCriteria{constraints: policy.Policy.Spec.MatchConstraints}
|
2025-01-31 12:07:22 +01:00
|
|
|
if matches, err := e.matcher.Match(&criteria, attr, namespace); err != nil {
|
|
|
|
response.Rules = handlers.WithResponses(engineapi.RuleError("match", engineapi.Validation, "failed to execute matching", err, nil))
|
|
|
|
return response
|
|
|
|
} else if !matches {
|
2025-01-30 00:07:01 +01:00
|
|
|
return response
|
|
|
|
}
|
|
|
|
}
|
2025-02-03 12:40:05 +01:00
|
|
|
results, err := policy.CompiledPolicy.Evaluate(ctx, attr, request, namespace, context)
|
2025-01-21 17:41:45 +01:00
|
|
|
// TODO: error is about match conditions here ?
|
2025-01-20 10:43:05 +01:00
|
|
|
if err != nil {
|
2025-01-30 00:07:01 +01:00
|
|
|
response.Rules = handlers.WithResponses(engineapi.RuleError("evaluation", engineapi.Validation, "failed to load context", err, nil))
|
2025-01-20 10:43:05 +01:00
|
|
|
} else {
|
2025-02-03 12:13:06 +01:00
|
|
|
for index, validationResult := range results {
|
2025-01-21 17:41:45 +01:00
|
|
|
ruleName := fmt.Sprintf("rule-%d", index)
|
2025-02-03 12:13:06 +01:00
|
|
|
if validationResult.Error != nil {
|
2025-01-30 00:07:01 +01:00
|
|
|
response.Rules = append(response.Rules, *engineapi.RuleError(ruleName, engineapi.Validation, "error", err, nil))
|
2025-02-03 12:13:06 +01:00
|
|
|
} else if result, err := utils.ConvertToNative[bool](validationResult.Result); err != nil {
|
2025-01-30 00:07:01 +01:00
|
|
|
response.Rules = append(response.Rules, *engineapi.RuleError(ruleName, engineapi.Validation, "conversion error", err, nil))
|
2025-01-21 17:41:45 +01:00
|
|
|
} else if result {
|
2025-01-30 00:07:01 +01:00
|
|
|
response.Rules = append(response.Rules, *engineapi.RulePass(ruleName, engineapi.Validation, "success", nil))
|
2025-01-21 17:41:45 +01:00
|
|
|
} else {
|
2025-02-03 12:13:06 +01:00
|
|
|
response.Rules = append(response.Rules, *engineapi.RuleFail(ruleName, engineapi.Validation, validationResult.Message, nil))
|
2025-01-21 17:41:45 +01:00
|
|
|
}
|
|
|
|
}
|
2025-01-20 10:43:05 +01:00
|
|
|
}
|
2025-01-30 00:07:01 +01:00
|
|
|
return response
|
2025-01-20 10:43:05 +01:00
|
|
|
}
|