1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

fix: return all the exceptions that match the incoming resource ()

* fix: return all the exceptions that match the incoming resource

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix: modify log messages

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

---------

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
Mariam Fahmy 2024-07-25 20:36:19 +03:00 committed by GitHub
parent 2855d27ce4
commit 716611b7ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 836 additions and 167 deletions

View file

@ -90,7 +90,7 @@ func printCheckResult(
// patchedTargetSubresourceName string
// podSecurityChecks contains pod security checks (only if this is a pod security rule)
"podSecurityChecks": rule.PodSecurityChecks(),
"exception ": rule.Exception(),
"exceptions": rule.Exceptions(),
}
if check.Assert.Value != nil {
errs, err := assert.Assert(ctx, nil, assert.Parse(ctx, check.Assert.Value), data, nil)

View file

@ -2,6 +2,7 @@ package background
import (
"context"
"strings"
"time"
"github.com/go-logr/logr"
@ -361,8 +362,8 @@ func (c *controller) reconcileReport(
}
policyNameToLabel[key] = reportutils.PolicyLabel(policy)
}
for _, exception := range exceptions {
key, err := cache.MetaNamespaceKeyFunc(exception)
for i, exception := range exceptions {
key, err := cache.MetaNamespaceKeyFunc(&exceptions[i])
if err != nil {
return err
}
@ -376,13 +377,24 @@ func (c *controller) reconcileReport(
policyNameToLabel[key] = reportutils.ValidatingAdmissionPolicyBindingLabel(binding)
}
for _, result := range observed.GetResults() {
// if the policy did not change, keep the result
// The result is kept as it is if:
// 1. The Kyverno policy and its matched exceptions are unchanged
// 2. The ValidatingAdmissionPolicy and its matched binding are unchanged
keepResult := true
exception := result.Properties["exceptions"]
exceptions := strings.Split(exception, ",")
for _, exception := range exceptions {
exceptionLabel := policyNameToLabel[exception]
if exceptionLabel != "" && expected[exceptionLabel] != actual[exceptionLabel] {
keepResult = false
break
}
}
label := policyNameToLabel[result.Policy]
exceptionLabel := policyNameToLabel[result.Properties["exception"]]
vapBindingLabel := policyNameToLabel[result.Properties["binding"]]
if (label != "" && expected[label] == actual[label]) ||
(exceptionLabel != "" && expected[exceptionLabel] == actual[exceptionLabel]) ||
(vapBindingLabel != "" && expected[vapBindingLabel] == actual[vapBindingLabel]) {
(vapBindingLabel != "" && expected[vapBindingLabel] == actual[vapBindingLabel]) || keepResult {
ruleResults = append(ruleResults, result)
}
}

View file

@ -43,8 +43,8 @@ type RuleResponse struct {
patchedTargetSubresourceName string
// podSecurityChecks contains pod security checks (only if this is a pod security rule)
podSecurityChecks *PodSecurityChecks
// exception is the exception applied (if any)
exception *kyvernov2.PolicyException
// exceptions are the exceptions applied (if any)
exceptions []kyvernov2.PolicyException
// binding is the validatingadmissionpolicybinding (if any)
binding *v1alpha1.ValidatingAdmissionPolicyBinding
// emitWarning enable passing rule message as warning to api server warning header
@ -88,8 +88,8 @@ func RuleFail(name string, ruleType RuleType, msg string) *RuleResponse {
return NewRuleResponse(name, ruleType, msg, RuleStatusFail)
}
func (r RuleResponse) WithException(exception *kyvernov2.PolicyException) *RuleResponse {
r.exception = exception
func (r RuleResponse) WithExceptions(exceptions []kyvernov2.PolicyException) *RuleResponse {
r.exceptions = exceptions
return &r
}
@ -129,8 +129,8 @@ func (r *RuleResponse) Stats() ExecutionStats {
return r.stats
}
func (r *RuleResponse) Exception() *kyvernov2.PolicyException {
return r.exception
func (r *RuleResponse) Exceptions() []kyvernov2.PolicyException {
return r.exceptions
}
func (r *RuleResponse) ValidatingAdmissionPolicyBinding() *v1alpha1.ValidatingAdmissionPolicyBinding {
@ -138,7 +138,7 @@ func (r *RuleResponse) ValidatingAdmissionPolicyBinding() *v1alpha1.ValidatingAd
}
func (r *RuleResponse) IsException() bool {
return r.exception != nil
return len(r.exceptions) > 0
}
func (r *RuleResponse) PodSecurityChecks() *PodSecurityChecks {

View file

@ -2,6 +2,7 @@ package engine
import (
"context"
"strings"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
@ -64,17 +65,21 @@ func (e *engine) filterRule(
logger.Error(err, "failed to get exceptions")
return nil
}
// check if there is a policy exception matches the incoming resource
exception := engineutils.MatchesException(exceptions, policyContext, logger)
if exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return engineapi.RuleError(rule.Name, ruleType, "failed to compute exception key", err)
} else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return engineapi.RuleSkip(rule.Name, ruleType, "rule skipped due to policy exception "+key).WithException(exception)
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return engineapi.RuleError(rule.Name, ruleType, "failed to compute exception key", err)
}
keys = append(keys, key)
}
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return engineapi.RuleSkip(rule.Name, ruleType, "rule is skipped due to policy exception "+strings.Join(keys, ", ")).WithExceptions(matchedExceptions)
}
newResource := policyContext.NewResource()

View file

@ -2,6 +2,7 @@ package mutation
import (
"context"
"strings"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
@ -37,19 +38,23 @@ func (h mutateExistingHandler) Process(
contextLoader engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
// check if there is a policy exception matches the incoming resource
exception := engineutils.MatchesException(exceptions, policyContext, logger)
if exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Mutation, "failed to compute exception key", err)
} else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule skipped due to policy exception "+key).WithException(exception),
)
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Mutation, "failed to compute exception key", err)
}
keys = append(keys, key)
}
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions),
)
}
var responses []engineapi.RuleResponse

