Skip to content

Commit

Permalink
Kyverno CLI - Namespace Selector (#1669)
Browse files Browse the repository at this point in the history
* added struct for namespace selector

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added logic for namespace selector

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added test case

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* improved code

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>
  • Loading branch information
NoSkillGirl committed Mar 9, 2021
1 parent 4f37988 commit af4b85d
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 14 deletions.
11 changes: 9 additions & 2 deletions pkg/kyverno/apply/command.go
Expand Up @@ -93,6 +93,13 @@ To apply policy with variables:
values:
<variable1 in policy2>: <value>
<variable2 in policy2>: <value>
namespaceSelector:
- name: <namespace1 name>
labels:
<label key>: <label value>
- name: <namespace2 name>
labels:
<label key>: <label value>
More info: https://kyverno.io/docs/kyverno-cli/
`
Expand Down Expand Up @@ -147,7 +154,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err)
}

variables, valuesMap, err := common.GetVariable(variablesString, valuesFile, fs, false, "")
variables, valuesMap, namespaceSelectorMap, err := common.GetVariable(variablesString, valuesFile, fs, false, "")
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to decode yaml", err)
Expand Down Expand Up @@ -268,7 +275,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err)
}

ers, validateErs, responseError, rcErs, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport)
ers, validateErs, responseError, rcErs, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap)
if err != nil {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
}
Expand Down
38 changes: 28 additions & 10 deletions pkg/kyverno/common/common.go
Expand Up @@ -44,7 +44,13 @@ type Policy struct {
}

type Values struct {
Policies []Policy `json:"policies"`
Policies []Policy `json:"policies"`
NamespaceSelectors []NamespaceSelector `json:"namespaceSelector"`
}

type NamespaceSelector struct {
Name string `json:"name"`
Labels map[string]string `json:"labels"`
}

func GetPolicies(paths []string) (policies []*v1.ClusterPolicy, errors []error) {
Expand Down Expand Up @@ -300,8 +306,9 @@ func RemoveDuplicateVariables(matches [][]string) string {
}

// GetVariable - get the variables from console/file
func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit bool, policyresoucePath string) (map[string]string, map[string]map[string]Resource, error) {
func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit bool, policyresoucePath string) (map[string]string, map[string]map[string]Resource, map[string]map[string]string, error) {
valuesMap := make(map[string]map[string]Resource)
namespaceSelectorMap := make(map[string]map[string]string)
variables := make(map[string]string)
var yamlFile []byte
var err error
Expand All @@ -324,17 +331,17 @@ func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit
}

if err != nil {
return variables, valuesMap, sanitizederror.NewWithError("unable to read yaml", err)
return variables, valuesMap, namespaceSelectorMap, sanitizederror.NewWithError("unable to read yaml", err)
}

valuesBytes, err := yaml.ToJSON(yamlFile)
if err != nil {
return variables, valuesMap, sanitizederror.NewWithError("failed to convert json", err)
return variables, valuesMap, namespaceSelectorMap, sanitizederror.NewWithError("failed to convert json", err)
}

values := &Values{}
if err := json.Unmarshal(valuesBytes, values); err != nil {
return variables, valuesMap, sanitizederror.NewWithError("failed to decode yaml", err)
return variables, valuesMap, namespaceSelectorMap, sanitizederror.NewWithError("failed to decode yaml", err)
}

for _, p := range values.Policies {
Expand All @@ -344,9 +351,13 @@ func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit
}
valuesMap[p.Name] = pmap
}

for _, n := range values.NamespaceSelectors {
namespaceSelectorMap[n.Name] = n.Labels
}
}

return variables, valuesMap, nil
return variables, valuesMap, namespaceSelectorMap, nil
}

// MutatePolices - function to apply mutation on policies
Expand All @@ -369,12 +380,18 @@ func MutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {

// ApplyPolicyOnResource - function to apply policy on resource
func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured,
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool) ([]*response.EngineResponse, *response.EngineResponse, bool, bool, error) {
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool, namespaceSelectorMap map[string]map[string]string) ([]*response.EngineResponse, *response.EngineResponse, bool, bool, error) {

responseError := false
rcError := false
engineResponses := make([]*response.EngineResponse, 0)
namespaceLabels := make(map[string]string)
resourceNamespace := resource.GetNamespace()
namespaceLabels = namespaceSelectorMap[resource.GetNamespace()]

if resourceNamespace != "default" && len(namespaceLabels) < 1 {
return engineResponses, &response.EngineResponse{}, responseError, rcError, sanitizederror.NewWithError(fmt.Sprintf("failed to get namesapce labels for resource %s. use --values-file flag to pass the namespace labels", resource.GetName()), nil)
}
resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName())
log.Log.V(3).Info("applying policy on resource", "policy", policy.Name, "resource", resPath)

Expand Down Expand Up @@ -409,7 +426,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
ctx.AddJSON(jsonData)
}

mutateResponse := engine.Mutate(&engine.PolicyContext{Policy: *policy, NewResource: *resource, JSONContext: ctx})
mutateResponse := engine.Mutate(&engine.PolicyContext{Policy: *policy, NewResource: *resource, JSONContext: ctx, NamespaceLabels: namespaceLabels})
engineResponses = append(engineResponses, mutateResponse)

if !mutateResponse.IsSuccessful() {
Expand Down Expand Up @@ -451,7 +468,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
}
}

policyCtx := &engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx}
policyCtx := &engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx, NamespaceLabels: namespaceLabels}
validateResponse := engine.Validate(policyCtx)
if !policyReport {
if !validateResponse.IsSuccessful() {
Expand Down Expand Up @@ -481,7 +498,8 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
ExcludeResourceFunc: func(s1, s2, s3 string) bool {
return false
},
JSONContext: context.NewContext(),
JSONContext: context.NewContext(),
NamespaceLabels: namespaceLabels,
}
generateResponse := engine.Generate(policyContext)
engineResponses = append(engineResponses, generateResponse)
Expand Down
91 changes: 91 additions & 0 deletions pkg/kyverno/common/common_test.go
@@ -0,0 +1,91 @@
package common

import (
"testing"

ut "github.com/kyverno/kyverno/pkg/utils"
"gotest.tools/assert"
)

var policyNamespaceSeelector = []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "enforce-pod-name"
},
"spec": {
"validationFailureAction": "audit",
"background": true,
"rules": [
{
"name": "validate-name",
"match": {
"resources": {
"kinds": [
"Pod"
],
"namespaceSelector": {
"matchExpressions": [
{
"key": "foo.com/managed-state",
"operator": "In",
"values": [
"managed"
]
}
]
}
}
},
"validate": {
"message": "The Pod must end with -nginx",
"pattern": {
"metadata": {
"name": "*-nginx"
}
}
}
}
]
}
}
`)

