From 8414681e60372c75266fb5276689d210a0d4f2c0 Mon Sep 17 00:00:00 2001 From: shivkumar dudhani Date: Thu, 12 Dec 2019 18:25:54 -0800 Subject: [PATCH] support variable substitution in overlay mutation --- pkg/engine/context/evaluate.go | 2 +- pkg/engine/mutation.go | 3 +- pkg/engine/mutation_test.go | 89 ++++++++++++++++++++++++++++ pkg/engine/overlay.go | 11 +++- pkg/webhooks/{utils.go => common.go} | 11 ++++ pkg/webhooks/mutation.go | 7 +++ pkg/webhooks/validation.go | 12 +--- 7 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 pkg/engine/mutation_test.go rename pkg/webhooks/{utils.go => common.go} (94%) diff --git a/pkg/engine/context/evaluate.go b/pkg/engine/context/evaluate.go index 8716483c2a..67ba133dc2 100644 --- a/pkg/engine/context/evaluate.go +++ b/pkg/engine/context/evaluate.go @@ -2,7 +2,7 @@ package context import ( "github.com/golang/glog" - jmespath "github.com/jmespath/go-jmespath" + "github.com/jmespath/go-jmespath" ) //Query searches for query in the context diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index 5c677461f2..da8534feda 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -12,6 +12,7 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { startTime := time.Now() policy := policyContext.Policy resource := policyContext.NewResource + ctx := policyContext.Context // policy information func() { @@ -61,7 +62,7 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { // Process Overlay if rule.Mutation.Overlay != nil { var ruleResponse response.RuleResponse - ruleResponse, patchedResource = processOverlay(rule, patchedResource) + ruleResponse, patchedResource = processOverlay(ctx, rule, patchedResource) if ruleResponse.Success == true && ruleResponse.Patches == nil { // overlay pattern does not match the resource conditions glog.V(4).Infof(ruleResponse.Message) diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go new file mode 100644 index 0000000000..79b3d3c0a3 --- /dev/null +++ b/pkg/engine/mutation_test.go @@ -0,0 +1,89 @@ +package engine + +import ( + "encoding/json" + "reflect" + "testing" + + kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" + "github.com/nirmata/kyverno/pkg/engine/context" + "gotest.tools/assert" +) + +func Test_VariableSubstitutionOverlay(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "add-label" + }, + "spec": { + "rules": [ + { + "name": "add-name-label", + "match": { + "resources": { + "kinds": [ + "Pod" + ] + } + }, + "mutate": { + "overlay": { + "metadata": { + "labels": { + "appname": "{{resource.metadata.name}}" + } + } + } + } + } + ] + } + } + `) + rawResource := []byte(` + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "check-root-user" + }, + "spec": { + "containers": [ + { + "name": "check-root-user", + "image": "nginxinc/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true + } + } + ] + } + } + `) + expectedPatch := []byte(`{ "op": "add", "path": "/metadata/labels", "value": {"appname":"check-root-user"} }`) + + var policy kyverno.ClusterPolicy + json.Unmarshal(rawPolicy, &policy) + resourceUnstructured, err := ConvertToUnstructured(rawResource) + assert.NilError(t, err) + ctx := context.NewContext() + ctx.Add("resource", rawResource) + value, err := ctx.Query("resource.metadata.name") + t.Log(value) + if err != nil { + t.Error(err) + } + policyContext := PolicyContext{ + Policy: policy, + Context: ctx, + NewResource: *resourceUnstructured} + er := Mutate(policyContext) + t.Log(string(expectedPatch)) + t.Log(string(er.PolicyResponse.Rules[0].Patches[0])) + if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches[0]) { + t.Error("patches dont match") + } +} diff --git a/pkg/engine/overlay.go b/pkg/engine/overlay.go index 218dc8ddba..cac30f4d38 100644 --- a/pkg/engine/overlay.go +++ b/pkg/engine/overlay.go @@ -15,12 +15,14 @@ import ( jsonpatch "github.com/evanphx/json-patch" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" "github.com/nirmata/kyverno/pkg/engine/anchor" + "github.com/nirmata/kyverno/pkg/engine/context" + "github.com/nirmata/kyverno/pkg/engine/variables" "github.com/nirmata/kyverno/pkg/engine/response" "github.com/nirmata/kyverno/pkg/engine/validate" ) // processOverlay processes validation patterns on the resource -func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp response.RuleResponse, patchedResource unstructured.Unstructured) { +func processOverlay(ctx context.EvalInterface, rule kyverno.Rule, resource unstructured.Unstructured) (resp response.RuleResponse, patchedResource unstructured.Unstructured) { startTime := time.Now() glog.V(4).Infof("started applying overlay rule %q (%v)", rule.Name, startTime) resp.Name = rule.Name @@ -29,8 +31,13 @@ func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp resp.RuleStats.ProcessingTime = time.Since(startTime) glog.V(4).Infof("finished applying overlay rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime) }() + // substitute variables + // first pass we substitute all the JMESPATH substitution for the variable + // variable: {{}} + // if a JMESPATH fails, we dont return error but variable is substitured with nil and error log + overlay := variables.SubstituteVariables(ctx, rule.Mutation.Overlay) - patches, overlayerr := processOverlayPatches(resource.UnstructuredContent(), rule.Mutation.Overlay) + patches, overlayerr := processOverlayPatches(resource.UnstructuredContent(), overlay) // resource does not satisfy the overlay pattern, we don't apply this rule if !reflect.DeepEqual(overlayerr, overlayError{}) { switch overlayerr.statusCode { diff --git a/pkg/webhooks/utils.go b/pkg/webhooks/common.go similarity index 94% rename from pkg/webhooks/utils.go rename to pkg/webhooks/common.go index 6ca60cbccf..ec4d35a3d9 100644 --- a/pkg/webhooks/utils.go +++ b/pkg/webhooks/common.go @@ -3,6 +3,7 @@ package webhooks import ( "fmt" "strings" + "encoding/json" "github.com/golang/glog" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" @@ -10,6 +11,7 @@ import ( "github.com/nirmata/kyverno/pkg/engine/response" "k8s.io/api/admission/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + authenticationv1 "k8s.io/api/authentication/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -152,3 +154,12 @@ func convertToUnstructured(data []byte) (*unstructured.Unstructured, error) { } return resource, nil } + +func transformUser(userInfo authenticationv1.UserInfo) []byte { + data, err := json.Marshal(userInfo) + if err != nil { + glog.Errorf("failed to marshall resource %v: %v", userInfo, err) + return nil + } + return data +} diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index 4719ef8ff7..af44ee7959 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -4,6 +4,7 @@ import ( "github.com/golang/glog" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" engine "github.com/nirmata/kyverno/pkg/engine" + "github.com/nirmata/kyverno/pkg/engine/context" "github.com/nirmata/kyverno/pkg/engine/response" policyctr "github.com/nirmata/kyverno/pkg/policy" "github.com/nirmata/kyverno/pkg/utils" @@ -62,6 +63,12 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest, polic resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind}) resource.SetNamespace(request.Namespace) var engineResponses []response.EngineResponse + // build context + ctx := context.NewContext() + // load incoming resource into the context + ctx.Add("resource", request.Object.Raw) + ctx.Add("user", transformUser(request.UserInfo)) + policyContext := engine.PolicyContext{ NewResource: *resource, AdmissionInfo: engine.RequestInfo{ diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go index 67b1392fa3..e2d5f3f879 100644 --- a/pkg/webhooks/validation.go +++ b/pkg/webhooks/validation.go @@ -1,7 +1,6 @@ package webhooks import ( - "encoding/json" "reflect" "time" @@ -13,7 +12,6 @@ import ( policyctr "github.com/nirmata/kyverno/pkg/policy" "github.com/nirmata/kyverno/pkg/utils" v1beta1 "k8s.io/api/admission/v1beta1" - authenticationv1 "k8s.io/api/authentication/v1" ) // HandleValidation handles validating webhook admission request @@ -66,6 +64,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pol // load incoming resource into the context ctx.Add("resource", request.Object.Raw) ctx.Add("user", transformUser(request.UserInfo)) + policyContext := engine.PolicyContext{ NewResource: newR, OldResource: oldR, @@ -127,12 +126,3 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pol glog.V(4).Infof("report: %v %s/%s/%s", time.Since(reportTime), request.Kind, request.Namespace, request.Name) return true, "" } - -func transformUser(userInfo authenticationv1.UserInfo) []byte { - data, err := json.Marshal(userInfo) - if err != nil { - glog.Errorf("failed to marshall resource %v: %v", userInfo, err) - return nil - } - return data -}