View file

@ -2,6 +2,7 @@ package mutation
import (
"context"
"strings"
json_patch "github.com/evanphx/json-patch/v5"
"github.com/go-logr/logr"
@ -68,19 +69,23 @@ func (h mutateImageHandler) Process(
contextLoader engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
// check if there is a policy exception matches the incoming resource
exception := engineutils.MatchesException(exceptions, policyContext, logger)
if exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Mutation, "failed to compute exception key", err)
} else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule skipped due to policy exception "+key).WithException(exception),
)
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Mutation, "failed to compute exception key", err)
}
keys = append(keys, key)
}
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions),
)
}
jsonContext := policyContext.JSONContext()

View file

@ -2,6 +2,7 @@ package mutation
import (
"context"
"strings"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
@ -30,19 +31,23 @@ func (h mutateResourceHandler) Process(
contextLoader engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
// check if there is a policy exception matches the incoming resource
exception := engineutils.MatchesException(exceptions, policyContext, logger)
if exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Mutation, "failed to compute exception key", err)
} else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule skipped due to policy exception "+key).WithException(exception),
)
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Mutation, "failed to compute exception key", err)
}
keys = append(keys, key)
}
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Mutation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions),
)
}
_, subresource := policyContext.ResourceKind()

View file

@ -3,6 +3,7 @@ package validation
import (
"context"
"fmt"
"strings"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
@ -47,19 +48,23 @@ func (h validateCELHandler) Process(
_ engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
// check if there is a policy exception matches the incoming resource
exception := engineutils.MatchesException(exceptions, policyContext, logger)
if exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
} else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule skipped due to policy exception "+key).WithException(exception),
)
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
}
keys = append(keys, key)
}
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions),
)
}
// check if a corresponding validating admission policy is generated

View file

