diff --git a/pkg/engine/mutate/overlay.go b/pkg/engine/mutate/overlay.go index 21e5e7b77e..3b76f30c74 100644 --- a/pkg/engine/mutate/overlay.go +++ b/pkg/engine/mutate/overlay.go @@ -116,7 +116,7 @@ func processOverlayPatches(resource, overlay interface{}) ([][]byte, overlayErro } } - patchBytes, err := mutateResourceWithOverlay(resource, overlay) + patchBytes, err := MutateResourceWithOverlay(resource, overlay) if err != nil { return patchBytes, newOverlayError(overlayFailure, err.Error()) } @@ -124,8 +124,8 @@ func processOverlayPatches(resource, overlay interface{}) ([][]byte, overlayErro return patchBytes, overlayError{} } -// mutateResourceWithOverlay is a start of overlaying process -func mutateResourceWithOverlay(resource, pattern interface{}) ([][]byte, error) { +// MutateResourceWithOverlay is a start of overlaying process +func MutateResourceWithOverlay(resource, pattern interface{}) ([][]byte, error) { // It assumes that mutation is started from root, so "/" is passed return applyOverlay(resource, pattern, "/") } diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index d1fab46211..98a63eb268 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -1,10 +1,15 @@ package engine import ( + "fmt" "reflect" "strings" "time" + "github.com/nirmata/kyverno/pkg/engine/utils" + + "github.com/nirmata/kyverno/pkg/engine/context" + "github.com/golang/glog" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" "github.com/nirmata/kyverno/pkg/engine/mutate" @@ -122,6 +127,71 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { resp.PatchedResource = patchedResource return resp } + +func mutateResourceWithOverlay(resource unstructured.Unstructured, overlay interface{}) (unstructured.Unstructured, error) { + patches, err := mutate.MutateResourceWithOverlay(resource.UnstructuredContent(), overlay) + if err != nil { + return unstructured.Unstructured{}, err + } + if len(patches) == 0 { + return resource, nil + } + + // convert to RAW + resourceRaw, err := resource.MarshalJSON() + if err != nil { + return unstructured.Unstructured{}, err + } + + var patchResource []byte + patchResource, err = utils.ApplyPatches(resourceRaw, patches) + if err != nil { + return unstructured.Unstructured{}, err + } + + resource = unstructured.Unstructured{} + err = resource.UnmarshalJSON(patchResource) + if err != nil { + return unstructured.Unstructured{}, err + } + + return resource, nil +} + +// ForceMutate does not check any conditions, it simply mutates the given resource +func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (unstructured.Unstructured, error) { + var err error + for _, rule := range policy.Spec.Rules { + if !rule.HasMutate() { + continue + } + + mutation := rule.Mutation.DeepCopy() + + if mutation.Overlay != nil { + overlay := mutation.Overlay + if overlay, err = variables.SubstituteVars(ctx, overlay); err != nil { + return unstructured.Unstructured{}, err + } + + resource, err = mutateResourceWithOverlay(resource, overlay) + if err != nil { + return unstructured.Unstructured{}, fmt.Errorf("could not mutate resource with overlay on rule %v:%v", rule.Name, err) + } + } + + if rule.Mutation.Patches != nil { + var resp response.RuleResponse + resp, resource = mutate.ProcessPatches(rule, resource) + if !resp.Success { + return unstructured.Unstructured{}, fmt.Errorf(resp.Message) + } + } + } + + return resource, nil +} + func incrementAppliedRuleCount(resp *response.EngineResponse) { resp.PolicyResponse.RulesAppliedCount++ } diff --git a/pkg/openapi/crdSync.go b/pkg/openapi/crdSync.go index 955a8abb28..da428c2955 100644 --- a/pkg/openapi/crdSync.go +++ b/pkg/openapi/crdSync.go @@ -4,6 +4,8 @@ import ( "encoding/json" "time" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/golang/glog" "gopkg.in/yaml.v2" @@ -66,28 +68,45 @@ func (c *crdSync) sync() { return } + deleteCRDFromPreviousSync() + for _, crd := range crds.Items { - var crdDefinition crdDefinition - crdRaw, _ := json.Marshal(crd.Object) - _ = json.Unmarshal(crdRaw, &crdDefinition) - - crdName := crdDefinition.Spec.Names.Kind - if len(crdDefinition.Spec.Versions) < 1 { - glog.V(4).Infof("could not parse crd schema, no versions present") - continue - } - - var schema yaml.MapSlice - schemaRaw, _ := json.Marshal(crdDefinition.Spec.Versions[0].Schema.OpenAPIV3Schema) - _ = yaml.Unmarshal(schemaRaw, &schema) - - parsedSchema, err := openapi_v2.NewSchema(schema, compiler.NewContext("schema", nil)) - if err != nil { - glog.V(4).Infof("could not parse crd schema:%v", err) - continue - } - - openApiGlobalState.kindToDefinitionName[crdName] = crdName - openApiGlobalState.definitions[crdName] = parsedSchema + parseCRD(crd) } } + +func deleteCRDFromPreviousSync() { + for _, crd := range openApiGlobalState.crdList { + delete(openApiGlobalState.kindToDefinitionName, crd) + delete(openApiGlobalState.definitions, crd) + } + + openApiGlobalState.crdList = []string{} +} + +func parseCRD(crd unstructured.Unstructured) { + var crdDefinition crdDefinition + crdRaw, _ := json.Marshal(crd.Object) + _ = json.Unmarshal(crdRaw, &crdDefinition) + + crdName := crdDefinition.Spec.Names.Kind + if len(crdDefinition.Spec.Versions) < 1 { + glog.V(4).Infof("could not parse crd schema, no versions present") + return + } + + var schema yaml.MapSlice + schemaRaw, _ := json.Marshal(crdDefinition.Spec.Versions[0].Schema.OpenAPIV3Schema) + _ = yaml.Unmarshal(schemaRaw, &schema) + + parsedSchema, err := openapi_v2.NewSchema(schema, compiler.NewContext("schema", nil)) + if err != nil { + glog.V(4).Infof("could not parse crd schema:%v", err) + return + } + + openApiGlobalState.crdList = append(openApiGlobalState.crdList, crdName) + + openApiGlobalState.kindToDefinitionName[crdName] = crdName + openApiGlobalState.definitions[crdName] = parsedSchema +} diff --git a/pkg/openapi/validation.go b/pkg/openapi/validation.go index 38b58e92e0..c8fc9084b5 100644 --- a/pkg/openapi/validation.go +++ b/pkg/openapi/validation.go @@ -29,21 +29,19 @@ var openApiGlobalState struct { document *openapi_v2.Document definitions map[string]*openapi_v2.Schema kindToDefinitionName map[string]string + crdList []string models proto.Models - isSet bool } func init() { - if !openApiGlobalState.isSet { - defaultDoc, err := getSchemaDocument() - if err != nil { - panic(err) - } + defaultDoc, err := getSchemaDocument() + if err != nil { + panic(err) + } - err = useOpenApiDocument(defaultDoc) - if err != nil { - panic(err) - } + err = useOpenApiDocument(defaultDoc) + if err != nil { + panic(err) } } @@ -51,11 +49,6 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error { openApiGlobalState.mutex.RLock() defer openApiGlobalState.mutex.RUnlock() - if !openApiGlobalState.isSet { - glog.V(4).Info("Cannot Validate policy: Validation global state not set") - return nil - } - var kindToRules = make(map[string][]v1.Rule) for _, rule := range policy.Spec.Rules { if rule.HasMutate() { @@ -73,7 +66,7 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error { } for kind, rules := range kindToRules { - newPolicy := policy + newPolicy := *policy.DeepCopy() newPolicy.Spec.Rules = rules resource, _ := generateEmptyResource(openApiGlobalState.definitions[openApiGlobalState.kindToDefinitionName[kind]]).(map[string]interface{}) if resource == nil { @@ -89,22 +82,11 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error { glog.V(4).Infof("Failed to load service account in context:%v", err) } - policyContext := engine.PolicyContext{ - Policy: newPolicy, - NewResource: newResource, - Context: ctx, + patchedResource, err := engine.ForceMutate(ctx, *newPolicy.DeepCopy(), newResource) + if err != nil { + return err } - resp := engine.Mutate(policyContext) - if len(resp.GetSuccessRules()) != len(rules) { - var errMessages []string - for _, rule := range resp.PolicyResponse.Rules { - if !rule.Success { - errMessages = append(errMessages, fmt.Sprintf("Invalid rule : %v, %v", rule.Name, rule.Message)) - } - } - return fmt.Errorf(strings.Join(errMessages, "\n")) - } - err = ValidateResource(*resp.PatchedResource.DeepCopy(), kind) + err = ValidateResource(*patchedResource.DeepCopy(), kind) if err != nil { return err } @@ -113,22 +95,11 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error { return nil } -// For crd, we do not store definition in document -func getSchemaFromDefinitions(kind string) (proto.Schema, error) { - path := proto.NewPath(kind) - return (&proto.Definitions{}).ParseSchema(openApiGlobalState.definitions[kind], &path) -} - func ValidateResource(patchedResource unstructured.Unstructured, kind string) error { openApiGlobalState.mutex.RLock() defer openApiGlobalState.mutex.RUnlock() var err error - if !openApiGlobalState.isSet { - glog.V(4).Info("Cannot Validate resource: Validation global state not set") - return nil - } - kind = openApiGlobalState.kindToDefinitionName[kind] schema := openApiGlobalState.models.LookupModel(kind) if schema == nil { @@ -171,8 +142,6 @@ func useOpenApiDocument(customDoc *openapi_v2.Document) error { return err } - openApiGlobalState.isSet = true - return nil } @@ -186,6 +155,12 @@ func getSchemaDocument() (*openapi_v2.Document, error) { return openapi_v2.NewDocument(spec, compiler.NewContext("$root", nil)) } +// For crd, we do not store definition in document +func getSchemaFromDefinitions(kind string) (proto.Schema, error) { + path := proto.NewPath(kind) + return (&proto.Definitions{}).ParseSchema(openApiGlobalState.definitions[kind], &path) +} + func generateEmptyResource(kindSchema *openapi_v2.Schema) interface{} { types := kindSchema.GetType().GetValue()