mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
522 adding force mutate function
This commit is contained in:
parent
7aa1e1515b
commit
4db0cf7a87
4 changed files with 133 additions and 69 deletions
|
@ -116,7 +116,7 @@ func processOverlayPatches(resource, overlay interface{}) ([][]byte, overlayErro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
patchBytes, err := mutateResourceWithOverlay(resource, overlay)
|
patchBytes, err := MutateResourceWithOverlay(resource, overlay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return patchBytes, newOverlayError(overlayFailure, err.Error())
|
return patchBytes, newOverlayError(overlayFailure, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -124,8 +124,8 @@ func processOverlayPatches(resource, overlay interface{}) ([][]byte, overlayErro
|
||||||
return patchBytes, overlayError{}
|
return patchBytes, overlayError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mutateResourceWithOverlay is a start of overlaying process
|
// MutateResourceWithOverlay is a start of overlaying process
|
||||||
func mutateResourceWithOverlay(resource, pattern interface{}) ([][]byte, error) {
|
func MutateResourceWithOverlay(resource, pattern interface{}) ([][]byte, error) {
|
||||||
// It assumes that mutation is started from root, so "/" is passed
|
// It assumes that mutation is started from root, so "/" is passed
|
||||||
return applyOverlay(resource, pattern, "/")
|
return applyOverlay(resource, pattern, "/")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine/utils"
|
||||||
|
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/mutate"
|
"github.com/nirmata/kyverno/pkg/engine/mutate"
|
||||||
|
@ -122,6 +127,71 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
|
||||||
resp.PatchedResource = patchedResource
|
resp.PatchedResource = patchedResource
|
||||||
return resp
|
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) {
|
func incrementAppliedRuleCount(resp *response.EngineResponse) {
|
||||||
resp.PolicyResponse.RulesAppliedCount++
|
resp.PolicyResponse.RulesAppliedCount++
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
@ -66,28 +68,45 @@ func (c *crdSync) sync() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteCRDFromPreviousSync()
|
||||||
|
|
||||||
for _, crd := range crds.Items {
|
for _, crd := range crds.Items {
|
||||||
var crdDefinition crdDefinition
|
parseCRD(crd)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -29,21 +29,19 @@ var openApiGlobalState struct {
|
||||||
document *openapi_v2.Document
|
document *openapi_v2.Document
|
||||||
definitions map[string]*openapi_v2.Schema
|
definitions map[string]*openapi_v2.Schema
|
||||||
kindToDefinitionName map[string]string
|
kindToDefinitionName map[string]string
|
||||||
|
crdList []string
|
||||||
models proto.Models
|
models proto.Models
|
||||||
isSet bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if !openApiGlobalState.isSet {
|
defaultDoc, err := getSchemaDocument()
|
||||||
defaultDoc, err := getSchemaDocument()
|
if err != nil {
|
||||||
if err != nil {
|
panic(err)
|
||||||
panic(err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
err = useOpenApiDocument(defaultDoc)
|
err = useOpenApiDocument(defaultDoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +49,6 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error {
|
||||||
openApiGlobalState.mutex.RLock()
|
openApiGlobalState.mutex.RLock()
|
||||||
defer openApiGlobalState.mutex.RUnlock()
|
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)
|
var kindToRules = make(map[string][]v1.Rule)
|
||||||
for _, rule := range policy.Spec.Rules {
|
for _, rule := range policy.Spec.Rules {
|
||||||
if rule.HasMutate() {
|
if rule.HasMutate() {
|
||||||
|
@ -73,7 +66,7 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for kind, rules := range kindToRules {
|
for kind, rules := range kindToRules {
|
||||||
newPolicy := policy
|
newPolicy := *policy.DeepCopy()
|
||||||
newPolicy.Spec.Rules = rules
|
newPolicy.Spec.Rules = rules
|
||||||
resource, _ := generateEmptyResource(openApiGlobalState.definitions[openApiGlobalState.kindToDefinitionName[kind]]).(map[string]interface{})
|
resource, _ := generateEmptyResource(openApiGlobalState.definitions[openApiGlobalState.kindToDefinitionName[kind]]).(map[string]interface{})
|
||||||
if resource == nil {
|
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)
|
glog.V(4).Infof("Failed to load service account in context:%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
policyContext := engine.PolicyContext{
|
patchedResource, err := engine.ForceMutate(ctx, *newPolicy.DeepCopy(), newResource)
|
||||||
Policy: newPolicy,
|
if err != nil {
|
||||||
NewResource: newResource,
|
return err
|
||||||
Context: ctx,
|
|
||||||
}
|
}
|
||||||
resp := engine.Mutate(policyContext)
|
err = ValidateResource(*patchedResource.DeepCopy(), kind)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -113,22 +95,11 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error {
|
||||||
return nil
|
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 {
|
func ValidateResource(patchedResource unstructured.Unstructured, kind string) error {
|
||||||
openApiGlobalState.mutex.RLock()
|
openApiGlobalState.mutex.RLock()
|
||||||
defer openApiGlobalState.mutex.RUnlock()
|
defer openApiGlobalState.mutex.RUnlock()
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !openApiGlobalState.isSet {
|
|
||||||
glog.V(4).Info("Cannot Validate resource: Validation global state not set")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
kind = openApiGlobalState.kindToDefinitionName[kind]
|
kind = openApiGlobalState.kindToDefinitionName[kind]
|
||||||
schema := openApiGlobalState.models.LookupModel(kind)
|
schema := openApiGlobalState.models.LookupModel(kind)
|
||||||
if schema == nil {
|
if schema == nil {
|
||||||
|
@ -171,8 +142,6 @@ func useOpenApiDocument(customDoc *openapi_v2.Document) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
openApiGlobalState.isSet = true
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +155,12 @@ func getSchemaDocument() (*openapi_v2.Document, error) {
|
||||||
return openapi_v2.NewDocument(spec, compiler.NewContext("$root", nil))
|
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{} {
|
func generateEmptyResource(kindSchema *openapi_v2.Schema) interface{} {
|
||||||
|
|
||||||
types := kindSchema.GetType().GetValue()
|
types := kindSchema.GetType().GetValue()
|
||||||
|
|
Loading…
Add table
Reference in a new issue