Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redo changes reverted by merge #1526

Merged
merged 2 commits into from Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 25 additions & 3 deletions pkg/engine/apiPath.go
Expand Up @@ -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],
Expand All @@ -41,6 +43,7 @@ func NewAPIPath(path string) (*APIPath, error) {
}, nil
}

// /api/v1/namespaces/foo
if len(paths) == 4 {
return &APIPath{
Root: paths[0],
Expand All @@ -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/
Expand Down Expand Up @@ -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 {
Expand All @@ -116,7 +139,6 @@ func (a *APIPath) String() string {
}
}


result := "/" + strings.Join(paths, "/")
result = strings.ReplaceAll(result, "//", "/")
return result
Expand Down
38 changes: 31 additions & 7 deletions pkg/engine/context/context.go
Expand Up @@ -38,17 +38,17 @@ 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
// builtInVars is the list of known variables (e.g. serviceAccountName)
func NewContext(builtInVars ...string) *Context {
ctx := Context{
// data: map[string]interface{}{},
jsonRaw: []byte(`{}`), // empty json struct
builtInVars: builtInVars,
log: log.Log.WithName("context"),
Expand All @@ -60,8 +60,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 {
Expand Down Expand Up @@ -179,3 +179,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)
}
4 changes: 2 additions & 2 deletions pkg/engine/context/evaluate.go
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions pkg/engine/generation.go
Expand Up @@ -81,6 +81,9 @@ func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleR
return nil
}

policyContext.JSONContext.Checkpoint()
defer policyContext.JSONContext.Restore()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on why we need to copy/restore context per rule?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each rule can have separate variables in the context. For example, if a rule loads a config map, it is meant to be used as part of processing that rule and should not be available to subsequent rules.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each rule can have separate variables in the context. For example, if a rule loads a config map, it is meant to be used as part of processing that rule and should not be available to subsequent rules.


// 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())
Expand Down
17 changes: 12 additions & 5 deletions pkg/engine/jsonContext.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions pkg/engine/mutation.go
Expand Up @@ -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)
Expand All @@ -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
}

Expand Down
11 changes: 7 additions & 4 deletions pkg/engine/validation.go
Expand Up @@ -90,20 +90,23 @@ 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
}

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
}

Expand Down
30 changes: 15 additions & 15 deletions pkg/engine/validation_test.go
Expand Up @@ -127,7 +127,7 @@ func TestValidate_image_tag_fail(t *testing.T) {
"validation rule 'validate-tag' passed.",
"validation error: imagePullPolicy 'Always' required with tag 'latest'. Rule validate-latest failed at path /spec/containers/0/imagePullPolicy/",
}
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])
}
Expand Down Expand Up @@ -226,7 +226,7 @@ func TestValidate_image_tag_pass(t *testing.T) {
"validation rule 'validate-tag' passed.",
"validation rule 'validate-latest' passed.",
}
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])
}
Expand Down Expand Up @@ -300,7 +300,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
assert.Assert(t, !er.IsSuccessful())

msgs := []string{"validation error: A namespace is required. Rule check-default-namespace[0] failed at path /metadata/namespace/. Rule check-default-namespace[1] failed at path /metadata/namespace/."}
Expand Down Expand Up @@ -383,7 +383,7 @@ func TestValidate_host_network_port(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation error: Host network and port are not allowed. Rule validate-host-network-port failed at path /spec/containers/0/ports/0/hostPort/"}

for index, r := range er.PolicyResponse.Rules {
Expand Down Expand Up @@ -473,7 +473,7 @@ func TestValidate_anchor_arraymap_pass(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation rule 'validate-host-path' passed."}

for index, r := range er.PolicyResponse.Rules {
Expand Down Expand Up @@ -561,7 +561,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) {
assert.NilError(t, err)
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation error: Host path '/var/lib/' is not allowed. Rule validate-host-path failed at path /spec/volumes/0/hostPath/path/"}

for index, r := range er.PolicyResponse.Rules {
Expand Down Expand Up @@ -631,7 +631,7 @@ func TestValidate_anchor_map_notfound(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation rule 'pod rule 2' passed."}

for index, r := range er.PolicyResponse.Rules {
Expand Down Expand Up @@ -704,7 +704,7 @@ func TestValidate_anchor_map_found_valid(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation rule 'pod rule 2' passed."}

for index, r := range er.PolicyResponse.Rules {
Expand Down Expand Up @@ -778,7 +778,7 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation error: pod: validate run as non root user. Rule pod rule 2 failed at path /spec/securityContext/runAsNonRoot/"}

for index, r := range er.PolicyResponse.Rules {
Expand Down Expand Up @@ -853,7 +853,7 @@ func TestValidate_AnchorList_pass(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation rule 'pod image rule' passed."}

for index, r := range er.PolicyResponse.Rules {
Expand Down Expand Up @@ -928,7 +928,7 @@ func TestValidate_AnchorList_fail(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
assert.Assert(t, !er.IsSuccessful())
}

Expand Down Expand Up @@ -998,7 +998,7 @@ func TestValidate_existenceAnchor_fail(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
assert.Assert(t, !er.IsSuccessful())
}

Expand Down Expand Up @@ -1068,7 +1068,7 @@ func TestValidate_existenceAnchor_pass(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation rule 'pod image rule' passed."}

for index, r := range er.PolicyResponse.Rules {
Expand Down Expand Up @@ -1156,7 +1156,7 @@ func TestValidate_negationAnchor_deny(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation error: Host path is not allowed. Rule validate-host-path failed at path /spec/volumes/0/hostPath/"}

for index, r := range er.PolicyResponse.Rules {
Expand Down Expand Up @@ -1243,7 +1243,7 @@ func TestValidate_negationAnchor_pass(t *testing.T) {

resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
msgs := []string{"validation rule 'validate-host-path' passed."}

for index, r := range er.PolicyResponse.Rules {
Expand Down