func Test_NamespaceSelector(t *testing.T) {
type TestCase struct {
policy []byte
resource []byte
namespaceSelectorMap map[string]map[string]string
sucess bool
}

testcases := []TestCase{
{
policy: policyNamespaceSeelector,
resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"nginx","namespace":"test1"},"spec":{"containers":[{"image":"nginx:latest","name":"test-fail"}]}}`),
namespaceSelectorMap: map[string]map[string]string{
"test1": {
"foo.com/managed-state": "managed",
},
},
sucess: false,
},
{
policy: policyNamespaceSeelector,
resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-nginx","namespace":"test1"},"spec":{"containers":[{"image":"nginx:latest","name":"test-pass"}]}}`),
namespaceSelectorMap: map[string]map[string]string{
"test1": {
"foo.com/managed-state": "managed",
},
},
sucess: true,
},
}

for _, tc := range testcases {
policyArray, _ := ut.GetPolicy(tc.policy)
resourceArray, _ := GetResource(tc.resource)
_, validateErs, _, _, _ := ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, false, tc.namespaceSelectorMap)
assert.Assert(t, tc.sucess == validateErs.IsSuccessful())
}
}
4 changes: 2 additions & 2 deletions pkg/kyverno/test/command.go
Expand Up @@ -269,7 +269,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s

fmt.Printf("\nExecuting %s...", values.Name)

_, valuesMap, err := common.GetVariable(variablesString, values.Variables, fs, isGit, policyresoucePath)
_, valuesMap, namespaceSelectorMap, err := common.GetVariable(variablesString, values.Variables, fs, isGit, policyresoucePath)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return sanitizederror.NewWithError("failed to decode yaml", err)
Expand Down Expand Up @@ -334,7 +334,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
return sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err)
}

ers, validateErs, _, _, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true)
ers, validateErs, _, _, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap)
if err != nil {
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
}
Expand Down

0 comments on commit af4b85d

Please sign in to comment.