2023-02-02 10:58:34 +00:00
package engine
import (
"context"
2023-03-27 11:22:54 +00:00
"fmt"
2023-03-30 11:59:32 +00:00
"time"
2023-02-02 10:58:34 +00:00
2023-03-27 11:22:54 +00:00
"github.com/go-logr/logr"
2023-07-07 10:22:26 +00:00
gojmespath "github.com/kyverno/go-jmespath"
2023-02-08 05:55:03 +00:00
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
2023-02-02 10:58:34 +00:00
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
2023-02-08 05:55:03 +00:00
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
2023-03-24 16:24:00 +00:00
"github.com/kyverno/kyverno/pkg/engine/handlers"
2023-02-09 15:15:51 +00:00
"github.com/kyverno/kyverno/pkg/engine/internal"
2023-04-13 11:29:40 +00:00
"github.com/kyverno/kyverno/pkg/engine/jmespath"
2023-03-27 11:22:54 +00:00
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
2023-08-06 19:54:52 +00:00
"github.com/kyverno/kyverno/pkg/imageverifycache"
2023-02-09 15:15:51 +00:00
"github.com/kyverno/kyverno/pkg/logging"
2023-04-04 15:07:43 +00:00
"github.com/kyverno/kyverno/pkg/metrics"
2023-03-27 11:22:54 +00:00
"github.com/kyverno/kyverno/pkg/tracing"
2023-05-08 07:34:23 +00:00
stringutils "github.com/kyverno/kyverno/pkg/utils/strings"
2023-06-19 09:09:08 +00:00
"go.opentelemetry.io/otel"
2023-05-11 10:16:48 +00:00
"go.opentelemetry.io/otel/metric"
2023-03-27 11:22:54 +00:00
"go.opentelemetry.io/otel/trace"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2023-02-02 10:58:34 +00:00
)
2023-02-03 05:01:11 +00:00
type engine struct {
2024-02-08 12:10:29 +00:00
configuration config . Configuration
metricsConfiguration config . MetricsConfiguration
jp jmespath . Interface
client engineapi . Client
rclientFactory engineapi . RegistryClientFactory
ivCache imageverifycache . Client
contextLoader engineapi . ContextLoaderFactory
exceptionSelector engineapi . PolicyExceptionSelector
2023-04-04 15:07:43 +00:00
// metrics
2023-05-11 10:16:48 +00:00
resultCounter metric . Int64Counter
durationHistogram metric . Float64Histogram
2023-02-03 05:01:11 +00:00
}
2023-02-02 10:58:34 +00:00
2023-04-03 19:58:58 +00:00
type handlerFactory = func ( ) ( handlers . Handler , error )
2023-02-03 05:01:11 +00:00
func NewEngine (
configuration config . Configuration ,
2023-04-04 15:07:43 +00:00
metricsConfiguration config . MetricsConfiguration ,
2023-04-13 11:29:40 +00:00
jp jmespath . Interface ,
2023-06-10 09:20:34 +00:00
client engineapi . Client ,
2023-06-16 13:37:08 +00:00
rclientFactory engineapi . RegistryClientFactory ,
2023-08-06 19:54:52 +00:00
ivCache imageverifycache . Client ,
2023-02-03 05:01:11 +00:00
contextLoader engineapi . ContextLoaderFactory ,
2023-02-06 05:49:47 +00:00
exceptionSelector engineapi . PolicyExceptionSelector ,
2023-02-03 05:01:11 +00:00
) engineapi . Engine {
2023-06-19 09:09:08 +00:00
meter := otel . GetMeterProvider ( ) . Meter ( metrics . MeterName )
2023-04-04 15:07:43 +00:00
resultCounter , err := meter . Int64Counter (
"kyverno_policy_results" ,
2023-05-11 10:16:48 +00:00
metric . WithDescription ( "can be used to track the results associated with the policies applied in the user's cluster, at the level from rule to policy to admission requests" ) ,
2023-04-04 15:07:43 +00:00
)
if err != nil {
logging . Error ( err , "failed to register metric kyverno_policy_results" )
}
2023-04-04 16:34:58 +00:00
durationHistogram , err := meter . Float64Histogram (
"kyverno_policy_execution_duration_seconds" ,
2023-05-11 10:16:48 +00:00
metric . WithDescription ( "can be used to track the latencies (in seconds) associated with the execution/processing of the individual rules under Kyverno policies whenever they evaluate incoming resource requests" ) ,
2023-04-04 16:34:58 +00:00
)
if err != nil {
logging . Error ( err , "failed to register metric kyverno_policy_execution_duration_seconds" )
}
2023-03-28 05:47:53 +00:00
return & engine {
2024-02-08 12:10:29 +00:00
configuration : configuration ,
metricsConfiguration : metricsConfiguration ,
jp : jp ,
client : client ,
rclientFactory : rclientFactory ,
ivCache : ivCache ,
contextLoader : contextLoader ,
exceptionSelector : exceptionSelector ,
resultCounter : resultCounter ,
durationHistogram : durationHistogram ,
2023-02-03 05:01:11 +00:00
}
2023-02-02 10:58:34 +00:00
}
func ( e * engine ) Validate (
ctx context . Context ,
policyContext engineapi . PolicyContext ,
2023-03-22 14:55:00 +00:00
) engineapi . EngineResponse {
2023-04-05 17:07:04 +00:00
startTime := time . Now ( )
response := engineapi . NewEngineResponseFromPolicyContext ( policyContext )
2023-02-09 15:15:51 +00:00
logger := internal . LoggerWithPolicyContext ( logging . WithName ( "engine.validate" ) , policyContext )
2024-02-02 08:32:28 +00:00
if internal . MatchPolicyContext ( logger , e . client , policyContext , e . configuration ) {
2023-03-30 11:59:32 +00:00
policyResponse := e . validate ( ctx , logger , policyContext )
response = response . WithPolicyResponse ( policyResponse )
2023-03-23 16:03:40 +00:00
}
2023-04-05 17:07:04 +00:00
response = response . WithStats ( engineapi . NewExecutionStats ( startTime , time . Now ( ) ) )
2023-04-04 15:07:43 +00:00
e . reportMetrics ( ctx , logger , policyContext . Operation ( ) , policyContext . AdmissionOperation ( ) , response )
return response
2023-02-02 10:58:34 +00:00
}
func ( e * engine ) Mutate (
ctx context . Context ,
policyContext engineapi . PolicyContext ,
2023-03-22 14:55:00 +00:00
) engineapi . EngineResponse {
2023-04-05 17:07:04 +00:00
startTime := time . Now ( )
response := engineapi . NewEngineResponseFromPolicyContext ( policyContext )
2023-02-09 15:15:51 +00:00
logger := internal . LoggerWithPolicyContext ( logging . WithName ( "engine.mutate" ) , policyContext )
2024-02-02 08:32:28 +00:00
if internal . MatchPolicyContext ( logger , e . client , policyContext , e . configuration ) {
2023-03-30 11:59:32 +00:00
policyResponse , patchedResource := e . mutate ( ctx , logger , policyContext )
response = response .
WithPolicyResponse ( policyResponse ) .
WithPatchedResource ( patchedResource )
2023-03-23 16:03:40 +00:00
}
2023-04-05 17:07:04 +00:00
response = response . WithStats ( engineapi . NewExecutionStats ( startTime , time . Now ( ) ) )
2023-04-04 15:07:43 +00:00
e . reportMetrics ( ctx , logger , policyContext . Operation ( ) , policyContext . AdmissionOperation ( ) , response )
return response
2023-02-02 10:58:34 +00:00
}
2023-04-03 04:57:48 +00:00
func ( e * engine ) Generate (
ctx context . Context ,
policyContext engineapi . PolicyContext ,
) engineapi . EngineResponse {
2023-04-05 17:07:04 +00:00
startTime := time . Now ( )
response := engineapi . NewEngineResponseFromPolicyContext ( policyContext )
2023-04-03 04:57:48 +00:00
logger := internal . LoggerWithPolicyContext ( logging . WithName ( "engine.generate" ) , policyContext )
2024-02-02 08:32:28 +00:00
if internal . MatchPolicyContext ( logger , e . client , policyContext , e . configuration ) {
2023-04-03 04:57:48 +00:00
policyResponse := e . generateResponse ( ctx , logger , policyContext )
response = response . WithPolicyResponse ( policyResponse )
}
2023-04-05 17:07:04 +00:00
response = response . WithStats ( engineapi . NewExecutionStats ( startTime , time . Now ( ) ) )
2023-04-04 15:07:43 +00:00
e . reportMetrics ( ctx , logger , policyContext . Operation ( ) , policyContext . AdmissionOperation ( ) , response )
return response
2023-04-03 04:57:48 +00:00
}
2023-02-02 10:58:34 +00:00
func ( e * engine ) VerifyAndPatchImages (
ctx context . Context ,
policyContext engineapi . PolicyContext ,
2023-03-23 12:58:52 +00:00
) ( engineapi . EngineResponse , engineapi . ImageVerificationMetadata ) {
2023-04-05 17:07:04 +00:00
startTime := time . Now ( )
response := engineapi . NewEngineResponseFromPolicyContext ( policyContext )
2023-03-30 11:59:32 +00:00
ivm := engineapi . ImageVerificationMetadata { }
2023-02-09 15:15:51 +00:00
logger := internal . LoggerWithPolicyContext ( logging . WithName ( "engine.verify" ) , policyContext )
2024-02-02 08:32:28 +00:00
if internal . MatchPolicyContext ( logger , e . client , policyContext , e . configuration ) {
2023-06-05 12:33:23 +00:00
policyResponse , patchedResource , innerIvm := e . verifyAndPatchImages ( ctx , logger , policyContext )
response , ivm = response .
WithPolicyResponse ( policyResponse ) .
WithPatchedResource ( patchedResource ) , innerIvm
2023-03-23 16:03:40 +00:00
}
2023-04-05 17:07:04 +00:00
response = response . WithStats ( engineapi . NewExecutionStats ( startTime , time . Now ( ) ) )
2023-04-04 15:07:43 +00:00
e . reportMetrics ( ctx , logger , policyContext . Operation ( ) , policyContext . AdmissionOperation ( ) , response )
return response , ivm
2023-02-03 05:01:11 +00:00
}
func ( e * engine ) ApplyBackgroundChecks (
2023-02-08 05:55:03 +00:00
ctx context . Context ,
2023-02-03 05:01:11 +00:00
policyContext engineapi . PolicyContext ,
2023-03-22 14:55:00 +00:00
) engineapi . EngineResponse {
2023-04-05 17:07:04 +00:00
startTime := time . Now ( )
response := engineapi . NewEngineResponseFromPolicyContext ( policyContext )
2023-02-09 15:15:51 +00:00
logger := internal . LoggerWithPolicyContext ( logging . WithName ( "engine.background" ) , policyContext )
2024-02-02 08:32:28 +00:00
if internal . MatchPolicyContext ( logger , e . client , policyContext , e . configuration ) {
2023-03-30 11:59:32 +00:00
policyResponse := e . applyBackgroundChecks ( ctx , logger , policyContext )
response = response . WithPolicyResponse ( policyResponse )
2023-03-23 16:03:40 +00:00
}
2023-04-05 17:07:04 +00:00
response = response . WithStats ( engineapi . NewExecutionStats ( startTime , time . Now ( ) ) )
2023-04-04 15:07:43 +00:00
e . reportMetrics ( ctx , logger , policyContext . Operation ( ) , policyContext . AdmissionOperation ( ) , response )
return response
2023-02-03 05:01:11 +00:00
}
func ( e * engine ) ContextLoader (
2023-02-08 05:55:03 +00:00
policy kyvernov1 . PolicyInterface ,
rule kyvernov1 . Rule ,
) engineapi . EngineContextLoader {
2023-04-03 19:58:58 +00:00
loader := e . contextLoader ( policy , rule )
return func ( ctx context . Context , contextEntries [ ] kyvernov1 . ContextEntry , jsonContext enginecontext . Interface ) error {
return loader . Load (
ctx ,
2023-04-13 11:29:40 +00:00
e . jp ,
2023-04-03 19:58:58 +00:00
e . client ,
2023-06-16 13:37:08 +00:00
e . rclientFactory ,
2023-04-03 19:58:58 +00:00
contextEntries ,
jsonContext ,
)
}
2023-03-28 05:47:53 +00:00
}
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
2023-06-05 11:47:46 +00:00
func ( e * engine ) matches (
2023-03-28 05:47:53 +00:00
rule kyvernov1 . Rule ,
policyContext engineapi . PolicyContext ,
resource unstructured . Unstructured ,
) error {
2023-06-05 11:47:46 +00:00
if policyContext . AdmissionOperation ( ) {
request := policyContext . AdmissionInfo ( )
if e . configuration . IsExcluded ( request . AdmissionUserInfo . Username , request . AdmissionUserInfo . Groups , request . Roles , request . ClusterRoles ) {
return fmt . Errorf ( "excluded by configuration" )
}
}
2023-03-28 05:47:53 +00:00
gvk , subresource := policyContext . ResourceKind ( )
err := engineutils . MatchesResourceDescription (
resource ,
rule ,
policyContext . AdmissionInfo ( ) ,
policyContext . NamespaceLabels ( ) ,
policyContext . Policy ( ) . GetNamespace ( ) ,
gvk ,
subresource ,
2023-03-29 04:22:21 +00:00
policyContext . Operation ( ) ,
2023-03-28 05:47:53 +00:00
)
if err == nil {
return nil
}
oldResource := policyContext . OldResource ( )
2023-05-18 21:57:57 +00:00
if resource . Object == nil && oldResource . Object != nil {
2023-03-28 05:47:53 +00:00
err := engineutils . MatchesResourceDescription (
policyContext . OldResource ( ) ,
rule ,
policyContext . AdmissionInfo ( ) ,
policyContext . NamespaceLabels ( ) ,
policyContext . Policy ( ) . GetNamespace ( ) ,
gvk ,
subresource ,
2023-03-29 04:22:21 +00:00
policyContext . Operation ( ) ,
2023-02-08 05:55:03 +00:00
)
2023-03-28 05:47:53 +00:00
if err == nil {
return nil
}
2023-02-08 05:55:03 +00:00
}
2023-03-28 05:47:53 +00:00
return err
2023-02-02 10:58:34 +00:00
}
2023-03-27 11:22:54 +00:00
func ( e * engine ) invokeRuleHandler (
ctx context . Context ,
logger logr . Logger ,
2023-04-03 19:58:58 +00:00
handlerFactory handlerFactory ,
2023-03-27 11:22:54 +00:00
policyContext engineapi . PolicyContext ,
resource unstructured . Unstructured ,
rule kyvernov1 . Rule ,
2023-03-28 05:47:53 +00:00
ruleType engineapi . RuleType ,
2023-03-27 11:22:54 +00:00
) ( unstructured . Unstructured , [ ] engineapi . RuleResponse ) {
return tracing . ChildSpan2 (
ctx ,
"pkg/engine" ,
fmt . Sprintf ( "RULE %s" , rule . Name ) ,
2023-06-27 16:58:50 +00:00
func ( ctx context . Context , span trace . Span ) ( patchedResource unstructured . Unstructured , results [ ] engineapi . RuleResponse ) {
2023-03-27 11:22:54 +00:00
// check if resource and rule match
2023-06-05 11:47:46 +00:00
if err := e . matches ( rule , policyContext , resource ) ; err != nil {
2023-03-27 11:22:54 +00:00
logger . V ( 4 ) . Info ( "rule not matched" , "reason" , err . Error ( ) )
return resource , nil
}
2023-04-03 19:58:58 +00:00
if handlerFactory == nil {
2023-04-05 10:35:38 +00:00
return resource , handlers . WithError ( rule , ruleType , "failed to instantiate handler" , nil )
2023-04-03 19:58:58 +00:00
} else if handler , err := handlerFactory ( ) ; err != nil {
2023-04-05 10:35:38 +00:00
return resource , handlers . WithError ( rule , ruleType , "failed to instantiate handler" , err )
2023-04-03 19:58:58 +00:00
} else if handler != nil {
2023-06-27 16:58:50 +00:00
policyContext . JSONContext ( ) . Checkpoint ( )
defer func ( ) {
policyContext . JSONContext ( ) . Restore ( )
if patchedResource . Object != nil {
if err := policyContext . JSONContext ( ) . AddResource ( patchedResource . Object ) ; err != nil {
logger . Error ( err , "failed to add resource in the json context" )
}
}
} ( )
2023-04-03 19:58:58 +00:00
// load rule context
contextLoader := e . ContextLoader ( policyContext . Policy ( ) , rule )
if err := contextLoader ( ctx , rule . Context , policyContext . JSONContext ( ) ) ; err != nil {
if _ , ok := err . ( gojmespath . NotFoundError ) ; ok {
logger . V ( 3 ) . Info ( "failed to load context" , "reason" , err . Error ( ) )
} else {
logger . Error ( err , "failed to load context" )
}
2023-04-05 10:35:38 +00:00
return resource , handlers . WithError ( rule , ruleType , "failed to load context" , err )
2023-04-03 19:58:58 +00:00
}
// check preconditions
2023-05-08 07:34:23 +00:00
preconditionsPassed , msg , err := internal . CheckPreconditions ( logger , policyContext . JSONContext ( ) , rule . GetAnyAllConditions ( ) )
2023-04-03 19:58:58 +00:00
if err != nil {
2023-04-05 10:35:38 +00:00
return resource , handlers . WithError ( rule , ruleType , "failed to evaluate preconditions" , err )
2023-04-03 19:58:58 +00:00
}
if ! preconditionsPassed {
2023-05-08 07:34:23 +00:00
s := stringutils . JoinNonEmpty ( [ ] string { "preconditions not met" , msg } , "; " )
return resource , handlers . WithSkip ( rule , ruleType , s )
2023-04-03 19:58:58 +00:00
}
2023-11-13 15:43:25 +00:00
// get policy exceptions that matches both policy and rule name
exceptions , err := e . GetPolicyExceptions ( policyContext . Policy ( ) , rule . Name )
if err != nil {
logger . Error ( err , "failed to get exceptions" )
return resource , nil
2023-09-27 14:52:39 +00:00
}
2023-11-13 15:43:25 +00:00
// process handler
resource , ruleResponses := handler . Process ( ctx , logger , policyContext , resource , rule , contextLoader , exceptions )
2023-09-27 14:52:39 +00:00
return resource , ruleResponses
2023-03-27 11:22:54 +00:00
}
2023-04-03 19:58:58 +00:00
return resource , nil
2023-03-27 11:22:54 +00:00
} ,
)
}