2020-01-07 10:33:28 -08:00
package webhooks
import (
2020-11-09 11:26:12 -08:00
contextdefault "context"
2020-07-09 11:48:34 -07:00
"fmt"
2020-08-31 23:55:13 +05:30
"reflect"
"sort"
"strings"
"time"
2020-10-07 11:12:31 -07:00
2020-12-22 11:07:31 -08: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"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/event"
2020-12-01 12:30:08 -08:00
kyvernoutils "github.com/kyverno/kyverno/pkg/utils"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/webhooks/generate"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2020-12-15 04:22:13 +05:30
"k8s.io/apimachinery/pkg/labels"
2020-01-07 10:33:28 -08:00
)
2020-01-24 12:05:53 -08:00
//HandleGenerate handles admission-requests for policies with generate rules
2020-08-14 12:21:06 -07:00
func ( ws * WebhookServer ) HandleGenerate ( request * v1beta1 . AdmissionRequest , policies [ ] * kyverno . ClusterPolicy , ctx * context . Context , userRequestInfo kyverno . RequestInfo , dynamicConfig config . Interface ) {
2020-03-17 11:05:20 -07:00
logger := ws . log . WithValues ( "action" , "generation" , "uid" , request . UID , "kind" , request . Kind , "namespace" , request . Namespace , "name" , request . Name , "operation" , request . Operation )
logger . V ( 4 ) . Info ( "incoming request" )
2020-12-23 15:10:07 -08:00
var engineResponses [ ] * response . EngineResponse
2020-01-07 10:33:28 -08:00
2020-07-02 12:49:10 -07:00
if len ( policies ) == 0 {
2020-07-09 11:48:34 -07:00
return
2020-07-02 12:49:10 -07:00
}
2020-01-07 10:33:28 -08:00
// convert RAW to unstructured
2020-12-01 12:30:08 -08:00
new , old , err := kyvernoutils . ExtractResources ( nil , request )
2020-01-07 10:33:28 -08:00
if err != nil {
2020-12-01 12:30:08 -08:00
logger . Error ( err , "failed to extract resource" )
2020-01-07 10:33:28 -08:00
}
policyContext := engine . PolicyContext {
2020-12-16 12:29:16 -08:00
NewResource : new ,
OldResource : old ,
AdmissionInfo : userRequestInfo ,
ExcludeGroupRole : dynamicConfig . GetExcludeGroupRole ( ) ,
ExcludeResourceFunc : ws . configHandler . ToFilter ,
ResourceCache : ws . resCache ,
JSONContext : ctx ,
2020-01-07 10:33:28 -08:00
}
for _ , policy := range policies {
2020-12-22 11:07:31 -08:00
var rules [ ] response . RuleResponse
2020-05-26 10:36:56 -07:00
policyContext . Policy = * policy
2020-01-24 12:05:53 -08:00
engineResponse := engine . Generate ( policyContext )
2020-09-04 03:04:23 +05:30
for _ , rule := range engineResponse . PolicyResponse . Rules {
2020-08-31 23:55:13 +05:30
if ! rule . Success {
2020-12-16 12:29:16 -08:00
ws . deleteGR ( logger , engineResponse )
continue
2020-08-31 23:55:13 +05:30
}
2020-12-16 12:29:16 -08:00
rules = append ( rules , rule )
2020-08-31 23:55:13 +05:30
}
2020-09-04 03:04:23 +05:30
if len ( rules ) > 0 {
engineResponse . PolicyResponse . Rules = rules
2020-01-07 10:33:28 -08:00
// some generate rules do apply to the resource
engineResponses = append ( engineResponses , engineResponse )
2020-11-30 11:22:20 -08:00
ws . statusListener . Update ( generateStats {
2020-03-04 15:45:20 +05:30
resp : engineResponse ,
2020-03-07 12:53:37 +05:30
} )
2020-01-07 10:33:28 -08:00
}
2020-08-31 23:55:13 +05:30
}
2020-09-04 03:04:23 +05:30
2020-01-07 10:33:28 -08:00
// Adds Generate Request to a channel(queue size 1000) to generators
2020-07-09 11:48:34 -07:00
if failedResponse := applyGenerateRequest ( ws . grGenerator , userRequestInfo , request . Operation , engineResponses ... ) ; err != nil {
// report failure event
for _ , failedGR := range failedResponse {
2020-12-01 12:30:08 -08:00
events := failedEvents ( fmt . Errorf ( "failed to create Generate Request: %v" , failedGR . err ) , failedGR . gr , new )
2020-07-09 11:48:34 -07:00
ws . eventGen . Add ( events ... )
}
2020-01-07 10:33:28 -08:00
}
2020-06-22 18:49:43 -07:00
2020-07-09 11:48:34 -07:00
return
2020-01-07 10:33:28 -08:00
}
2020-12-23 15:10:07 -08:00
func ( ws * WebhookServer ) deleteGR ( logger logr . Logger , engineResponse * response . EngineResponse ) {
2020-12-16 12:29:16 -08:00
logger . V ( 4 ) . Info ( "querying all generate requests" )
selector := labels . SelectorFromSet ( labels . Set ( map [ string ] string {
"policyName" : engineResponse . PolicyResponse . Policy ,
"resourceName" : engineResponse . PolicyResponse . Resource . Name ,
"resourceKind" : engineResponse . PolicyResponse . Resource . Kind ,
"ResourceNamespace" : engineResponse . PolicyResponse . Resource . Namespace ,
} ) )
grList , err := ws . grLister . List ( selector )
if err != nil {
logger . Error ( err , "failed to get generate request for the resource" , "kind" , engineResponse . PolicyResponse . Resource . Kind , "name" , engineResponse . PolicyResponse . Resource . Name , "namespace" , engineResponse . PolicyResponse . Resource . Namespace )
}
for _ , v := range grList {
err := ws . kyvernoClient . KyvernoV1 ( ) . GenerateRequests ( config . KyvernoNamespace ) . Delete ( contextdefault . TODO ( ) , v . GetName ( ) , metav1 . DeleteOptions { } )
if err != nil {
logger . Error ( err , "failed to update gr" )
}
}
}
2020-07-09 11:48:34 -07:00
func applyGenerateRequest ( gnGenerator generate . GenerateRequests , userRequestInfo kyverno . RequestInfo ,
2020-12-23 15:10:07 -08:00
action v1beta1 . Operation , engineResponses ... * response . EngineResponse ) ( failedGenerateRequest [ ] generateRequestResponse ) {
2020-07-09 11:48:34 -07:00
2020-01-07 10:33:28 -08:00
for _ , er := range engineResponses {
2020-07-09 11:48:34 -07:00
gr := transform ( userRequestInfo , er )
if err := gnGenerator . Apply ( gr , action ) ; err != nil {
failedGenerateRequest = append ( failedGenerateRequest , generateRequestResponse { gr : gr , err : err } )
2020-01-07 10:33:28 -08:00
}
}
2020-12-01 12:30:08 -08:00
2020-07-09 11:48:34 -07:00
return
2020-01-07 10:33:28 -08:00
}
2020-12-23 15:10:07 -08:00
func transform ( userRequestInfo kyverno . RequestInfo , er * response . EngineResponse ) kyverno . GenerateRequestSpec {
2020-01-07 10:33:28 -08:00
gr := kyverno . GenerateRequestSpec {
Policy : er . PolicyResponse . Policy ,
Resource : kyverno . ResourceSpec {
Kind : er . PolicyResponse . Resource . Kind ,
Namespace : er . PolicyResponse . Resource . Namespace ,
Name : er . PolicyResponse . Resource . Name ,
} ,
Context : kyverno . GenerateRequestContext {
UserRequestInfo : userRequestInfo ,
} ,
}
2020-12-23 15:10:07 -08:00
2020-01-07 10:33:28 -08:00
return gr
}
2020-02-29 22:39:27 +05:30
type generateStats struct {
2020-12-23 15:10:07 -08:00
resp * response . EngineResponse
2020-02-29 22:39:27 +05:30
}
2020-03-04 15:45:20 +05:30
func ( gs generateStats ) PolicyName ( ) string {
return gs . resp . PolicyResponse . Policy
2020-02-29 22:39:27 +05:30
}
2020-03-04 15:45:20 +05:30
func ( gs generateStats ) UpdateStatus ( status kyverno . PolicyStatus ) kyverno . PolicyStatus {
2020-02-29 22:39:27 +05:30
if reflect . DeepEqual ( response . EngineResponse { } , gs . 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 gs . 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 ++
}
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
}
func updateAverageTime ( newTime time . Duration , oldAverageTimeString string , averageOver int64 ) time . Duration {
if averageOver == 0 {
return newTime
}
oldAverageExecutionTime , _ := time . ParseDuration ( oldAverageTimeString )
numerator := ( oldAverageExecutionTime . Nanoseconds ( ) * averageOver ) + newTime . Nanoseconds ( )
denominator := averageOver + 1
newAverageTimeInNanoSeconds := numerator / denominator
return time . Duration ( newAverageTimeInNanoSeconds ) * time . Nanosecond
}
2020-07-09 11:48:34 -07:00
type generateRequestResponse struct {
gr v1 . GenerateRequestSpec
err error
}
func ( resp generateRequestResponse ) info ( ) string {
return strings . Join ( [ ] string { resp . gr . Resource . Kind , resp . gr . Resource . Namespace , resp . gr . Resource . Name } , "/" )
}
func ( resp generateRequestResponse ) error ( ) string {
return resp . err . Error ( )
}
func failedEvents ( err error , gr kyverno . GenerateRequestSpec , resource unstructured . Unstructured ) [ ] event . Info {
re := event . Info { }
re . Kind = resource . GetKind ( )
re . Namespace = resource . GetNamespace ( )
re . Name = resource . GetName ( )
re . Reason = event . PolicyFailed . String ( )
re . Source = event . GeneratePolicyController
re . Message = fmt . Sprintf ( "policy %s failed to apply: %v" , gr . Policy , err )
2020-07-20 08:00:02 -07:00
return [ ] event . Info { re }
2020-07-09 11:48:34 -07:00
}