diff --git a/pkg/engine/apiPath.go b/pkg/engine/apiPath.go index cca6ae47c6e..21bc6db274c 100644 --- a/pkg/engine/apiPath.go +++ b/pkg/engine/apiPath.go @@ -33,6 +33,8 @@ func NewAPIPath(path string) (*APIPath, error) { } if paths[0] == "api" { + + // /api/v1/namespaces if len(paths) == 3 { return &APIPath{ Root: paths[0], @@ -41,6 +43,7 @@ func NewAPIPath(path string) (*APIPath, error) { }, nil } + // /api/v1/namespaces/foo if len(paths) == 4 { return &APIPath{ Root: paths[0], @@ -50,7 +53,27 @@ func NewAPIPath(path string) (*APIPath, error) { }, nil } - return nil, fmt.Errorf("invalid /api/v1 path %s", path) + // /api/v1/namespaces/foo/pods + if len(paths) == 5 { + return &APIPath{ + Root: paths[0], + Group: paths[1], + Namespace: paths[3], + ResourceType: paths[4], + }, nil + } + + if len(paths) == 6 { + return &APIPath{ + Root: paths[0], + Group: paths[1], + Namespace: paths[3], + ResourceType: paths[4], + Name: paths[5], + }, nil + } + + return nil, fmt.Errorf("invalid API v1 path %s", path) } // /apis/GROUP/VERSION/RESOURCETYPE/ @@ -97,7 +120,7 @@ func NewAPIPath(path string) (*APIPath, error) { }, nil } - return nil, fmt.Errorf("invalid /apis path %s", path) + return nil, fmt.Errorf("invalid API path %s", path) } func (a *APIPath) String() string { @@ -116,7 +139,6 @@ func (a *APIPath) String() string { } } - result := "/" + strings.Join(paths, "/") result = strings.ReplaceAll(result, "//", "/") return result diff --git a/pkg/engine/context/context.go b/pkg/engine/context/context.go index cb72aeba222..7ab83439891 100644 --- a/pkg/engine/context/context.go +++ b/pkg/engine/context/context.go @@ -38,10 +38,11 @@ type EvalInterface interface { //Context stores the data resources as JSON type Context struct { - mu sync.RWMutex - jsonRaw []byte - builtInVars []string - log logr.Logger + mutex sync.RWMutex + jsonRaw []byte + jsonRawCheckpoint []byte + builtInVars []string + log logr.Logger } //NewContext returns a new context @@ -60,8 +61,8 @@ func NewContext(builtInVars ...string) *Context { // AddJSON merges json data func (ctx *Context) AddJSON(dataRaw []byte) error { var err error - ctx.mu.Lock() - defer ctx.mu.Unlock() + ctx.mutex.Lock() + defer ctx.mutex.Unlock() // merge json ctx.jsonRaw, err = jsonpatch.MergePatch(ctx.jsonRaw, dataRaw) if err != nil { @@ -179,3 +180,27 @@ func (ctx *Context) AddServiceAccount(userName string) error { return nil } + +// Checkpoint creates a copy of the internal state. +// Prior checkpoints will be overridden. +func (ctx *Context) Checkpoint() { + ctx.mutex.Lock() + defer ctx.mutex.Unlock() + + ctx.jsonRawCheckpoint = make([]byte, len(ctx.jsonRaw)) + copy(ctx.jsonRawCheckpoint, ctx.jsonRaw) +} + +// Restore restores internal state from a prior checkpoint, if one exists. +// If a prior checkpoint does not exist, the state will not be changed. +func (ctx *Context) Restore() { + ctx.mutex.Lock() + defer ctx.mutex.Unlock() + + if ctx.jsonRawCheckpoint == nil || len(ctx.jsonRawCheckpoint) == 0 { + return + } + + ctx.jsonRaw = make([]byte, len(ctx.jsonRawCheckpoint)) + copy(ctx.jsonRaw, ctx.jsonRawCheckpoint) +} \ No newline at end of file diff --git a/pkg/engine/context/evaluate.go b/pkg/engine/context/evaluate.go index 124731ced12..e740a9be560 100644 --- a/pkg/engine/context/evaluate.go +++ b/pkg/engine/context/evaluate.go @@ -28,8 +28,8 @@ func (ctx *Context) Query(query string) (interface{}, error) { return emptyResult, fmt.Errorf("incorrect query %s: %v", query, err) } // search - ctx.mu.RLock() - defer ctx.mu.RUnlock() + ctx.mutex.RLock() + defer ctx.mutex.RUnlock() var data interface{} if err := json.Unmarshal(ctx.jsonRaw, &data); err != nil { diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index 5c3a52bff3f..98f7629fa11 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -81,6 +81,9 @@ func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleR return nil } + policyContext.JSONContext.Checkpoint() + defer policyContext.JSONContext.Restore() + // add configmap json data to context if err := LoadContext(logger, rule.Context, resCache, policyContext); err != nil { logger.V(4).Info("cannot add configmaps to context", "reason", err.Error()) diff --git a/pkg/engine/jsonContext.go b/pkg/engine/jsonContext.go index 6dc929d2593..a83629fef85 100644 --- a/pkg/engine/jsonContext.go +++ b/pkg/engine/jsonContext.go @@ -8,6 +8,7 @@ import ( "github.com/jmespath/go-jmespath" kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/engine/context" + "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/resourcecache" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/dynamic/dynamiclister" @@ -44,7 +45,7 @@ func LoadContext(logger logr.Logger, contextEntries []kyverno.ContextEntry, resC } func loadAPIData(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyContext) error { - jsonData, err := fetchAPIData(entry, ctx) + jsonData, err := fetchAPIData(logger, entry, ctx) if err != nil { return err } @@ -94,14 +95,20 @@ func applyJMESPath(jmesPath string, jsonData []byte) (interface{}, error) { return jp.Search(data) } -func fetchAPIData(entry kyverno.ContextEntry, ctx *PolicyContext) ([]byte, error) { +func fetchAPIData(log logr.Logger, entry kyverno.ContextEntry, ctx *PolicyContext) ([]byte, error) { if entry.APICall == nil { - return nil, fmt.Errorf("missing APICall in context entry %v", entry) + return nil, fmt.Errorf("missing APICall in context entry %s %v", entry.Name, entry.APICall) } - p, err := NewAPIPath(entry.APICall.URLPath) + path, err := variables.SubstituteVars(log, ctx.JSONContext, entry.APICall.URLPath) if err != nil { - return nil, fmt.Errorf("failed to build API path for %v: %v", entry, err) + return nil, fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.APICall.URLPath, err) + } + + pathStr := path.(string) + p, err := NewAPIPath(pathStr) + if err != nil { + return nil, fmt.Errorf("failed to build API path for %s %v: %v", entry.Name, entry.APICall, err) } var jsonData []byte diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index cd3571fe16e..7ee20820387 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -44,6 +44,9 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { return } + policyContext.JSONContext.Checkpoint() + defer policyContext.JSONContext.Restore() + for _, rule := range policy.Spec.Rules { var ruleResponse response.RuleResponse logger := logger.WithValues("rule", rule.Name) @@ -66,9 +69,9 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) { logger.V(3).Info("matched mutate rule") - // add configmap json data to context + policyContext.JSONContext.Restore() if err := LoadContext(logger, rule.Context, resCache, policyContext); err != nil { - logger.V(2).Info("failed to add configmaps to context", "reason", err.Error()) + logger.V(2).Info("failed to load context", "reason", err.Error()) continue } diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 58a42917ed0..cbbc2fb1807 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -90,6 +90,9 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo return resp } + ctx.JSONContext.Checkpoint() + defer ctx.JSONContext.Restore() + for _, rule := range ctx.Policy.Spec.Rules { if !rule.HasValidate() { continue @@ -97,13 +100,13 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo log = log.WithValues("rule", rule.Name) - // add configmap json data to context - if err := LoadContext(log, rule.Context, ctx.ResourceCache, ctx); err != nil { - log.V(2).Info("failed to add configmaps to context", "reason", err.Error()) + if !matches(log, rule, ctx) { continue } - if !matches(log, rule, ctx) { + ctx.JSONContext.Restore() + if err := LoadContext(log, rule.Context, ctx.ResourceCache, ctx); err != nil { + log.V(2).Info("failed to load context", "reason", err.Error()) continue }