1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 07:26:55 +00:00

Supporting ValidatingAdmissionPolicy in kyverno cli (apply and test command) (#6656)

* feat: add policy reporter to the dev lab

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* refactor: remove obsolete structs from CLI

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* codegen

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* Supporting ValidatingAdmissionPolicy in kyverno apply

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* chore: bump k8s from v0.26.3 to v0.27.0-rc.0

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* Support validating admission policy in kyverno apply

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* Support validating admission policy in kyverno test

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* refactoring

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* Adding kyverno apply tests for validating admission policy

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* fix

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* fix

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* running codegen-all

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* fix

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* Adding IsVap field in TestResults

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* chore: bump k8s from v0.27.0-rc.0 to v0.27.1

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* fix

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* fix

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* Fix vap in engine response

Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>

* codegen

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Signed-off-by: Mariam Fahmy <mariamfahmy66@gmail.com>
Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
Mariam Fahmy 2023-05-10 11:12:53 +03:00 committed by GitHub
parent 7a25afd01f
commit bb628e1fe6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1167 additions and 369 deletions

View file

@ -24,6 +24,7 @@ import (
gitutils "github.com/kyverno/kyverno/pkg/utils/git"
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
"github.com/spf13/cobra"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
@ -255,6 +256,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso
}
var policies []kyvernov1.PolicyInterface
var validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy
isGit := common.IsGitSourcePath(c.PolicyPaths)
@ -289,7 +291,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso
sort.Strings(policyYamls)
c.PolicyPaths = policyYamls
}
policies, err = common.GetPoliciesFromPaths(fs, c.PolicyPaths, isGit, "")
policies, validatingAdmissionPolicies, err = common.GetPoliciesFromPaths(fs, c.PolicyPaths, isGit, "")
if err != nil {
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
osExit(1)
@ -326,7 +328,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso
return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError("failed to marshal mutated policy", err)
}
resources, err = common.GetResourceAccordingToResourcePath(fs, c.ResourcePaths, c.Cluster, policies, dClient, c.Namespace, c.PolicyReport, false, "")
resources, err = common.GetResourceAccordingToResourcePath(fs, c.ResourcePaths, c.Cluster, policies, validatingAdmissionPolicies, dClient, c.Namespace, c.PolicyReport, false, "")
if err != nil {
fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
osExit(1)
@ -387,6 +389,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso
skipInvalidPolicies.skipped = make([]string, 0)
skipInvalidPolicies.invalid = make([]string, 0)
kyvernoPolicy := common.KyvernoPolicies{}
for _, policy := range policies {
_, err := policyvalidation.Validate(policy, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName()))
if err != nil {
@ -435,7 +438,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso
AuditWarn: c.AuditWarn,
Subresources: subresources,
}
ers, err := common.ApplyPolicyOnResource(applyPolicyConfig)
ers, err := kyvernoPolicy.ApplyPolicyOnResource(applyPolicyConfig)
if err != nil {
return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err)
}
@ -488,6 +491,26 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso
}
}
validatingAdmissionPolicy := common.ValidatingAdmissionPolicies{}
for _, policy := range validatingAdmissionPolicies {
for _, resource := range resources {
applyPolicyConfig := common.ApplyPolicyConfig{
ValidatingAdmissionPolicy: policy,
Resource: resource,
PolicyReport: c.PolicyReport,
Rc: rc,
Client: dClient,
AuditWarn: c.AuditWarn,
Subresources: subresources,
}
ers, err := validatingAdmissionPolicy.ApplyPolicyOnResource(applyPolicyConfig)
if err != nil {
return rc, resources, skipInvalidPolicies, responses, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err)
}
responses = append(responses, ers...)
}
}
return rc, resources, skipInvalidPolicies, responses, nil
}

View file

