diff --git a/Makefile b/Makefile index 86244cb940..45d49f0c82 100644 --- a/Makefile +++ b/Makefile @@ -267,7 +267,7 @@ test-clean: ## Clean tests cache go clean -testcache ./... .PHONY: test-cli -test-cli: test-cli-policies test-cli-local test-cli-local-mutate test-cli-test-case-selector-flag test-cli-registry +test-cli: test-cli-policies test-cli-local test-cli-local-mutate test-cli-local-generate test-cli-test-case-selector-flag test-cli-registry .PHONY: test-cli-policies test-cli-policies: cli @@ -281,6 +281,10 @@ test-cli-local: cli test-cli-local-mutate: cli cmd/cli/kubectl-kyverno/kyverno test ./test/cli/test-mutate +.PHONY: test-cli-local-generate +test-cli-local-generate: cli + cmd/cli/kubectl-kyverno/kyverno test ./test/cli/test-generate + .PHONY: test-cli-test-case-selector-flag test-cli-test-case-selector-flag: cli cmd/cli/kubectl-kyverno/kyverno test ./test/cli/test --test-case-selector "policy=disallow-latest-tag, rule=require-image-tag, resource=test-require-image-tag-pass" diff --git a/cmd/cli/kubectl-kyverno/apply/apply_command.go b/cmd/cli/kubectl-kyverno/apply/apply_command.go index f643659bf9..cafdb87b08 100644 --- a/cmd/cli/kubectl-kyverno/apply/apply_command.go +++ b/cmd/cli/kubectl-kyverno/apply/apply_command.go @@ -313,7 +313,7 @@ func applyCommandHelper(resourcePaths []string, userInfoPath string, cluster boo return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.GetName(), resource.GetName()), err) } - _, info, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, userInfo, policyReport, namespaceSelectorMap, stdin, rc, true) + _, info, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, userInfo, policyReport, namespaceSelectorMap, stdin, rc, true, nil) if err != nil { return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err) } diff --git a/cmd/cli/kubectl-kyverno/test/test_command.go b/cmd/cli/kubectl-kyverno/test/test_command.go index 853bd26445..c2e1afe8aa 100644 --- a/cmd/cli/kubectl-kyverno/test/test_command.go +++ b/cmd/cli/kubectl-kyverno/test/test_command.go @@ -244,15 +244,34 @@ type Test struct { } type TestResults struct { - Policy string `json:"policy"` - Rule string `json:"rule"` - Result policyreportv1alpha2.PolicyResult `json:"result"` - Status policyreportv1alpha2.PolicyResult `json:"status"` - Resource string `json:"resource"` - Kind string `json:"kind"` - Namespace string `json:"namespace"` - PatchedResource string `json:"patchedResource"` - AutoGeneratedRule string `json:"auto_generated_rule"` + // Policy mentions the name of the policy. + Policy string `json:"policy"` + // Rule mentions the name of the rule in the policy. + Rule string `json:"rule"` + // Result mentions the result that the user is expecting. + // Possible values are pass, fail and skip. + Result policyreportv1alpha2.PolicyResult `json:"result"` + // Status mentions the status that the user is expecting. + // Possible values are pass, fail and skip. + Status policyreportv1alpha2.PolicyResult `json:"status"` + // Resource mentions the name of the resource on which the policy is to be applied. + Resource string `json:"resource"` + // Kind mentions the kind of the resource on which the policy is to be applied. + Kind string `json:"kind"` + // Namespace mentions the namespace of the policy which has namespace scope. + Namespace string `json:"namespace"` + // PatchedResource takes a resource configuration file in yaml format from + // the user to compare it against the Kyverno mutated resource configuration. + PatchedResource string `json:"patchedResource"` + // AutoGeneratedRule is internally set by the CLI command. It takes values either + // autogen or autogen-cronjob. + AutoGeneratedRule string `json:"auto_generated_rule"` + // GeneratedResource takes a resource configuration file in yaml format from + // the user to compare it against the Kyverno generated resource configuration. + GeneratedResource string `json:"generatedResource"` + // CloneSourceResource takes the resource configuration file in yaml format + // from the user which is meant to be cloned by the generate rule. + CloneSourceResource string `json:"cloneSourceResource"` } type ReportResult struct { @@ -553,6 +572,39 @@ func buildPolicyResults(engineResponses []*response.EngineResponse, testResults results[resultsKey] = result } } + + for _, rule := range resp.PolicyResponse.Rules { + if rule.Type != response.Generation || test.Rule != rule.Name { + continue + } + + var resultsKey []string + var resultKey string + var result policyreportv1alpha2.PolicyReportResult + resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name, resourceNamespace, resourceKind, resourceName) + for _, key := range resultsKey { + if val, ok := results[key]; ok { + result = val + resultKey = key + } else { + continue + } + + if rule.Status == response.RuleStatusSkip { + result.Result = policyreportv1alpha2.StatusSkip + } else if rule.Status == response.RuleStatusError { + result.Result = policyreportv1alpha2.StatusError + } else { + var x string + result.Result = policyreportv1alpha2.StatusFail + x = getAndCompareResource(test.GeneratedResource, rule.GeneratedResource, isGit, policyResourcePath, fs, true) + if x == "pass" { + result.Result = policyreportv1alpha2.StatusPass + } + } + results[resultKey] = result + } + } } for _, rule := range resp.PolicyResponse.Rules { @@ -580,7 +632,7 @@ func buildPolicyResults(engineResponses []*response.EngineResponse, testResults var x string for _, path := range patchedResourcePath { result.Result = policyreportv1alpha2.StatusFail - x = getAndComparePatchedResource(path, resp.PatchedResource, isGit, policyResourcePath, fs) + x = getAndCompareResource(path, resp.PatchedResource, isGit, policyResourcePath, fs, false) if x == "pass" { result.Result = policyreportv1alpha2.StatusPass break @@ -663,17 +715,22 @@ func getUserDefinedPolicyNameAndNamespace(policyName string) (string, string) { return "", policyName } -// getAndComparePatchedResource --> Get the patchedResource from the path provided by user -// And compare this patchedResource with engine generated patcheResource. -func getAndComparePatchedResource(path string, enginePatchedResource unstructured.Unstructured, isGit bool, policyResourcePath string, fs billy.Filesystem) string { +// getAndCompareResource --> Get the patchedResource or generatedResource from the path provided by user +// And compare this resource with engine generated resource. +func getAndCompareResource(path string, engineResource unstructured.Unstructured, isGit bool, policyResourcePath string, fs billy.Filesystem, isGenerate bool) string { var status string - patchedResources, err := common.GetPatchedResourceFromPath(fs, path, isGit, policyResourcePath) + resourceType := "patchedResource" + if isGenerate { + resourceType = "generatedResource" + } + + userResource, err := common.GetResourceFromPath(fs, path, isGit, policyResourcePath, resourceType) if err != nil { os.Exit(1) } - matched, err := generate.ValidateResourceWithPattern(log.Log, enginePatchedResource.UnstructuredContent(), patchedResources.UnstructuredContent()) + matched, err := generate.ValidateResourceWithPattern(log.Log, engineResource.UnstructuredContent(), userResource.UnstructuredContent()) if err != nil { - log.Log.V(3).Info("patched resource mismatch", "error", err.Error()) + log.Log.V(3).Info(resourceType+" mismatch", "error", err.Error()) status = "fail" } @@ -763,8 +820,16 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool, for i, result := range values.Results { arrPatchedResource := []string{result.PatchedResource} + arrGeneratedResource := []string{result.GeneratedResource} + arrCloneSourceResource := []string{result.CloneSourceResource} + patchedResourceFullPath := getFullPath(arrPatchedResource, policyResourcePath, isGit) + generatedResourceFullPath := getFullPath(arrGeneratedResource, policyResourcePath, isGit) + CloneSourceResourceFullPath := getFullPath(arrCloneSourceResource, policyResourcePath, isGit) + values.Results[i].PatchedResource = patchedResourceFullPath[0] + values.Results[i].GeneratedResource = generatedResourceFullPath[0] + values.Results[i].CloneSourceResource = CloneSourceResourceFullPath[0] } policies, err := common.GetPoliciesFromPaths(fs, policyFullPath, isGit, policyResourcePath) @@ -783,6 +848,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool, } } + var ruleToCloneSourceResource = map[string]string{} for _, p := range filteredPolicies { filteredRules := []kyvernov1.Rule{} @@ -790,6 +856,23 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool, for _, res := range values.Results { if rule.Name == res.Rule { filteredRules = append(filteredRules, rule) + if rule.HasGenerate() { + ruleUnstr, err := generate.GetUnstrRule(rule.Generation.DeepCopy()) + if err != nil { + fmt.Printf("Error: failed to get unstructured rule\nCause: %s\n", err) + break + } + + genClone, _, err := unstructured.NestedMap(ruleUnstr.Object, "clone") + if err != nil { + fmt.Printf("Error: failed to read data\nCause: %s\n", err) + break + } + + if len(genClone) != 0 { + ruleToCloneSourceResource[rule.Name] = res.CloneSourceResource + } + } break } } @@ -868,7 +951,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool, return sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.GetName(), resource.GetName()), err) } - ers, info, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, userInfo, true, namespaceSelectorMap, false, &resultCounts, false) + ers, info, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, userInfo, true, namespaceSelectorMap, false, &resultCounts, false, ruleToCloneSourceResource) if err != nil { return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err) } diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go index 13daf6a578..b372d3b7ae 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common.go @@ -21,6 +21,7 @@ import ( sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store" "github.com/kyverno/kyverno/pkg/autogen" + "github.com/kyverno/kyverno/pkg/background/generate" "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/engine" engineContext "github.com/kyverno/kyverno/pkg/engine/context" @@ -32,6 +33,7 @@ import ( "github.com/kyverno/kyverno/pkg/utils" yamlv2 "gopkg.in/yaml.v2" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -397,7 +399,7 @@ func MutatePolicies(policies []kyvernov1.PolicyInterface) ([]kyvernov1.PolicyInt func ApplyPolicyOnResource(policy kyvernov1.PolicyInterface, resource *unstructured.Unstructured, mutateLogPath string, mutateLogPathIsDir bool, variables map[string]interface{}, userInfo kyvernov1beta1.RequestInfo, policyReport bool, namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts, - printPatchResource bool, + printPatchResource bool, ruleToCloneSourceResource map[string]string, ) ([]*response.EngineResponse, policyreport.Info, error) { var engineResponses []*response.EngineResponse namespaceLabels := make(map[string]string) @@ -561,11 +563,17 @@ OuterLoop: ExcludeResourceFunc: func(s1, s2, s3 string) bool { return false }, - JSONContext: engineContext.NewContext(), + JSONContext: ctx, NamespaceLabels: namespaceLabels, } generateResponse := engine.ApplyBackgroundChecks(policyContext) if generateResponse != nil && !generateResponse.IsEmpty() { + newRuleResponse, err := handleGeneratePolicy(generateResponse, *policyContext, ruleToCloneSourceResource) + if err != nil { + log.Log.Error(err, "failed to apply generate policy") + } else { + generateResponse.PolicyResponse.Rules = newRuleResponse + } engineResponses = append(engineResponses, generateResponse) } updateResultCounts(policy, generateResponse, resPath, rc) @@ -995,35 +1003,110 @@ func GetKindsFromPolicy(policy kyvernov1.PolicyInterface) map[string]struct{} { return kindOnwhichPolicyIsApplied } -// GetPatchedResourceFromPath - get patchedResource from given path -func GetPatchedResourceFromPath(fs billy.Filesystem, path string, isGit bool, policyResourcePath string) (unstructured.Unstructured, error) { - var patchedResourceBytes []byte - var patchedResource unstructured.Unstructured +//GetResourceFromPath - get patchedResource and generatedResource from given path +func GetResourceFromPath(fs billy.Filesystem, path string, isGit bool, policyResourcePath string, resourceType string) (unstructured.Unstructured, error) { + var resourceBytes []byte + var resource unstructured.Unstructured var err error - if isGit { if len(path) > 0 { filep, fileErr := fs.Open(filepath.Join(policyResourcePath, path)) if fileErr != nil { - fmt.Printf("Unable to open patchedResource file: %s. \nerror: %s", path, fileErr) + fmt.Printf("Unable to open %s file: %s. \nerror: %s", resourceType, path, err) } - patchedResourceBytes, err = ioutil.ReadAll(filep) + resourceBytes, err = ioutil.ReadAll(filep) } } else { - patchedResourceBytes, err = getFileBytes(path) + resourceBytes, err = getFileBytes(path) } if err != nil { - fmt.Printf("\n----------------------------------------------------------------------\nfailed to load patchedResource: %s. \nerror: %s\n----------------------------------------------------------------------\n", path, err) - return patchedResource, err + fmt.Printf("\n----------------------------------------------------------------------\nfailed to load %s: %s. \nerror: %s\n----------------------------------------------------------------------\n", resourceType, path, err) + return resource, err } - patchedResource, err = GetPatchedResource(patchedResourceBytes) + resource, err = GetPatchedAndGeneratedResource(resourceBytes) if err != nil { - return patchedResource, err + return resource, err } - return patchedResource, nil + return resource, nil +} + +// initializeMockController initializes a basic Generate Controller with a fake dynamic client. +func initializeMockController(objects []runtime.Object) (*generate.GenerateController, error) { + client, err := dclient.NewMockClient(runtime.NewScheme(), nil, objects...) + if err != nil { + fmt.Printf("Failed to mock dynamic client") + return nil, err + } + + client.SetDiscovery(dclient.NewFakeDiscoveryClient(nil)) + c := generate.NewGenerateControllerWithOnlyClient(client) + return c, nil +} + +// handleGeneratePolicy returns a new RuleResponse with the Kyverno generated resource configuration by applying the generate rule. +func handleGeneratePolicy(generateResponse *response.EngineResponse, policyContext engine.PolicyContext, ruleToCloneSourceResource map[string]string) ([]response.RuleResponse, error) { + objects := []runtime.Object{&policyContext.NewResource} + var resources = []*unstructured.Unstructured{} + for _, rule := range generateResponse.PolicyResponse.Rules { + if path, ok := ruleToCloneSourceResource[rule.Name]; ok { + resourceBytes, err := getFileBytes(path) + if err != nil { + fmt.Printf("failed to get resource bytes\n") + } else { + resources, err = GetResource(resourceBytes) + if err != nil { + fmt.Printf("failed to convert resource bytes to unstructured format\n") + } + } + } + } + + for _, res := range resources { + objects = append(objects, res) + } + + c, err := initializeMockController(objects) + if err != nil { + fmt.Println("error at controller") + return nil, err + } + + gr := kyvernov1beta1.UpdateRequest{ + Spec: kyvernov1beta1.UpdateRequestSpec{ + Type: kyvernov1beta1.Generate, + Policy: generateResponse.PolicyResponse.Policy.Name, + Resource: kyvernov1.ResourceSpec{ + Kind: generateResponse.PolicyResponse.Resource.Kind, + Namespace: generateResponse.PolicyResponse.Resource.Namespace, + Name: generateResponse.PolicyResponse.Resource.Name, + APIVersion: generateResponse.PolicyResponse.Resource.APIVersion, + }, + }, + } + + var newRuleResponse []response.RuleResponse + + for _, rule := range generateResponse.PolicyResponse.Rules { + genResource, _, err := c.ApplyGeneratePolicy(log.Log, &policyContext, gr, []string{rule.Name}) + if err != nil { + rule.Status = response.RuleStatusError + return nil, err + } + + unstrGenResource, err := c.GetUnstrResource(genResource[0]) + if err != nil { + rule.Status = response.RuleStatusError + return nil, err + } + + rule.GeneratedResource = *unstrGenResource + newRuleResponse = append(newRuleResponse, rule) + } + + return newRuleResponse, nil } // GetUserInfoFromPath - get the request info as user info from a given path diff --git a/cmd/cli/kubectl-kyverno/utils/common/common_test.go b/cmd/cli/kubectl-kyverno/utils/common/common_test.go index 7cb26fe48c..9d986e0f8a 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common_test.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common_test.go @@ -100,7 +100,7 @@ func Test_NamespaceSelector(t *testing.T) { for _, tc := range testcases { policyArray, _ := ut.GetPolicy(tc.policy) resourceArray, _ := GetResource(tc.resource) - ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, v1beta1.RequestInfo{}, false, tc.namespaceSelectorMap, false, rc, false) + ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, v1beta1.RequestInfo{}, false, tc.namespaceSelectorMap, false, rc, false, nil) assert.Equal(t, int64(rc.Pass), int64(tc.result.Pass)) assert.Equal(t, int64(rc.Fail), int64(tc.result.Fail)) // TODO: autogen rules seem to not be present when autogen internals is disabled diff --git a/cmd/cli/kubectl-kyverno/utils/common/fetch.go b/cmd/cli/kubectl-kyverno/utils/common/fetch.go index a33271b605..77fd8cb9f4 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/fetch.go +++ b/cmd/cli/kubectl-kyverno/utils/common/fetch.go @@ -278,13 +278,13 @@ func convertResourceToUnstructured(resourceYaml []byte) (*unstructured.Unstructu } // GetPatchedResource converts raw bytes to unstructured object -func GetPatchedResource(patchResourceBytes []byte) (unstructured.Unstructured, error) { - getPatchedResource, err := GetResource(patchResourceBytes) +func GetPatchedAndGeneratedResource(resourceBytes []byte) (unstructured.Unstructured, error) { + getResource, err := GetResource(resourceBytes) if err != nil { return unstructured.Unstructured{}, err } - patchedResource := *getPatchedResource[0] - return patchedResource, nil + resource := *getResource[0] + return resource, nil } // GetKindsFromRule will return the kinds from policy match block diff --git a/pkg/background/generate/generate.go b/pkg/background/generate/generate.go index 87369cbfa7..5516a93164 100644 --- a/pkg/background/generate/generate.go +++ b/pkg/background/generate/generate.go @@ -227,7 +227,7 @@ func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, u } // Apply the generate rule on resource - return c.applyGeneratePolicy(logger, policyContext, ur, applicableRules) + return c.ApplyGeneratePolicy(logger, policyContext, ur, applicableRules) } // cleanupClonedResource deletes cloned resource if sync is not enabled for the clone policy @@ -301,7 +301,7 @@ func updateStatus(statusControl common.StatusControlInterface, ur kyvernov1beta1 return nil } -func (c *GenerateController) applyGeneratePolicy(log logr.Logger, policyContext *engine.PolicyContext, ur kyvernov1beta1.UpdateRequest, applicableRules []string) (genResources []kyvernov1.ResourceSpec, processExisting bool, err error) { +func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext *engine.PolicyContext, ur kyvernov1beta1.UpdateRequest, applicableRules []string) (genResources []kyvernov1.ResourceSpec, processExisting bool, err error) { // Get the response as the actions to be performed on the resource // - - substitute values policy := policyContext.Policy @@ -388,7 +388,7 @@ func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, r var err error var mode ResourceMode var noGenResource kyvernov1.ResourceSpec - genUnst, err := getUnstrRule(rule.Generation.DeepCopy()) + genUnst, err := GetUnstrRule(rule.Generation.DeepCopy()) if err != nil { return noGenResource, err } @@ -614,7 +614,7 @@ const ( Update = "UPDATE" ) -func getUnstrRule(rule *kyvernov1.Generation) (*unstructured.Unstructured, error) { +func GetUnstrRule(rule *kyvernov1.Generation) (*unstructured.Unstructured, error) { ruleData, err := json.Marshal(rule) if err != nil { return nil, err @@ -622,6 +622,36 @@ func getUnstrRule(rule *kyvernov1.Generation) (*unstructured.Unstructured, error return utils.ConvertToUnstructured(ruleData) } +func (c *GenerateController) ApplyResource(resource *unstructured.Unstructured) error { + kind, _, namespace, apiVersion, err := getResourceInfo(resource.Object) + if err != nil { + return err + } + + _, err = c.client.CreateResource(apiVersion, kind, namespace, resource, false) + if err != nil { + return err + } + + return nil +} + +// NewGenerateControllerWithOnlyClient returns an instance of Controller with only the client. +func NewGenerateControllerWithOnlyClient(client dclient.Interface) *GenerateController { + c := GenerateController{ + client: client, + } + return &c +} + +// GetUnstrResource converts ResourceSpec object to type Unstructured +func (c *GenerateController) GetUnstrResource(genResourceSpec kyvernov1.ResourceSpec) (*unstructured.Unstructured, error) { + resource, err := c.client.GetResource(genResourceSpec.APIVersion, genResourceSpec.Kind, genResourceSpec.Namespace, genResourceSpec.Name) + if err != nil { + return nil, err + } + return resource, nil +} func deleteGeneratedResources(log logr.Logger, client dclient.Interface, ur kyvernov1beta1.UpdateRequest) error { for _, genResource := range ur.Status.GeneratedResources { err := client.DeleteResource("", genResource.Kind, genResource.Namespace, genResource.Name, false) diff --git a/pkg/engine/background.go b/pkg/engine/background.go index a61551acac..f9d64f12ad 100644 --- a/pkg/engine/background.go +++ b/pkg/engine/background.go @@ -96,7 +96,7 @@ func filterRule(rule kyvernov1.Rule, policyContext *PolicyContext) *response.Rul } } } - + logger.V(4).Info("rule not matched", "reason", err.Error()) return nil } diff --git a/pkg/engine/response/response.go b/pkg/engine/response/response.go index 937d45c67a..00e73d4df0 100644 --- a/pkg/engine/response/response.go +++ b/pkg/engine/response/response.go @@ -102,6 +102,9 @@ type RuleResponse struct { // JSON patches, for mutation rules Patches [][]byte `json:"patches,omitempty"` + // Resource generated by the generate rules of a policy + GeneratedResource unstructured.Unstructured `json:"generatedResource,omitempty"` + // rule status Status RuleStatus `json:"status"` diff --git a/test/cli/test-generate/add-network-policy/generatedResource.yaml b/test/cli/test-generate/add-network-policy/generatedResource.yaml new file mode 100644 index 0000000000..d3477821c9 --- /dev/null +++ b/test/cli/test-generate/add-network-policy/generatedResource.yaml @@ -0,0 +1,10 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny + namespace: hello-world-namespace +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress \ No newline at end of file diff --git a/test/cli/test-generate/add-network-policy/kyverno-test.yaml b/test/cli/test-generate/add-network-policy/kyverno-test.yaml new file mode 100644 index 0000000000..ce51c9721e --- /dev/null +++ b/test/cli/test-generate/add-network-policy/kyverno-test.yaml @@ -0,0 +1,12 @@ +name: deny-all-traffic +policies: + - policy.yaml +resources: + - resource.yaml +results: + - policy: add-networkpolicy + rule: default-deny + resource: hello-world-namespace + generatedResource: generatedResource.yaml + kind: Namespace + result: pass \ No newline at end of file diff --git a/test/cli/test-generate/add-network-policy/policy.yaml b/test/cli/test-generate/add-network-policy/policy.yaml new file mode 100644 index 0000000000..b0c296ecdb --- /dev/null +++ b/test/cli/test-generate/add-network-policy/policy.yaml @@ -0,0 +1,37 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: add-networkpolicy + annotations: + policies.kyverno.io/title: Add Network Policy + policies.kyverno.io/category: Multi-Tenancy + policies.kyverno.io/subject: NetworkPolicy + policies.kyverno.io/description: >- + By default, Kubernetes allows communications across all Pods within a cluster. + The NetworkPolicy resource and a CNI plug-in that supports NetworkPolicy must be used to restrict + communications. A default NetworkPolicy should be configured for each Namespace to + default deny all ingress and egress traffic to the Pods in the Namespace. Application + teams can then configure additional NetworkPolicy resources to allow desired traffic + to application Pods from select sources. This policy will create a new NetworkPolicy resource + named `default-deny` which will deny all traffic anytime a new Namespace is created. +spec: + rules: + - name: default-deny + match: + resources: + kinds: + - Namespace + generate: + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + name: default-deny + namespace: "{{request.object.metadata.name}}" + synchronize: true + data: + spec: + # select all pods in the namespace + podSelector: {} + # deny all traffic + policyTypes: + - Ingress + - Egress \ No newline at end of file diff --git a/test/cli/test-generate/add-network-policy/resource.yaml b/test/cli/test-generate/add-network-policy/resource.yaml new file mode 100644 index 0000000000..9164c7d1d8 --- /dev/null +++ b/test/cli/test-generate/add-network-policy/resource.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: hello-world-namespace \ No newline at end of file diff --git a/test/cli/test-generate/add-quota/generatedLimitRange.yaml b/test/cli/test-generate/add-quota/generatedLimitRange.yaml new file mode 100644 index 0000000000..acca90d26a --- /dev/null +++ b/test/cli/test-generate/add-quota/generatedLimitRange.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: LimitRange +metadata: + name: default-limitrange + namespace: hello-world-namespace +spec: + limits: + - default: + cpu: 500m + memory: 1Gi + defaultRequest: + cpu: 200m + memory: 256Mi + type: Container \ No newline at end of file diff --git a/test/cli/test-generate/add-quota/generatedResourceQuota.yaml b/test/cli/test-generate/add-quota/generatedResourceQuota.yaml new file mode 100644 index 0000000000..8a30131f2e --- /dev/null +++ b/test/cli/test-generate/add-quota/generatedResourceQuota.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: + name: default-resourcequota + namespace: hello-world-namespace +spec: + hard: + requests.cpu: '4' + requests.memory: '16Gi' + limits.cpu: '4' + limits.memory: '16Gi' \ No newline at end of file diff --git a/test/cli/test-generate/add-quota/kyverno-test.yaml b/test/cli/test-generate/add-quota/kyverno-test.yaml new file mode 100644 index 0000000000..24a7706928 --- /dev/null +++ b/test/cli/test-generate/add-quota/kyverno-test.yaml @@ -0,0 +1,18 @@ +name: add-quota +policies: + - policy.yaml +resources: + - resource.yaml +results: + - policy: add-ns-quota + rule: generate-resourcequota + resource: hello-world-namespace + generatedResource: generatedResourceQuota.yaml + kind: Namespace + result: pass + - policy: add-ns-quota + rule: generate-limitrange + resource: hello-world-namespace + generatedResource: generatedLimitRange.yaml + kind: Namespace + result: pass diff --git a/test/cli/test-generate/add-quota/policy.yaml b/test/cli/test-generate/add-quota/policy.yaml new file mode 100644 index 0000000000..67868b1613 --- /dev/null +++ b/test/cli/test-generate/add-quota/policy.yaml @@ -0,0 +1,55 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: add-ns-quota + annotations: + policies.kyverno.io/title: Add Quota + policies.kyverno.io/category: Multi-Tenancy + policies.kyverno.io/subject: ResourceQuota, LimitRange + policies.kyverno.io/description: >- + To better control the number of resources that can be created in a given + Namespace and provide default resource consumption limits for Pods, + ResourceQuota and LimitRange resources are recommended. + This policy will generate ResourceQuota and LimitRange resources when + a new Namespace is created. +spec: + rules: + - name: generate-resourcequota + match: + resources: + kinds: + - Namespace + generate: + apiVersion: v1 + kind: ResourceQuota + name: default-resourcequota + synchronize: true + namespace: "{{request.object.metadata.name}}" + data: + spec: + hard: + requests.cpu: '4' + requests.memory: '16Gi' + limits.cpu: '4' + limits.memory: '16Gi' + - name: generate-limitrange + match: + resources: + kinds: + - Namespace + generate: + apiVersion: v1 + kind: LimitRange + name: default-limitrange + synchronize: true + namespace: "{{request.object.metadata.name}}" + data: + spec: + limits: + - default: + cpu: 500m + memory: 1Gi + defaultRequest: + cpu: 200m + memory: 256Mi + type: Container \ No newline at end of file diff --git a/test/cli/test-generate/add-quota/resource.yaml b/test/cli/test-generate/add-quota/resource.yaml new file mode 100644 index 0000000000..9164c7d1d8 --- /dev/null +++ b/test/cli/test-generate/add-quota/resource.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: hello-world-namespace \ No newline at end of file diff --git a/test/cli/test-generate/create-default-pdb/generatedResource.yaml b/test/cli/test-generate/create-default-pdb/generatedResource.yaml new file mode 100644 index 0000000000..664155903a --- /dev/null +++ b/test/cli/test-generate/create-default-pdb/generatedResource.yaml @@ -0,0 +1,10 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: nginx-deployment-default-pdb + namespace: hello-world +spec: + minAvailable: 1 + selector: + matchLabels: + app: nginx \ No newline at end of file diff --git a/test/cli/test-generate/create-default-pdb/kyverno-test.yaml b/test/cli/test-generate/create-default-pdb/kyverno-test.yaml new file mode 100644 index 0000000000..035e70bde0 --- /dev/null +++ b/test/cli/test-generate/create-default-pdb/kyverno-test.yaml @@ -0,0 +1,13 @@ +name: pdb-test +policies: + - policy.yaml +resources: + - resource.yaml +results: + - policy: create-default-pdb + rule: create-default-pdb + resource: nginx-deployment + generatedResource: generatedResource.yaml + kind: Deployment + result: pass + namespace: hello-world \ No newline at end of file diff --git a/test/cli/test-generate/create-default-pdb/policy.yaml b/test/cli/test-generate/create-default-pdb/policy.yaml new file mode 100644 index 0000000000..3ae3fb037f --- /dev/null +++ b/test/cli/test-generate/create-default-pdb/policy.yaml @@ -0,0 +1,23 @@ +apiVersion: kyverno.io/v1 +kind: Policy +metadata: + name: create-default-pdb + namespace: hello-world +spec: + rules: + - name: create-default-pdb + match: + resources: + kinds: + - Deployment + generate: + apiVersion: policy/v1 + kind: PodDisruptionBudget + name: "{{request.object.metadata.name}}-default-pdb" + namespace: "{{request.object.metadata.namespace}}" + data: + spec: + minAvailable: 1 + selector: + matchLabels: + "{{request.object.metadata.labels}}" \ No newline at end of file diff --git a/test/cli/test-generate/create-default-pdb/resource.yaml b/test/cli/test-generate/create-default-pdb/resource.yaml new file mode 100644 index 0000000000..b505b5f975 --- /dev/null +++ b/test/cli/test-generate/create-default-pdb/resource.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + namespace: hello-world + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 \ No newline at end of file diff --git a/test/cli/test-generate/sync-secrets/cloneSourceResource.yaml b/test/cli/test-generate/sync-secrets/cloneSourceResource.yaml new file mode 100644 index 0000000000..43a02a7cab --- /dev/null +++ b/test/cli/test-generate/sync-secrets/cloneSourceResource.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: regcred + namespace: default +type: Opaque +data: + password: MWYyZDFlMmU2N2Rm \ No newline at end of file diff --git a/test/cli/test-generate/sync-secrets/generatedResource.yaml b/test/cli/test-generate/sync-secrets/generatedResource.yaml new file mode 100644 index 0000000000..3adbaf3c8a --- /dev/null +++ b/test/cli/test-generate/sync-secrets/generatedResource.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: regcred + namespace: hello-world-namespace +type: Opaque +data: + password: MWYyZDFlMmU2N2Rm \ No newline at end of file diff --git a/test/cli/test-generate/sync-secrets/kyverno-test.yaml b/test/cli/test-generate/sync-secrets/kyverno-test.yaml new file mode 100644 index 0000000000..d1f42e1588 --- /dev/null +++ b/test/cli/test-generate/sync-secrets/kyverno-test.yaml @@ -0,0 +1,13 @@ +name: sync-secrets +policies: + - policy.yaml +resources: + - resource.yaml +results: + - policy: sync-secrets + rule: sync-image-pull-secret + resource: hello-world-namespace + generatedResource: generatedResource.yaml + cloneSourceResource: cloneSourceResource.yaml + kind: Namespace + result: pass \ No newline at end of file diff --git a/test/cli/test-generate/sync-secrets/policy.yaml b/test/cli/test-generate/sync-secrets/policy.yaml new file mode 100644 index 0000000000..2357ace270 --- /dev/null +++ b/test/cli/test-generate/sync-secrets/policy.yaml @@ -0,0 +1,31 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: sync-secrets + annotations: + policies.kyverno.io/title: Sync Secrets + policies.kyverno.io/category: Sample + policies.kyverno.io/subject: Secret + policies.kyverno.io/description: >- + Secrets like registry credentials often need to exist in multiple + Namespaces so Pods there have access. Manually duplicating those Secrets + is time consuming and error prone. This policy will copy a + Secret called `regcred` which exists in the `default` Namespace to + new Namespaces when they are created. It will also push updates to + the copied Secrets should the source Secret be changed. +spec: + rules: + - name: sync-image-pull-secret + match: + resources: + kinds: + - Namespace + generate: + apiVersion: v1 + kind: Secret + name: regcred + namespace: "{{request.object.metadata.name}}" + synchronize: true + clone: + namespace: default + name: regcred \ No newline at end of file diff --git a/test/cli/test-generate/sync-secrets/resource.yaml b/test/cli/test-generate/sync-secrets/resource.yaml new file mode 100644 index 0000000000..1398eabf5c --- /dev/null +++ b/test/cli/test-generate/sync-secrets/resource.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: hello-world-namespace diff --git a/test/cli/test/images/digest/kyverno-test.yaml b/test/cli/test/images/digest/kyverno-test.yaml index ed1027f7ac..df9b19862a 100644 --- a/test/cli/test/images/digest/kyverno-test.yaml +++ b/test/cli/test/images/digest/kyverno-test.yaml @@ -8,9 +8,11 @@ results: rule: check-digest resource: no-digest kind: Pod + namespace: test status: fail - policy: require-image-digest rule: check-digest resource: with-digest kind: Pod + namespace: test status: pass diff --git a/test/cli/test/limit-configmap-for-sa/kyverno-test.yaml b/test/cli/test/limit-configmap-for-sa/kyverno-test.yaml index 4831975ea5..ac1dca28ed 100644 --- a/test/cli/test/limit-configmap-for-sa/kyverno-test.yaml +++ b/test/cli/test/limit-configmap-for-sa/kyverno-test.yaml @@ -10,6 +10,7 @@ results: rule: limit-configmap-for-sa-developer resource: any-configmap-name-good kind: ConfigMap + namespace: any-namespace result: fail - policy: limit-configmap-for-sa rule: limit-configmap-for-sa-developer diff --git a/test/cli/test/simple/kyverno-test.yaml b/test/cli/test/simple/kyverno-test.yaml index 43be2de992..d2b497854b 100644 --- a/test/cli/test/simple/kyverno-test.yaml +++ b/test/cli/test/simple/kyverno-test.yaml @@ -9,11 +9,13 @@ results: rule: require-image-tag resource: test-require-image-tag-pass kind: Pod + namespace: test status: pass - policy: disallow-latest-tag rule: require-image-tag resource: test-require-image-tag-fail kind: Pod + namespace: test status: fail - policy: disallow-latest-tag rule: validate-image-tag @@ -23,32 +25,38 @@ results: - policy: disallow-latest-tag rule: validate-image-tag resource: test-validate-image-tag-fail + namespace: test kind: Pod status: fail - policy: disallow-latest-tag rule: validate-image-tag resource: test-validate-image-tag-pass kind: Pod + namespace: test status: pass - policy: duration-test rule: greater-than resource: test-lifetime-fail kind: Pod + namespace: test status: fail - policy: duration-test rule: less-than resource: test-lifetime-fail kind: Pod + namespace: test status: pass - policy: duration-test rule: greater-equal-than resource: test-lifetime-fail kind: Pod + namespace: test status: fail - policy: duration-test rule: less-equal-than resource: test-lifetime-fail kind: Pod + namespace: test status: pass - policy: restrict-pod-counts @@ -60,11 +68,13 @@ results: rule: restrict-pod-count resource: test-require-image-tag-pass kind: Pod + namespace: test status: fail - policy: restrict-pod-counts rule: restrict-pod-count resource: test-require-image-tag-fail kind: Pod + namespace: test status: fail - policy: restrict-pod-counts rule: restrict-pod-count @@ -75,9 +85,11 @@ results: rule: restrict-pod-count resource: test-validate-image-tag-fail kind: Pod + namespace: test status: fail - policy: restrict-pod-counts rule: restrict-pod-count resource: test-validate-image-tag-pass kind: Pod + namespace: test status: fail \ No newline at end of file