mirror of
https://github.com/kyverno/kyverno.git
synced 2025-04-09 18:52:36 +00:00
fix deny rules
Signed-off-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
parent
536b64bed1
commit
a905a61581
10 changed files with 431 additions and 33 deletions
|
@ -1,5 +1,7 @@
|
|||
package common
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// CopyMap creates a full copy of the target map
|
||||
func CopyMap(m map[string]interface{}) map[string]interface{} {
|
||||
mapCopy := make(map[string]interface{})
|
||||
|
@ -17,3 +19,22 @@ func CopySlice(s []interface{}) []interface{} {
|
|||
|
||||
return sliceCopy
|
||||
}
|
||||
|
||||
func ToMap(data interface{}) (map[string]interface{}, error) {
|
||||
if m, ok := data.(map[string]interface{}); ok {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mapData := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &mapData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapData, nil
|
||||
}
|
|
@ -188,6 +188,7 @@ func (ctx *Context) AddResourceAsObject(data interface{}) error {
|
|||
ctx.log.Error(err, "failed to marshal the resource")
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.AddJSON(objRaw)
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
|
|||
ruleResp := response.RuleResponse{
|
||||
Name: ruleCopy.Name,
|
||||
Type: utils.Validation.String(),
|
||||
Message: fmt.Sprintf("variable substitution failed for rule %s: %s", ruleCopy.Name, err.Error()),
|
||||
Message: fmt.Sprintf("variable substitution failed: %s", err.Error()),
|
||||
Status: response.RuleStatusPass,
|
||||
}
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
|
|||
JSONContext: ctx,
|
||||
NewResource: *resourceUnstructured}
|
||||
er := Mutate(policyContext)
|
||||
expectedErrorStr := "variable substitution failed for rule test-path-not-exist: Unknown key \"name1\" in path"
|
||||
expectedErrorStr := "variable substitution failed: Unknown key \"name1\" in path"
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Message, expectedErrorStr)
|
||||
}
|
||||
|
||||
|
|
|
@ -40,3 +40,18 @@ type PolicyContext struct {
|
|||
// NamespaceLabels stores the label of namespace to be processed by namespace selector
|
||||
NamespaceLabels map[string]string
|
||||
}
|
||||
|
||||
func (pc *PolicyContext) Copy() *PolicyContext {
|
||||
return &PolicyContext{
|
||||
Policy: pc.Policy,
|
||||
NewResource: pc.NewResource,
|
||||
OldResource: pc.OldResource,
|
||||
AdmissionInfo: pc.AdmissionInfo,
|
||||
Client: pc.Client,
|
||||
ExcludeGroupRole: pc.ExcludeGroupRole,
|
||||
ExcludeResourceFunc: pc.ExcludeResourceFunc,
|
||||
ResourceCache: pc.ResourceCache,
|
||||
JSONContext: pc.JSONContext,
|
||||
NamespaceLabels: pc.NamespaceLabels,
|
||||
}
|
||||
}
|
|
@ -19,11 +19,13 @@ func MatchPattern(logger logr.Logger, resource, pattern interface{}) (error, str
|
|||
ac := common.NewAnchorMap()
|
||||
elemPath, err := validateResourceElement(logger, resource, pattern, pattern, "/", ac)
|
||||
if err != nil {
|
||||
// if conditional or global anchors report errors, the rule does not apply to the resource
|
||||
if common.IsConditionalAnchorError(err.Error()) || common.IsGlobalAnchorError(err.Error()) {
|
||||
logger.V(3).Info(ac.AnchorError.Message)
|
||||
return ac.AnchorError.Error(), ""
|
||||
logger.V(3).Info("skipping resource as anchor does not apply", "msg", ac.AnchorError.Error())
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// check if an anchor defined in the policy rule is missing in the resource
|
||||
if ac.IsAnchorError() {
|
||||
logger.V(3).Info("missing anchor in resource")
|
||||
return err, ""
|
||||
|
|
|
@ -1506,7 +1506,7 @@ func TestConditionalAnchorWithMultiplePatterns(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
executeTestCase(t, testCase)
|
||||
testMatchPattern(t, testCase)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1518,17 +1518,24 @@ func Test_global_anchor(t *testing.T) {
|
|||
nilErr bool
|
||||
} {
|
||||
{
|
||||
name: "check global anchor",
|
||||
name: "check global anchor_skip",
|
||||
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`),
|
||||
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "imagePullPolicy": "Always"}]}}`),
|
||||
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx:v1", "imagePullPolicy": "Always"}]}}`),
|
||||
nilErr: true,
|
||||
},
|
||||
{
|
||||
name: "check global anchor_apply",
|
||||
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`),
|
||||
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx:latest", "imagePullPolicy": "Always"}]}}`),
|
||||
nilErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
executeTestCase(t, testCases[0])
|
||||
testMatchPattern(t, testCases[0])
|
||||
testMatchPattern(t, testCases[1])
|
||||
}
|
||||
|
||||
func executeTestCase(t *testing.T, testCase struct {name string;pattern []byte;resource []byte;nilErr bool}) {
|
||||
func testMatchPattern(t *testing.T, testCase struct {name string;pattern []byte;resource []byte;nilErr bool}) {
|
||||
var pattern, resource interface{}
|
||||
err := json.Unmarshal(testCase.pattern, &pattern)
|
||||
assert.NilError(t, err)
|
||||
|
@ -1543,4 +1550,4 @@ func executeTestCase(t *testing.T, testCase struct {name string;pattern []b
|
|||
err != nil,
|
||||
fmt.Sprintf("\ntest: %s\npattern: %s\nresource: %s\nmsg: %v", testCase.name, pattern, resource, err))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package engine
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/kyverno/kyverno/pkg/engine/common"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"reflect"
|
||||
|
@ -189,7 +190,7 @@ func (v *validator) validate() *response.RuleResponse {
|
|||
return ruleResponse(v.rule, "", response.RuleStatusSkip)
|
||||
}
|
||||
|
||||
if v.rule.Validation.Pattern != nil || v.rule.Validation.AnyPattern != nil {
|
||||
if v.pattern != nil || v.anyPattern != nil {
|
||||
if err = v.substitutePatterns(); err != nil {
|
||||
return ruleError(v.rule, "variable substitution failed", err)
|
||||
}
|
||||
|
@ -197,7 +198,7 @@ func (v *validator) validate() *response.RuleResponse {
|
|||
ruleResponse := v.validateResourceWithRule()
|
||||
return ruleResponse
|
||||
|
||||
} else if v.rule.Validation.Deny != nil {
|
||||
} else if v.deny != nil {
|
||||
ruleResponse := v.validateDeny()
|
||||
return ruleResponse
|
||||
}
|
||||
|
@ -235,27 +236,46 @@ func (v *validator) validateForEach() *response.RuleResponse {
|
|||
for _, e := range elements {
|
||||
v.ctx.JSONContext.Reset()
|
||||
|
||||
elementData := make(map[string]interface{})
|
||||
elementData["element"] = e
|
||||
jsonData, err := json.Marshal(elementData)
|
||||
if err != nil {
|
||||
return ruleError(v.rule, fmt.Sprintf("failed to marshall element %v", e), err)
|
||||
ctx := v.ctx.Copy()
|
||||
if err := addElementToContext(ctx, e); err != nil {
|
||||
v.log.Error(err, "failed to add element to context")
|
||||
return ruleError(v.rule, "failed to process foreach", err)
|
||||
}
|
||||
|
||||
if err := v.ctx.JSONContext.AddJSON(jsonData); err != nil {
|
||||
return ruleError(v.rule, fmt.Sprintf("failed add element (%s) to context", string(jsonData)), err)
|
||||
}
|
||||
|
||||
foreachValidator := newForeachValidator(v.log, v.ctx, v.rule)
|
||||
foreachValidator := newForeachValidator(v.log, ctx, v.rule)
|
||||
r := foreachValidator.validate()
|
||||
if r.Status != response.RuleStatusPass {
|
||||
if r == nil {
|
||||
v.log.Info("skipping rule due to empty result")
|
||||
|
||||
} else if r.Status == response.RuleStatusSkip {
|
||||
v.log.Info("skipping rule as preconditions were not met")
|
||||
|
||||
} else if r.Status != response.RuleStatusPass {
|
||||
msg := fmt.Sprintf("validation failed in foreach rule for %v", e)
|
||||
return ruleResponse(v.rule, msg, r.Status)
|
||||
}
|
||||
}
|
||||
|
||||
return ruleResponse(v.rule, "", response.RuleStatusPass)
|
||||
}
|
||||
|
||||
func addElementToContext(ctx *PolicyContext, e interface{}) error {
|
||||
data, err := common.ToMap(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u := unstructured.Unstructured{}
|
||||
u.SetUnstructuredContent(data)
|
||||
ctx.NewResource = u
|
||||
|
||||
if err := ctx.JSONContext.AddResourceAsObject(e); err != nil {
|
||||
return errors.Wrapf(err, "failed to add resource (%v) to JSON context", e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *validator) evaluateList(jmesPath string) ([]interface{}, error) {
|
||||
i, err := v.ctx.JSONContext.Query(jmesPath)
|
||||
if err != nil {
|
||||
|
@ -302,9 +322,9 @@ func (v *validator) checkPreconditions() (bool, error) {
|
|||
|
||||
func (v *validator) validateDeny() *response.RuleResponse {
|
||||
anyAllCond := v.deny.AnyAllConditions
|
||||
anyAllCond, err := variables.SubstituteAllInPreconditions(v.log, v.ctx.JSONContext, anyAllCond)
|
||||
anyAllCond, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, anyAllCond)
|
||||
if err != nil {
|
||||
return ruleError(v.rule, "failed to substitute variables in preconditions", err)
|
||||
return ruleError(v.rule, "failed to substitute variables in deny conditions", err)
|
||||
}
|
||||
|
||||
if err = v.substituteDeny(); err != nil {
|
||||
|
@ -557,7 +577,7 @@ func (v *validator) substituteDeny() error {
|
|||
}
|
||||
|
||||
func ruleError(rule *kyverno.Rule, msg string, err error) *response.RuleResponse {
|
||||
msg = fmt.Sprintf("%s for rule %s: %s", msg, rule.Name, err.Error())
|
||||
msg = fmt.Sprintf("%s: %s", msg, err.Error())
|
||||
return ruleResponse(rule, msg, response.RuleStatusError)
|
||||
}
|
||||
|
||||
|
|
|
@ -1479,7 +1479,7 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
|
|||
er := Validate(policyContext)
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusError)
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Message,
|
||||
"variable substitution failed for rule test-path-not-exist: Unknown key \"name1\" in path")
|
||||
"variable substitution failed: Unknown key \"name1\" in path")
|
||||
}
|
||||
|
||||
func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSubstitutionFails(t *testing.T) {
|
||||
|
@ -1570,7 +1570,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSu
|
|||
NewResource: *resourceUnstructured}
|
||||
er := Validate(policyContext)
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusError)
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed for rule test-path-not-exist: Unknown key \"name1\" in path")
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed: Unknown key \"name1\" in path")
|
||||
}
|
||||
|
||||
func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) {
|
||||
|
@ -1720,7 +1720,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test
|
|||
NewResource: *resourceUnstructured}
|
||||
er := Validate(policyContext)
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusError)
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed for rule test-path-not-exist: Unknown key \"name1\" in path")
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed: Unknown key \"name1\" in path")
|
||||
}
|
||||
|
||||
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatternSatisfy(t *testing.T) {
|
||||
|
@ -1932,8 +1932,8 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
|
|||
policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace must be the same as metadata.namespace","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`),
|
||||
// referred variable path not present
|
||||
resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team"},"prune":true,"validation":"client"}}`),
|
||||
expectedResults: []response.RuleStatus{response.RuleStatusPass, response.RuleStatusFail},
|
||||
expectedMessages: []string{"validation rule 'serviceAccountName' passed.", "spec.sourceRef.namespace must be the same as metadata.namespace"},
|
||||
expectedResults: []response.RuleStatus{response.RuleStatusPass, response.RuleStatusError},
|
||||
expectedMessages: []string{"validation rule 'serviceAccountName' passed.", "failed to substitute variables in deny conditions: Unknown key \"namespace\" in path"},
|
||||
},
|
||||
{
|
||||
name: "resource-with-violation",
|
||||
|
@ -1970,7 +1970,7 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
|
|||
er := Validate(policyContext)
|
||||
|
||||
for i, rule := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, er.PolicyResponse.Rules[i].Status, test.expectedResults[i])
|
||||
assert.Equal(t, er.PolicyResponse.Rules[i].Status, test.expectedResults[i], "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedResults[i].String(), er.PolicyResponse.Rules[i].Status.String())
|
||||
assert.Equal(t, er.PolicyResponse.Rules[i].Message, test.expectedMessages[i], "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedMessages[i], rule.Message)
|
||||
}
|
||||
}
|
||||
|
@ -2418,3 +2418,335 @@ func Test_StringInDenyCondition(t *testing.T) {
|
|||
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: ctx})
|
||||
assert.Assert(t, er.IsSuccessful())
|
||||
}
|
||||
|
||||
func Test_foreach_container_pass(t *testing.T) {
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": { "template": { "spec": {
|
||||
"containers": [
|
||||
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
|
||||
{"name": "pod2-valid", "image": "nginx/nginx:v2"},
|
||||
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
|
||||
]
|
||||
}}}}`)
|
||||
|
||||
policyraw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "test-path-not-exist",
|
||||
"match": {"resources": { "kinds": [ "Deployment" ] } },
|
||||
"validate": {
|
||||
"foreach": {
|
||||
"list": "request.object.spec.template.spec.containers",
|
||||
"pattern": {
|
||||
"name": "*-valid"
|
||||
}
|
||||
}
|
||||
}}]}}`)
|
||||
|
||||
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusPass)
|
||||
}
|
||||
|
||||
func Test_foreach_container_fail(t *testing.T) {
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": { "template": { "spec": {
|
||||
"containers": [
|
||||
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
|
||||
{"name": "pod2-invalid", "image": "nginx/nginx:v2"},
|
||||
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
|
||||
]
|
||||
}}}}`)
|
||||
|
||||
policyraw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "test",
|
||||
"match": {"resources": { "kinds": [ "Deployment" ] } },
|
||||
"validate": {
|
||||
"foreach": {
|
||||
"list": "request.object.spec.template.spec.containers",
|
||||
"pattern": {
|
||||
"name": "*-valid"
|
||||
}
|
||||
}
|
||||
}}]}}`)
|
||||
|
||||
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
|
||||
}
|
||||
|
||||
func Test_foreach_container_deny_fail(t *testing.T) {
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": { "template": { "spec": {
|
||||
"containers": [
|
||||
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
|
||||
{"name": "pod2-invalid", "image": "docker.io/nginx/nginx:v2"},
|
||||
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
|
||||
]
|
||||
}}}}`)
|
||||
|
||||
policyraw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "test",
|
||||
"match": {"resources": { "kinds": [ "Deployment" ] } },
|
||||
"validate": {
|
||||
"foreach": {
|
||||
"list": "request.object.spec.template.spec.containers",
|
||||
"deny": {
|
||||
"conditions": [
|
||||
{"key": "{{ regex_match('{{request.object.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
|
||||
]
|
||||
}
|
||||
}
|
||||
}}]}}`)
|
||||
|
||||
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
|
||||
}
|
||||
|
||||
func Test_foreach_container_deny_success(t *testing.T) {
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": { "template": { "spec": {
|
||||
"containers": [
|
||||
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
|
||||
{"name": "pod2-invalid", "image": "nginx/nginx:v2"},
|
||||
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
|
||||
]
|
||||
}}}}`)
|
||||
|
||||
policyraw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "test",
|
||||
"match": {"resources": { "kinds": [ "Deployment" ] } },
|
||||
"validate": {
|
||||
"foreach": {
|
||||
"list": "request.object.spec.template.spec.containers",
|
||||
"deny": {
|
||||
"conditions": [
|
||||
{"key": "{{ regex_match('{{request.object.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
|
||||
]
|
||||
}
|
||||
}
|
||||
}}]}}`)
|
||||
|
||||
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
|
||||
}
|
||||
|
||||
func Test_foreach_container_deny_error(t *testing.T) {
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": { "template": { "spec": {
|
||||
"containers": [
|
||||
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
|
||||
{"name": "pod2-invalid", "image": "nginx/nginx:v2"},
|
||||
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
|
||||
]
|
||||
}}}}`)
|
||||
|
||||
policyraw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "test",
|
||||
"match": {"resources": { "kinds": [ "Deployment" ] } },
|
||||
"validate": {
|
||||
"foreach": {
|
||||
"list": "request.object.spec.template.spec.containers",
|
||||
"deny": {
|
||||
"conditions": [
|
||||
{"key": "{{ regex_match_INVALID('{{request.object.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
|
||||
]
|
||||
}
|
||||
}
|
||||
}}]}}`)
|
||||
|
||||
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusError)
|
||||
}
|
||||
|
||||
func Test_foreach_context_preconditions(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": { "template": { "spec": {
|
||||
"containers": [
|
||||
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
|
||||
{"name": "pod2-valid", "image": "nginx/nginx:v2"},
|
||||
{"name": "pod3-valid", "image": "nginx/nginx:v3"},
|
||||
{"name": "pod4-valid", "image": "nginx/nginx:v4"}
|
||||
]
|
||||
}}}}`)
|
||||
|
||||
policyraw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "test",
|
||||
"match": {"resources": { "kinds": [ "Deployment" ] } },
|
||||
"validate": {
|
||||
"foreach": {
|
||||
"list": "request.object.spec.template.spec.containers",
|
||||
"context": [{"name": "tags", "configMap": {"name": "mycmap", "namespace": "default"}}],
|
||||
"preconditions": { "all": [
|
||||
{
|
||||
"key": "{{request.object.name}}",
|
||||
"operator": "Equals",
|
||||
"value": "pod1-valid | pod2-valid | pod3-valid"
|
||||
}
|
||||
]},
|
||||
"deny": {
|
||||
"conditions": [
|
||||
{"key": "images.{{ request.object.name }}.tag", "operator": "NotEquals", "value": "{{ tags.data.{{ request.object.name }} }}"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}}]}}`)
|
||||
|
||||
configMapVariableContext := store.Context{
|
||||
Policies: []store.Policy{
|
||||
{
|
||||
Name: "test",
|
||||
Rules: []store.Rule{
|
||||
{
|
||||
Name: "test",
|
||||
Values: map[string]string{
|
||||
"tags.data.pod1-valid": "v1",
|
||||
"tags.data.pod2-valid": "v2",
|
||||
"tags.data.pod3-valid": "v3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
store.SetContext(configMapVariableContext)
|
||||
store.SetMock(true)
|
||||
|
||||
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusPass)
|
||||
}
|
||||
|
||||
func Test_foreach_context_preconditions_fail(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": { "template": { "spec": {
|
||||
"containers": [
|
||||
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
|
||||
{"name": "pod2-valid", "image": "nginx/nginx:v2"},
|
||||
{"name": "pod3-valid", "image": "nginx/nginx:v3"},
|
||||
{"name": "pod4-valid", "image": "nginx/nginx:v4"}
|
||||
]
|
||||
}}}}`)
|
||||
|
||||
policyraw := []byte(`{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "test",
|
||||
"match": {"resources": { "kinds": [ "Deployment" ] } },
|
||||
"validate": {
|
||||
"foreach": {
|
||||
"list": "request.object.spec.template.spec.containers",
|
||||
"context": [{"name": "tags", "configMap": {"name": "mycmap", "namespace": "default"}}],
|
||||
"preconditions": { "all": [
|
||||
{
|
||||
"key": "{{request.object.name}}",
|
||||
"operator": "Equals",
|
||||
"value": "pod1-valid | pod2-valid | pod3-valid"
|
||||
}
|
||||
]},
|
||||
"deny": {
|
||||
"conditions": [
|
||||
{"key": "images.{{ request.object.name }}.tag", "operator": "NotEquals", "value": "{{ tags.data.{{ request.object.name }} }}"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}}]}}`)
|
||||
|
||||
configMapVariableContext := store.Context{
|
||||
Policies: []store.Policy{
|
||||
{
|
||||
Name: "test",
|
||||
Rules: []store.Rule{
|
||||
{
|
||||
Name: "test",
|
||||
Values: map[string]string{
|
||||
"tags.data.pod1-valid": "v1",
|
||||
"tags.data.pod2-valid": "v22",
|
||||
"tags.data.pod3-valid": "v3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
store.SetContext(configMapVariableContext)
|
||||
store.SetMock(true)
|
||||
|
||||
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
|
||||
}
|
||||
|
||||
func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string, status response.RuleStatus) {
|
||||
var policy kyverno.ClusterPolicy
|
||||
assert.NilError(t, json.Unmarshal(policyraw, &policy))
|
||||
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctx := context.NewContext()
|
||||
err = ctx.AddResource(resourceRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
policyContext := &PolicyContext{
|
||||
Policy: policy,
|
||||
JSONContext: ctx,
|
||||
NewResource: *resourceUnstructured}
|
||||
er := Validate(policyContext)
|
||||
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, status)
|
||||
if msg != "" {
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Message, msg)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ func UntypedToRule(untyped interface{}) (kyverno.Rule, error) {
|
|||
func substituteAll(log logr.Logger, ctx context.EvalInterface, document interface{}, resolver VariableResolver) (_ interface{}, err error) {
|
||||
document, err = substituteReferences(log, document)
|
||||
if err != nil {
|
||||
return kyverno.Rule{}, err
|
||||
return document, err
|
||||
}
|
||||
|
||||
return substituteVars(log, ctx, document, resolver)
|
||||
|
|
Loading…
Add table
Reference in a new issue