mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
feat: Extend CLI to cover generate policies (#3456)
- Change in namespace for test-generate example - Change cloneResource to cloneSourceResource - Add support for namespaced Policy and fix log messages - Add test-generate in Makefile and an example of namespaced Policy - Fix namespaced policy issue and add comments - Refactor according to new generate controller - Add json tag to GeneratedResource field of RuleResponse struct Signed-off-by: Shubham Nazare <shubham4443@gmail.com> Co-authored-by: Prateek Pandey <prateek.pandey@nirmata.com> Co-authored-by: Vyankatesh Kudtarkar <vyankateshkd@gmail.com>
This commit is contained in:
parent
fbbe57f5e1
commit
165c5d9fc3
30 changed files with 559 additions and 44 deletions
6
Makefile
6
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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"`
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: default-deny
|
||||
namespace: hello-world-namespace
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
12
test/cli/test-generate/add-network-policy/kyverno-test.yaml
Normal file
12
test/cli/test-generate/add-network-policy/kyverno-test.yaml
Normal file
|
@ -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
|
37
test/cli/test-generate/add-network-policy/policy.yaml
Normal file
37
test/cli/test-generate/add-network-policy/policy.yaml
Normal file
|
@ -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
|
4
test/cli/test-generate/add-network-policy/resource.yaml
Normal file
4
test/cli/test-generate/add-network-policy/resource.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: hello-world-namespace
|
14
test/cli/test-generate/add-quota/generatedLimitRange.yaml
Normal file
14
test/cli/test-generate/add-quota/generatedLimitRange.yaml
Normal file
|
@ -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
|
11
test/cli/test-generate/add-quota/generatedResourceQuota.yaml
Normal file
11
test/cli/test-generate/add-quota/generatedResourceQuota.yaml
Normal file
|
@ -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'
|
18
test/cli/test-generate/add-quota/kyverno-test.yaml
Normal file
18
test/cli/test-generate/add-quota/kyverno-test.yaml
Normal file
|
@ -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
|
55
test/cli/test-generate/add-quota/policy.yaml
Normal file
55
test/cli/test-generate/add-quota/policy.yaml
Normal file
|
@ -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
|
4
test/cli/test-generate/add-quota/resource.yaml
Normal file
4
test/cli/test-generate/add-quota/resource.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: hello-world-namespace
|
|
@ -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
|
13
test/cli/test-generate/create-default-pdb/kyverno-test.yaml
Normal file
13
test/cli/test-generate/create-default-pdb/kyverno-test.yaml
Normal file
|
@ -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
|
23
test/cli/test-generate/create-default-pdb/policy.yaml
Normal file
23
test/cli/test-generate/create-default-pdb/policy.yaml
Normal file
|
@ -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}}"
|
22
test/cli/test-generate/create-default-pdb/resource.yaml
Normal file
22
test/cli/test-generate/create-default-pdb/resource.yaml
Normal file
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: regcred
|
||||
namespace: default
|
||||
type: Opaque
|
||||
data:
|
||||
password: MWYyZDFlMmU2N2Rm
|
|
@ -0,0 +1,8 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: regcred
|
||||
namespace: hello-world-namespace
|
||||
type: Opaque
|
||||
data:
|
||||
password: MWYyZDFlMmU2N2Rm
|
13
test/cli/test-generate/sync-secrets/kyverno-test.yaml
Normal file
13
test/cli/test-generate/sync-secrets/kyverno-test.yaml
Normal file
|
@ -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
|
31
test/cli/test-generate/sync-secrets/policy.yaml
Normal file
31
test/cli/test-generate/sync-secrets/policy.yaml
Normal file
|
@ -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
|
4
test/cli/test-generate/sync-secrets/resource.yaml
Normal file
4
test/cli/test-generate/sync-secrets/resource.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: hello-world-namespace
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue