mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-15 20:20:22 +00:00
refactor validating admission policies (#7835)
Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
parent
3ed7303efe
commit
34a6119cc3
19 changed files with 483 additions and 264 deletions
|
@ -345,6 +345,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (*common.ResultCounts, []*unst
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
policyRulesCount += len(autogen.ComputeRules(policy))
|
policyRulesCount += len(autogen.ComputeRules(policy))
|
||||||
}
|
}
|
||||||
|
policyRulesCount += len(validatingAdmissionPolicies)
|
||||||
fmt.Printf("\nApplying %d policy rule(s) to %d resource(s)...\n", policyRulesCount, len(resources))
|
fmt.Printf("\nApplying %d policy rule(s) to %d resource(s)...\n", policyRulesCount, len(resources))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -176,8 +176,8 @@ func Test_Apply(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: ApplyCommandConfig{
|
config: ApplyCommandConfig{
|
||||||
PolicyPaths: []string{"../../../../test/cli/test-validating-admission-policy/validate-deployment/policy.yaml"},
|
PolicyPaths: []string{"../../../../test/cli/test-vap/check-deployments-replica/policy.yaml"},
|
||||||
ResourcePaths: []string{"../../../../test/cli/test-validating-admission-policy/validate-deployment/deployment1.yaml"},
|
ResourcePaths: []string{"../../../../test/cli/test-vap/check-deployments-replica/deployment1.yaml"},
|
||||||
PolicyReport: true,
|
PolicyReport: true,
|
||||||
},
|
},
|
||||||
expectedPolicyReports: []preport.PolicyReport{
|
expectedPolicyReports: []preport.PolicyReport{
|
||||||
|
@ -194,8 +194,44 @@ func Test_Apply(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: ApplyCommandConfig{
|
config: ApplyCommandConfig{
|
||||||
PolicyPaths: []string{"../../../../test/cli/test-validating-admission-policy/validate-deployment/policy.yaml"},
|
PolicyPaths: []string{"../../../../test/cli/test-vap/check-deployments-replica/policy.yaml"},
|
||||||
ResourcePaths: []string{"../../../../test/cli/test-validating-admission-policy/validate-deployment/deployment2.yaml"},
|
ResourcePaths: []string{"../../../../test/cli/test-vap/check-deployments-replica/deployment2.yaml"},
|
||||||
|
PolicyReport: true,
|
||||||
|
},
|
||||||
|
expectedPolicyReports: []preport.PolicyReport{
|
||||||
|
{
|
||||||
|
Summary: preport.PolicyReportSummary{
|
||||||
|
Pass: 0,
|
||||||
|
Fail: 1,
|
||||||
|
Skip: 0,
|
||||||
|
Error: 0,
|
||||||
|
Warn: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: ApplyCommandConfig{
|
||||||
|
PolicyPaths: []string{"../../../../test/cli/test-vap/disallow-host-path/policy.yaml"},
|
||||||
|
ResourcePaths: []string{"../../../../test/cli/test-vap/disallow-host-path/pod1.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-vap/disallow-host-path/policy.yaml"},
|
||||||
|
ResourcePaths: []string{"../../../../test/cli/test-vap/disallow-host-path/pod2.yaml"},
|
||||||
PolicyReport: true,
|
PolicyReport: true,
|
||||||
},
|
},
|
||||||
expectedPolicyReports: []preport.PolicyReport{
|
expectedPolicyReports: []preport.PolicyReport{
|
||||||
|
|
|
@ -64,10 +64,10 @@ func buildPolicyResults(auditWarn bool, engineResponses ...engineapi.EngineRespo
|
||||||
isVAP := engineResponse.IsValidatingAdmissionPolicy()
|
isVAP := engineResponse.IsValidatingAdmissionPolicy()
|
||||||
|
|
||||||
if isVAP {
|
if isVAP {
|
||||||
validatingAdmissionPolicy := engineResponse.ValidatingAdmissionPolicy()
|
vap := engineResponse.ValidatingAdmissionPolicy()
|
||||||
ns = validatingAdmissionPolicy.GetNamespace()
|
ns = vap.GetNamespace()
|
||||||
policyName = validatingAdmissionPolicy.GetName()
|
policyName = vap.GetName()
|
||||||
ann = validatingAdmissionPolicy.GetAnnotations()
|
ann = vap.GetAnnotations()
|
||||||
} else {
|
} else {
|
||||||
kyvernoPolicy := engineResponse.Policy()
|
kyvernoPolicy := engineResponse.Policy()
|
||||||
ns = kyvernoPolicy.GetNamespace()
|
ns = kyvernoPolicy.GetNamespace()
|
||||||
|
|
|
@ -21,10 +21,10 @@ type TestResults struct {
|
||||||
// It's required in case policy is a kyverno policy.
|
// It's required in case policy is a kyverno policy.
|
||||||
// +optional
|
// +optional
|
||||||
Rule string `json:"rule,omitempty"`
|
Rule string `json:"rule,omitempty"`
|
||||||
// IsVap indicates if the policy is a validating admission policy.
|
// IsValidatingAdmissionPolicy indicates if the policy is a validating admission policy.
|
||||||
// It's required in case policy is a validating admission policy.
|
// It's required in case policy is a validating admission policy.
|
||||||
// +optional
|
// +optional
|
||||||
IsVap bool `json:"isVap"`
|
IsValidatingAdmissionPolicy bool `json:"isValidatingAdmissionPolicy"`
|
||||||
// Result mentions the result that the user is expecting.
|
// Result mentions the result that the user is expecting.
|
||||||
// Possible values are pass, fail and skip.
|
// Possible values are pass, fail and skip.
|
||||||
Result policyreportv1alpha2.PolicyResult `json:"result"`
|
Result policyreportv1alpha2.PolicyResult `json:"result"`
|
||||||
|
|
|
@ -136,7 +136,7 @@ func applyPoliciesFromPath(
|
||||||
|
|
||||||
for _, rule := range autogen.ComputeRules(p) {
|
for _, rule := range autogen.ComputeRules(p) {
|
||||||
for _, res := range values.Results {
|
for _, res := range values.Results {
|
||||||
if res.IsVap {
|
if res.IsValidatingAdmissionPolicy {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,10 +340,10 @@ func buildPolicyResults(
|
||||||
isVAP := resp.IsValidatingAdmissionPolicy()
|
isVAP := resp.IsValidatingAdmissionPolicy()
|
||||||
|
|
||||||
if isVAP {
|
if isVAP {
|
||||||
validatingAdmissionPolicy := resp.ValidatingAdmissionPolicy()
|
vap := resp.ValidatingAdmissionPolicy()
|
||||||
ns = validatingAdmissionPolicy.GetNamespace()
|
ns = vap.GetNamespace()
|
||||||
name = validatingAdmissionPolicy.GetName()
|
name = vap.GetName()
|
||||||
ann = validatingAdmissionPolicy.GetAnnotations()
|
ann = vap.GetAnnotations()
|
||||||
} else {
|
} else {
|
||||||
kyvernoPolicy := resp.Policy()
|
kyvernoPolicy := resp.Policy()
|
||||||
ns = kyvernoPolicy.GetNamespace()
|
ns = kyvernoPolicy.GetNamespace()
|
||||||
|
@ -398,8 +398,8 @@ func buildPolicyResults(
|
||||||
for _, resource := range test.Resources {
|
for _, resource := range test.Resources {
|
||||||
if resource == resourceName {
|
if resource == resourceName {
|
||||||
var resultsKey string
|
var resultsKey string
|
||||||
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource, test.IsVap)
|
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource, test.IsValidatingAdmissionPolicy)
|
||||||
if !test.IsVap {
|
if !test.IsValidatingAdmissionPolicy {
|
||||||
if !slices.Contains(rules, test.Rule) {
|
if !slices.Contains(rules, test.Rule) {
|
||||||
if !slices.Contains(rules, "autogen-"+test.Rule) {
|
if !slices.Contains(rules, "autogen-"+test.Rule) {
|
||||||
if !slices.Contains(rules, "autogen-cronjob-"+test.Rule) {
|
if !slices.Contains(rules, "autogen-cronjob-"+test.Rule) {
|
||||||
|
@ -407,12 +407,12 @@ func buildPolicyResults(
|
||||||
} else {
|
} else {
|
||||||
testResults[i].AutoGeneratedRule = "autogen-cronjob"
|
testResults[i].AutoGeneratedRule = "autogen-cronjob"
|
||||||
test.Rule = "autogen-cronjob-" + test.Rule
|
test.Rule = "autogen-cronjob-" + test.Rule
|
||||||
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource, test.IsVap)
|
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource, test.IsValidatingAdmissionPolicy)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
testResults[i].AutoGeneratedRule = "autogen"
|
testResults[i].AutoGeneratedRule = "autogen"
|
||||||
test.Rule = "autogen-" + test.Rule
|
test.Rule = "autogen-" + test.Rule
|
||||||
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource, test.IsVap)
|
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, resource, test.IsValidatingAdmissionPolicy)
|
||||||
}
|
}
|
||||||
|
|
||||||
if results[resultsKey].Result == "" {
|
if results[resultsKey].Result == "" {
|
||||||
|
@ -434,8 +434,8 @@ func buildPolicyResults(
|
||||||
if test.Resource != "" {
|
if test.Resource != "" {
|
||||||
if test.Policy == policyName && test.Resource == resourceName {
|
if test.Policy == policyName && test.Resource == resourceName {
|
||||||
var resultsKey string
|
var resultsKey string
|
||||||
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource, test.IsVap)
|
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource, test.IsValidatingAdmissionPolicy)
|
||||||
if !test.IsVap {
|
if !test.IsValidatingAdmissionPolicy {
|
||||||
if !slices.Contains(rules, test.Rule) {
|
if !slices.Contains(rules, test.Rule) {
|
||||||
if !slices.Contains(rules, "autogen-"+test.Rule) {
|
if !slices.Contains(rules, "autogen-"+test.Rule) {
|
||||||
if !slices.Contains(rules, "autogen-cronjob-"+test.Rule) {
|
if !slices.Contains(rules, "autogen-cronjob-"+test.Rule) {
|
||||||
|
@ -443,12 +443,12 @@ func buildPolicyResults(
|
||||||
} else {
|
} else {
|
||||||
testResults[i].AutoGeneratedRule = "autogen-cronjob"
|
testResults[i].AutoGeneratedRule = "autogen-cronjob"
|
||||||
test.Rule = "autogen-cronjob-" + test.Rule
|
test.Rule = "autogen-cronjob-" + test.Rule
|
||||||
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource, test.IsVap)
|
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource, test.IsValidatingAdmissionPolicy)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
testResults[i].AutoGeneratedRule = "autogen"
|
testResults[i].AutoGeneratedRule = "autogen"
|
||||||
test.Rule = "autogen-" + test.Rule
|
test.Rule = "autogen-" + test.Rule
|
||||||
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource, test.IsVap)
|
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource, test.IsValidatingAdmissionPolicy)
|
||||||
}
|
}
|
||||||
|
|
||||||
if results[resultsKey].Result == "" {
|
if results[resultsKey].Result == "" {
|
||||||
|
@ -474,7 +474,7 @@ func buildPolicyResults(
|
||||||
var resultsKey []string
|
var resultsKey []string
|
||||||
var resultKey string
|
var resultKey string
|
||||||
var result policyreportv1alpha2.PolicyReportResult
|
var result policyreportv1alpha2.PolicyReportResult
|
||||||
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName, test.IsVap)
|
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName, test.IsValidatingAdmissionPolicy)
|
||||||
for _, key := range resultsKey {
|
for _, key := range resultsKey {
|
||||||
if val, ok := results[key]; ok {
|
if val, ok := results[key]; ok {
|
||||||
result = val
|
result = val
|
||||||
|
@ -507,7 +507,7 @@ func buildPolicyResults(
|
||||||
var resultsKey []string
|
var resultsKey []string
|
||||||
var resultKey string
|
var resultKey string
|
||||||
var result policyreportv1alpha2.PolicyReportResult
|
var result policyreportv1alpha2.PolicyReportResult
|
||||||
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName, test.IsVap)
|
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName, test.IsValidatingAdmissionPolicy)
|
||||||
for _, key := range resultsKey {
|
for _, key := range resultsKey {
|
||||||
if val, ok := results[key]; ok {
|
if val, ok := results[key]; ok {
|
||||||
result = val
|
result = val
|
||||||
|
@ -537,14 +537,14 @@ func buildPolicyResults(
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range resp.PolicyResponse.Rules {
|
for _, rule := range resp.PolicyResponse.Rules {
|
||||||
if rule.RuleType() != engineapi.Validation && rule.RuleType() != engineapi.ImageVerify || test.Rule != rule.Name() && !test.IsVap {
|
if rule.RuleType() != engineapi.Validation && rule.RuleType() != engineapi.ImageVerify || test.Rule != rule.Name() && !test.IsValidatingAdmissionPolicy {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultsKey []string
|
var resultsKey []string
|
||||||
var resultKey string
|
var resultKey string
|
||||||
var result policyreportv1alpha2.PolicyReportResult
|
var result policyreportv1alpha2.PolicyReportResult
|
||||||
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName, test.IsVap)
|
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name(), resourceNamespace, resourceKind, resourceName, test.IsValidatingAdmissionPolicy)
|
||||||
for _, key := range resultsKey {
|
for _, key := range resultsKey {
|
||||||
if val, ok := results[key]; ok {
|
if val, ok := results[key]; ok {
|
||||||
result = val
|
result = val
|
||||||
|
@ -579,11 +579,11 @@ func buildPolicyResults(
|
||||||
return results, testResults
|
return results, testResults
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllPossibleResultsKey(policyNamespace, policy, rule, resourceNamespace, kind, resource string, isVap bool) []string {
|
func GetAllPossibleResultsKey(policyNamespace, policy, rule, resourceNamespace, kind, resource string, isVAP bool) []string {
|
||||||
var resultsKey []string
|
var resultsKey []string
|
||||||
var resultKey1, resultKey2, resultKey3, resultKey4 string
|
var resultKey1, resultKey2, resultKey3, resultKey4 string
|
||||||
|
|
||||||
if isVap {
|
if isVAP {
|
||||||
resultKey1 = fmt.Sprintf("%s-%s-%s", policy, kind, resource)
|
resultKey1 = fmt.Sprintf("%s-%s-%s", policy, kind, resource)
|
||||||
resultKey2 = fmt.Sprintf("%s-%s-%s-%s", policy, resourceNamespace, kind, resource)
|
resultKey2 = fmt.Sprintf("%s-%s-%s-%s", policy, resourceNamespace, kind, resource)
|
||||||
resultKey3 = fmt.Sprintf("%s-%s-%s-%s", policyNamespace, policy, kind, resource)
|
resultKey3 = fmt.Sprintf("%s-%s-%s-%s", policyNamespace, policy, kind, resource)
|
||||||
|
@ -599,9 +599,9 @@ func GetAllPossibleResultsKey(policyNamespace, policy, rule, resourceNamespace,
|
||||||
return resultsKey
|
return resultsKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetResultKeyAccordingToTestResults(policyNs, policy, rule, resourceNs, kind, resource string, isVap bool) string {
|
func GetResultKeyAccordingToTestResults(policyNs, policy, rule, resourceNs, kind, resource string, isVAP bool) string {
|
||||||
var resultKey string
|
var resultKey string
|
||||||
if isVap {
|
if isVAP {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s", policy, kind, resource)
|
resultKey = fmt.Sprintf("%s-%s-%s", policy, kind, resource)
|
||||||
|
|
||||||
if policyNs != "" && resourceNs != "" {
|
if policyNs != "" && resourceNs != "" {
|
||||||
|
|
|
@ -158,7 +158,7 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
|
||||||
testCount++
|
testCount++
|
||||||
row.Resource = color.Resource(v.Kind, v.Namespace, resource)
|
row.Resource = color.Resource(v.Kind, v.Namespace, resource)
|
||||||
var ruleNameInResultKey string
|
var ruleNameInResultKey string
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
if v.AutoGeneratedRule != "" {
|
if v.AutoGeneratedRule != "" {
|
||||||
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
|
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
|
||||||
} else {
|
} else {
|
||||||
|
@ -167,7 +167,7 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultKey string
|
var resultKey string
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, resource)
|
||||||
} else {
|
} else {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s", v.Policy, v.Kind, resource)
|
resultKey = fmt.Sprintf("%s-%s-%s", v.Policy, v.Kind, resource)
|
||||||
|
@ -177,13 +177,13 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
|
||||||
var ns string
|
var ns string
|
||||||
ns, v.Policy = getUserDefinedPolicyNameAndNamespace(v.Policy)
|
ns, v.Policy = getUserDefinedPolicyNameAndNamespace(v.Policy)
|
||||||
if found && v.Namespace != "" {
|
if found && v.Namespace != "" {
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, resource)
|
||||||
} else {
|
} else {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, v.Namespace, v.Kind, resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, v.Namespace, v.Kind, resource)
|
||||||
}
|
}
|
||||||
} else if found {
|
} else if found {
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, resource)
|
||||||
} else {
|
} else {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s", ns, v.Policy, v.Kind, resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s", ns, v.Policy, v.Kind, resource)
|
||||||
|
@ -193,7 +193,7 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
|
||||||
} else if v.Namespace != "" {
|
} else if v.Namespace != "" {
|
||||||
row.Resource = color.Resource(v.Kind, v.Namespace, resource)
|
row.Resource = color.Resource(v.Kind, v.Namespace, resource)
|
||||||
|
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, resource)
|
||||||
} else {
|
} else {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, v.Namespace, v.Kind, resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, v.Namespace, v.Kind, resource)
|
||||||
|
@ -242,7 +242,7 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
|
||||||
countDeprecatedResource++
|
countDeprecatedResource++
|
||||||
row.Resource = color.Resource(v.Kind, v.Namespace, v.Resource)
|
row.Resource = color.Resource(v.Kind, v.Namespace, v.Resource)
|
||||||
var ruleNameInResultKey string
|
var ruleNameInResultKey string
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
if v.AutoGeneratedRule != "" {
|
if v.AutoGeneratedRule != "" {
|
||||||
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
|
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
|
||||||
} else {
|
} else {
|
||||||
|
@ -251,7 +251,7 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultKey string
|
var resultKey string
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
|
||||||
} else {
|
} else {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s", v.Policy, v.Kind, v.Resource)
|
resultKey = fmt.Sprintf("%s-%s-%s", v.Policy, v.Kind, v.Resource)
|
||||||
|
@ -261,13 +261,13 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
|
||||||
var ns string
|
var ns string
|
||||||
ns, v.Policy = getUserDefinedPolicyNameAndNamespace(v.Policy)
|
ns, v.Policy = getUserDefinedPolicyNameAndNamespace(v.Policy)
|
||||||
if found && v.Namespace != "" {
|
if found && v.Namespace != "" {
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
|
||||||
} else {
|
} else {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, v.Namespace, v.Kind, v.Resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, v.Namespace, v.Kind, v.Resource)
|
||||||
}
|
}
|
||||||
} else if found {
|
} else if found {
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
|
||||||
} else {
|
} else {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s", ns, v.Policy, v.Kind, v.Resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s", ns, v.Policy, v.Kind, v.Resource)
|
||||||
|
@ -278,7 +278,7 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
|
||||||
} else if v.Namespace != "" {
|
} else if v.Namespace != "" {
|
||||||
row.Resource = color.Resource(v.Kind, v.Namespace, v.Resource)
|
row.Resource = color.Resource(v.Kind, v.Namespace, v.Resource)
|
||||||
|
|
||||||
if !v.IsVap {
|
if !v.IsValidatingAdmissionPolicy {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
|
||||||
} else {
|
} else {
|
||||||
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, v.Namespace, v.Kind, v.Resource)
|
resultKey = fmt.Sprintf("%s-%s-%s-%s", v.Policy, v.Namespace, v.Kind, v.Resource)
|
||||||
|
|
|
@ -17,9 +17,7 @@ import (
|
||||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||||
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
|
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
|
||||||
"golang.org/x/text/cases"
|
"github.com/kyverno/kyverno/pkg/validatingadmissionpolicy"
|
||||||
"golang.org/x/text/language"
|
|
||||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
|
||||||
"k8s.io/api/admissionregistration/v1alpha1"
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
@ -347,62 +345,16 @@ func GetKindsFromRule(rule kyvernov1.Rule, client dclient.Interface) (map[schema
|
||||||
return resourceTypesMap, subresourceMap
|
return resourceTypesMap, subresourceMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKindsFromValidatingAdmissionRule(rule admissionregistrationv1.Rule, client dclient.Interface) (map[schema.GroupVersionKind]bool, map[schema.GroupVersionKind]values.Subresource, error) {
|
func getKindsFromValidatingAdmissionPolicy(policy v1alpha1.ValidatingAdmissionPolicy, client dclient.Interface) (map[schema.GroupVersionKind]bool, map[schema.GroupVersionKind]values.Subresource) {
|
||||||
resourceTypesMap := make(map[schema.GroupVersionKind]bool)
|
resourceTypesMap := make(map[schema.GroupVersionKind]bool)
|
||||||
subresourceMap := make(map[schema.GroupVersionKind]values.Subresource)
|
subresourceMap := make(map[schema.GroupVersionKind]values.Subresource)
|
||||||
|
|
||||||
group := rule.APIGroups[0]
|
kinds := validatingadmissionpolicy.GetKinds(policy)
|
||||||
if group == "" {
|
for _, kind := range kinds {
|
||||||
group = "*"
|
addGVKToResourceTypesMap(kind, resourceTypesMap, subresourceMap, client)
|
||||||
}
|
|
||||||
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] = values.Subresource{
|
|
||||||
APIResource: child,
|
|
||||||
ParentResource: metav1.APIResource{
|
|
||||||
Group: parent.Group,
|
|
||||||
Version: parent.Version,
|
|
||||||
Kind: parent.Kind,
|
|
||||||
Name: parent.Resource,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resourceTypesMap, subresourceMap, nil
|
return resourceTypesMap, subresourceMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGVKToResourceTypesMap(kind string, resourceTypesMap map[schema.GroupVersionKind]bool, subresourceMap map[schema.GroupVersionKind]values.Subresource, client dclient.Interface) {
|
func addGVKToResourceTypesMap(kind string, resourceTypesMap map[schema.GroupVersionKind]bool, subresourceMap map[schema.GroupVersionKind]values.Subresource, client dclient.Interface) {
|
||||||
|
|
|
@ -1,146 +1,23 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
"github.com/kyverno/kyverno/pkg/validatingadmissionpolicy"
|
||||||
"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{}
|
type ValidatingAdmissionPolicies struct{}
|
||||||
|
|
||||||
func (p *ValidatingAdmissionPolicies) ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error) {
|
func (p *ValidatingAdmissionPolicies) ApplyPolicyOnResource(c ApplyPolicyConfig) ([]engineapi.EngineResponse, error) {
|
||||||
var engineResponses []engineapi.EngineResponse
|
engineResp := validatingadmissionpolicy.Validate(c.ValidatingAdmissionPolicy, *c.Resource)
|
||||||
|
ruleResp := engineResp.PolicyResponse.Rules[0]
|
||||||
|
|
||||||
resPath := fmt.Sprintf("%s/%s/%s", c.Resource.GetNamespace(), c.Resource.GetKind(), c.Resource.GetName())
|
if ruleResp.Status() == engineapi.RuleStatusPass {
|
||||||
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++
|
c.Rc.Pass++
|
||||||
ruleResp = engineapi.RulePass(c.ValidatingAdmissionPolicy.GetName(), engineapi.Validation, "")
|
} else if ruleResp.Status() == engineapi.RuleStatusFail {
|
||||||
|
c.Rc.Fail++
|
||||||
|
} else if ruleResp.Status() == engineapi.RuleStatusError {
|
||||||
|
c.Rc.Error++
|
||||||
}
|
}
|
||||||
|
|
||||||
policyResp.Add(engineapi.NewExecutionStats(startTime, time.Now()), *ruleResp)
|
return []engineapi.EngineResponse{engineResp}, nil
|
||||||
|
|
||||||
engineResponse = engineResponse.WithPolicyResponse(policyResp)
|
|
||||||
engineResponses = append(engineResponses, engineResponse)
|
|
||||||
|
|
||||||
return engineResponses, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,15 +21,13 @@ func (r *ValidatingAdmissionResources) FetchResourcesFromPolicy(resourcePaths []
|
||||||
var subresourceMap map[schema.GroupVersionKind]values.Subresource
|
var subresourceMap map[schema.GroupVersionKind]values.Subresource
|
||||||
|
|
||||||
for _, policy := range r.policies {
|
for _, policy := range r.policies {
|
||||||
for _, rule := range policy.Spec.MatchConstraints.ResourceRules {
|
var resourceTypesInRule map[schema.GroupVersionKind]bool
|
||||||
var resourceTypesInRule map[schema.GroupVersionKind]bool
|
resourceTypesInRule, subresourceMap = getKindsFromValidatingAdmissionPolicy(policy, dClient)
|
||||||
resourceTypesInRule, subresourceMap, err = getKindsFromValidatingAdmissionRule(rule.RuleWithOperations.Rule, dClient)
|
if err != nil {
|
||||||
if err != nil {
|
return resources, err
|
||||||
return resources, err
|
}
|
||||||
}
|
for resourceKind := range resourceTypesInRule {
|
||||||
for resourceKind := range resourceTypesInRule {
|
resourceTypesMap[resourceKind] = true
|
||||||
resourceTypesMap[resourceKind] = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
pkg/validatingadmissionpolicy/log.go
Normal file
5
pkg/validatingadmissionpolicy/log.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package validatingadmissionpolicy
|
||||||
|
|
||||||
|
import "github.com/kyverno/kyverno/pkg/logging"
|
||||||
|
|
||||||
|
var logger = logging.WithName("validatingadmissionpolicy")
|
182
pkg/validatingadmissionpolicy/validatingadmissionpolicy.go
Normal file
182
pkg/validatingadmissionpolicy/validatingadmissionpolicy.go
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
package validatingadmissionpolicy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||||
|
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetKinds(policy v1alpha1.ValidatingAdmissionPolicy) []string {
|
||||||
|
var kindList []string
|
||||||
|
|
||||||
|
matchResources := policy.Spec.MatchConstraints
|
||||||
|
for _, rule := range matchResources.ResourceRules {
|
||||||
|
group := rule.APIGroups[0]
|
||||||
|
version := rule.APIVersions[0]
|
||||||
|
for _, resource := range rule.Resources {
|
||||||
|
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]
|
||||||
|
|
||||||
|
if group == "" {
|
||||||
|
kindList = append(kindList, strings.Join([]string{version, kind, subresource}, "/"))
|
||||||
|
} else {
|
||||||
|
kindList = append(kindList, strings.Join([]string{group, version, kind, subresource}, "/"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resource = cases.Title(language.English, cases.NoLower).String(resource)
|
||||||
|
resource, _ = strings.CutSuffix(resource, "s")
|
||||||
|
kind := resource
|
||||||
|
|
||||||
|
if group == "" {
|
||||||
|
kindList = append(kindList, strings.Join([]string{version, kind}, "/"))
|
||||||
|
} else {
|
||||||
|
kindList = append(kindList, strings.Join([]string{group, version, kind}, "/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kindList
|
||||||
|
}
|
||||||
|
|
||||||
|
func Validate(policy v1alpha1.ValidatingAdmissionPolicy, resource unstructured.Unstructured) engineapi.EngineResponse {
|
||||||
|
resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName())
|
||||||
|
logger.V(3).Info("applying policy on resource", "policy", policy.GetName(), "resource", resPath)
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
var expressions, messageExpressions, matchExpressions, auditExpressions []cel.ExpressionAccessor
|
||||||
|
|
||||||
|
validations := policy.Spec.Validations
|
||||||
|
matchConditions := policy.Spec.MatchConditions
|
||||||
|
auditAnnotations := policy.Spec.AuditAnnotations
|
||||||
|
|
||||||
|
hasParam := policy.Spec.ParamKind != nil
|
||||||
|
|
||||||
|
var failPolicy admissionregistrationv1.FailurePolicyType
|
||||||
|
if policy.Spec.FailurePolicy == nil {
|
||||||
|
failPolicy = admissionregistrationv1.Fail
|
||||||
|
} else {
|
||||||
|
failPolicy = admissionregistrationv1.FailurePolicyType(*policy.Spec.FailurePolicy)
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchPolicy v1alpha1.MatchPolicyType
|
||||||
|
if policy.Spec.MatchConstraints.MatchPolicy == nil {
|
||||||
|
matchPolicy = v1alpha1.Equivalent
|
||||||
|
} else {
|
||||||
|
matchPolicy = *policy.Spec.MatchConstraints.MatchPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cel := range validations {
|
||||||
|
condition := &validatingadmissionpolicy.ValidationCondition{
|
||||||
|
Expression: cel.Expression,
|
||||||
|
Message: cel.Message,
|
||||||
|
}
|
||||||
|
messageCondition := &validatingadmissionpolicy.MessageExpressionCondition{
|
||||||
|
MessageExpression: cel.MessageExpression,
|
||||||
|
}
|
||||||
|
expressions = append(expressions, condition)
|
||||||
|
messageExpressions = append(messageExpressions, messageCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expression := range matchConditions {
|
||||||
|
condition := &matchconditions.MatchCondition{
|
||||||
|
Name: expression.Name,
|
||||||
|
Expression: expression.Expression,
|
||||||
|
}
|
||||||
|
matchExpressions = append(matchExpressions, condition)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, auditAnnotation := range auditAnnotations {
|
||||||
|
auditCondition := &validatingadmissionpolicy.AuditAnnotationCondition{
|
||||||
|
Key: auditAnnotation.Key,
|
||||||
|
ValueExpression: auditAnnotation.ValueExpression,
|
||||||
|
}
|
||||||
|
auditExpressions = append(auditExpressions, auditCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
filterCompiler := cel.NewFilterCompiler()
|
||||||
|
filter := filterCompiler.Compile(
|
||||||
|
expressions,
|
||||||
|
cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false},
|
||||||
|
celconfig.PerCallLimit,
|
||||||
|
)
|
||||||
|
messageExpressionfilter := filterCompiler.Compile(
|
||||||
|
messageExpressions,
|
||||||
|
cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false},
|
||||||
|
celconfig.PerCallLimit,
|
||||||
|
)
|
||||||
|
auditAnnotationFilter := filterCompiler.Compile(
|
||||||
|
auditExpressions,
|
||||||
|
cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false},
|
||||||
|
celconfig.PerCallLimit,
|
||||||
|
)
|
||||||
|
matchConditionFilter := filterCompiler.Compile(
|
||||||
|
matchExpressions,
|
||||||
|
cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false},
|
||||||
|
celconfig.PerCallLimit,
|
||||||
|
)
|
||||||
|
|
||||||
|
newMatcher := matchconditions.NewMatcher(matchConditionFilter, nil, &failPolicy, string(matchPolicy), "")
|
||||||
|
validator := validatingadmissionpolicy.NewValidator(filter, newMatcher, auditAnnotationFilter, messageExpressionfilter, nil, nil)
|
||||||
|
|
||||||
|
admissionAttributes := admission.NewAttributesRecord(
|
||||||
|
resource.DeepCopyObject(),
|
||||||
|
nil, resource.GroupVersionKind(),
|
||||||
|
resource.GetNamespace(),
|
||||||
|
resource.GetName(),
|
||||||
|
schema.GroupVersionResource{},
|
||||||
|
"",
|
||||||
|
admission.Create,
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
versionedAttr, _ := admission.NewVersionedAttributes(admissionAttributes, admissionAttributes.GetKind(), nil)
|
||||||
|
validateResult := validator.Validate(context.TODO(), versionedAttr, nil, celconfig.RuntimeCELCostBudget)
|
||||||
|
|
||||||
|
engineResponse := engineapi.NewEngineResponseWithValidatingAdmissionPolicy(resource, policy, nil)
|
||||||
|
policyResp := engineapi.NewPolicyResponse()
|
||||||
|
var ruleResp *engineapi.RuleResponse
|
||||||
|
isPass := true
|
||||||
|
|
||||||
|
for _, policyDecision := range validateResult.Decisions {
|
||||||
|
if policyDecision.Evaluation == validatingadmissionpolicy.EvalError {
|
||||||
|
isPass = false
|
||||||
|
ruleResp = engineapi.RuleError(policy.GetName(), engineapi.Validation, policyDecision.Message, nil)
|
||||||
|
break
|
||||||
|
} else if policyDecision.Action == validatingadmissionpolicy.ActionDeny {
|
||||||
|
isPass = false
|
||||||
|
ruleResp = engineapi.RuleFail(policy.GetName(), engineapi.Validation, policyDecision.Message)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPass {
|
||||||
|
ruleResp = engineapi.RulePass(policy.GetName(), engineapi.Validation, "")
|
||||||
|
}
|
||||||
|
policyResp.Add(engineapi.NewExecutionStats(startTime, time.Now()), *ruleResp)
|
||||||
|
engineResponse = engineResponse.WithPolicyResponse(policyResp)
|
||||||
|
|
||||||
|
return engineResponse
|
||||||
|
}
|
137
pkg/validatingadmissionpolicy/validatingadmissionpolicy_test.go
Normal file
137
pkg/validatingadmissionpolicy/validatingadmissionpolicy_test.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package validatingadmissionpolicy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetKinds(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
name string
|
||||||
|
policy []byte
|
||||||
|
wantKinds []string
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []test{
|
||||||
|
{
|
||||||
|
name: "Matching pods",
|
||||||
|
policy: []byte(`
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||||
|
kind: ValidatingAdmissionPolicy
|
||||||
|
metadata:
|
||||||
|
name: "policy-1"
|
||||||
|
spec:
|
||||||
|
failurePolicy: Fail
|
||||||
|
matchConstraints:
|
||||||
|
resourceRules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
operations: ["CREATE", "UPDATE"]
|
||||||
|
resources: ["pods"]
|
||||||
|
validations:
|
||||||
|
- expression: "object.metadata.name.matches('nginx')"
|
||||||
|
`),
|
||||||
|
wantKinds: []string{"v1/Pod"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Matching deployments, replicasets, daemonsets and statefulsets",
|
||||||
|
policy: []byte(`
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||||
|
kind: ValidatingAdmissionPolicy
|
||||||
|
metadata:
|
||||||
|
name: "policy-2"
|
||||||
|
spec:
|
||||||
|
failurePolicy: Fail
|
||||||
|
matchConstraints:
|
||||||
|
resourceRules:
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
operations: ["CREATE", "UPDATE"]
|
||||||
|
resources: ["deployments", "replicasets", "daemonsets", "statefulsets"]
|
||||||
|
validations:
|
||||||
|
- expression: "object.spec.replicas <= 5"
|
||||||
|
`),
|
||||||
|
wantKinds: []string{"apps/v1/Deployment", "apps/v1/Replicaset", "apps/v1/Daemonset", "apps/v1/Statefulset"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Matching deployments/scale",
|
||||||
|
policy: []byte(`
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||||
|
kind: ValidatingAdmissionPolicy
|
||||||
|
metadata:
|
||||||
|
name: "policy-3"
|
||||||
|
spec:
|
||||||
|
failurePolicy: Fail
|
||||||
|
matchConstraints:
|
||||||
|
resourceRules:
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
operations: ["CREATE", "UPDATE"]
|
||||||
|
resources: ["deployments/scale"]
|
||||||
|
validations:
|
||||||
|
- expression: "object.spec.replicas <= 5"
|
||||||
|
`),
|
||||||
|
wantKinds: []string{"apps/v1/Deployment/scale"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Matching jobs and cronjobs",
|
||||||
|
policy: []byte(`
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||||
|
kind: ValidatingAdmissionPolicy
|
||||||
|
metadata:
|
||||||
|
name: "policy-4"
|
||||||
|
spec:
|
||||||
|
failurePolicy: Fail
|
||||||
|
matchConstraints:
|
||||||
|
resourceRules:
|
||||||
|
- apiGroups: ["batch"]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
operations: ["CREATE", "UPDATE"]
|
||||||
|
resources: ["jobs", "cronjobs"]
|
||||||
|
validations:
|
||||||
|
- expression: "object.spec.jobTemplate.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.readOnlyRootFilesystem) && container.securityContext.readOnlyRootFilesystem == true)"
|
||||||
|
`),
|
||||||
|
wantKinds: []string{"batch/v1/Job", "batch/v1/Cronjob"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple resource rules",
|
||||||
|
policy: []byte(`
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||||
|
kind: ValidatingAdmissionPolicy
|
||||||
|
metadata:
|
||||||
|
name: "policy-5"
|
||||||
|
spec:
|
||||||
|
failurePolicy: Fail
|
||||||
|
matchConstraints:
|
||||||
|
resourceRules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
operations: ["CREATE", "UPDATE"]
|
||||||
|
resources: ["pods"]
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
operations: ["CREATE", "UPDATE"]
|
||||||
|
resources: ["deployments", "replicasets", "daemonsets", "statefulsets"]
|
||||||
|
- apiGroups: ["batch"]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
operations: ["CREATE", "UPDATE"]
|
||||||
|
resources: ["jobs", "cronjobs"]
|
||||||
|
validations:
|
||||||
|
- expression: "object.spec.replicas <= 5"
|
||||||
|
`),
|
||||||
|
wantKinds: []string{"v1/Pod", "apps/v1/Deployment", "apps/v1/Replicaset", "apps/v1/Daemonset", "apps/v1/Statefulset", "batch/v1/Job", "batch/v1/Cronjob"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, policy, _ := yamlutils.GetPolicy(tt.policy)
|
||||||
|
kinds := GetKinds(policy[0])
|
||||||
|
if !reflect.DeepEqual(kinds, tt.wantKinds) {
|
||||||
|
t.Errorf("Expected %v, got %v", tt.wantKinds, kinds)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
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'"
|
|
|
@ -5,7 +5,7 @@ metadata:
|
||||||
labels:
|
labels:
|
||||||
app: nginx
|
app: nginx
|
||||||
spec:
|
spec:
|
||||||
replicas: 3
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: nginx
|
app: nginx
|
||||||
|
@ -16,7 +16,4 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: nginx
|
- name: nginx
|
||||||
image: nginx:1.14.2
|
image: nginx:latest
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
|
|
|
@ -17,6 +17,3 @@ spec:
|
||||||
containers:
|
containers:
|
||||||
- name: nginx
|
- name: nginx
|
||||||
image: nginx:latest
|
image: nginx:latest
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
|
|
14
test/cli/test-vap/check-deployments-replica/policy.yaml
Normal file
14
test/cli/test-vap/check-deployments-replica/policy.yaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||||
|
kind: ValidatingAdmissionPolicy
|
||||||
|
metadata:
|
||||||
|
name: "chech-deployment-replicas"
|
||||||
|
spec:
|
||||||
|
matchConstraints:
|
||||||
|
resourceRules:
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
operations: ["CREATE", "UPDATE"]
|
||||||
|
resources: ["deployments"]
|
||||||
|
validations:
|
||||||
|
- expression: "object.spec.replicas <= 2"
|
||||||
|
message: "Deployment replicas must be less than or equal 2"
|
14
test/cli/test-vap/disallow-host-path/pod1.yaml
Normal file
14
test/cli/test-vap/disallow-host-path/pod1.yaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: good-pod
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name:
|
||||||
|
image: nginx
|
||||||
|
volumeMounts:
|
||||||
|
- name: udev
|
||||||
|
mountPath: /data
|
||||||
|
volumes:
|
||||||
|
- name: udev
|
||||||
|
emptyDir: {}
|
15
test/cli/test-vap/disallow-host-path/pod2.yaml
Normal file
15
test/cli/test-vap/disallow-host-path/pod2.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: bad-pod
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name:
|
||||||
|
image: nginx
|
||||||
|
volumeMounts:
|
||||||
|
- name: udev
|
||||||
|
mountPath: /data
|
||||||
|
volumes:
|
||||||
|
- name: udev
|
||||||
|
hostPath:
|
||||||
|
path: /etc/udev
|
14
test/cli/test-vap/disallow-host-path/policy.yaml
Normal file
14
test/cli/test-vap/disallow-host-path/policy.yaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||||
|
kind: ValidatingAdmissionPolicy
|
||||||
|
metadata:
|
||||||
|
name: "disallow-host-path"
|
||||||
|
spec:
|
||||||
|
matchConstraints:
|
||||||
|
resourceRules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
apiVersions: ["v1"]
|
||||||
|
operations: ["CREATE", "UPDATE"]
|
||||||
|
resources: ["pods"]
|
||||||
|
validations:
|
||||||
|
- expression: "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
|
||||||
|
message: "HostPath volumes are forbidden. The field spec.volumes[*].hostPath must be unset."
|
Loading…
Add table
Reference in a new issue