@ -47,19 +47,23 @@ func (h validateImageHandler) Process(
_ engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
// check if there is a policy exception matches the incoming resource
exception := engineutils.MatchesException(exceptions, policyContext, logger)
if exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.ImageVerify, "failed to compute exception key", err)
} else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.ImageVerify, "rule skipped due to policy exception "+key).WithException(exception),
)
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
}
keys = append(keys, key)
}
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions),
)
}
skippedImages := make([]string, 0)

View file

@ -59,19 +59,23 @@ func (h validateManifestHandler) Process(
_ engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
// check if there is a policy exception matches the incoming resource
exception := engineutils.MatchesException(exceptions, policyContext, logger)
if exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
} else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule skipped due to policy exception "+key).WithException(exception),
)
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
}
keys = append(keys, key)
}
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions),
)
}
// verify manifest

View file

@ -44,17 +44,29 @@ func (h validatePssHandler) Process(
return resource, nil
}
// check if there is a policy exception matches the incoming resource
exception := engineutils.MatchesException(exceptions, policyContext, logger)
if exception != nil && !exception.HasPodSecurity() {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
} else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
var polex kyvernov2.PolicyException
hasPodSecurity := true
for i, exception := range matchedExceptions {
if !exception.HasPodSecurity() {
hasPodSecurity = false
polex = matchedExceptions[i]
break
}
}
if !hasPodSecurity {
key, err := cache.MetaNamespaceKeyFunc(&polex)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", polex.GetNamespace(), "name", polex.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
}
logger.V(3).Info("policy rule is skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule skipped due to policy exception "+key).WithException(exception),
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exception "+key).WithExceptions([]kyvernov2.PolicyException{polex}),
)
}
}
@ -91,21 +103,25 @@ func (h validatePssHandler) Process(
)
} else {
// apply pod security exceptions if exist
if exception != nil && exception.HasPodSecurity() {
pssChecks, err = pss.ApplyPodSecurityExclusion(levelVersion, exception.Spec.PodSecurity, pssChecks, pod)
if len(pssChecks) == 0 && err == nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
} else {
podSecurityChecks.Checks = pssChecks
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule skipped due to policy exception "+key).WithException(exception).WithPodSecurityChecks(podSecurityChecks),
)
}
var excludes []kyvernov1.PodSecurityStandard
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
}
keys = append(keys, key)
excludes = append(excludes, exception.Spec.PodSecurity...)
}
pssChecks, err = pss.ApplyPodSecurityExclusion(levelVersion, excludes, pssChecks, pod)
if len(pssChecks) == 0 && err == nil {
podSecurityChecks.Checks = pssChecks
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions "+strings.Join(keys, ", ")).WithExceptions(matchedExceptions).WithPodSecurityChecks(podSecurityChecks),
)
}
msg := fmt.Sprintf(`Validation rule '%s' failed. It violates PodSecurity "%s:%s": %s`, rule.Name, podSecurity.Level, podSecurity.Version, pss.FormatChecksPrint(pssChecks))
return resource, handlers.WithResponses(

View file

@ -40,19 +40,23 @@ func (h validateResourceHandler) Process(
contextLoader engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
// check if there is a policy exception matches the incoming resource
exception := engineutils.MatchesException(exceptions, policyContext, logger)
if exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
} else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule skipped due to policy exception "+key).WithException(exception),
)
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils.MatchesException(exceptions, policyContext, logger)
if len(matchedExceptions) > 0 {
var keys []string
for i, exception := range matchedExceptions {
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
}
keys = append(keys, key)
}
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions"+strings.Join(keys, ", ")).WithExceptions(matchedExceptions),
)
}
v := newValidator(logger, contextLoader, policyContext, rule)
return resource, handlers.WithResponses(v.validate(ctx))

View file

