2025-01-17 10:53:17 +01:00
package engine
import (
"context"
2025-02-19 10:38:44 +02:00
"strings"
2025-01-17 10:53:17 +01:00
2025-02-11 19:05:22 +02:00
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
2025-02-15 06:56:51 +02:00
vpolautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
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-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-02-06 04:49:41 +01:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/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-02-19 10:38:44 +02:00
"k8s.io/client-go/tools/cache"
2025-02-06 04:49:41 +01:00
"k8s.io/utils/ptr"
2025-01-17 10:53:17 +01:00
)
type EngineRequest struct {
2025-02-06 04:49:41 +01:00
request admissionv1 . AdmissionRequest
context contextlib . ContextInterface
}
func RequestFromAdmission ( context contextlib . ContextInterface , request admissionv1 . AdmissionRequest ) EngineRequest {
return EngineRequest {
request : request ,
context : context ,
}
}
func Request (
context contextlib . ContextInterface ,
gvk schema . GroupVersionKind ,
gvr schema . GroupVersionResource ,
subResource string ,
name string ,
namespace string ,
operation admissionv1 . Operation ,
// userInfo authenticationv1.UserInfo,
object runtime . Object ,
oldObject runtime . Object ,
dryRun bool ,
options runtime . Object ,
) EngineRequest {
request := admissionv1 . AdmissionRequest {
Kind : metav1 . GroupVersionKind ( gvk ) ,
Resource : metav1 . GroupVersionResource ( gvr ) ,
SubResource : subResource ,
RequestKind : ptr . To ( metav1 . GroupVersionKind ( gvk ) ) ,
RequestResource : ptr . To ( metav1 . GroupVersionResource ( gvr ) ) ,
RequestSubResource : subResource ,
Name : name ,
Namespace : namespace ,
Operation : operation ,
// UserInfo: userInfo,
Object : runtime . RawExtension { Object : object } ,
OldObject : runtime . RawExtension { Object : oldObject } ,
DryRun : & dryRun ,
Options : runtime . RawExtension { Object : options } ,
}
return RequestFromAdmission ( context , request )
}
func ( r * EngineRequest ) AdmissionRequest ( ) admissionv1 . AdmissionRequest {
return r . request
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 ]
2025-02-11 19:05:22 +02:00
Policy policiesv1alpha1 . ValidatingPolicy
2025-02-04 05:29:40 +01:00
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 ) {
2025-02-06 04:49:41 +01:00
var response EngineResponse
// fetch compiled policies
2025-01-20 10:43:05 +01:00
policies , err := e . provider . CompiledPolicies ( ctx )
if err != nil {
return response , err
}
2025-02-06 04:49:41 +01:00
// load objects
object , oldObject , err := admissionutils . ExtractResources ( nil , request . request )
if err != nil {
return response , err
}
response . Resource = & object
if response . Resource . Object == nil {
response . Resource = & oldObject
}
// default dry run
dryRun := false
if request . request . DryRun != nil {
dryRun = * request . request . DryRun
}
// create admission attributes
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 ,
)
2025-01-20 12:43:13 +01:00
// resolve namespace
2025-01-31 00:58:12 +01:00
var namespace runtime . Object
2025-02-06 04:49:41 +01:00
if ns := request . request . Namespace ; ns != "" {
namespace = e . nsResolver ( ns )
2025-01-20 12:43:13 +01:00
}
2025-02-06 04:49:41 +01:00
// evaluate policies
2025-01-17 10:53:17 +01:00
for _ , policy := range policies {
2025-02-06 04:49:41 +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-15 06:56:51 +02:00
func ( e * engine ) matchPolicy ( policy CompiledPolicy , attr admission . Attributes , namespace runtime . Object ) ( bool , int , error ) {
match := func ( constraints * admissionregistrationv1 . MatchResources ) ( bool , error ) {
criteria := matchCriteria { constraints : constraints }
matches , err := e . matcher . Match ( & criteria , attr , namespace )
if err != nil {
return false , err
}
return matches , nil
}
// match against main policy constraints
matches , err := match ( policy . Policy . Spec . MatchConstraints )
if err != nil {
return false , - 1 , err
}
if matches {
return true , - 1 , nil
}
// match against autogen rules
autogenRules := vpolautogen . ComputeRules ( & policy . Policy )
for i , autogenRule := range autogenRules {
matches , err := match ( autogenRule . MatchConstraints )
if err != nil {
return false , - 1 , err
}
if matches {
return true , i , nil
}
}
return false , - 1 , nil
}
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
}
2025-02-15 06:56:51 +02:00
autogenIndex := - 1
2025-01-30 00:07:01 +01:00
if e . matcher != nil {
2025-02-15 06:56:51 +02:00
matches , index , err := e . matchPolicy ( policy , attr , namespace )
if err != nil {
2025-01-31 12:07:22 +01:00
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-15 06:56:51 +02:00
autogenIndex = index
2025-01-30 00:07:01 +01:00
}
2025-02-19 14:10:01 +01:00
result , err := policy . CompiledPolicy . Evaluate ( ctx , attr , request , namespace , context , autogenIndex )
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-02-19 14:10:01 +01:00
} else if len ( result . Exceptions ) > 0 {
2025-02-19 10:38:44 +02:00
var keys [ ] string
2025-02-19 14:10:01 +01:00
for i := range result . Exceptions {
key , err := cache . MetaNamespaceKeyFunc ( & result . Exceptions [ i ] )
2025-02-19 10:38:44 +02:00
if err != nil {
response . Rules = handlers . WithResponses ( engineapi . RuleError ( "exception" , engineapi . Validation , "failed to compute exception key" , err , nil ) )
return response
}
keys = append ( keys , key )
}
2025-02-19 14:10:01 +01:00
response . Rules = handlers . WithResponses ( engineapi . RuleSkip ( "exception" , engineapi . Validation , "rule is skipped due to policy exception: " + strings . Join ( keys , ", " ) , nil ) . WithCELExceptions ( result . Exceptions ) )
2025-01-20 10:43:05 +01:00
} else {
2025-02-19 14:10:01 +01:00
// TODO: do we want to set a rule name?
ruleName := ""
if result . Error != nil {
response . Rules = append ( response . Rules , * engineapi . RuleError ( ruleName , engineapi . Validation , "error" , err , nil ) )
} else if result . Result {
response . Rules = append ( response . Rules , * engineapi . RulePass ( ruleName , engineapi . Validation , "success" , nil ) )
} else {
response . Rules = append ( response . Rules , * engineapi . RuleFail ( ruleName , engineapi . Validation , result . Message , result . AuditAnnotations ) )
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
}