1
0
Fork 0
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:
Shubham Nazare 2022-05-25 19:56:22 +05:30 committed by GitHub
parent fbbe57f5e1
commit 165c5d9fc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 559 additions and 44 deletions

View file

@ -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"

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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
}

View file

@ -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"`

View file

@ -0,0 +1,10 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: hello-world-namespace
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

View 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

View 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

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: hello-world-namespace

View 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

View 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'

View 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

View 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

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: hello-world-namespace

View file

@ -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

View 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

View 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}}"

View 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

View file

@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: regcred
namespace: default
type: Opaque
data:
password: MWYyZDFlMmU2N2Rm

View file

@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: regcred
namespace: hello-world-namespace
type: Opaque
data:
password: MWYyZDFlMmU2N2Rm

View 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

View 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

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: hello-world-namespace

View file

@ -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

View file

@ -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

View file

@ -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