2019-07-15 16:07:56 -07:00
package webhooks
import (
2021-03-16 00:34:21 +02:00
"errors"
2020-02-29 22:39:27 +05:30
"reflect"
"sort"
2020-01-16 11:57:28 -08:00
"time"
2021-05-15 19:15:04 +05:30
"github.com/go-logr/logr"
2020-10-07 11:12:31 -07:00
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
2021-02-04 02:39:42 +05:30
"github.com/kyverno/kyverno/pkg/common"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
2021-05-15 19:15:04 +05:30
"github.com/kyverno/kyverno/pkg/metrics"
2021-05-15 21:34:26 +05:30
policyRuleExecutionLatency "github.com/kyverno/kyverno/pkg/metrics/policyruleexecutionlatency"
2021-05-15 19:15:04 +05:30
policyRuleResults "github.com/kyverno/kyverno/pkg/metrics/policyruleresults"
2019-07-15 16:07:56 -07:00
v1beta1 "k8s.io/api/admission/v1beta1"
2020-01-06 19:24:24 -08:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-07-15 16:07:56 -07:00
)
// HandleMutation handles mutating webhook admission request
2021-05-15 23:33:41 +05:30
// return value: generated patches, triggered policies, engine responses correspdonding to the triggered policies
2020-05-17 14:37:05 -07:00
func ( ws * WebhookServer ) HandleMutation (
request * v1beta1 . AdmissionRequest ,
resource unstructured . Unstructured ,
2020-05-26 10:36:56 -07:00
policies [ ] * kyverno . ClusterPolicy ,
2020-05-18 12:32:42 -07:00
ctx * context . Context ,
2021-05-15 19:15:04 +05:30
userRequestInfo kyverno . RequestInfo ,
2021-05-15 23:33:41 +05:30
admissionRequestTimestamp int64 ) ( [ ] byte , [ ] kyverno . ClusterPolicy , [ ] * response . EngineResponse ) {
2020-05-17 14:37:05 -07:00
2020-07-02 12:49:10 -07:00
if len ( policies ) == 0 {
2021-05-15 23:33:41 +05:30
return nil , nil , nil
2020-07-02 12:49:10 -07:00
}
2020-05-17 14:37:05 -07:00
resourceName := request . Kind . Kind + "/" + request . Name
if request . Namespace != "" {
resourceName = request . Namespace + "/" + resourceName
}
2021-05-13 12:03:13 -07:00
logger := ws . log . WithValues ( "action" , "mutate" , "resource" , resourceName , "operation" , request . Operation , "gvk" , request . Kind . String ( ) )
2019-08-23 18:34:23 -07:00
2019-08-09 12:59:37 -07:00
var patches [ ] [ ] byte
2020-12-23 15:10:07 -08:00
var engineResponses [ ] * response . EngineResponse
2021-05-15 21:34:26 +05:30
var triggeredPolicies [ ] kyverno . ClusterPolicy
2020-12-23 15:10:07 -08:00
policyContext := & engine . PolicyContext {
2020-12-16 12:29:16 -08:00
NewResource : resource ,
AdmissionInfo : userRequestInfo ,
ExcludeGroupRole : ws . configHandler . GetExcludeGroupRole ( ) ,
ExcludeResourceFunc : ws . configHandler . ToFilter ,
ResourceCache : ws . resCache ,
JSONContext : ctx ,
2021-02-22 12:08:26 -08:00
Client : ws . client ,
2019-11-08 18:57:27 -08:00
}
2019-08-14 11:51:01 -07:00
2020-06-25 17:24:10 -07:00
if request . Operation == v1beta1 . Update {
// set OldResource to inform engine of operation type
policyContext . OldResource = resource
}
2019-11-08 18:57:27 -08:00
for _ , policy := range policies {
2020-05-15 14:49:34 -07:00
logger . V ( 3 ) . Info ( "evaluating policy" , "policy" , policy . Name )
2019-11-13 11:46:46 -08:00
2020-05-26 10:36:56 -07:00
policyContext . Policy = * policy
2021-02-04 02:39:42 +05:30
if request . Kind . Kind != "Namespace" && request . Namespace != "" {
policyContext . NamespaceLabels = common . GetNamespaceSelectorsFromNamespaceLister ( request . Kind . Kind , request . Namespace , ws . nsLister , logger )
}
2019-11-08 18:57:27 -08:00
engineResponse := engine . Mutate ( policyContext )
2020-11-30 11:22:20 -08:00
policyPatches := engineResponse . GetPatches ( )
if engineResponse . PolicyResponse . RulesAppliedCount > 0 && len ( policyPatches ) > 0 {
ws . statusListener . Update ( mutateStats { resp : engineResponse , namespace : policy . Namespace } )
}
2020-05-17 14:37:05 -07:00
2020-11-24 10:11:05 -08:00
if ! engineResponse . IsSuccessful ( ) && len ( engineResponse . GetFailedRules ( ) ) > 0 {
2021-03-16 00:34:21 +02:00
logger . Error ( errors . New ( "some rules failed" ) , "failed to apply policy" , "policy" , policy . Name , "failed rules" , engineResponse . GetFailedRules ( ) )
2019-08-09 12:59:37 -07:00
continue
2019-07-15 16:07:56 -07:00
}
2020-05-17 14:37:05 -07:00
2021-05-13 12:03:13 -07:00
err := ws . openAPIController . ValidateResource ( * engineResponse . PatchedResource . DeepCopy ( ) , engineResponse . PatchedResource . GetAPIVersion ( ) , engineResponse . PatchedResource . GetKind ( ) )
2020-03-04 19:27:08 +05:30
if err != nil {
2021-06-08 02:05:53 +05:30
logger . Info ( "validation error" , "policy" , policy . Name , "error" , err . Error ( ) )
2020-03-04 19:27:08 +05:30
continue
}
2020-05-17 14:37:05 -07:00
2020-06-30 11:53:27 -07:00
if len ( policyPatches ) > 0 {
patches = append ( patches , policyPatches ... )
rules := engineResponse . GetSuccessRules ( )
2020-07-09 11:48:34 -07:00
logger . Info ( "mutation rules from policy applied successfully" , "policy" , policy . Name , "rules" , rules )
2020-05-15 13:11:28 -07:00
}
2020-01-15 18:15:48 -08:00
policyContext . NewResource = engineResponse . PatchedResource
2020-06-01 19:37:48 -07:00
engineResponses = append ( engineResponses , engineResponse )
2021-05-15 19:15:04 +05:30
// registering the kyverno_policy_rule_results_info metric concurrently
go ws . registerPolicyRuleResultsMetricMutation ( logger , string ( request . Operation ) , * policy , * engineResponse , admissionRequestTimestamp )
2021-05-15 21:34:26 +05:30
triggeredPolicies = append ( triggeredPolicies , * policy )
// registering the kyverno_policy_rule_execution_latency_milliseconds metric concurrently
go ws . registerPolicyRuleExecutionLatencyMetricMutate ( logger , string ( request . Operation ) , * policy , * engineResponse , admissionRequestTimestamp )
2021-05-15 23:33:41 +05:30
triggeredPolicies = append ( triggeredPolicies , * policy )
2019-07-15 16:07:56 -07:00
}
2019-10-07 18:31:14 -07:00
// generate annotations
2020-03-17 11:05:20 -07:00
if annPatches := generateAnnotationPatches ( engineResponses , logger ) ; annPatches != nil {
2019-10-07 18:31:14 -07:00
patches = append ( patches , annPatches )
}
2020-02-19 19:24:34 -08:00
// REPORTING EVENTS
// Scenario 1:
2020-06-30 11:53:27 -07:00
// some/all policies failed to apply on the resource. a policy violation is generated.
2020-02-19 19:24:34 -08:00
// create an event on the resource and the policy that failed
// Scenario 2:
2020-06-30 11:53:27 -07:00
// all policies were applied successfully.
2020-02-19 19:24:34 -08:00
// create an event on the resource
2019-08-09 12:59:37 -07:00
// ADD EVENTS
2020-03-17 11:05:20 -07:00
events := generateEvents ( engineResponses , false , ( request . Operation == v1beta1 . Update ) , logger )
2019-08-26 13:34:42 -07:00
ws . eventGen . Add ( events ... )
2020-01-15 21:46:58 -08:00
// debug info
2020-01-16 11:57:28 -08:00
func ( ) {
if len ( patches ) != 0 {
2020-03-17 11:05:20 -07:00
logger . V ( 4 ) . Info ( "JSON patches generated" )
2020-01-16 11:57:28 -08:00
}
2020-01-15 21:46:58 -08:00
2020-01-16 11:57:28 -08:00
// if any of the policies fails, print out the error
2020-12-01 22:50:40 -08:00
if ! isResponseSuccessful ( engineResponses ) {
2021-03-16 00:34:21 +02:00
logger . Error ( errors . New ( getErrorMsg ( engineResponses ) ) , "failed to apply mutation rules on the resource, reporting policy violation" )
2020-01-16 11:57:28 -08:00
}
} ( )
// patches holds all the successful patches, if no patch is created, it returns nil
2021-05-15 23:33:41 +05:30
return engineutils . JoinPatches ( patches ) , triggeredPolicies , engineResponses
2019-07-15 16:07:56 -07:00
}
2020-02-29 22:39:27 +05:30
2021-05-15 19:15:04 +05:30
func ( ws * WebhookServer ) registerPolicyRuleResultsMetricMutation ( logger logr . Logger , resourceRequestOperation string , policy kyverno . ClusterPolicy , engineResponse response . EngineResponse , admissionRequestTimestamp int64 ) {
resourceRequestOperationPromAlias , err := policyRuleResults . ParseResourceRequestOperation ( resourceRequestOperation )
if err != nil {
logger . Error ( err , "error occurred while registering kyverno_policy_rule_results_info metrics for the above policy" , "name" , policy . Name )
}
if err := policyRuleResults . ParsePromMetrics ( * ws . promConfig . Metrics ) . ProcessEngineResponse ( policy , engineResponse , metrics . AdmissionRequest , resourceRequestOperationPromAlias , admissionRequestTimestamp ) ; err != nil {
logger . Error ( err , "error occurred while registering kyverno_policy_rule_results_info metrics for the above policy" , "name" , policy . Name )
}
}
2021-05-15 21:34:26 +05:30
func ( ws * WebhookServer ) registerPolicyRuleExecutionLatencyMetricMutate ( logger logr . Logger , resourceRequestOperation string , policy kyverno . ClusterPolicy , engineResponse response . EngineResponse , admissionRequestTimestamp int64 ) {
resourceRequestOperationPromAlias , err := policyRuleExecutionLatency . ParseResourceRequestOperation ( resourceRequestOperation )
if err != nil {
logger . Error ( err , "error occurred while registering kyverno_policy_rule_execution_latency_milliseconds metrics for the above policy" , "name" , policy . Name )
}
if err := policyRuleExecutionLatency . ParsePromMetrics ( * ws . promConfig . Metrics ) . ProcessEngineResponse ( policy , engineResponse , metrics . AdmissionRequest , "" , resourceRequestOperationPromAlias , admissionRequestTimestamp ) ; err != nil {
logger . Error ( err , "error occurred while registering kyverno_policy_rule_execution_latency_milliseconds metrics for the above policy" , "name" , policy . Name )
}
}
2020-02-29 22:39:27 +05:30
type mutateStats struct {
2020-12-23 15:10:07 -08:00
resp * response . EngineResponse
2020-08-19 21:37:23 +05:30
namespace string
2020-02-29 22:39:27 +05:30
}
2020-03-04 15:45:20 +05:30
func ( ms mutateStats ) PolicyName ( ) string {
2020-08-19 21:37:23 +05:30
if ms . namespace == "" {
return ms . resp . PolicyResponse . Policy
}
return ms . namespace + "/" + ms . resp . PolicyResponse . Policy
2020-02-29 22:39:27 +05:30
}
2020-03-04 15:45:20 +05:30
func ( ms mutateStats ) UpdateStatus ( status kyverno . PolicyStatus ) kyverno . PolicyStatus {
2020-02-29 22:39:27 +05:30
if reflect . DeepEqual ( response . EngineResponse { } , ms . resp ) {
2020-03-04 15:45:20 +05:30
return status
2020-02-29 22:39:27 +05:30
}
var nameToRule = make ( map [ string ] v1 . RuleStats )
for _ , rule := range status . Rules {
nameToRule [ rule . Name ] = rule
}
for _ , rule := range ms . resp . PolicyResponse . Rules {
ruleStat := nameToRule [ rule . Name ]
ruleStat . Name = rule . Name
averageOver := int64 ( ruleStat . AppliedCount + ruleStat . FailedCount )
ruleStat . ExecutionTime = updateAverageTime (
rule . ProcessingTime ,
ruleStat . ExecutionTime ,
averageOver ) . String ( )
if rule . Success {
status . RulesAppliedCount ++
status . ResourcesMutatedCount ++
ruleStat . AppliedCount ++
ruleStat . ResourcesMutatedCount ++
} else {
status . RulesFailedCount ++
ruleStat . FailedCount ++
}
nameToRule [ rule . Name ] = ruleStat
}
var policyAverageExecutionTime time . Duration
var ruleStats = make ( [ ] v1 . RuleStats , 0 , len ( nameToRule ) )
for _ , ruleStat := range nameToRule {
executionTime , err := time . ParseDuration ( ruleStat . ExecutionTime )
if err == nil {
policyAverageExecutionTime += executionTime
}
ruleStats = append ( ruleStats , ruleStat )
}
sort . Slice ( ruleStats , func ( i , j int ) bool {
return ruleStats [ i ] . Name < ruleStats [ j ] . Name
} )
status . AvgExecutionTime = policyAverageExecutionTime . String ( )
status . Rules = ruleStats
2020-03-04 15:45:20 +05:30
return status
2020-02-29 22:39:27 +05:30
}