2019-07-15 16:07:56 -07:00
package webhooks
import (
2019-11-13 13:13:07 -08:00
"reflect"
2020-02-29 22:39:27 +05:30
"sort"
2019-11-12 14:41:29 -08:00
"time"
2021-02-04 02:39:42 +05:30
client "github.com/kyverno/kyverno/pkg/dclient"
2020-07-09 11:48:34 -07:00
"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"
2020-11-09 11:26:12 -08:00
"github.com/kyverno/kyverno/pkg/config"
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"
2020-11-09 11:26:12 -08:00
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/policyreport"
"github.com/kyverno/kyverno/pkg/policystatus"
"github.com/kyverno/kyverno/pkg/resourcecache"
"github.com/kyverno/kyverno/pkg/utils"
2019-07-15 16:07:56 -07:00
v1beta1 "k8s.io/api/admission/v1beta1"
2020-05-19 00:14:23 -07:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
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
)
2019-12-30 17:08:50 -08:00
// HandleValidation handles validating webhook admission request
2019-07-15 16:07:56 -07:00
// If there are no errors in validating rule we apply generation rules
2019-08-23 18:34:23 -07:00
// patchedResource is the (resource + patches) after applying mutation rules
2020-07-09 11:48:34 -07:00
func HandleValidation (
2021-05-15 19:15:04 +05:30
promConfig * metrics . PromConfig ,
2020-05-17 14:37:05 -07:00
request * v1beta1 . AdmissionRequest ,
2020-05-26 10:36:56 -07:00
policies [ ] * kyverno . ClusterPolicy ,
2020-05-18 12:32:42 -07:00
patchedResource [ ] byte ,
ctx * context . Context ,
2020-07-09 11:48:34 -07:00
userRequestInfo kyverno . RequestInfo ,
statusListener policystatus . Listener ,
eventGen event . Interface ,
2020-11-09 11:26:12 -08:00
prGenerator policyreport . GeneratorInterface ,
2020-08-07 17:09:24 -07:00
log logr . Logger ,
2020-09-23 02:41:49 +05:30
dynamicConfig config . Interface ,
2021-02-01 12:59:13 -08:00
resCache resourcecache . ResourceCache ,
2021-02-04 02:39:42 +05:30
client * client . Client ,
2021-05-15 19:15:04 +05:30
namespaceLabels map [ string ] string ,
admissionRequestTimestamp int64 ) ( bool , string ) {
2020-05-17 14:37:05 -07:00
2020-07-02 12:49:10 -07:00
if len ( policies ) == 0 {
return true , ""
}
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 := log . WithValues ( "action" , "validate" , "resource" , resourceName , "operation" , request . Operation , "gvk" , request . Kind . String ( ) )
2019-08-23 18:34:23 -07:00
2019-11-13 13:13:07 -08:00
// Get new and old resource
2020-05-07 02:35:24 +05:30
newR , oldR , err := utils . ExtractResources ( patchedResource , request )
2019-08-23 18:34:23 -07:00
if err != nil {
2019-11-13 13:13:07 -08:00
// as resource cannot be parsed, we skip processing
2020-03-17 11:05:20 -07:00
logger . Error ( err , "failed to extract resource" )
2019-08-23 18:34:23 -07:00
return true , ""
}
2019-07-21 14:13:00 -07:00
2020-05-19 00:14:23 -07:00
var deletionTimeStamp * metav1 . Time
if reflect . DeepEqual ( newR , unstructured . Unstructured { } ) {
deletionTimeStamp = newR . GetDeletionTimestamp ( )
} else {
deletionTimeStamp = oldR . GetDeletionTimestamp ( )
}
if deletionTimeStamp != nil && request . Operation == v1beta1 . Update {
return true , ""
}
2021-03-23 10:34:03 -07:00
if err := ctx . AddImageInfo ( & newR ) ; err != nil {
logger . Error ( err , "unable to add image info to variables context" )
}
2020-12-23 15:10:07 -08:00
policyContext := & engine . PolicyContext {
2020-12-16 12:29:16 -08:00
NewResource : newR ,
OldResource : oldR ,
AdmissionInfo : userRequestInfo ,
ExcludeGroupRole : dynamicConfig . GetExcludeGroupRole ( ) ,
ExcludeResourceFunc : dynamicConfig . ToFilter ,
ResourceCache : resCache ,
JSONContext : ctx ,
2021-02-01 12:59:13 -08:00
Client : client ,
2019-11-08 18:57:27 -08:00
}
2020-05-18 20:01:20 -07:00
2020-12-23 15:10:07 -08:00
var engineResponses [ ] * response . EngineResponse
2019-07-15 16:07:56 -07:00
for _ , policy := range policies {
2020-05-15 14:49:34 -07:00
logger . V ( 3 ) . Info ( "evaluating policy" , "policy" , policy . Name )
2020-05-26 10:36:56 -07:00
policyContext . Policy = * policy
2021-02-04 02:39:42 +05:30
policyContext . NamespaceLabels = namespaceLabels
2019-11-08 18:57:27 -08:00
engineResponse := engine . Validate ( policyContext )
2019-12-30 17:08:50 -08:00
if reflect . DeepEqual ( engineResponse , response . EngineResponse { } ) {
2019-11-13 13:13:07 -08:00
// we get an empty response if old and new resources created the same response
// allow updates if resource update doesnt change the policy evaluation
continue
}
2020-11-30 11:22:20 -08:00
2021-05-15 19:15:04 +05:30
// registering the kyverno_policy_rule_results_info metric concurrently
go registerPolicyRuleResultsMetricValidation ( promConfig , logger , string ( request . Operation ) , policyContext . Policy , * engineResponse , admissionRequestTimestamp )
2021-05-15 21:34:26 +05:30
// registering the kyverno_policy_rule_execution_latency_milliseconds metric concurrently
go registerPolicyRuleExecutionLatencyMetricValidate ( promConfig , logger , string ( request . Operation ) , policyContext . Policy , * engineResponse , admissionRequestTimestamp )
2021-05-15 19:15:04 +05:30
2019-08-23 18:34:23 -07:00
engineResponses = append ( engineResponses , engineResponse )
2020-11-30 11:22:20 -08:00
statusListener . Update ( validateStats {
2020-08-19 21:37:23 +05:30
resp : engineResponse ,
namespace : policy . Namespace ,
2020-03-07 12:53:37 +05:30
} )
2020-11-30 11:22:20 -08:00
2020-06-30 11:53:27 -07:00
if ! engineResponse . IsSuccessful ( ) {
2021-01-02 01:10:14 -08:00
logger . V ( 2 ) . Info ( "validation failed" , "policy" , policy . Name , "failed rules" , engineResponse . GetFailedRules ( ) )
2019-07-15 16:07:56 -07:00
continue
}
2020-05-15 14:49:34 -07:00
2021-01-02 01:10:14 -08:00
if len ( engineResponse . GetSuccessRules ( ) ) > 0 {
logger . V ( 2 ) . Info ( "validation passed" , "policy" , policy . Name )
}
2019-07-15 16:07:56 -07:00
}
2020-11-30 11:22:20 -08:00
2019-07-15 16:07:56 -07:00
// If Validation fails then reject the request
2020-01-06 14:41:02 -08:00
// no violations will be created on "enforce"
2020-03-17 11:05:20 -07:00
blocked := toBlockResource ( engineResponses , logger )
2020-02-19 19:24:34 -08:00
// REPORTING EVENTS
// Scenario 1:
// resource is blocked, as there is a policy in "enforce" mode that failed.
// create an event on the policy to inform the resource request was blocked
// Scenario 2:
2020-12-04 10:04:46 -08: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 3:
2020-11-17 12:01:01 -08:00
// all policies were applied successfully.
2020-02-19 19:24:34 -08:00
// create an event on the resource
2020-03-17 11:05:20 -07:00
events := generateEvents ( engineResponses , blocked , ( request . Operation == v1beta1 . Update ) , logger )
2020-07-09 11:48:34 -07:00
eventGen . Add ( events ... )
2020-01-06 14:41:02 -08:00
if blocked {
2020-03-17 11:05:20 -07:00
logger . V ( 4 ) . Info ( "resource blocked" )
2020-01-16 11:57:28 -08:00
return false , getEnforceFailureErrorMsg ( engineResponses )
2019-07-15 16:07:56 -07:00
}
2020-01-06 17:07:11 -08:00
2020-12-08 23:04:16 -08:00
if request . Operation == v1beta1 . Delete {
2020-12-21 11:04:19 -08:00
prGenerator . Add ( buildDeletionPrInfo ( oldR ) )
2021-04-16 13:51:35 -07:00
return true , ""
2020-11-09 11:26:12 -08:00
}
2020-12-08 23:04:16 -08:00
2021-04-16 13:51:35 -07:00
prInfos := policyreport . GeneratePRsFromEngineResponse ( engineResponses , logger )
prGenerator . Add ( prInfos ... )
2019-08-23 18:34:23 -07:00
return true , ""
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 registerPolicyRuleResultsMetricValidation ( promConfig * metrics . PromConfig , logger logr . Logger , requestOperation string , policy kyverno . ClusterPolicy , engineResponse response . EngineResponse , admissionRequestTimestamp int64 ) {
resourceRequestOperationPromAlias , err := policyRuleResults . ParseResourceRequestOperation ( requestOperation )
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 ( * 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 registerPolicyRuleExecutionLatencyMetricValidate ( promConfig * metrics . PromConfig , logger logr . Logger , requestOperation string , policy kyverno . ClusterPolicy , engineResponse response . EngineResponse , admissionRequestTimestamp int64 ) {
resourceRequestOperationPromAlias , err := policyRuleExecutionLatency . ParseResourceRequestOperation ( requestOperation )
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 ( * 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-12-21 11:04:19 -08:00
func buildDeletionPrInfo ( oldR unstructured . Unstructured ) policyreport . Info {
return policyreport . Info {
Namespace : oldR . GetNamespace ( ) ,
Results : [ ] policyreport . EngineResponseResult {
{ Resource : response . ResourceSpec {
Kind : oldR . GetKind ( ) ,
APIVersion : oldR . GetAPIVersion ( ) ,
Namespace : oldR . GetNamespace ( ) ,
Name : oldR . GetName ( ) ,
UID : string ( oldR . GetUID ( ) ) ,
} } ,
} ,
}
}
2020-02-29 22:39:27 +05:30
type validateStats 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 ( vs validateStats ) PolicyName ( ) string {
2020-08-19 21:37:23 +05:30
if vs . namespace == "" {
return vs . resp . PolicyResponse . Policy
}
return vs . namespace + "/" + vs . resp . PolicyResponse . Policy
2020-02-29 22:39:27 +05:30
}
2020-03-04 15:45:20 +05:30
func ( vs validateStats ) UpdateStatus ( status kyverno . PolicyStatus ) kyverno . PolicyStatus {
2020-02-29 22:39:27 +05:30
if reflect . DeepEqual ( response . EngineResponse { } , vs . 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 vs . 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 ++
ruleStat . AppliedCount ++
} else {
status . RulesFailedCount ++
ruleStat . FailedCount ++
if vs . resp . PolicyResponse . ValidationFailureAction == "enforce" {
status . ResourcesBlockedCount ++
ruleStat . ResourcesBlockedCount ++
}
}
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
}