@ -15,7 +15,8 @@ import (
// MatchesException takes a list of exceptions and checks if there is an exception applies to the incoming resource.
// It returns the matched policy exception.
func MatchesException(polexs []*kyvernov2.PolicyException, policyContext engineapi.PolicyContext, logger logr.Logger) *kyvernov2.PolicyException {
func MatchesException(polexs []*kyvernov2.PolicyException, policyContext engineapi.PolicyContext, logger logr.Logger) []kyvernov2.PolicyException {
var matchedExceptions []kyvernov2.PolicyException
gvk, subresource := policyContext.ResourceKind()
resource := policyContext.NewResource()
if resource.Object == nil {
@ -40,10 +41,10 @@ func MatchesException(polexs []*kyvernov2.PolicyException, policyContext enginea
continue
}
}
return polex
matchedExceptions = append(matchedExceptions, *polex)
}
}
return nil
return matchedExceptions
}
func checkMatchesResources(

View file

@ -222,16 +222,51 @@ func NewBackgroundSuccessEvent(source Source, policy kyvernov1.PolicyInterface,
}
func NewPolicyExceptionEvents(engineResponse engineapi.EngineResponse, ruleResp engineapi.RuleResponse, source Source) []Info {
exception := ruleResp.Exception()
exceptionName, exceptionNamespace := exception.GetName(), exception.GetNamespace()
policyMessage := fmt.Sprintf("resource %s was skipped from rule %s due to policy exception %s/%s", resourceKey(engineResponse.PatchedResource), ruleResp.Name(), exceptionNamespace, exceptionName)
pol := engineResponse.Policy().AsKyvernoPolicy()
var exceptionMessage string
exceptions := ruleResp.Exceptions()
exceptionNames := make([]string, 0, len(exceptions))
events := make([]Info, 0, len(exceptions))
// build the events of the policy exceptions
pol := engineResponse.Policy().AsKyvernoPolicy()
if pol.GetNamespace() == "" {
exceptionMessage = fmt.Sprintf("resource %s was skipped from policy rule %s/%s", resourceKey(engineResponse.PatchedResource), pol.GetName(), ruleResp.Name())
} else {
exceptionMessage = fmt.Sprintf("resource %s was skipped from policy rule %s/%s/%s", resourceKey(engineResponse.PatchedResource), pol.GetNamespace(), pol.GetName(), ruleResp.Name())
}
related := engineResponse.GetResourceSpec()
for _, exception := range exceptions {
ns := exception.GetNamespace()
name := exception.GetName()
exceptionNames = append(exceptionNames, ns+"/"+name)
exceptionEvent := Info{
Regarding: corev1.ObjectReference{
// TODO: iirc it's not safe to assume api version is set
APIVersion: "kyverno.io/v2",
Kind: "PolicyException",
Name: name,
Namespace: ns,
UID: exception.GetUID(),
},
Related: &corev1.ObjectReference{
APIVersion: related.APIVersion,
Kind: related.Kind,
Name: related.Name,
Namespace: related.Namespace,
UID: types.UID(related.UID),
},
Reason: PolicySkipped,
Message: exceptionMessage,
Source: source,
Action: ResourcePassed,
}
events = append(events, exceptionEvent)
}
// build the policy events
policyMessage := fmt.Sprintf("resource %s was skipped from rule %s due to policy exceptions %s", resourceKey(engineResponse.PatchedResource), ruleResp.Name(), strings.Join(exceptionNames, ", "))
regarding := corev1.ObjectReference{
// TODO: iirc it's not safe to assume api version is set
APIVersion: "kyverno.io/v1",
@ -240,7 +275,6 @@ func NewPolicyExceptionEvents(engineResponse engineapi.EngineResponse, ruleResp
Namespace: pol.GetNamespace(),
UID: pol.GetUID(),
}
related := engineResponse.GetResourceSpec()
policyEvent := Info{
Regarding: regarding,
Related: &corev1.ObjectReference{
@ -255,28 +289,8 @@ func NewPolicyExceptionEvents(engineResponse engineapi.EngineResponse, ruleResp
Source: source,
Action: ResourcePassed,
}
exceptionEvent := Info{
Regarding: corev1.ObjectReference{
// TODO: iirc it's not safe to assume api version is set
APIVersion: "kyverno.io/v2",
Kind: "PolicyException",
Name: exceptionName,
Namespace: exceptionNamespace,
UID: exception.GetUID(),
},
Related: &corev1.ObjectReference{
APIVersion: related.APIVersion,
Kind: related.Kind,
Name: related.Name,
Namespace: related.Namespace,
UID: types.UID(related.UID),
},
Reason: PolicySkipped,
Message: exceptionMessage,
Source: source,
Action: ResourcePassed,
}
return []Info{policyEvent, exceptionEvent}
events = append(events, policyEvent)
return events
}
func NewCleanupPolicyEvent(policy kyvernov2.CleanupPolicyInterface, resource unstructured.Unstructured, err error) Info {

View file

@ -110,8 +110,13 @@ func ToPolicyReportResult(policyType engineapi.PolicyType, policyName string, ru
*resource,
}
}
if ruleResult.Exception() != nil {
addProperty("exception", ruleResult.Exception().Name, &result)
exceptions := ruleResult.Exceptions()
if len(exceptions) > 0 {
var names []string
for _, exception := range exceptions {
names = append(names, exception.Name)
}
addProperty("exceptions", strings.Join(names, ","), &result)
}
pss := ruleResult.PodSecurityChecks()
if pss != nil && len(pss.Checks) > 0 {

View file

@ -0,0 +1,18 @@
## Description
This test creates two policy exceptions that match the same policy. It is expected that the pod that satisfies both exceptions will be created successfully.
## Expected Behavior
1. Create a policy that applies the baseline profile.
2. Create two exceptions for the init containters as follows:
- The first exception `init1-exception-baseline` allows the values of `NET_ADMIN` and `NET_RAW` capabilities in the init containers.
- The second exception `init2-exception-baseline` allows the values of `SYS_TIME` capabilities in the init containers.
3. Create a pod with two init containers. The first init container should have the `NET_ADMIN` and `NET_RAW` capabilities, and the second init container should have the `SYS_TIME` capability. It is expected that the pod will be created successfully as it matches both exceptions.
## Reference Issue(s)
#10580

View file

@ -0,0 +1,21 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: multiple-exceptions-with-pod-security
spec:
steps:
- name: step-01
try:
- apply:
file: policy.yaml
- assert:
file: policy-assert.yaml
- name: step-02
try:
- apply:
file: exceptions.yaml
- name: step-03
try:
- apply:
file: pod.yaml

View file

@ -0,0 +1,44 @@
apiVersion: kyverno.io/v2
kind: PolicyException
metadata:
name: init1-exception-baseline
spec:
exceptions:
- policyName: psp-baseline
ruleNames:
- baseline
match:
any:
- resources:
kinds:
- Pod
podSecurity:
- controlName: Capabilities
images:
- 'alpine:latest'
restrictedField: spec.initContainers[*].securityContext.capabilities.add
values:
- NET_ADMIN
- NET_RAW
---
apiVersion: kyverno.io/v2
kind: PolicyException
metadata:
name: init2-exception-baseline
spec:
exceptions:
- policyName: psp-baseline
ruleNames:
- baseline
match:
any:
- resources:
kinds:
- Pod
podSecurity:
- controlName: Capabilities
images:
- 'busybox:latest'
restrictedField: spec.initContainers[*].securityContext.capabilities.add
values:
- SYS_TIME

View file

@ -0,0 +1,56 @@
---
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- image: alpine:latest
imagePullPolicy: IfNotPresent
name: primary
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
initContainers:
- image: alpine:latest
imagePullPolicy: IfNotPresent
name: init1
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 10001
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
- image: busybox:latest
imagePullPolicy: IfNotPresent
name: init2
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- SYS_TIME
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 10002
runAsNonRoot: true
runAsUser: 10002
seccompProfile:
type: RuntimeDefault

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: psp-baseline
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,19 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: psp-baseline
spec:
failurePolicy: Ignore
background: true
validationFailureAction: Enforce
rules:
- name: baseline
match:
any:
- resources:
kinds:
- Pod
validate:
podSecurity:
level: baseline
version: v1.29

View file

@ -0,0 +1,18 @@
## Description
This test creates two policy exceptions that match the same policy. It is expected that the pod that satisfies both exceptions will be created successfully.
## Expected Behavior
1. Create a policy that applies the baseline profile.
2. Create two exceptions as follows:
- The first exception `exception-baseline` that exempts the whole pod from the baseline profile.
- The second exception `init-exception-baseline` allows the values of `SYS_TIME` capabilities in the init containers.
3. Create a pod with two init containers. The first init container should have the `NET_ADMIN` and `NET_RAW` capabilities, and the second init container should have the `SYS_TIME` capability. It is expected that the pod will be created successfully as it matches both exceptions.
## Reference Issue(s)
#10580

View file

@ -0,0 +1,21 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: multiple-exceptions
spec:
steps:
- name: step-01
try:
- apply:
file: policy.yaml
- assert:
file: policy-assert.yaml
- name: step-02
try:
- apply:
file: exceptions.yaml
- name: step-03
try:
- apply:
file: pod.yaml

View file

@ -0,0 +1,36 @@
apiVersion: kyverno.io/v2
kind: PolicyException
metadata:
name: exception-baseline
spec:
exceptions:
- policyName: psp-baseline
ruleNames:
- baseline
match:
any:
- resources:
kinds:
- Pod
---
apiVersion: kyverno.io/v2
kind: PolicyException
metadata:
name: init-exception-baseline
spec:
exceptions:
- policyName: psp-baseline
ruleNames:
- baseline
match:
any:
- resources:
kinds:
- Pod
podSecurity:
- controlName: Capabilities
images:
- 'busybox:latest'
restrictedField: spec.initContainers[*].securityContext.capabilities.add
values:
- SYS_TIME

View file

@ -0,0 +1,56 @@
---
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- image: alpine:latest
imagePullPolicy: IfNotPresent
name: primary
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
initContainers:
- image: alpine:latest
imagePullPolicy: IfNotPresent
name: init1
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 10001
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
- image: busybox:latest
imagePullPolicy: IfNotPresent
name: init2
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- SYS_TIME
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 10002
runAsNonRoot: true
runAsUser: 10002
seccompProfile:
type: RuntimeDefault

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: psp-baseline
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,19 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: psp-baseline
spec:
failurePolicy: Ignore
background: true
validationFailureAction: Enforce
rules:
- name: baseline
match:
any:
- resources:
kinds:
- Pod
validate:
podSecurity:
level: baseline
version: v1.29

View file

@ -16,7 +16,7 @@ results:
scored: true
source: kyverno
properties:
exception: mynewpolex
exceptions: mynewpolex
summary:
error: 0
fail: 0

View file

@ -9,7 +9,7 @@ metadata:
results:
- policy: psa-1
properties:
exception: pod-security-exception
exceptions: pod-security-exception
result: skip
rule: restricted
scored: true

View file

@ -16,7 +16,7 @@ results:
scored: true
source: kyverno
properties:
exception: mynewpolex
exceptions: mynewpolex
summary:
error: 0
fail: 0

View file

@ -0,0 +1,25 @@
## Description
This test makes sure that the report is generated correctly when multiple exceptions are created for the same policy.
## Expected Behavior
1. Create a pod with two init containers. The first init container should have the `NET_ADMIN` and `NET_RAW` capabilities, and the second init container should have the `SYS_TIME` capability.
2. Create a policy that applies the baseline profile.
3. Create two exceptions for the init containters as follows:
- The first exception `init1-exception-baseline` allows the values of `NET_ADMIN` and `NET_RAW` capabilities in the init containers.
- The second exception `init2-exception-baseline` allows the values of `SYS_TIME` capabilities in the init containers.
4. It is expected that a policy report is generated with a `skip` result.
5. Delete the first exception.
6. It is expected that a policy report is updated with a `fail` result since the first init container violates the policy and it isn't excluded by the second exception.
## Reference Issue(s)
#10580

View file

@ -0,0 +1,45 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: multiple-exceptions-with-pod-security
spec:
steps:
- name: step-01
try:
- apply:
file: pod.yaml
- name: step-02
try:
- apply:
file: policy.yaml
- assert:
file: policy-assert.yaml
- name: step-03
try:
- apply:
file: exceptions.yaml
- name: step-04
try:
- sleep:
duration: 5s
- name: step-05
try:
- assert:
file: report-skip-assert.yaml
- name: step-06
try:
- script:
env:
- name: NAMESPACE
value: ($namespace)
content: |
kubectl delete polex init1-exception-baseline -n $NAMESPACE
- name: step-07
try:
- sleep:
duration: 5s
- name: step-08
try:
- assert:
file: report-fail-assert.yaml

View file

@ -0,0 +1,44 @@
apiVersion: kyverno.io/v2
kind: PolicyException
metadata:
name: init1-exception-baseline
spec:
exceptions:
- policyName: psp-baseline
ruleNames:
- baseline
match:
any:
- resources:
kinds:
- Pod
podSecurity:
- controlName: Capabilities
images:
- 'alpine:latest'
restrictedField: spec.initContainers[*].securityContext.capabilities.add
values:
- NET_ADMIN
- NET_RAW
---
apiVersion: kyverno.io/v2
kind: PolicyException
metadata:
name: init2-exception-baseline
spec:
exceptions:
- policyName: psp-baseline
ruleNames:
- baseline
match:
any:
- resources:
kinds:
- Pod
podSecurity:
- controlName: Capabilities
images:
- 'busybox:latest'
restrictedField: spec.initContainers[*].securityContext.capabilities.add
values:
- SYS_TIME

View file

@ -0,0 +1,56 @@
---
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- image: alpine:latest
imagePullPolicy: IfNotPresent
name: primary
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
initContainers:
- image: alpine:latest
imagePullPolicy: IfNotPresent
name: init1
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 10001
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
- image: busybox:latest
imagePullPolicy: IfNotPresent
name: init2
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- SYS_TIME
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 10002
runAsNonRoot: true
runAsUser: 10002
seccompProfile:
type: RuntimeDefault

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: psp-baseline
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,19 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: psp-baseline
spec:
failurePolicy: Ignore
background: true
validationFailureAction: Enforce
rules:
- name: baseline
match:
any:
- resources:
kinds:
- Pod
validate:
podSecurity:
level: baseline
version: v1.29

View file

@ -0,0 +1,33 @@
apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
ownerReferences:
- apiVersion: v1
kind: Pod
name: test-pod
results:
- message: 'Validation rule ''baseline'' failed. It violates PodSecurity "baseline:v1.29":
(Forbidden reason: non-default capabilities, field error list: [spec.initContainers[0].securityContext.capabilities.add
is forbidden, forbidden values found: [NET_ADMIN NET_RAW]])'
policy: psp-baseline
properties:
controls: capabilities_baseline
controlsJSON: '[{"ID":"capabilities_baseline","Name":"Capabilities","Images":["docker.io/alpine:latest","docker.io/busybox:latest"]}]'
standard: baseline
version: v1.29
result: fail
rule: baseline
scored: true
source: kyverno
scope:
apiVersion: v1
kind: Pod
name: test-pod
summary:
error: 0
fail: 1
pass: 0
skip: 0
warn: 0

View file

@ -0,0 +1,27 @@
apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
ownerReferences:
- apiVersion: v1
kind: Pod
name: test-pod
results:
- policy: psp-baseline
properties:
exceptions: init1-exception-baseline,init2-exception-baseline
result: skip
rule: baseline
scored: true
source: kyverno
scope:
apiVersion: v1
kind: Pod
name: test-pod
summary:
error: 0
fail: 0
pass: 0
skip: 1
warn: 0