@ -174,6 +174,42 @@ func Test_Apply(t *testing.T) {
},
},
},
{
config: ApplyCommandConfig{
PolicyPaths: []string{"../../../../test/cli/test-validating-admission-policy/validate-deployment/policy.yaml"},
ResourcePaths: []string{"../../../../test/cli/test-validating-admission-policy/validate-deployment/deployment1.yaml"},
PolicyReport: true,
},
expectedPolicyReports: []preport.PolicyReport{
{
Summary: preport.PolicyReportSummary{
Pass: 1,
Fail: 0,
Skip: 0,
Error: 0,
Warn: 0,
},
},
},
},
{
config: ApplyCommandConfig{
PolicyPaths: []string{"../../../../test/cli/test-validating-admission-policy/validate-deployment/policy.yaml"},
ResourcePaths: []string{"../../../../test/cli/test-validating-admission-policy/validate-deployment/deployment2.yaml"},
PolicyReport: true,
},
expectedPolicyReports: []preport.PolicyReport{
{
Summary: preport.PolicyReportSummary{
Pass: 0,
Fail: 1,
Skip: 0,
Error: 0,
Warn: 0,
},
},
},
},
}
compareSummary := func(expected preport.PolicyReportSummary, actual map[string]interface{}, desc string) {

View file

@ -77,9 +77,24 @@ func buildPolicyResults(auditWarn bool, engineResponses ...engineapi.EngineRespo
now := metav1.Timestamp{Seconds: time.Now().Unix()}
for _, engineResponse := range engineResponses {
policy := engineResponse.Policy()
var ns, policyName string
var ann map[string]string
isVAP := engineResponse.IsValidatingAdmissionPolicy()
if isVAP {
validatingAdmissionPolicy := engineResponse.ValidatingAdmissionPolicy()
ns = validatingAdmissionPolicy.GetNamespace()
policyName = validatingAdmissionPolicy.GetName()
ann = validatingAdmissionPolicy.GetAnnotations()
} else {
kyvernoPolicy := engineResponse.Policy()
ns = kyvernoPolicy.GetNamespace()
policyName = kyvernoPolicy.GetName()
ann = kyvernoPolicy.GetAnnotations()
}
var appname string
ns := policy.GetNamespace()
if ns != "" {
appname = fmt.Sprintf("policyreport-ns-%s", ns)
} else {
@ -92,7 +107,7 @@ func buildPolicyResults(auditWarn bool, engineResponses ...engineapi.EngineRespo
}
result := policyreportv1alpha2.PolicyReportResult{
Policy: policy.GetName(),
Policy: policyName,
Resources: []corev1.ObjectReference{
{
Kind: engineResponse.Resource.GetKind(),
@ -105,7 +120,6 @@ func buildPolicyResults(auditWarn bool, engineResponses ...engineapi.EngineRespo
Scored: true,
}
ann := engineResponse.Policy().GetAnnotations()
if ruleResponse.Status() == engineapi.RuleStatusSkip {
result.Result = policyreportv1alpha2.StatusSkip
} else if ruleResponse.Status() == engineapi.RuleStatusError {
@ -124,7 +138,9 @@ func buildPolicyResults(auditWarn bool, engineResponses ...engineapi.EngineRespo
fmt.Println(ruleResponse)
}
result.Rule = ruleResponse.Name()
if !isVAP {
result.Rule = ruleResponse.Name()
}
result.Message = ruleResponse.Message()
result.Source = kyvernov1.ValueKyvernoApp
result.Timestamp = now

View file

@ -92,7 +92,7 @@ kyverno oci pull -i <imgref> -d policies`,
if err != nil {
return fmt.Errorf("reading layer blob: %v", err)
}
policies, err := yamlutils.GetPolicy(layerBytes)
policies, _, err := yamlutils.GetPolicy(layerBytes)
if err != nil {
return fmt.Errorf("unmarshaling layer blob: %v", err)
}

View file

@ -38,7 +38,7 @@ kyverno oci push -p policies. -i <imgref>`,
return errors.New("image reference is required")
}
policies, errs := common.GetPolicies([]string{policyRef})
policies, _, errs := common.GetPolicies([]string{policyRef})
if len(errs) != 0 {
return fmt.Errorf("unable to read policy file or directory %s: %w", policyRef, multierr.Combine(errs...))
}

View file

@ -18,7 +18,13 @@ type TestResults struct {
// 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"`
// It's required in case policy is a kyverno policy.
// +optional
Rule string `json:"rule,omitempty"`
// IsVap indicates if the policy is a validating admission policy.
// It's required in case policy is a validating admission policy.
// +optional
IsVap bool `json:"isVap"`
// Result mentions the result that the user is expecting.
// Possible values are pass, fail and skip.
Result policyreportv1alpha2.PolicyResult `json:"result"`

View file

@ -24,6 +24,7 @@ import (
"github.com/kyverno/kyverno/pkg/openapi"
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
"golang.org/x/exp/slices"
"k8s.io/api/admissionregistration/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/log"
@ -102,7 +103,7 @@ func applyPoliciesFromPath(
values.Results[i].CloneSourceResource = CloneSourceResourceFullPath[0]
}
policies, err := common.GetPoliciesFromPaths(fs, policyFullPath, isGit, policyResourcePath)
policies, validatingAdmissionPolicies, err := common.GetPoliciesFromPaths(fs, policyFullPath, isGit, policyResourcePath)
if err != nil {
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
os.Exit(1)
@ -118,12 +119,27 @@ func applyPoliciesFromPath(
}
}
var filteredVAPs []v1alpha1.ValidatingAdmissionPolicy
for _, p := range validatingAdmissionPolicies {
for _, res := range values.Results {
if p.GetName() == res.Policy {
filteredVAPs = append(filteredVAPs, p)
break
}
}
}
validatingAdmissionPolicies = filteredVAPs
ruleToCloneSourceResource := map[string]string{}
for _, p := range filteredPolicies {
var filteredRules []kyvernov1.Rule
for _, rule := range autogen.ComputeRules(p) {
for _, res := range values.Results {
if res.IsVap {
continue
}
if rule.Name == res.Rule {
filteredRules = append(filteredRules, rule)
if rule.HasGenerate() {
@ -156,7 +172,7 @@ func applyPoliciesFromPath(
return nil, nil, sanitizederror.NewWithError("failed to print mutated policy", err)
}
resources, err := common.GetResourceAccordingToResourcePath(fs, resourceFullPath, false, policies, dClient, "", false, isGit, policyResourcePath)
resources, err := common.GetResourceAccordingToResourcePath(fs, resourceFullPath, false, policies, validatingAdmissionPolicies, dClient, "", false, isGit, policyResourcePath)
if err != nil {
fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
os.Exit(1)
@ -165,8 +181,8 @@ func applyPoliciesFromPath(
checkableResources := selectResourcesForCheck(resources, values)
msgPolicies := "1 policy"
if len(policies) > 1 {
msgPolicies = fmt.Sprintf("%d policies", len(policies))
if len(policies)+len(validatingAdmissionPolicies) > 1 {
msgPolicies = fmt.Sprintf("%d policies", len(policies)+len(validatingAdmissionPolicies))
}
msgResources := "1 resource"
@ -178,6 +194,7 @@ func applyPoliciesFromPath(
fmt.Printf("applying %s to %s... \n", msgPolicies, msgResources)
}
kyvernoPolicy := common.KyvernoPolicies{}
for _, policy := range policies {
_, err := policyvalidation.Validate(policy, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName()))
if err != nil {
@ -217,7 +234,26 @@ func applyPoliciesFromPath(
Client: dClient,
Subresources: subresources,
}
ers, err := common.ApplyPolicyOnResource(applyPolicyConfig)
ers, err := kyvernoPolicy.ApplyPolicyOnResource(applyPolicyConfig)
if err != nil {
return nil, nil, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err)
}
engineResponses = append(engineResponses, ers...)
}
}
validatingAdmissionPolicy := common.ValidatingAdmissionPolicies{}
for _, policy := range validatingAdmissionPolicies {
for _, resource := range resources {
applyPolicyConfig := common.ApplyPolicyConfig{
ValidatingAdmissionPolicy: policy,
Resource: resource,
PolicyReport: true,
Rc: &resultCounts,
Client: dClient,
Subresources: subresources,
}
ers, err := validatingAdmissionPolicy.ApplyPolicyOnResource(applyPolicyConfig)
if err != nil {
return nil, nil, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err)
}
@ -304,11 +340,28 @@ func buildPolicyResults(
results := map[string]policyreportv1alpha2.PolicyReportResult{}
for _, resp := range engineResponses {
policyName := resp.Policy().GetName()
var ns, name string
var ann map[string]string
isVAP := resp.IsValidatingAdmissionPolicy()
if isVAP {
validatingAdmissionPolicy := resp.ValidatingAdmissionPolicy()
ns = validatingAdmissionPolicy.GetNamespace()
name = validatingAdmissionPolicy.GetName()
ann = validatingAdmissionPolicy.GetAnnotations()
} else {
kyvernoPolicy := resp.Policy()
ns = kyvernoPolicy.GetNamespace()
name = kyvernoPolicy.GetName()
ann = kyvernoPolicy.GetAnnotations()
}
policyName := name
resourceName := resp.Resource.GetName()
resourceKind := resp.Resource.GetKind()
resourceNamespace := resp.Resource.GetNamespace()
policyNamespace := resp.Policy().GetNamespace()
policyNamespace := ns
var rules []string
for _, rule := range resp.PolicyResponse.Rules {
@ -351,29 +404,32 @@ func buildPolicyResults(
for _, resource := range test.Resources {
if resource == resourceName {
var resultsKey string
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource)
if !slices.Contains(rules, test.Rule) {
if !slices.Contains(rules, "autogen-"+test.Rule) {
if !slices.Contains(rules, "autogen-cronjob-"+test.Rule) {
result.Result = policyreportv1alpha2.StatusSkip
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource, test.IsVap)
if !test.IsVap {
if !slices.Contains(rules, test.Rule) {
if !slices.Contains(rules, "autogen-"+test.Rule) {
if !slices.Contains(rules, "autogen-cronjob-"+test.Rule) {
result.Result = policyreportv1alpha2.StatusSkip
} else {
testResults[i].AutoGeneratedRule = "autogen-cronjob"
test.Rule = "autogen-cronjob-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource, test.IsVap)
}
} else {
testResults[i].AutoGeneratedRule = "autogen-cronjob"
test.Rule = "autogen-cronjob-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource)
testResults[i].AutoGeneratedRule = "autogen"
test.Rule = "autogen-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource, test.IsVap)
}
if results[resultsKey].Result == "" {
result.Result = policyreportv1alpha2.StatusSkip
results[resultsKey] = result
}
} else {
testResults[i].AutoGeneratedRule = "autogen"
test.Rule = "autogen-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource)
}
if results[resultsKey].Result == "" {
result.Result = policyreportv1alpha2.StatusSkip
results[resultsKey] = result
}
patchedResourcePath = append(patchedResourcePath, test.PatchedResource)
}
patchedResourcePath = append(patchedResourcePath, test.PatchedResource)
if _, ok := results[resultsKey]; !ok {
results[resultsKey] = result
}
@ -384,29 +440,32 @@ func buildPolicyResults(
if test.Resource != "" {
if test.Policy == policyName && test.Resource == resourceName {
var resultsKey string
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource)
if !slices.Contains(rules, test.Rule) {
if !slices.Contains(rules, "autogen-"+test.Rule) {
if !slices.Contains(rules, "autogen-cronjob-"+test.Rule) {
result.Result = policyreportv1alpha2.StatusSkip
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource, test.IsVap)
if !test.IsVap {
if !slices.Contains(rules, test.Rule) {
if !slices.Contains(rules, "autogen-"+test.Rule) {
if !slices.Contains(rules, "autogen-cronjob-"+test.Rule) {
result.Result = policyreportv1alpha2.StatusSkip
} else {
testResults[i].AutoGeneratedRule = "autogen-cronjob"
test.Rule = "autogen-cronjob-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource, test.IsVap)
}
} else {
testResults[i].AutoGeneratedRule = "autogen-cronjob"
test.Rule = "autogen-cronjob-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource)
testResults[i].AutoGeneratedRule = "autogen"
test.Rule = "autogen-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource, test.IsVap)
}
if results[resultsKey].Result == "" {
result.Result = policyreportv1alpha2.StatusSkip
results[resultsKey] = result
}
} else {
testResults[i].AutoGeneratedRule = "autogen"
test.Rule = "autogen-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource)
}
if results[resultsKey].Result == "" {
result.Result = policyreportv1alpha2.StatusSkip
results[resultsKey] = result
}
patchedResourcePath = append(patchedResourcePath, test.PatchedResource)
}
patchedResourcePath = append(patchedResourcePath, test.PatchedResource)
if _, ok := results[resultsKey]; !ok {
results[resultsKey] = result
}
@ -421,7 +480,7 @@ func buildPolicyResults(
var resultsKey []string
var resultKey string
var result policyreportv1alpha2.PolicyReportResult
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName)
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName, test.IsVap)
for _, key := range resultsKey {
if val, ok := results[key]; ok {
result = val
@ -454,7 +513,7 @@ func buildPolicyResults(
var resultsKey []string
var resultKey string
var result policyreportv1alpha2.PolicyReportResult
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName)
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName, test.IsVap)
for _, key := range resultsKey {
if val, ok := results[key]; ok {
result = val
@ -484,14 +543,14 @@ func buildPolicyResults(
}
for _, rule := range resp.PolicyResponse.Rules {
if rule.RuleType() != engineapi.Validation && rule.RuleType() != engineapi.ImageVerify || test.Rule != rule.Name() {
if rule.RuleType() != engineapi.Validation && rule.RuleType() != engineapi.ImageVerify || test.Rule != rule.Name() && !test.IsVap {
continue
}
var resultsKey []string
var resultKey string
var result policyreportv1alpha2.PolicyReportResult
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName)
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName, test.IsVap)
for _, key := range resultsKey {
if val, ok := results[key]; ok {
result = val
@ -500,7 +559,6 @@ func buildPolicyResults(
continue
}
ann := resp.Policy().GetAnnotations()
if rule.Status() == engineapi.RuleStatusSkip {
result.Result = policyreportv1alpha2.StatusSkip
} else if rule.Status() == engineapi.RuleStatusError {
@ -527,27 +585,50 @@ func buildPolicyResults(
return results, testResults
}
func GetAllPossibleResultsKey(policyNamespace, policy, rule, resourceNamespace, kind, resource string) []string {
func GetAllPossibleResultsKey(policyNamespace, policy, rule, resourceNamespace, kind, resource string, isVap bool) []string {
var resultsKey []string
resultKey1 := fmt.Sprintf("%s-%s-%s-%s", policy, rule, kind, resource)
resultKey2 := fmt.Sprintf("%s-%s-%s-%s-%s", policy, rule, resourceNamespace, kind, resource)
resultKey3 := fmt.Sprintf("%s-%s-%s-%s-%s", policyNamespace, policy, rule, kind, resource)
resultKey4 := fmt.Sprintf("%s-%s-%s-%s-%s-%s", policyNamespace, policy, rule, resourceNamespace, kind, resource)
var resultKey1, resultKey2, resultKey3, resultKey4 string
if isVap {
resultKey1 = fmt.Sprintf("%s-%s-%s", policy, kind, resource)
resultKey2 = fmt.Sprintf("%s-%s-%s-%s", policy, resourceNamespace, kind, resource)
resultKey3 = fmt.Sprintf("%s-%s-%s-%s", policyNamespace, policy, kind, resource)
resultKey4 = fmt.Sprintf("%s-%s-%s-%s-%s", policyNamespace, policy, resourceNamespace, kind, resource)
} else {
resultKey1 = fmt.Sprintf("%s-%s-%s-%s", policy, rule, kind, resource)
resultKey2 = fmt.Sprintf("%s-%s-%s-%s-%s", policy, rule, resourceNamespace, kind, resource)
resultKey3 = fmt.Sprintf("%s-%s-%s-%s-%s", policyNamespace, policy, rule, kind, resource)
resultKey4 = fmt.Sprintf("%s-%s-%s-%s-%s-%s", policyNamespace, policy, rule, resourceNamespace, kind, resource)
}
resultsKey = append(resultsKey, resultKey1, resultKey2, resultKey3, resultKey4)
return resultsKey
}
func GetResultKeyAccordingToTestResults(policyNs, policy, rule, resourceNs, kind, resource string) string {
func GetResultKeyAccordingToTestResults(policyNs, policy, rule, resourceNs, kind, resource string, isVap bool) string {
var resultKey string
resultKey = fmt.Sprintf("%s-%s-%s-%s", policy, rule, kind, resource)
if isVap {
resultKey = fmt.Sprintf("%s-%s-%s", policy, kind, resource)
if policyNs != "" && resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", policyNs, policy, rule, resourceNs, kind, resource)
} else if policyNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", policyNs, policy, rule, kind, resource)
} else if resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", policy, rule, resourceNs, kind, resource)
if policyNs != "" && resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", policyNs, policy, resourceNs, kind, resource)
} else if policyNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s", policyNs, policy, kind, resource)
} else if resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s", policy, resourceNs, kind, resource)
}
} else {
resultKey = fmt.Sprintf("%s-%s-%s-%s", policy, rule, kind, resource)
if policyNs != "" && resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", policyNs, policy, rule, resourceNs, kind, resource)
} else if policyNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", policyNs, policy, rule, kind, resource)
} else if resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", policy, rule, resourceNs, kind, resource)
}
}
return resultKey
}

View file

@ -195,25 +195,47 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
testCount++
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(resource)
var ruleNameInResultKey string
if v.AutoGeneratedRule != "" {
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
} else {
ruleNameInResultKey = v.Rule
if !v.IsVap {
if v.AutoGeneratedRule != "" {
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
} else {
ruleNameInResultKey = v.Rule
}
}
var resultKey string
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, resource)
} else {
resultKey = fmt.Sprintf("%s-%s-%s", v.Policy, v.Kind, resource)
}
resultKey := fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, resource)
found, _ := isNamespacedPolicy(v.Policy)
var ns string
ns, v.Policy = getUserDefinedPolicyNameAndNamespace(v.Policy)
if found && v.Namespace != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, resource)
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, resource)
} else {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, v.Namespace, v.Kind, resource)
}
} else if found {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, resource)
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, resource)
} else {
resultKey = fmt.Sprintf("%s-%s-%s-%s", ns, v.Policy, v.Kind, resource)
}
row.Policy = boldFgCyan.Sprint(ns) + "/" + boldFgCyan.Sprint(v.Policy)
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(resource)
} else if v.Namespace != "" {
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(resource)
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, resource)
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, resource)
} else {
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, v.Namespace, v.Kind, resource)
}
}
var testRes policyreportv1alpha2.PolicyReportResult
@ -258,25 +280,47 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
countDeprecatedResource++
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(v.Resource)
var ruleNameInResultKey string
if v.AutoGeneratedRule != "" {
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
} else {
ruleNameInResultKey = v.Rule
if !v.IsVap {
if v.AutoGeneratedRule != "" {
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
} else {
ruleNameInResultKey = v.Rule
}
}
var resultKey string
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
} else {
resultKey = fmt.Sprintf("%s-%s-%s", v.Policy, v.Kind, v.Resource)
}
resultKey := fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
found, _ := isNamespacedPolicy(v.Policy)
var ns string
ns, v.Policy = getUserDefinedPolicyNameAndNamespace(v.Policy)
if found && v.Namespace != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
} else {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, v.Namespace, v.Kind, v.Resource)
}
} else if found {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
} else {
resultKey = fmt.Sprintf("%s-%s-%s-%s", ns, v.Policy, v.Kind, v.Resource)
}
row.Policy = boldFgCyan.Sprint(ns) + "/" + boldFgCyan.Sprint(v.Policy)
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(v.Resource)
} else if v.Namespace != "" {
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(v.Resource)
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
} else {
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, v.Namespace, v.Kind, v.Resource)
}
}
var testRes policyreportv1alpha2.PolicyReportResult

View file

@ -59,7 +59,7 @@ func Test_selectResourcesForCheck(t *testing.T) {
assert.NilError(t, err)
// read policies
policies, err := common.GetPoliciesFromPaths(
policies, validatingAdmissionPolicies, err := common.GetPoliciesFromPaths(
fs,
[]string{filepath.Join(baseTestDir, values.Policies[0])},
false,
@ -73,6 +73,7 @@ func Test_selectResourcesForCheck(t *testing.T) {
[]string{filepath.Join(baseTestDir, values.Resources[0])},
false,
policies,
validatingAdmissionPolicies,
nil,
"",
false,

View file

@ -22,15 +22,14 @@ import (
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
engineContext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/registryclient"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
yamlv2 "gopkg.in/yaml.v2"
"k8s.io/api/admissionregistration/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@ -83,6 +82,7 @@ type NamespaceSelector struct {
type ApplyPolicyConfig struct {
Policy kyvernov1.PolicyInterface
ValidatingAdmissionPolicy v1alpha1.ValidatingAdmissionPolicy
Resource *unstructured.Unstructured
MutateLogPath string
MutateLogPathIsDir bool
@ -107,7 +107,7 @@ func HasVariables(policy kyvernov1.PolicyInterface) [][]string {
}
// GetPolicies - Extracting the policies from multiple YAML
func GetPolicies(paths []string) (policies []kyvernov1.PolicyInterface, errors []error) {
func GetPolicies(paths []string) (policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, errors []error) {
for _, path := range paths {
log.V(5).Info("reading policies", "path", path)
@ -146,9 +146,10 @@ func GetPolicies(paths []string) (policies []kyvernov1.PolicyInterface, errors [
}
}
policiesFromDir, errorsFromDir := GetPolicies(listOfFiles)
policiesFromDir, admissionPoliciesFromDir, errorsFromDir := GetPolicies(listOfFiles)
errors = append(errors, errorsFromDir...)
policies = append(policies, policiesFromDir...)
validatingAdmissionPolicies = append(validatingAdmissionPolicies, admissionPoliciesFromDir...)
} else {
var fileBytes []byte
if isHTTPPath {
@ -190,7 +191,7 @@ func GetPolicies(paths []string) (policies []kyvernov1.PolicyInterface, errors [
}
}
policiesFromFile, errFromFile := yamlutils.GetPolicy(fileBytes)
policiesFromFile, admissionPoliciesFromFile, errFromFile := yamlutils.GetPolicy(fileBytes)
if errFromFile != nil {
err := fmt.Errorf("failed to process %s: %v", path, errFromFile.Error())
errors = append(errors, err)
@ -198,11 +199,12 @@ func GetPolicies(paths []string) (policies []kyvernov1.PolicyInterface, errors [
}
policies = append(policies, policiesFromFile...)
validatingAdmissionPolicies = append(validatingAdmissionPolicies, admissionPoliciesFromFile...)
}
}
log.V(3).Info("read policies", "policies", len(policies), "errors", len(errors))
return policies, errors
return policies, validatingAdmissionPolicies, errors
}
// IsInputFromPipe - check if input is passed using pipe
@ -370,191 +372,6 @@ func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit
return variables, globalValMap, valuesMapResource, namespaceSelectorMap, subresources, nil
}
// ApplyPolicyOnResource - function to apply policy on resource
func ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error) {
jp := jmespath.New(config.NewDefaultConfiguration(false))
var engineResponses []engineapi.EngineResponse
namespaceLabels := make(map[string]string)
operationIsDelete := false
if c.Variables["request.operation"] == "DELETE" {
operationIsDelete = true
}
policyWithNamespaceSelector := false
OuterLoop:
for _, p := range autogen.ComputeRules(c.Policy) {
if p.MatchResources.ResourceDescription.NamespaceSelector != nil ||
p.ExcludeResources.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break
}
for _, m := range p.MatchResources.Any {
if m.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
for _, m := range p.MatchResources.All {
if m.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
for _, e := range p.ExcludeResources.Any {
if e.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
for _, e := range p.ExcludeResources.All {
if e.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
}
if policyWithNamespaceSelector {
resourceNamespace := c.Resource.GetNamespace()
namespaceLabels = c.NamespaceSelectorMap[c.Resource.GetNamespace()]
if resourceNamespace != "default" && len(namespaceLabels) < 1 {
return engineResponses, sanitizederror.NewWithError(fmt.Sprintf("failed to get namespace labels for resource %s. use --values-file flag to pass the namespace labels", c.Resource.GetName()), nil)
}
}
resPath := fmt.Sprintf("%s/%s/%s", c.Resource.GetNamespace(), c.Resource.GetKind(), c.Resource.GetName())
log.V(3).Info("applying policy on resource", "policy", c.Policy.GetName(), "resource", resPath)
resourceRaw, err := c.Resource.MarshalJSON()
if err != nil {
log.Error(err, "failed to marshal resource")
}
updatedResource, err := kubeutils.BytesToUnstructured(resourceRaw)
if err != nil {
log.Error(err, "unable to convert raw resource to unstructured")
}
ctx := engineContext.NewContext(jp)
if operationIsDelete {
err = engineContext.AddOldResource(ctx, resourceRaw)
} else {
err = engineContext.AddResource(ctx, resourceRaw)
}
if err != nil {
log.Error(err, "failed to load resource in context")
}
for key, value := range c.Variables {
err = ctx.AddVariable(key, value)
if err != nil {
log.Error(err, "failed to add variable to context")
}
}
cfg := config.NewDefaultConfiguration(false)
if err := ctx.AddImageInfos(c.Resource, cfg); err != nil {
log.Error(err, "failed to add image variables to context")
}
gvk, subresource := updatedResource.GroupVersionKind(), ""
// If --cluster flag is not set, then we need to find the top level resource GVK and subresource
if c.Client == nil {
for _, s := range c.Subresources {
subgvk := schema.GroupVersionKind{
Group: s.APIResource.Group,
Version: s.APIResource.Version,
Kind: s.APIResource.Kind,
}
if gvk == subgvk {
gvk = schema.GroupVersionKind{
Group: s.ParentResource.Group,
Version: s.ParentResource.Version,
Kind: s.ParentResource.Kind,
}
parts := strings.Split(s.APIResource.Name, "/")
subresource = parts[1]
}
}
}
eng := engine.NewEngine(
cfg,
config.NewDefaultMetricsConfiguration(),
jmespath.New(cfg),
c.Client,
registryclient.NewOrDie(),
store.ContextLoaderFactory(nil),
nil,
)
policyContext := engine.NewPolicyContextWithJsonContext(kyvernov1.Create, ctx).
WithPolicy(c.Policy).
WithNewResource(*updatedResource).
WithNamespaceLabels(namespaceLabels).
WithAdmissionInfo(c.UserInfo).
WithResourceKind(gvk, subresource)
mutateResponse := eng.Mutate(context.Background(), policyContext)
engineResponses = append(engineResponses, mutateResponse)
err = processMutateEngineResponse(c, &mutateResponse, resPath)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return engineResponses, sanitizederror.NewWithError("failed to print mutated result", err)
}
}
var policyHasValidate bool
for _, rule := range autogen.ComputeRules(c.Policy) {
if rule.HasValidate() || rule.HasVerifyImageChecks() {
policyHasValidate = true
}
}
policyContext = policyContext.WithNewResource(mutateResponse.PatchedResource)
var validateResponse engineapi.EngineResponse
if policyHasValidate {
validateResponse = eng.Validate(context.Background(), policyContext)
ProcessValidateEngineResponse(c.Policy, validateResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn)
}
if !validateResponse.IsEmpty() {
engineResponses = append(engineResponses, validateResponse)
}
verifyImageResponse, _ := eng.VerifyAndPatchImages(context.TODO(), policyContext)
if !verifyImageResponse.IsEmpty() {
engineResponses = append(engineResponses, verifyImageResponse)
ProcessValidateEngineResponse(c.Policy, verifyImageResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn)
}
var policyHasGenerate bool
for _, rule := range autogen.ComputeRules(c.Policy) {
if rule.HasGenerate() {
policyHasGenerate = true
}
}
if policyHasGenerate {
generateResponse := eng.ApplyBackgroundChecks(context.TODO(), policyContext)
if !generateResponse.IsEmpty() {
newRuleResponse, err := handleGeneratePolicy(&generateResponse, *policyContext, c.RuleToCloneSourceResource)
if err != nil {
log.Error(err, "failed to apply generate policy")
} else {
generateResponse.PolicyResponse.Rules = newRuleResponse
}
engineResponses = append(engineResponses, generateResponse)
}
updateResultCounts(c.Policy, &generateResponse, resPath, c.Rc, c.AuditWarn)
}
return engineResponses, nil
}
func ProcessValidateEngineResponse(policy kyvernov1.PolicyInterface, validateResponse engineapi.EngineResponse, resPath string, rc *ResultCounts, policyReport bool, auditWarn bool) {
printCount := 0
for _, policyRule := range autogen.ComputeRules(policy) {
@ -641,7 +458,7 @@ func PrintMutatedOutput(mutateLogPath string, mutateLogPathIsDir bool, yaml stri
}
// GetPoliciesFromPaths - get policies according to the resource path
func GetPoliciesFromPaths(fs billy.Filesystem, dirPath []string, isGit bool, policyResourcePath string) (policies []kyvernov1.PolicyInterface, err error) {
func GetPoliciesFromPaths(fs billy.Filesystem, dirPath []string, isGit bool, policyResourcePath string) (policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, err error) {
if isGit {
for _, pp := range dirPath {
filep, err := fs.Open(filepath.Join(policyResourcePath, pp))
@ -659,12 +476,13 @@ func GetPoliciesFromPaths(fs billy.Filesystem, dirPath []string, isGit bool, pol
fmt.Printf("failed to convert to JSON: %v", err)
continue
}
policiesFromFile, errFromFile := yamlutils.GetPolicy(policyBytes)
policiesFromFile, admissionPoliciesFromFile, errFromFile := yamlutils.GetPolicy(policyBytes)
if errFromFile != nil {
fmt.Printf("failed to process : %v", errFromFile.Error())
continue
}
policies = append(policies, policiesFromFile...)
validatingAdmissionPolicies = append(validatingAdmissionPolicies, admissionPoliciesFromFile...)
}
} else {
if len(dirPath) > 0 && dirPath[0] == "-" {
@ -675,19 +493,19 @@ func GetPoliciesFromPaths(fs billy.Filesystem, dirPath []string, isGit bool, pol
policyStr = policyStr + scanner.Text() + "\n"
}
yamlBytes := []byte(policyStr)
policies, err = yamlutils.GetPolicy(yamlBytes)
policies, validatingAdmissionPolicies, err = yamlutils.GetPolicy(yamlBytes)
if err != nil {
return nil, sanitizederror.NewWithError("failed to extract the resources", err)
return nil, nil, sanitizederror.NewWithError("failed to extract the resources", err)
}
}
} else {
var errors []error
policies, errors = GetPolicies(dirPath)
if len(policies) == 0 {
policies, validatingAdmissionPolicies, errors = GetPolicies(dirPath)
if len(policies) == 0 && len(validatingAdmissionPolicies) == 0 {
if len(errors) > 0 {
return nil, sanitizederror.NewWithErrors("failed to read file", errors)
return nil, nil, sanitizederror.NewWithErrors("failed to read file", errors)
}
return nil, sanitizederror.New(fmt.Sprintf("no file found in paths %v", dirPath))
return nil, nil, sanitizederror.New(fmt.Sprintf("no file found in paths %v", dirPath))
}
if len(errors) > 0 && log.V(1).Enabled() {
fmt.Printf("ignoring errors: \n")
@ -702,7 +520,7 @@ func GetPoliciesFromPaths(fs billy.Filesystem, dirPath []string, isGit bool, pol
// GetResourceAccordingToResourcePath - get resources according to the resource path
func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []string,
cluster bool, policies []kyvernov1.PolicyInterface, dClient dclient.Interface, namespace string, policyReport bool, isGit bool, policyResourcePath string,
cluster bool, policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, dClient dclient.Interface, namespace string, policyReport bool, isGit bool, policyResourcePath string,
) (resources []*unstructured.Unstructured, err error) {
if isGit {
resources, err = GetResourcesWithTest(fs, policies, resourcePaths, isGit, policyResourcePath)
@ -746,7 +564,7 @@ func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []str
}
}
resources, err = GetResources(policies, resourcePaths, dClient, cluster, namespace, policyReport)
resources, err = GetResources(policies, validatingAdmissionPolicies, resourcePaths, dClient, cluster, namespace, policyReport)
if err != nil {
return resources, err
}

View file

@ -96,8 +96,9 @@ func Test_NamespaceSelector(t *testing.T) {
},
}
rc := &ResultCounts{}
kyvernoPolicy := KyvernoPolicies{}
for _, tc := range testcases {
policyArray, _ := yamlutils.GetPolicy(tc.policy)
policyArray, _, _ := yamlutils.GetPolicy(tc.policy)
resourceArray, _ := GetResource(tc.resource)
applyPolicyConfig := ApplyPolicyConfig{
Policy: policyArray[0],
@ -107,7 +108,7 @@ func Test_NamespaceSelector(t *testing.T) {
NamespaceSelectorMap: tc.namespaceSelectorMap,
Rc: rc,
}
ApplyPolicyOnResource(applyPolicyConfig)
kyvernoPolicy.ApplyPolicyOnResource(applyPolicyConfig)
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

@ -16,6 +16,10 @@ import (
"github.com/kyverno/kyverno/pkg/clients/dclient"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
"golang.org/x/text/cases"
"golang.org/x/text/language"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/api/admissionregistration/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -28,34 +32,33 @@ import (
// - local paths to resources, if given
// - the k8s cluster, if given
func GetResources(
policies []kyvernov1.PolicyInterface, resourcePaths []string, dClient dclient.Interface, cluster bool,
policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, resourcePaths []string, dClient dclient.Interface, cluster bool,
namespace string, policyReport bool,
) ([]*unstructured.Unstructured, error) {
resources := make([]*unstructured.Unstructured, 0)
var err error
if cluster && dClient != nil {
resourceTypesMap := make(map[schema.GroupVersionKind]bool)
var resourceTypes []schema.GroupVersionKind
var subresourceMap map[schema.GroupVersionKind]Subresource
if len(policies) > 0 {
matchedResources := &KyvernoResources{
policies: policies,
}
for _, policy := range policies {
for _, rule := range autogen.ComputeRules(policy) {
var resourceTypesInRule map[schema.GroupVersionKind]bool
resourceTypesInRule, subresourceMap = GetKindsFromRule(rule, dClient)
for resourceKind := range resourceTypesInRule {
resourceTypesMap[resourceKind] = true
}
resources, err = matchedResources.FetchResourcesFromPolicy(resourcePaths, dClient, namespace, policyReport)
if err != nil {
return resources, err
}
}
for kind := range resourceTypesMap {
resourceTypes = append(resourceTypes, kind)
}
if len(validatingAdmissionPolicies) > 0 {
matchedResources := &ValidatingAdmissionResources{
policies: validatingAdmissionPolicies,
}
resources, err = whenClusterIsTrue(resourceTypes, subresourceMap, dClient, namespace, resourcePaths, policyReport)
if err != nil {
return resources, err
resources, err = matchedResources.FetchResourcesFromPolicy(resourcePaths, dClient, namespace, policyReport)
if err != nil {
return resources, err
}
}
} else if len(resourcePaths) > 0 {
resources, err = whenClusterIsFalse(resourcePaths, policyReport)
@ -340,6 +343,64 @@ func GetKindsFromRule(rule kyvernov1.Rule, client dclient.Interface) (map[schema
return resourceTypesMap, subresourceMap
}
func getKindsFromValidatingAdmissionRule(rule admissionregistrationv1.Rule, client dclient.Interface) (map[schema.GroupVersionKind]bool, map[schema.GroupVersionKind]Subresource, error) {
resourceTypesMap := make(map[schema.GroupVersionKind]bool)
subresourceMap := make(map[schema.GroupVersionKind]Subresource)
group := rule.APIGroups[0]
if group == "" {
group = "*"
}
version := rule.APIVersions[0]
for _, resource := range rule.Resources {
var kind, subresource string
isSubresource := kubeutils.IsSubresource(resource)
if isSubresource {
parts := strings.Split(resource, "/")
kind = cases.Title(language.English, cases.NoLower).String(parts[0])
kind, _ = strings.CutSuffix(kind, "s")
subresource = parts[1]
} else {
resource = cases.Title(language.English, cases.NoLower).String(resource)
resource, _ = strings.CutSuffix(resource, "s")
kind = resource
subresource = ""
}
gvrss, err := client.Discovery().FindResources(group, version, kind, subresource)
if err != nil {
log.Info("failed to find resource", "kind", kind, "error", err)
return resourceTypesMap, subresourceMap, err
}
for parent, child := range gvrss {
// The resource is not a subresource
if parent.SubResource == "" {
resourceTypesMap[parent.GroupVersionKind()] = true
} else {
gvk := schema.GroupVersionKind{
Group: child.Group, Version: child.Version, Kind: child.Kind,
}
subresourceMap[gvk] = Subresource{
APIResource: child,
ParentResource: metav1.APIResource{
Group: parent.Group,
Version: parent.Version,
Kind: parent.Kind,
Name: parent.Resource,
},
}
}
}
}
return resourceTypesMap, subresourceMap, nil
}
func addGVKToResourceTypesMap(kind string, resourceTypesMap map[schema.GroupVersionKind]bool, subresourceMap map[schema.GroupVersionKind]Subresource, client dclient.Interface) {
group, version, kind, subresource := kubeutils.ParseKindSelector(kind)
gvrss, err := client.Discovery().FindResources(group, version, kind, subresource)

View file

@ -0,0 +1,206 @@
package common
import (
"context"
"fmt"
"strings"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
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/config"
"github.com/kyverno/kyverno/pkg/engine"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
engineContext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/registryclient"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type KyvernoPolicies struct{}
func (p *KyvernoPolicies) ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error) {
jp := jmespath.New(config.NewDefaultConfiguration(false))
var engineResponses []engineapi.EngineResponse
namespaceLabels := make(map[string]string)
operationIsDelete := false
if c.Variables["request.operation"] == "DELETE" {
operationIsDelete = true
}
policyWithNamespaceSelector := false
OuterLoop:
for _, p := range autogen.ComputeRules(c.Policy) {
if p.MatchResources.ResourceDescription.NamespaceSelector != nil ||
p.ExcludeResources.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break
}
for _, m := range p.MatchResources.Any {
if m.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
for _, m := range p.MatchResources.All {
if m.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
for _, e := range p.ExcludeResources.Any {
if e.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
for _, e := range p.ExcludeResources.All {
if e.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
}
if policyWithNamespaceSelector {
resourceNamespace := c.Resource.GetNamespace()
namespaceLabels = c.NamespaceSelectorMap[c.Resource.GetNamespace()]
if resourceNamespace != "default" && len(namespaceLabels) < 1 {
return engineResponses, sanitizederror.NewWithError(fmt.Sprintf("failed to get namespace labels for resource %s. use --values-file flag to pass the namespace labels", c.Resource.GetName()), nil)
}
}
resPath := fmt.Sprintf("%s/%s/%s", c.Resource.GetNamespace(), c.Resource.GetKind(), c.Resource.GetName())
log.V(3).Info("applying policy on resource", "policy", c.Policy.GetName(), "resource", resPath)
resourceRaw, err := c.Resource.MarshalJSON()
if err != nil {
log.Error(err, "failed to marshal resource")
}
updatedResource, err := kubeutils.BytesToUnstructured(resourceRaw)
if err != nil {
log.Error(err, "unable to convert raw resource to unstructured")
}
ctx := engineContext.NewContext(jp)
if operationIsDelete {
err = engineContext.AddOldResource(ctx, resourceRaw)
} else {
err = engineContext.AddResource(ctx, resourceRaw)
}
if err != nil {
log.Error(err, "failed to load resource in context")
}
for key, value := range c.Variables {
err = ctx.AddVariable(key, value)
if err != nil {
log.Error(err, "failed to add variable to context")
}
}
cfg := config.NewDefaultConfiguration(false)
if err := ctx.AddImageInfos(c.Resource, cfg); err != nil {
log.Error(err, "failed to add image variables to context")
}
gvk, subresource := updatedResource.GroupVersionKind(), ""
// If --cluster flag is not set, then we need to find the top level resource GVK and subresource
if c.Client == nil {
for _, s := range c.Subresources {
subgvk := schema.GroupVersionKind{
Group: s.APIResource.Group,
Version: s.APIResource.Version,
Kind: s.APIResource.Kind,
}
if gvk == subgvk {
gvk = schema.GroupVersionKind{
Group: s.ParentResource.Group,
Version: s.ParentResource.Version,
Kind: s.ParentResource.Kind,
}
parts := strings.Split(s.APIResource.Name, "/")
subresource = parts[1]
}
}
}
eng := engine.NewEngine(
cfg,
config.NewDefaultMetricsConfiguration(),
jmespath.New(cfg),
c.Client,
registryclient.NewOrDie(),
store.ContextLoaderFactory(nil),
nil,
)
policyContext := engine.NewPolicyContextWithJsonContext(kyvernov1.Create, ctx).
WithPolicy(c.Policy).
WithNewResource(*updatedResource).
WithNamespaceLabels(namespaceLabels).
WithAdmissionInfo(c.UserInfo).
WithResourceKind(gvk, subresource)
mutateResponse := eng.Mutate(context.Background(), policyContext)
engineResponses = append(engineResponses, mutateResponse)
err = processMutateEngineResponse(c, &mutateResponse, resPath)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return engineResponses, sanitizederror.NewWithError("failed to print mutated result", err)
}
}
var policyHasValidate bool
for _, rule := range autogen.ComputeRules(c.Policy) {
if rule.HasValidate() || rule.HasVerifyImageChecks() {
policyHasValidate = true
}
}
policyContext = policyContext.WithNewResource(mutateResponse.PatchedResource)
var validateResponse engineapi.EngineResponse
if policyHasValidate {
validateResponse = eng.Validate(context.Background(), policyContext)
ProcessValidateEngineResponse(c.Policy, validateResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn)
}
if !validateResponse.IsEmpty() {
engineResponses = append(engineResponses, validateResponse)
}
verifyImageResponse, _ := eng.VerifyAndPatchImages(context.TODO(), policyContext)
if !verifyImageResponse.IsEmpty() {
engineResponses = append(engineResponses, verifyImageResponse)
ProcessValidateEngineResponse(c.Policy, verifyImageResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn)
}
var policyHasGenerate bool
for _, rule := range autogen.ComputeRules(c.Policy) {
if rule.HasGenerate() {
policyHasGenerate = true
}
}
if policyHasGenerate {
generateResponse := eng.ApplyBackgroundChecks(context.TODO(), policyContext)
if !generateResponse.IsEmpty() {
newRuleResponse, err := handleGeneratePolicy(&generateResponse, *policyContext, c.RuleToCloneSourceResource)
if err != nil {
log.Error(err, "failed to apply generate policy")
} else {
generateResponse.PolicyResponse.Rules = newRuleResponse
}
engineResponses = append(engineResponses, generateResponse)
}
updateResultCounts(c.Policy, &generateResponse, resPath, c.Rc, c.AuditWarn)
}
return engineResponses, nil
}

View file

@ -0,0 +1,40 @@
package common
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type KyvernoResources struct {
policies []kyvernov1.PolicyInterface
}
func (r *KyvernoResources) FetchResourcesFromPolicy(resourcePaths []string, dClient dclient.Interface, namespace string, policyReport bool) ([]*unstructured.Unstructured, error) {
var resources []*unstructured.Unstructured
var err error
resourceTypesMap := make(map[schema.GroupVersionKind]bool)
var resourceTypes []schema.GroupVersionKind
var subresourceMap map[schema.GroupVersionKind]Subresource
for _, policy := range r.policies {
for _, rule := range autogen.ComputeRules(policy) {
var resourceTypesInRule map[schema.GroupVersionKind]bool
resourceTypesInRule, subresourceMap = GetKindsFromRule(rule, dClient)
for resourceKind := range resourceTypesInRule {
resourceTypesMap[resourceKind] = true
}
}
}
for kind := range resourceTypesMap {
resourceTypes = append(resourceTypes, kind)
}
resources, err = whenClusterIsTrue(resourceTypes, subresourceMap, dClient, namespace, resourcePaths, policyReport)
return resources, err
}

View file

@ -0,0 +1,9 @@
package common
import (
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
)
type PolicyInterface interface {
ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error)
}

View file

@ -0,0 +1,11 @@
package common
import (
"github.com/kyverno/kyverno/pkg/clients/dclient"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// ResourceInterface abstracts the matched resources by either Kyverno Policies or Validating Admission Policies
type ResourceInterface interface {
FetchResourcesFromPolicy(resourcePaths []string, dClient dclient.Interface, namespace string, policyReport bool) ([]*unstructured.Unstructured, error)
}

View file

@ -0,0 +1,146 @@
package common
import (
"context"
"fmt"
"time"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/api/admissionregistration/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/cel"
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
celconfig "k8s.io/apiserver/pkg/apis/cel"
)
type ValidatingAdmissionPolicies struct{}
func (p *ValidatingAdmissionPolicies) ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error) {
var engineResponses []engineapi.EngineResponse
resPath := fmt.Sprintf("%s/%s/%s", c.Resource.GetNamespace(), c.Resource.GetKind(), c.Resource.GetName())
log.V(3).Info("applying policy on resource", "policy", c.ValidatingAdmissionPolicy.GetName(), "resource", resPath)
startTime := time.Now()
validations := c.ValidatingAdmissionPolicy.Spec.Validations
var expressions, messageExpressions []cel.ExpressionAccessor
for _, validation := range validations {
var reason metav1.StatusReason
if validation.Reason == nil {
reason = metav1.StatusReasonInvalid
} else {
reason = *validation.Reason
}
var message string
if validation.Message != "" && validation.MessageExpression != "" {
message = validation.MessageExpression
} else if validation.Message != "" {
message = validation.Message
} else if validation.MessageExpression != "" {
message = validation.MessageExpression
} else {
message = fmt.Sprintf("error: failed to create %s: %s \"%s\" is forbidden: ValidatingAdmissionPolicy '%s' denied request: failed expression: %s", c.Resource.GetKind(), c.Resource.GetAPIVersion(), c.Resource.GetName(), c.ValidatingAdmissionPolicy.Name, validation.Expression)
}
condition := &validatingadmissionpolicy.ValidationCondition{
Expression: validation.Expression,
Message: message,
Reason: &reason,
}
messageCondition := &validatingadmissionpolicy.MessageExpressionCondition{
MessageExpression: validation.MessageExpression,
}
expressions = append(expressions, condition)
messageExpressions = append(messageExpressions, messageCondition)
}
hasParams := c.ValidatingAdmissionPolicy.Spec.ParamKind != nil
filterCompiler := cel.NewFilterCompiler()
filter := filterCompiler.Compile(expressions, cel.OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: false}, celconfig.PerCallLimit)
messageExpressionfilter := filterCompiler.Compile(messageExpressions, cel.OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: false}, celconfig.PerCallLimit)
admissionAttributes := admission.NewAttributesRecord(c.Resource.DeepCopyObject(), nil, c.Resource.GroupVersionKind(), c.Resource.GetNamespace(), c.Resource.GetName(), schema.GroupVersionResource{}, "", admission.Create, nil, false, nil)
versionedAttr, _ := admission.NewVersionedAttributes(admissionAttributes, admissionAttributes.GetKind(), nil)
ctx := context.TODO()
failPolicy := admissionregistrationv1.FailurePolicyType(*c.ValidatingAdmissionPolicy.Spec.FailurePolicy)
matchConditions := c.ValidatingAdmissionPolicy.Spec.MatchConditions
var matchExpressions []cel.ExpressionAccessor
for _, expression := range matchConditions {
condition := &matchconditions.MatchCondition{
Name: expression.Name,
Expression: expression.Expression,
}
matchExpressions = append(matchExpressions, condition)
}
matchFilter := filterCompiler.Compile(matchExpressions, cel.OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: false}, celconfig.PerCallLimit)
var matchPolicy *v1alpha1.MatchPolicyType
if c.ValidatingAdmissionPolicy.Spec.MatchConstraints.MatchPolicy == nil {
equivalent := v1alpha1.Equivalent
matchPolicy = &equivalent
} else {
matchPolicy = c.ValidatingAdmissionPolicy.Spec.MatchConstraints.MatchPolicy
}
newMatcher := matchconditions.NewMatcher(matchFilter, nil, &failPolicy, string(*matchPolicy), "")
auditAnnotations := c.ValidatingAdmissionPolicy.Spec.AuditAnnotations
var auditExpressions []cel.ExpressionAccessor
for _, expression := range auditAnnotations {
condition := &validatingadmissionpolicy.AuditAnnotationCondition{
Key: expression.Key,
ValueExpression: expression.ValueExpression,
}
auditExpressions = append(auditExpressions, condition)
}
auditAnnotationFilter := filterCompiler.Compile(auditExpressions, cel.OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: false}, celconfig.PerCallLimit)
validator := validatingadmissionpolicy.NewValidator(filter, newMatcher, auditAnnotationFilter, messageExpressionfilter, &failPolicy, nil)
validateResult := validator.Validate(ctx, versionedAttr, nil, celconfig.RuntimeCELCostBudget)
engineResponse := engineapi.NewEngineResponseWithValidatingAdmissionPolicy(*c.Resource, c.ValidatingAdmissionPolicy, nil)
policyResp := engineapi.NewPolicyResponse()
var ruleResp *engineapi.RuleResponse
isPass := true
for _, policyDecision := range validateResult.Decisions {
if policyDecision.Evaluation == validatingadmissionpolicy.EvalError {
isPass = false
c.Rc.Error++
ruleResp = engineapi.RuleError(c.ValidatingAdmissionPolicy.GetName(), engineapi.Validation, policyDecision.Message, nil)
break
} else if policyDecision.Action == validatingadmissionpolicy.ActionDeny {
isPass = false
c.Rc.Fail++
ruleResp = engineapi.RuleFail(c.ValidatingAdmissionPolicy.GetName(), engineapi.Validation, policyDecision.Message)
break
}
}
if isPass {
c.Rc.Pass++
ruleResp = engineapi.RulePass(c.ValidatingAdmissionPolicy.GetName(), engineapi.Validation, "")
}
policyResp.Add(engineapi.NewExecutionStats(startTime, time.Now()), *ruleResp)
engineResponse = engineResponse.WithPolicyResponse(policyResp)
engineResponses = append(engineResponses, engineResponse)
return engineResponses, nil
}

View file

@ -0,0 +1,41 @@
package common
import (
"github.com/kyverno/kyverno/pkg/clients/dclient"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type ValidatingAdmissionResources struct {
policies []v1alpha1.ValidatingAdmissionPolicy
}
func (r *ValidatingAdmissionResources) FetchResourcesFromPolicy(resourcePaths []string, dClient dclient.Interface, namespace string, policyReport bool) ([]*unstructured.Unstructured, error) {
var resources []*unstructured.Unstructured
var err error
resourceTypesMap := make(map[schema.GroupVersionKind]bool)
var resourceTypes []schema.GroupVersionKind
var subresourceMap map[schema.GroupVersionKind]Subresource
for _, policy := range r.policies {
for _, rule := range policy.Spec.MatchConstraints.ResourceRules {
var resourceTypesInRule map[schema.GroupVersionKind]bool
resourceTypesInRule, subresourceMap, err = getKindsFromValidatingAdmissionRule(rule.RuleWithOperations.Rule, dClient)
if err != nil {
return resources, err
}
for resourceKind := range resourceTypesInRule {
resourceTypesMap[resourceKind] = true
}
}
}
for kind := range resourceTypesMap {
resourceTypes = append(resourceTypes, kind)
}
resources, err = whenClusterIsTrue(resourceTypes, subresourceMap, dClient, namespace, resourcePaths, policyReport)
return resources, err
}

6
go.mod
View file

@ -59,6 +59,7 @@ require (
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/text v0.9.0
google.golang.org/grpc v1.55.0
gopkg.in/inf.v0 v0.9.1
gopkg.in/yaml.v2 v2.4.0
@ -67,6 +68,7 @@ require (
k8s.io/api v0.27.1
k8s.io/apiextensions-apiserver v0.27.1
k8s.io/apimachinery v0.27.1
k8s.io/apiserver v0.27.1
k8s.io/cli-runtime v0.27.1
k8s.io/client-go v0.27.1
k8s.io/klog/v2 v2.100.1
@ -117,6 +119,7 @@ require (
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.2.7 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.19 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.18 // indirect
@ -189,6 +192,7 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/cel-go v0.12.6 // indirect
github.com/google/certificate-transparency-go v1.1.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-github/v45 v45.2.0 // indirect
@ -276,6 +280,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.15.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.1.3 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
@ -309,7 +314,6 @@ require (
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/api v0.115.0 // indirect

8
go.sum
View file

@ -185,6 +185,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -648,6 +650,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M=
github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs=
github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY=
@ -1318,6 +1322,7 @@ github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jH
github.com/spiffe/go-spiffe/v2 v2.1.3 h1:P5L9Ixo5eqJiHnktAU0UD/6UfHsQs7yAtc8a/FFUi9M=
github.com/spiffe/go-spiffe/v2 v2.1.3/go.mod h1:eVDqm9xFvyqao6C+eQensb9ZPkyNEeaUbqbBpOhBnNk=
github.com/ssgreg/nlreturn/v2 v2.1.0/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@ -2152,6 +2157,8 @@ k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc=
k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM=
k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
k8s.io/apiserver v0.20.2/go.mod h1:2nKd93WyMhZx4Hp3RfgH2K5PhwyTrprrkWYnI7id7jA=
k8s.io/apiserver v0.27.1 h1:phY+BtXjjzd+ta3a4kYbomC81azQSLa1K8jo9RBw7Lg=
k8s.io/apiserver v0.27.1/go.mod h1:UGrOjLY2KsieA9Fw6lLiTObxTb8Z1xEba4uqSuMY0WU=
k8s.io/cli-runtime v0.27.1 h1:MMzp5Q/Xmr5L1Lrowuc+Y/r95XINC6c6/fE3aN7JDRM=
k8s.io/cli-runtime v0.27.1/go.mod h1:tEbTB1XP/nTH3wujsi52bw91gWpErtWiS15R6CwYsAI=
k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
@ -2197,6 +2204,7 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.1 h1:MB1zkK+WMOmfLxEpjr1wEmkpcIhZC7kfTkZ0stg5bog=
sigs.k8s.io/controller-runtime v0.8.2/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU=
sigs.k8s.io/controller-runtime v0.8.3/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU=
sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA=

View file

@ -318,7 +318,7 @@ func Test_Any(t *testing.T) {
if err != nil {
t.Log(err)
}
policies, err := yamlutils.GetPolicy(file)
policies, _, err := yamlutils.GetPolicy(file)
if err != nil {
t.Log(err)
}
@ -356,7 +356,7 @@ func Test_All(t *testing.T) {
if err != nil {
t.Log(err)
}
policies, err := yamlutils.GetPolicy(file)
policies, _, err := yamlutils.GetPolicy(file)
if err != nil {
t.Log(err)
}
@ -395,7 +395,7 @@ func Test_Exclude(t *testing.T) {
if err != nil {
t.Log(err)
}
policies, err := yamlutils.GetPolicy(file)
policies, _, err := yamlutils.GetPolicy(file)
if err != nil {
t.Log(err)
}
@ -429,7 +429,7 @@ func Test_CronJobOnly(t *testing.T) {
if err != nil {
t.Log(err)
}
policies, err := yamlutils.GetPolicy(file)
policies, _, err := yamlutils.GetPolicy(file)
if err != nil {
t.Log(err)
}
@ -459,7 +459,7 @@ func Test_ForEachPod(t *testing.T) {
if err != nil {
t.Log(err)
}
policies, err := yamlutils.GetPolicy(file)
policies, _, err := yamlutils.GetPolicy(file)
if err != nil {
t.Log(err)
}
@ -494,7 +494,7 @@ func Test_CronJob_hasExclude(t *testing.T) {
if err != nil {
t.Log(err)
}
policies, err := yamlutils.GetPolicy(file)
policies, _, err := yamlutils.GetPolicy(file)
if err != nil {
t.Log(err)
}
@ -531,7 +531,7 @@ func Test_CronJobAndDeployment(t *testing.T) {
if err != nil {
t.Log(err)
}
policies, err := yamlutils.GetPolicy(file)
policies, _, err := yamlutils.GetPolicy(file)
if err != nil {
t.Log(err)
}
@ -603,7 +603,7 @@ func Test_UpdateVariablePath(t *testing.T) {
if err != nil {
t.Log(err)
}
policies, err := yamlutils.GetPolicy(file)
policies, _, err := yamlutils.GetPolicy(file)
if err != nil {
t.Log(err)
}
@ -633,7 +633,7 @@ func Test_Deny(t *testing.T) {
if err != nil {
t.Log(err)
}
policies, err := yamlutils.GetPolicy(file)
policies, _, err := yamlutils.GetPolicy(file)
if err != nil {
t.Log(err)
}
@ -841,7 +841,7 @@ kA==
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
policies, err := yamlutils.GetPolicy([]byte(test.policy))
policies, _, err := yamlutils.GetPolicy([]byte(test.policy))
assert.NilError(t, err)
assert.Equal(t, 1, len(policies))
rules := computeRules(policies[0])
@ -852,7 +852,7 @@ kA==
func Test_PodSecurityWithNoExceptions(t *testing.T) {
policy := []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"pod-security"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"restricted","match":{"all":[{"resources":{"kinds":["Pod"]}}]},"validate":{"podSecurity":{"level":"restricted","version":"v1.24"}}}]}}`)
policies, err := yamlutils.GetPolicy([]byte(policy))
policies, _, err := yamlutils.GetPolicy([]byte(policy))
assert.NilError(t, err)
assert.Equal(t, 1, len(policies))

View file

@ -7,6 +7,7 @@ import (
datautils "github.com/kyverno/kyverno/pkg/utils/data"
utils "github.com/kyverno/kyverno/pkg/utils/match"
"github.com/kyverno/kyverno/pkg/utils/wildcard"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -14,8 +15,10 @@ import (
type EngineResponse struct {
// Resource is the original resource
Resource unstructured.Unstructured
// policy is the original policy
// Policy is the original policy
policy kyvernov1.PolicyInterface
// Policy is the validating admission policy
validatingAdmissionPolicy v1alpha1.ValidatingAdmissionPolicy
// namespaceLabels given by policy context
namespaceLabels map[string]string
// PatchedResource is the resource patched with the engine action changes
@ -75,6 +78,19 @@ func (er EngineResponse) WithPatchedResource(patchedResource unstructured.Unstru
return er
}
func NewEngineResponseWithValidatingAdmissionPolicy(
resource unstructured.Unstructured,
policy v1alpha1.ValidatingAdmissionPolicy,
namespaceLabels map[string]string,
) EngineResponse {
response := EngineResponse{
Resource: resource,
validatingAdmissionPolicy: policy,
namespaceLabels: namespaceLabels,
}
return response
}
func (er EngineResponse) WithNamespaceLabels(namespaceLabels map[string]string) EngineResponse {
er.namespaceLabels = namespaceLabels
return er
@ -88,6 +104,10 @@ func (er *EngineResponse) Policy() kyvernov1.PolicyInterface {
return er.policy
}
func (er *EngineResponse) ValidatingAdmissionPolicy() v1alpha1.ValidatingAdmissionPolicy {
return er.validatingAdmissionPolicy
}
// IsOneOf checks if any rule has status in a given list
func (er EngineResponse) IsOneOf(status ...RuleStatus) bool {
for _, r := range er.PolicyResponse.Rules {
@ -128,6 +148,10 @@ func (er EngineResponse) IsNil() bool {
return datautils.DeepEqual(er, EngineResponse{})
}
func (er EngineResponse) IsValidatingAdmissionPolicy() bool {
return !datautils.DeepEqual(er.validatingAdmissionPolicy, v1alpha1.ValidatingAdmissionPolicy{})
}
// GetPatches returns all the patches joined
func (er EngineResponse) GetPatches() [][]byte {
var patches [][]byte

View file

@ -3,78 +3,98 @@ package yaml
import (
"encoding/json"
"fmt"
"strings"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
log "github.com/kyverno/kyverno/pkg/logging"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/yaml"
)
// GetPolicy extracts policies from YAML bytes
func GetPolicy(bytes []byte) (policies []kyvernov1.PolicyInterface, err error) {
func GetPolicy(bytes []byte) (policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, err error) {
documents, err := SplitDocuments(bytes)
if err != nil {
return nil, err
return nil, nil, err
}
for _, thisPolicyBytes := range documents {
policyBytes, err := yaml.ToJSON(thisPolicyBytes)
if err != nil {
return nil, fmt.Errorf("failed to convert to JSON: %v", err)
return nil, nil, fmt.Errorf("failed to convert to JSON: %v", err)
}
us := &unstructured.Unstructured{}
if err := json.Unmarshal(policyBytes, us); err != nil {
return nil, fmt.Errorf("failed to decode policy: %v", err)
return nil, nil, fmt.Errorf("failed to decode policy: %v", err)
}
if us.IsList() {
list, err := us.ToList()
if err != nil {
return nil, fmt.Errorf("failed to decode policy list: %v", err)
return nil, nil, fmt.Errorf("failed to decode policy list: %v", err)
}
for i := range list.Items {
item := list.Items[i]
if policies, err = addPolicy(policies, &item); err != nil {
return nil, err
if policies, validatingAdmissionPolicies, err = addPolicy(policies, validatingAdmissionPolicies, &item); err != nil {
return nil, nil, err
}
}
} else {
if policies, err = addPolicy(policies, us); err != nil {
return nil, err
if policies, validatingAdmissionPolicies, err = addPolicy(policies, validatingAdmissionPolicies, us); err != nil {
return nil, nil, err
}
}
}
return policies, nil
return policies, validatingAdmissionPolicies, nil
}
func addPolicy(policies []kyvernov1.PolicyInterface, us *unstructured.Unstructured) ([]kyvernov1.PolicyInterface, error) {
var policy kyvernov1.PolicyInterface
if us.GetKind() == "ClusterPolicy" {
policy = &kyvernov1.ClusterPolicy{}
} else {
policy = &kyvernov1.Policy{}
}
func addPolicy(policies []kyvernov1.PolicyInterface, validatingAdmissionPolicies []v1alpha1.ValidatingAdmissionPolicy, us *unstructured.Unstructured) ([]kyvernov1.PolicyInterface, []v1alpha1.ValidatingAdmissionPolicy, error) {
kind := us.GetKind()
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(us.Object, policy); err != nil {
return nil, fmt.Errorf("failed to decode policy: %v", err)
}
if strings.Compare(kind, "ValidatingAdmissionPolicy") == 0 {
validatingAdmissionPolicy := &v1alpha1.ValidatingAdmissionPolicy{}
if policy.GetKind() == "" {
log.V(3).Info("skipping file as policy.TypeMeta.Kind not found")
return policies, nil
}
if policy.GetKind() != "ClusterPolicy" && policy.GetKind() != "Policy" {
return nil, fmt.Errorf("resource %s/%s is not a Policy or a ClusterPolicy", policy.GetKind(), policy.GetName())
}
if policy.GetKind() == "Policy" {
if policy.GetNamespace() == "" {
policy.SetNamespace("default")
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(us.Object, validatingAdmissionPolicy); err != nil {
return policies, nil, fmt.Errorf("failed to decode policy: %v", err)
}
if validatingAdmissionPolicy.Kind == "" {
log.V(3).Info("skipping file as ValidatingAdmissionPolicy.Kind not found")
return policies, validatingAdmissionPolicies, nil
}
validatingAdmissionPolicies = append(validatingAdmissionPolicies, *validatingAdmissionPolicy)
} else {
policy.SetNamespace("")
var policy kyvernov1.PolicyInterface
if us.GetKind() == "ClusterPolicy" {
policy = &kyvernov1.ClusterPolicy{}
} else {
policy = &kyvernov1.Policy{}
}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(us.Object, policy); err != nil {
return nil, validatingAdmissionPolicies, fmt.Errorf("failed to decode policy: %v", err)
}
if policy.GetKind() == "" {
log.V(3).Info("skipping file as policy.TypeMeta.Kind not found")
return policies, validatingAdmissionPolicies, nil
}
if policy.GetKind() != "ClusterPolicy" && policy.GetKind() != "Policy" {
return nil, validatingAdmissionPolicies, fmt.Errorf("resource %s/%s is not a Policy or a ClusterPolicy", policy.GetKind(), policy.GetName())
}
if policy.GetKind() == "Policy" {
if policy.GetNamespace() == "" {
policy.SetNamespace("default")
}
} else {
policy.SetNamespace("")
}
policies = append(policies, policy)
}
policies = append(policies, policy)
return policies, nil
return policies, validatingAdmissionPolicies, nil
}

View file

@ -15,10 +15,11 @@ func TestGetPolicy(t *testing.T) {
namespace string
}
tests := []struct {
name string
args args
wantPolicies []policy
wantErr bool
name string
args args
wantPolicies []policy
validatingAdmissionPolicies []policy
wantErr bool
}{{
name: "policy",
args: args{
@ -297,10 +298,140 @@ items:
{"ClusterPolicy", ""},
},
wantErr: false,
}, {
name: "ValidatingAdmissionPolicy",
args: args{
[]byte(`
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
`),
}, validatingAdmissionPolicies: []policy{
{"ValidatingAdmissionPolicy", ""},
},
wantErr: false,
}, {
name: "ValidatingAdmissionPolicy and Policy",
args: args{
[]byte(`
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
---
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: generate-policy
namespace: ns-1
spec:
rules:
- name: copy-game-demo
match:
resources:
kinds:
- Namespace
exclude:
resources:
namespaces:
- kube-system
- default
- kube-public
- kyverno
generate:
kind: ConfigMap
name: game-demo
namespace: "{{request.object.metadata.name}}"
synchronize: true
clone:
namespace: default
name: game-demo
`),
}, wantPolicies: []policy{
{"Policy", "ns-1"},
},
validatingAdmissionPolicies: []policy{
{"ValidatingAdmissionPolicy", ""},
},
wantErr: false,
}, {
name: "ValidatingAdmissionPolicy and ClusterPolicy",
args: args{
[]byte(`
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-policy
spec:
rules:
- name: copy-game-demo
match:
resources:
kinds:
- Namespace
exclude:
resources:
namespaces:
- kube-system
- default
- kube-public
- kyverno
generate:
kind: ConfigMap
name: game-demo
namespace: "{{request.object.metadata.name}}"
synchronize: true
clone:
namespace: default
name: game-demo
`),
}, wantPolicies: []policy{
{"ClusterPolicy", ""},
},
validatingAdmissionPolicies: []policy{
{"ValidatingAdmissionPolicy", ""},
},
wantErr: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPolicies, err := GetPolicy(tt.args.bytes)
gotPolicies, gotValidatingAdmissionPolicies, err := GetPolicy(tt.args.bytes)
if tt.wantErr {
assert.Error(t, err)
} else {
@ -311,6 +442,13 @@ items:
assert.Equal(t, tt.wantPolicies[i].namespace, gotPolicies[i].GetNamespace())
}
}
if assert.Equal(t, len(tt.validatingAdmissionPolicies), len(gotValidatingAdmissionPolicies)) {
for i := range tt.validatingAdmissionPolicies {
assert.Equal(t, tt.validatingAdmissionPolicies[i].kind, gotValidatingAdmissionPolicies[i].Kind)
}
}
}
})
}

View file

@ -54,7 +54,7 @@ func TestNotAllowedVars_MatchSection(t *testing.T) {
}
`)
policy, err := yamlutils.GetPolicy(policyWithVarInMatch)
policy, _, err := yamlutils.GetPolicy(policyWithVarInMatch)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -106,7 +106,7 @@ func TestNotAllowedVars_ExcludeSection(t *testing.T) {
}
`)
policy, err := yamlutils.GetPolicy(policyWithVarInExclude)
policy, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -159,7 +159,7 @@ func TestNotAllowedVars_ExcludeSection_PositiveCase(t *testing.T) {
}
`)
policy, err := yamlutils.GetPolicy(policyWithVarInExclude)
policy, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -193,7 +193,7 @@ func TestNotAllowedVars_JSONPatchPath(t *testing.T) {
}
}`)
policy, err := yamlutils.GetPolicy(policyWithVarInExclude)
policy, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -238,7 +238,7 @@ func TestNotAllowedVars_JSONPatchPath_ContextRootPositive(t *testing.T) {
}
}`)
policy, err := yamlutils.GetPolicy(policyManifest)
policy, _, err := yamlutils.GetPolicy(policyManifest)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -281,7 +281,7 @@ func TestNotAllowedVars_JSONPatchPath_ContextSubPositive(t *testing.T) {
}
}`)
policy, err := yamlutils.GetPolicy(policyManifest)
policy, _, err := yamlutils.GetPolicy(policyManifest)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -315,7 +315,7 @@ func TestNotAllowedVars_JSONPatchPath_PositiveCase(t *testing.T) {
}
}`)
policy, err := yamlutils.GetPolicy(policyWithVarInExclude)
policy, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -347,7 +347,7 @@ spec:
policyJSON, err := yaml.ToJSON(policyYAML)
assert.NilError(t, err)
policy, err := yamlutils.GetPolicy(policyJSON)
policy, _, err := yamlutils.GetPolicy(policyJSON)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -435,7 +435,7 @@ func TestNotAllowedVars_VariableFormats(t *testing.T) {
value: "foo.com"
`, tc.input))
policy, err := yamlutils.GetPolicy(policyYAML)
policy, _, err := yamlutils.GetPolicy(policyYAML)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)
@ -481,7 +481,7 @@ spec:
policyJSON, err := yaml.ToJSON(policyYAML)
assert.NilError(t, err)
policy, err := yamlutils.GetPolicy(policyJSON)
policy, _, err := yamlutils.GetPolicy(policyJSON)
assert.NilError(t, err)
err = hasInvalidVariables(policy[0], false)

View file

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
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,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80

View file

@ -0,0 +1,20 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.template.spec.containers.all(container, !container.image.contains('latest'))"
message: "Using a mutable image tag e.g. 'latest' is not allowed."
- expression: "object.spec.replicas <= 5"
message: "Replicas must be less than or equal 5"
matchConditions:
- name: "deployment name"
expression: "object.metadata.name == 'nginx-deployment'"