mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Kyverno CLI - Namespace Selector (#1669)
* 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>
This commit is contained in:
parent
4f3798862a
commit
af4b85d3a8
4 changed files with 130 additions and 14 deletions
|
@ -93,6 +93,13 @@ To apply policy with variables:
|
||||||
values:
|
values:
|
||||||
<variable1 in policy2>: <value>
|
<variable1 in policy2>: <value>
|
||||||
<variable2 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/
|
More info: https://kyverno.io/docs/kyverno-cli/
|
||||||
`
|
`
|
||||||
|
@ -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)
|
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 err != nil {
|
||||||
if !sanitizederror.IsErrorSanitized(err) {
|
if !sanitizederror.IsErrorSanitized(err) {
|
||||||
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to decode yaml", err)
|
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to decode yaml", err)
|
||||||
|
@ -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)
|
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 {
|
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)
|
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,13 @@ type Policy struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Values 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) {
|
func GetPolicies(paths []string) (policies []*v1.ClusterPolicy, errors []error) {
|
||||||
|
@ -300,8 +306,9 @@ func RemoveDuplicateVariables(matches [][]string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVariable - get the variables from console/file
|
// 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)
|
valuesMap := make(map[string]map[string]Resource)
|
||||||
|
namespaceSelectorMap := make(map[string]map[string]string)
|
||||||
variables := make(map[string]string)
|
variables := make(map[string]string)
|
||||||
var yamlFile []byte
|
var yamlFile []byte
|
||||||
var err error
|
var err error
|
||||||
|
@ -324,17 +331,17 @@ func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
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)
|
valuesBytes, err := yaml.ToJSON(yamlFile)
|
||||||
if err != nil {
|
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{}
|
values := &Values{}
|
||||||
if err := json.Unmarshal(valuesBytes, values); err != nil {
|
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 {
|
for _, p := range values.Policies {
|
||||||
|
@ -344,9 +351,13 @@ func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit
|
||||||
}
|
}
|
||||||
valuesMap[p.Name] = pmap
|
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
|
// MutatePolices - function to apply mutation on policies
|
||||||
|
@ -369,12 +380,18 @@ func MutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {
|
||||||
|
|
||||||
// ApplyPolicyOnResource - function to apply policy on resource
|
// ApplyPolicyOnResource - function to apply policy on resource
|
||||||
func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured,
|
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
|
responseError := false
|
||||||
rcError := false
|
rcError := false
|
||||||
engineResponses := make([]*response.EngineResponse, 0)
|
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())
|
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)
|
log.Log.V(3).Info("applying policy on resource", "policy", policy.Name, "resource", resPath)
|
||||||
|
|
||||||
|
@ -409,7 +426,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
|
||||||
ctx.AddJSON(jsonData)
|
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)
|
engineResponses = append(engineResponses, mutateResponse)
|
||||||
|
|
||||||
if !mutateResponse.IsSuccessful() {
|
if !mutateResponse.IsSuccessful() {
|
||||||
|
@ -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)
|
validateResponse := engine.Validate(policyCtx)
|
||||||
if !policyReport {
|
if !policyReport {
|
||||||
if !validateResponse.IsSuccessful() {
|
if !validateResponse.IsSuccessful() {
|
||||||
|
@ -481,7 +498,8 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
|
||||||
ExcludeResourceFunc: func(s1, s2, s3 string) bool {
|
ExcludeResourceFunc: func(s1, s2, s3 string) bool {
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
JSONContext: context.NewContext(),
|
JSONContext: context.NewContext(),
|
||||||
|
NamespaceLabels: namespaceLabels,
|
||||||
}
|
}
|
||||||
generateResponse := engine.Generate(policyContext)
|
generateResponse := engine.Generate(policyContext)
|
||||||
engineResponses = append(engineResponses, generateResponse)
|
engineResponses = append(engineResponses, generateResponse)
|
||||||
|
|
91
pkg/kyverno/common/common_test.go
Normal file
91
pkg/kyverno/common/common_test.go
Normal file
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -269,7 +269,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
||||||
|
|
||||||
fmt.Printf("\nExecuting %s...", values.Name)
|
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 err != nil {
|
||||||
if !sanitizederror.IsErrorSanitized(err) {
|
if !sanitizederror.IsErrorSanitized(err) {
|
||||||
return sanitizederror.NewWithError("failed to decode yaml", err)
|
return sanitizederror.NewWithError("failed to decode yaml", err)
|
||||||
|
@ -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)
|
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 {
|
if err != nil {
|
||||||
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
|
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue