diff --git a/cmd/initContainer/main.go b/cmd/initContainer/main.go index 841a8c7c0d..c80f5ca792 100644 --- a/cmd/initContainer/main.go +++ b/cmd/initContainer/main.go @@ -7,6 +7,7 @@ import ( "flag" "os" "sync" + "time" "github.com/golang/glog" "github.com/nirmata/kyverno/pkg/config" @@ -41,7 +42,7 @@ func main() { // DYNAMIC CLIENT // - client for all registered resources - client, err := client.NewClient(clientConfig) + client, err := client.NewClient(clientConfig, 10*time.Second, stopCh) if err != nil { glog.Fatalf("Error creating client: %v\n", err) } diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index 2faae3cd60..429fc5e7fd 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -60,12 +60,13 @@ func main() { // DYNAMIC CLIENT // - client for all registered resources - client, err := dclient.NewClient(clientConfig) + // - invalidate local cache of registered resource every 10 seconds + client, err := dclient.NewClient(clientConfig, 10*time.Second, stopCh) if err != nil { glog.Fatalf("Error creating client: %v\n", err) } // CRD CHECK - // - verify if the CRD for Policy & PolicyViolation are avialalbe + // - verify if the CRD for Policy & PolicyViolation are available if !utils.CRDInstalled(client.DiscoveryClient) { glog.Fatalf("Required CRDs unavailable") } diff --git a/pkg/dclient/client.go b/pkg/dclient/client.go index e3c6bc16c4..5a5e4082e9 100644 --- a/pkg/dclient/client.go +++ b/pkg/dclient/client.go @@ -36,7 +36,7 @@ type Client struct { } //NewClient creates new instance of client -func NewClient(config *rest.Config) (*Client, error) { +func NewClient(config *rest.Config, resync time.Duration, stopCh <-chan struct{}) (*Client, error) { dclient, err := dynamic.NewForConfig(config) if err != nil { return nil, err @@ -52,6 +52,13 @@ func NewClient(config *rest.Config) (*Client, error) { } // Set discovery client discoveryClient := ServerPreferredResources{memory.NewMemCacheClient(kclient.Discovery())} + // client will invalidate registered resources cache every x seconds, + // As there is no way to identify if the registered resource is available or not + // we will be invalidating the local cache, so the next request get a fresh cache + // If a resource is removed then and cache is not invalidate yet, we will not detect the removal + // but the re-sync shall re-evaluate + go discoveryClient.Poll(resync, stopCh) + client.SetDiscovery(discoveryClient) return &client, nil } @@ -266,6 +273,25 @@ type ServerPreferredResources struct { cachedClient discovery.CachedDiscoveryInterface } +//Poll will keep invalidate the local cache +func (c ServerPreferredResources) Poll(resync time.Duration, stopCh <-chan struct{}) { + // start a ticker + ticker := time.NewTicker(resync) + defer func() { ticker.Stop() }() + glog.Infof("Starting registered resources sync: every %d seconds", resync) + for { + select { + case <-stopCh: + glog.Info("Stopping registered resources sync") + return + case <-ticker.C: + // set cache as stale + glog.V(6).Info("invalidating local client cache for registered resources") + c.cachedClient.Invalidate() + } + } +} + //GetGVRFromKind get the Group Version Resource from kind // if kind is not found in first attempt we invalidate the cache, // the retry will then fetch the new registered resources and check again diff --git a/pkg/engine/overlay.go b/pkg/engine/overlay.go index 9acfcc0770..de98550e85 100644 --- a/pkg/engine/overlay.go +++ b/pkg/engine/overlay.go @@ -281,7 +281,7 @@ func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([] if len(anchors) > 0 { // If we have anchors - choose corresponding resource element and mutate it - patches, err := applyOverlayWithAnchors(resource, overlayElement, anchors, path) + patches, err := applyOverlayWithAnchors(resource, overlayElement, path) if err != nil { return nil, err } @@ -312,21 +312,17 @@ func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([] return appliedPatches, nil } -func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, anchors map[string]interface{}, path string) ([][]byte, error) { +func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, path string) ([][]byte, error) { var appliedPatches [][]byte for i, resourceElement := range resource { - typedResource := resourceElement.(map[string]interface{}) - currentPath := path + strconv.Itoa(i) + "/" // currentPath example: /spec/template/spec/containers/3/ - if !skipArrayObject(typedResource, anchors) { - patches, err := applyOverlay(resourceElement, overlay, currentPath) - if err != nil { - return nil, err - } - appliedPatches = append(appliedPatches, patches...) + patches, err := applyOverlay(resourceElement, overlay, currentPath) + if err != nil { + return nil, err } + appliedPatches = append(appliedPatches, patches...) } return appliedPatches, nil diff --git a/pkg/engine/overlay_test.go b/pkg/engine/overlay_test.go index 75b6248e4f..25a84d58aa 100644 --- a/pkg/engine/overlay_test.go +++ b/pkg/engine/overlay_test.go @@ -1071,3 +1071,88 @@ func Test_wrapBoolean(t *testing.T) { assert.Assert(t, testcase.expected == out) } } + +func TestApplyOverlay_ConditionOnArray(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "myapp-pod", + "labels": { + "app": "myapp", + "dedicated": "spark" + } + }, + "spec": { + "containers": [ + { + "name": "myapp-container", + "image": "busybox", + "command": [ + "sh", + "-c", + "echo Hello Kubernetes! && sleep 3600" + ] + } + ], + "affinity": { + "nodeAffinity": { + "a": { + "b": [ + { + "matchExpressions": [ + { + "key": "dedicated", + "operator": "NotIn", + "values": [ + "spark" + ] + } + ] + } + ] + } + } + } + } + } + `) + + overlayRaw := []byte(` + { + "spec": { + "affinity": { + "nodeAffinity": { + "a": { + "b": [ + { + "matchExpressions": [ + { + "(key)": "dedicated", + "operator": "In", + "(values)": [ + "spark" + ] + } + ] + } + ] + } + } + } + } + } + `) + var resource, overlay interface{} + + assert.NilError(t, json.Unmarshal(resourceRaw, &resource)) + assert.NilError(t, json.Unmarshal(overlayRaw, &overlay)) + + expectedPatches := []byte(`[ +{ "op": "replace", "path": "/spec/affinity/nodeAffinity/a/b/0/matchExpressions/0/operator", "value":"In" } +]`) + p, err := applyOverlay(resource, overlay, "/") + assert.NilError(t, err) + assert.Assert(t, string(JoinPatches(p)) == string(expectedPatches)) +} diff --git a/pkg/engine/pattern.go b/pkg/engine/pattern.go index 6e3ae0039e..0bcfb36fa0 100644 --- a/pkg/engine/pattern.go +++ b/pkg/engine/pattern.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/golang/glog" - "github.com/minio/minio/pkg/wildcard" + apiresource "k8s.io/apimachinery/pkg/api/resource" ) // Operator is string alias that represents selection operators enum @@ -32,6 +32,14 @@ const ( const relativePrefix Operator = "./" const referenceSign Operator = "$()" +type quantity int + +const ( + equal quantity = 0 + lessThan quantity = -1 + greaterThan quantity = 1 +) + // ValidateValueWithPattern validates value with operators and wildcards func ValidateValueWithPattern(value, pattern interface{}) bool { switch typedPattern := pattern.(type) { @@ -187,7 +195,7 @@ func validateValueWithStringPattern(value interface{}, pattern string) bool { return validateString(value, str, operator) } - return validateNumberWithStr(value, number, str, operator) + return validateNumberWithStr(value, pattern, operator) } // Handler for string values @@ -212,53 +220,50 @@ func validateString(value interface{}, pattern string, operator Operator) bool { return false } -// validateNumberWithStr applies wildcard to suffix and operator to numerical part -func validateNumberWithStr(value interface{}, patternNumber, patternStr string, operator Operator) bool { - // pattern has suffix - if "" != patternStr { - typedValue, ok := value.(string) - if !ok { - glog.Warningf("Number must have suffix: %s", patternStr) - return false - } - - valueNumber, valueStr := getNumberAndStringPartsFromPattern(typedValue) - if !wildcard.Match(patternStr, valueStr) { - glog.Warningf("Suffix %s has not passed wildcard check: %s", valueStr, patternStr) - return false - } - - return validateNumber(valueNumber, patternNumber, operator) +// validateNumberWithStr compares quantity if pattern type is quantity +// or a wildcard match to pattern string +func validateNumberWithStr(value interface{}, pattern string, operator Operator) bool { + typedValue, err := convertToString(value) + if err != nil { + glog.Warning(err) + return false } - return validateNumber(value, patternNumber, operator) + patternQuan, err := apiresource.ParseQuantity(pattern) + // 1. nil error - quantity comparison + if err == nil { + valueQuan, err := apiresource.ParseQuantity(typedValue) + if err != nil { + glog.Warningf("Invalid quantity in resource %s, err: %v\n", typedValue, err) + return false + } + + return compareQuantity(valueQuan, patternQuan, operator) + } + + // 2. wildcard match + if !wildcard.Match(pattern, typedValue) { + glog.Warningf("Value '%s' has not passed wildcard check: %s", typedValue, pattern) + return false + } + return true } -// validateNumber compares two numbers with operator -func validateNumber(value, pattern interface{}, operator Operator) bool { - floatPattern, err := convertToFloat(pattern) - if err != nil { - return false - } - - floatValue, err := convertToFloat(value) - if err != nil { - return false - } - +func compareQuantity(value, pattern apiresource.Quantity, operator Operator) bool { + result := value.Cmp(pattern) switch operator { case Equal: - return floatValue == floatPattern + return result == int(equal) case NotEqual: - return floatValue != floatPattern + return result != int(equal) case More: - return floatValue > floatPattern - case MoreEqual: - return floatValue >= floatPattern + return result == int(greaterThan) case Less: - return floatValue < floatPattern + return result == int(lessThan) + case MoreEqual: + return (result == int(equal)) || (result == int(greaterThan)) case LessEqual: - return floatValue <= floatPattern + return (result == int(equal)) || (result == int(lessThan)) } return false diff --git a/pkg/engine/pattern_test.go b/pkg/engine/pattern_test.go index 2ce21ddde2..f9c3d171c7 100644 --- a/pkg/engine/pattern_test.go +++ b/pkg/engine/pattern_test.go @@ -152,6 +152,10 @@ func TestValidateValueWithPattern_StringsLogicalOr(t *testing.T) { assert.Assert(t, ValidateValueWithPattern(value, pattern)) } +func TestValidateValueWithPattern_EqualTwoFloats(t *testing.T) { + assert.Assert(t, ValidateValueWithPattern(7.0, 7.000)) +} + func TestValidateValueWithNilPattern_NullPatternStringValue(t *testing.T) { assert.Assert(t, !validateValueWithNilPattern("value")) } @@ -242,32 +246,36 @@ func TestGetNumberAndStringPartsFromPattern_Empty(t *testing.T) { assert.Equal(t, str, "") } -func TestValidateNumber_EqualTwoFloats(t *testing.T) { - assert.Assert(t, validateNumber(7.0, 7.000, Equal)) +func TestValidateNumberWithStr_LessFloatAndInt(t *testing.T) { + assert.Assert(t, validateNumberWithStr(7.00001, "7.000001", More)) + assert.Assert(t, validateNumberWithStr(7.00001, "7", NotEqual)) + + assert.Assert(t, validateNumberWithStr(7.0000, "7", Equal)) + assert.Assert(t, !validateNumberWithStr(6.000000001, "6", Less)) } -func TestValidateNumber_LessFloatAndInt(t *testing.T) { - assert.Assert(t, validateNumber(7, 7.00001, Less)) - assert.Assert(t, validateNumber(7, 7.00001, NotEqual)) - - assert.Assert(t, !validateNumber(7, 7.0000, NotEqual)) - assert.Assert(t, !validateNumber(6, 6.000000001, More)) +func TestValidateQuantity_InvalidQuantity(t *testing.T) { + assert.Assert(t, !validateNumberWithStr("1024Gi", "", Equal)) + assert.Assert(t, !validateNumberWithStr("gii", "1024Gi", Equal)) } -func TestValidateNumberWithStr_Equal(t *testing.T) { - assert.Assert(t, validateNumberWithStr("1024Gi", "1024", "Gi", Equal)) +func TestValidateQuantity_Equal(t *testing.T) { + assert.Assert(t, validateNumberWithStr("1024Gi", "1024Gi", Equal)) + assert.Assert(t, validateNumberWithStr("1024Mi", "1Gi", Equal)) + assert.Assert(t, validateNumberWithStr("0.2", "200m", Equal)) + assert.Assert(t, validateNumberWithStr("500", "500", Equal)) + assert.Assert(t, !validateNumberWithStr("2048", "1024", Equal)) + assert.Assert(t, validateNumberWithStr(1024, "1024", Equal)) } -func TestValidateNumberWithStr_More(t *testing.T) { - assert.Assert(t, !validateNumberWithStr("512Gi", "1024", "Gi", More)) -} - -func TestValidateNumberWithStr_MoreAndWildCard(t *testing.T) { - assert.Assert(t, validateNumberWithStr("2048Gi", "1024", "G?", More)) -} - -func TestValidateNumberWithStr_NoStr(t *testing.T) { - assert.Assert(t, validateNumberWithStr(2048, "1024", "", More)) +func TestValidateQuantity_Operation(t *testing.T) { + assert.Assert(t, validateNumberWithStr("1Gi", "1000Mi", More)) + assert.Assert(t, validateNumberWithStr("1G", "1Gi", Less)) + assert.Assert(t, validateNumberWithStr("500m", "0.5", MoreEqual)) + assert.Assert(t, validateNumberWithStr("1", "500m", MoreEqual)) + assert.Assert(t, validateNumberWithStr("0.5", ".5", LessEqual)) + assert.Assert(t, validateNumberWithStr("0.2", ".5", LessEqual)) + assert.Assert(t, validateNumberWithStr("0.2", ".5", NotEqual)) } func TestGetOperatorFromStringPattern_OneChar(t *testing.T) { diff --git a/pkg/engine/response.go b/pkg/engine/response.go index ae488ca1c5..bb2e9bb7f9 100644 --- a/pkg/engine/response.go +++ b/pkg/engine/response.go @@ -38,6 +38,11 @@ type ResourceSpec struct { Name string `json:"name"` } +//GetKey returns the key +func (rs ResourceSpec) GetKey() string { + return rs.Kind + "/" + rs.Namespace + "/" + rs.Name +} + //PolicyStats stores statistics for the single policy application type PolicyStats struct { // time required to process the policy rules on a resource diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index 51d00d4b06..47f74db0a5 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -318,25 +318,19 @@ func removeAnchor(key string) string { return key } -// convertToFloat converts string and any other value to float64 -func convertToFloat(value interface{}) (float64, error) { +// convertToString converts value to string +func convertToString(value interface{}) (string, error) { switch typed := value.(type) { case string: - var err error - floatValue, err := strconv.ParseFloat(typed, 64) - if err != nil { - return 0, err - } - - return floatValue, nil + return string(typed), nil case float64: - return typed, nil + return fmt.Sprintf("%f", typed), nil case int64: - return float64(typed), nil + return strconv.FormatInt(typed, 10), nil case int: - return float64(typed), nil + return strconv.Itoa(typed), nil default: - return 0, fmt.Errorf("Could not convert %T to float64", value) + return "", fmt.Errorf("Could not convert %T to string", value) } } diff --git a/pkg/event/controller.go b/pkg/event/controller.go index 144a6f11d7..6a36346c3f 100644 --- a/pkg/event/controller.go +++ b/pkg/event/controller.go @@ -26,8 +26,14 @@ type Generator struct { pLister kyvernolister.ClusterPolicyLister // returns true if the cluster policy store has been synced at least once pSynced cache.InformerSynced + // queue to store event generation requests queue workqueue.RateLimitingInterface - recorder record.EventRecorder + // events generated at policy controller + policyCtrRecorder record.EventRecorder + // events generated at admission control + admissionCtrRecorder record.EventRecorder + // events generated at namespaced policy controller to process 'generate' rule + genPolicyRecorder record.EventRecorder } //Interface to generate event @@ -39,17 +45,19 @@ type Interface interface { func NewEventGenerator(client *client.Client, pInformer kyvernoinformer.ClusterPolicyInformer) *Generator { gen := Generator{ - client: client, - pLister: pInformer.Lister(), - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), eventWorkQueueName), - pSynced: pInformer.Informer().HasSynced, - recorder: initRecorder(client), - } + client: client, + pLister: pInformer.Lister(), + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), eventWorkQueueName), + pSynced: pInformer.Informer().HasSynced, + policyCtrRecorder: initRecorder(client, PolicyController), + admissionCtrRecorder: initRecorder(client, AdmissionController), + genPolicyRecorder: initRecorder(client, GeneratePolicyController), + } return &gen } -func initRecorder(client *client.Client) record.EventRecorder { +func initRecorder(client *client.Client, eventSource Source) record.EventRecorder { // Initliaze Event Broadcaster err := scheme.AddToScheme(scheme.Scheme) if err != nil { @@ -68,7 +76,7 @@ func initRecorder(client *client.Client) record.EventRecorder { Interface: eventInterface}) recorder := eventBroadcaster.NewRecorder( scheme.Scheme, - v1.EventSource{Component: eventSource}) + v1.EventSource{Component: eventSource.String()}) return recorder } @@ -113,7 +121,7 @@ func (gen *Generator) handleErr(err error, key interface{}) { } // This controller retries if something goes wrong. After that, it stops trying. if gen.queue.NumRequeues(key) < workQueueRetryLimit { - glog.Warningf("Error syncing events %v: %v", key, err) + glog.Warningf("Error syncing events %v(re-queuing request, the resource might not have been created yet): %v", key, err) // Re-enqueue the key rate limited. Based on the rate limiter on the // queue and the re-enqueue history, the key will be processed later again. gen.queue.AddRateLimited(key) @@ -159,47 +167,45 @@ func (gen *Generator) syncHandler(key Info) error { //TODO: policy is clustered resource so wont need namespace robj, err = gen.pLister.Get(key.Name) if err != nil { - glog.Errorf("Error creating event: unable to get policy %s, will retry ", key.Name) + glog.V(4).Infof("Error creating event: unable to get policy %s, will retry ", key.Name) return err } default: robj, err = gen.client.GetResource(key.Kind, key.Namespace, key.Name) if err != nil { - glog.Errorf("Error creating event: unable to get resource %s, %s, will retry ", key.Kind, key.Namespace+"/"+key.Name) + glog.V(4).Infof("Error creating event: unable to get resource %s, %s, will retry ", key.Kind, key.Namespace+"/"+key.Name) return err } } + // set the event type based on reason + eventType := v1.EventTypeWarning if key.Reason == PolicyApplied.String() { - gen.recorder.Event(robj, v1.EventTypeNormal, key.Reason, key.Message) - } else { - gen.recorder.Event(robj, v1.EventTypeWarning, key.Reason, key.Message) + eventType = v1.EventTypeNormal + } + + // based on the source of event generation, use different event recorders + switch key.Source { + case AdmissionController: + gen.admissionCtrRecorder.Event(robj, eventType, key.Reason, key.Message) + case PolicyController: + gen.policyCtrRecorder.Event(robj, eventType, key.Reason, key.Message) + case GeneratePolicyController: + gen.genPolicyRecorder.Event(robj, eventType, key.Reason, key.Message) + default: + glog.Info("info.source not defined for the event generator request") } return nil } -//TODO: check if we need this ? -//NewEvent returns a new event -func NewEvent(rkind string, rnamespace string, rname string, reason Reason, message MsgKey, args ...interface{}) *Info { - msgText, err := getEventMsg(message, args...) - if err != nil { - glog.Error(err) - } - return &Info{ - Kind: rkind, - Name: rname, - Namespace: rnamespace, - Reason: reason.String(), - Message: msgText, - } -} - -func NewEventNew( +//NewEvent builds a event creation request +func NewEvent( rkind, rapiVersion, rnamespace, rname, reason string, + source Source, message MsgKey, args ...interface{}) Info { msgText, err := getEventMsg(message, args...) @@ -211,6 +217,7 @@ func NewEventNew( Name: rname, Namespace: rnamespace, Reason: reason, + Source: source, Message: msgText, } } diff --git a/pkg/event/source.go b/pkg/event/source.go new file mode 100644 index 0000000000..7ee1bac38b --- /dev/null +++ b/pkg/event/source.go @@ -0,0 +1,21 @@ +package event + +//Source of event generation +type Source int + +const ( + // AdmissionController : event generated in admission-control webhook + AdmissionController Source = iota + // PolicyController : event generated in policy-controller + PolicyController + // GeneratePolicyController : event generated in generate policyController + GeneratePolicyController +) + +func (s Source) String() string { + return [...]string{ + "admission-controller", + "policy-controller", + "generate-policy-controller", + }[s] +} diff --git a/pkg/event/util.go b/pkg/event/util.go index a2cab76e7c..677e79d986 100644 --- a/pkg/event/util.go +++ b/pkg/event/util.go @@ -1,8 +1,6 @@ package event -const eventSource = "policy-controller" - -const eventWorkQueueName = "policy-controller-events" +const eventWorkQueueName = "kyverno-events" const eventWorkerThreadCount = 1 @@ -15,4 +13,5 @@ type Info struct { Namespace string Reason string Message string + Source Source } diff --git a/pkg/namespace/report.go b/pkg/namespace/report.go index 592d2e42cd..c416263944 100644 --- a/pkg/namespace/report.go +++ b/pkg/namespace/report.go @@ -89,6 +89,7 @@ func generateEventsPerEr(er engine.EngineResponse) []event.Info { e.Namespace = "" // event generate on namespace resource e.Name = er.PolicyResponse.Resource.Name e.Reason = "Failure" + e.Source = event.GeneratePolicyController e.Message = fmt.Sprintf("policy '%s' (%s) rule '%s' not satisfied. %v", er.PolicyResponse.Policy, rule.Type, rule.Name, rule.Message) eventInfos = append(eventInfos, e) } @@ -102,6 +103,7 @@ func generateEventsPerEr(er engine.EngineResponse) []event.Info { e.Namespace = "" e.Name = er.PolicyResponse.Policy e.Reason = "Failure" + e.Source = event.GeneratePolicyController e.Message = fmt.Sprintf("policy '%s' rules '%v' on resource '%s/%s/%s' not stasified", er.PolicyResponse.Policy, er.GetFailedRules(), er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name) return eventInfos } diff --git a/pkg/policy/apply.go b/pkg/policy/apply.go index 58794aacd9..5d0882f962 100644 --- a/pkg/policy/apply.go +++ b/pkg/policy/apply.go @@ -1,8 +1,10 @@ package policy import ( + "encoding/json" "fmt" "reflect" + "strings" "time" jsonpatch "github.com/evanphx/json-patch" @@ -108,7 +110,6 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse if len(rule.Patches) == 0 { continue } - patch, err := jsonpatch.DecodePatch(utils.JoinPatches(rule.Patches)) if err != nil { glog.V(4).Infof("unable to decode patch %s: %v", rule.Patches, err) @@ -121,12 +122,32 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse glog.V(4).Infof("unable to apply patch %s: %v", rule.Patches, err) return engine.EngineResponse{}, err } - if !jsonpatch.Equal(patchedResource, rawResource) { glog.V(4).Infof("policy %s rule %s condition not satisifed by existing resource", engineResponse.PolicyResponse.Policy, rule.Name) engineResponse.PolicyResponse.Rules[index].Success = false - engineResponse.PolicyResponse.Rules[index].Message = fmt.Sprintf("rule not satisfied by existing resource.") + engineResponse.PolicyResponse.Rules[index].Message = fmt.Sprintf("mutation json patches not found at resource path %s", extractPatchPath(rule.Patches)) } } return engineResponse, nil } + +type jsonPatch struct { + Op string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value"` +} + +func extractPatchPath(patches [][]byte) string { + var resultPath []string + // extract the patch path and value + for _, patch := range patches { + glog.V(4).Infof("expected json patch not found in resource: %s", string(patch)) + var data jsonPatch + if err := json.Unmarshal(patch, &data); err != nil { + glog.V(4).Infof("Failed to decode the generated patch %v: Error %v", string(patch), err) + continue + } + resultPath = append(resultPath, data.Path) + } + return strings.Join(resultPath, ";") +} diff --git a/pkg/policy/report.go b/pkg/policy/report.go index 3a562aac01..5ac4e22792 100644 --- a/pkg/policy/report.go +++ b/pkg/policy/report.go @@ -109,6 +109,7 @@ func generateEventsPerEr(er engine.EngineResponse) []event.Info { e.Namespace = er.PolicyResponse.Resource.Namespace e.Name = er.PolicyResponse.Resource.Name e.Reason = event.PolicyViolation.String() + e.Source = event.PolicyController e.Message = fmt.Sprintf("policy '%s' (%s) rule '%s' not satisfied. %v", er.PolicyResponse.Policy, rule.Type, rule.Name, rule.Message) eventInfos = append(eventInfos, e) } @@ -123,6 +124,7 @@ func generateEventsPerEr(er engine.EngineResponse) []event.Info { e.Namespace = "" e.Name = er.PolicyResponse.Policy e.Reason = event.PolicyViolation.String() + e.Source = event.PolicyController e.Message = fmt.Sprintf("policy '%s' rules '%v' not satisfied on resource '%s/%s/%s'", er.PolicyResponse.Policy, er.GetFailedRules(), er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name) eventInfos = append(eventInfos, e) return eventInfos diff --git a/pkg/webhooks/report.go b/pkg/webhooks/report.go index febead68da..54ab9683a1 100644 --- a/pkg/webhooks/report.go +++ b/pkg/webhooks/report.go @@ -32,12 +32,13 @@ func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []ev var e event.Info // UPDATE // event on resource - e = event.NewEventNew( + e = event.NewEvent( er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.APIVersion, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name, reason.String(), + event.AdmissionController, event.FPolicyApplyBlockUpdate, filedRulesStr, er.PolicyResponse.Policy, @@ -46,14 +47,15 @@ func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []ev events = append(events, e) // event on policy - e = event.NewEventNew( + e = event.NewEvent( "ClusterPolicy", kyverno.SchemeGroupVersion.String(), "", er.PolicyResponse.Policy, reason.String(), + event.AdmissionController, event.FPolicyBlockResourceUpdate, - er.PolicyResponse.Resource.Namespace+"/"+er.PolicyResponse.Resource.Name, + er.PolicyResponse.Resource.GetKey(), filedRulesStr, ) glog.V(4).Infof("UPDATE event on policy %s", er.PolicyResponse.Policy) @@ -62,14 +64,15 @@ func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []ev } else { // CREATE // event on policy - e := event.NewEventNew( + e := event.NewEvent( "ClusterPolicy", kyverno.SchemeGroupVersion.String(), "", er.PolicyResponse.Policy, - event.RequestBlocked.String(), + reason.String(), + event.AdmissionController, event.FPolicyApplyBlockCreate, - er.PolicyResponse.Resource.Namespace+"/"+er.PolicyResponse.Resource.Name, + er.PolicyResponse.Resource.GetKey(), filedRulesStr, ) glog.V(4).Infof("CREATE event on policy %s", er.PolicyResponse.Policy) @@ -85,12 +88,13 @@ func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []ev successRules := er.GetSuccessRules() successRulesStr := strings.Join(successRules, ";") // event on resource - e := event.NewEventNew( + e := event.NewEvent( er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.APIVersion, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name, event.PolicyApplied.String(), + event.AdmissionController, event.SRulesApply, successRulesStr, er.PolicyResponse.Policy,