1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-13 19:28:55 +00:00

fix: polex matching code (#9955)

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2024-04-03 20:56:48 +02:00 committed by GitHub
parent 7bf4033c6e
commit c241cfce44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 365 additions and 31 deletions

View file

@ -144,6 +144,7 @@ func main() {
// informer factories
kyvernoInformer := kyvernoinformer.NewSharedInformerFactory(setup.KyvernoClient, resyncPeriod)
var wg sync.WaitGroup
polexCache, polexController := internal.NewExceptionSelector(setup.Logger, kyvernoInformer)
eventGenerator := event.NewEventGenerator(
setup.EventsClient,
logging.WithName("EventGenerator"),
@ -187,6 +188,7 @@ func main() {
setup.KyvernoClient,
setup.RegistrySecretLister,
apicall.NewAPICallConfiguration(maxAPICallResponseLength),
polexCache,
gcstore,
)
// start informers and wait for cache sync
@ -247,6 +249,9 @@ func main() {
// start non leader controllers
eventController.Run(signalCtx, setup.Logger, &wg)
gceController.Run(signalCtx, setup.Logger, &wg)
if polexController != nil {
polexController.Run(signalCtx, setup.Logger, &wg)
}
// start leader election
le.Run(signalCtx)
// wait for everything to shut down and exit

View file

@ -10,6 +10,7 @@ import (
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
exceptioncontroller "github.com/kyverno/kyverno/pkg/controllers/exceptions"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/adapters"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -18,7 +19,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine/context/resolvers"
"github.com/kyverno/kyverno/pkg/engine/factories"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/exceptions"
"github.com/kyverno/kyverno/pkg/imageverifycache"
"github.com/kyverno/kyverno/pkg/registryclient"
"k8s.io/client-go/kubernetes"
@ -38,10 +38,10 @@ func NewEngine(
kyvernoClient versioned.Interface,
secretLister corev1listers.SecretNamespaceLister,
apiCallConfig apicall.APICallConfiguration,
exceptionsSelector engineapi.PolicyExceptionSelector,
gctxStore loaders.Store,
) engineapi.Engine {
configMapResolver := NewConfigMapResolver(ctx, logger, kubeClient, 15*time.Minute)
exceptionsSelector := NewExceptionSelector(ctx, logger, kyvernoClient, 15*time.Minute)
logger = logger.WithName("engine")
logger.Info("setup engine...")
return engine.NewEngine(
@ -57,29 +57,26 @@ func NewEngine(
}
func NewExceptionSelector(
ctx context.Context,
logger logr.Logger,
kyvernoClient versioned.Interface,
resyncPeriod time.Duration,
) engineapi.PolicyExceptionSelector {
kyvernoInformer kyvernoinformer.SharedInformerFactory,
) (engineapi.PolicyExceptionSelector, Controller) {
logger = logger.WithName("exception-selector").WithValues("enablePolicyException", enablePolicyException, "exceptionNamespace", exceptionNamespace)
logger.Info("setup exception selector...")
var exceptionsLister engineapi.PolicyExceptionSelector
if enablePolicyException {
factory := kyvernoinformer.NewSharedInformerFactory(kyvernoClient, resyncPeriod)
var lister exceptions.Lister
if exceptionNamespace != "" {
lister = factory.Kyverno().V2beta1().PolicyExceptions().Lister().PolicyExceptions(exceptionNamespace)
} else {
lister = factory.Kyverno().V2beta1().PolicyExceptions().Lister()
}
// start informers and wait for cache sync
if !StartInformersAndWaitForCacheSync(ctx, logger, factory) {
checkError(logger, errors.New("failed to wait for cache sync"), "failed to wait for cache sync")
}
exceptionsLister = exceptions.New(lister)
if !enablePolicyException {
return nil, nil
}
return exceptionsLister
polexCache := exceptioncontroller.NewController(
kyvernoInformer.Kyverno().V1().ClusterPolicies(),
kyvernoInformer.Kyverno().V1().Policies(),
kyvernoInformer.Kyverno().V2beta1().PolicyExceptions(),
exceptionNamespace,
)
polexController := NewController(
exceptioncontroller.ControllerName,
polexCache,
exceptioncontroller.Workers,
)
return polexCache, polexController
}
func NewConfigMapResolver(

View file

@ -370,6 +370,7 @@ func main() {
),
globalcontextcontroller.Workers,
)
polexCache, polexController := internal.NewExceptionSelector(setup.Logger, kyvernoInformer)
eventController := internal.NewController(
event.ControllerName,
eventGenerator,
@ -415,6 +416,7 @@ func main() {
setup.KyvernoClient,
setup.RegistrySecretLister,
apicall.NewAPICallConfiguration(maxAPICallResponseLength),
polexCache,
gcstore,
)
// create non leader controllers
@ -577,6 +579,9 @@ func main() {
// start non leader controllers
eventController.Run(signalCtx, setup.Logger, &wg)
gceController.Run(signalCtx, setup.Logger, &wg)
if polexController != nil {
polexController.Run(signalCtx, setup.Logger, &wg)
}
for _, controller := range nonLeaderControllers {
controller.Run(signalCtx, setup.Logger.WithName("controllers"), &wg)
}

View file

@ -262,6 +262,7 @@ func main() {
// informer factories
kyvernoInformer := kyvernoinformer.NewSharedInformerFactory(setup.KyvernoClient, resyncPeriod)
var wg sync.WaitGroup
polexCache, polexController := internal.NewExceptionSelector(setup.Logger, kyvernoInformer)
eventGenerator := event.NewEventGenerator(
setup.EventsClient,
logging.WithName("EventGenerator"),
@ -300,6 +301,7 @@ func main() {
setup.KyvernoClient,
setup.RegistrySecretLister,
apicall.NewAPICallConfiguration(maxAPICallResponseLength),
polexCache,
gcstore,
)
// start informers and wait for cache sync
@ -376,6 +378,9 @@ func main() {
// start non leader controllers
eventController.Run(ctx, setup.Logger, &wg)
gceController.Run(ctx, setup.Logger, &wg)
if polexController != nil {
polexController.Run(ctx, setup.Logger, &wg)
}
// start leader election
le.Run(ctx)
// wait for everything to shut down and exit

View file

@ -0,0 +1,188 @@
package exceptions
import (
"cmp"
"context"
"slices"
"sync"
"time"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
"github.com/kyverno/kyverno/pkg/autogen"
kyvernov1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernov2beta1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v2beta1"
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
kyvernov2beta1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v2beta1"
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/util/workqueue"
)
type ruleIndex = map[string][]*kyvernov2beta1.PolicyException
type policyIndex = map[string]ruleIndex
type controller struct {
// listers
cpolLister kyvernov1listers.ClusterPolicyLister
polLister kyvernov1listers.PolicyLister
polexLister kyvernov2beta1listers.PolicyExceptionLister
// queue
queue workqueue.RateLimitingInterface
// state
lock sync.RWMutex
index policyIndex
namespace string
}
const (
maxRetries = 10
Workers = 3
ControllerName = "exceptions-controller"
)
func NewController(
cpolInformer kyvernov1informers.ClusterPolicyInformer,
polInformer kyvernov1informers.PolicyInformer,
polexInformer kyvernov2beta1informers.PolicyExceptionInformer,
namespace string,
) *controller {
queue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), ControllerName)
if _, _, err := controllerutils.AddDefaultEventHandlers(logger, cpolInformer.Informer(), queue); err != nil {
logger.Error(err, "failed to register event handlers")
}
if _, _, err := controllerutils.AddDefaultEventHandlers(logger, polInformer.Informer(), queue); err != nil {
logger.Error(err, "failed to register event handlers")
}
c := &controller{
cpolLister: cpolInformer.Lister(),
polLister: polInformer.Lister(),
polexLister: polexInformer.Lister(),
queue: queue,
index: policyIndex{},
namespace: namespace,
}
if _, err := controllerutils.AddEventHandlersT(polexInformer.Informer(), c.addPolex, c.updatePolex, c.deletePolex); err != nil {
logger.Error(err, "failed to register event handlers")
}
return c
}
func (c *controller) Run(ctx context.Context, workers int) {
controllerutils.Run(ctx, logger.V(3), ControllerName, time.Second, c.queue, workers, maxRetries, c.reconcile)
}
func (c *controller) Find(policyName string, ruleName string) ([]*kyvernov2beta1.PolicyException, error) {
c.lock.RLock()
defer c.lock.RUnlock()
return c.index[policyName][ruleName], nil
}
func (c *controller) addPolex(polex *kyvernov2beta1.PolicyException) {
names := sets.New[string]()
for _, ex := range polex.Spec.Exceptions {
names.Insert(ex.PolicyName)
}
for name := range names {
c.queue.Add(name)
}
}
func (c *controller) updatePolex(old *kyvernov2beta1.PolicyException, new *kyvernov2beta1.PolicyException) {
names := sets.New[string]()
for _, ex := range old.Spec.Exceptions {
names.Insert(ex.PolicyName)
}
for _, ex := range new.Spec.Exceptions {
names.Insert(ex.PolicyName)
}
for name := range names {
c.queue.Add(name)
}
}
func (c *controller) deletePolex(polex *kyvernov2beta1.PolicyException) {
names := sets.New[string]()
for _, ex := range polex.Spec.Exceptions {
names.Insert(ex.PolicyName)
}
for name := range names {
c.queue.Add(name)
}
}
func (c *controller) getPolicy(namespace, name string) (kyvernov1.PolicyInterface, error) {
if namespace == "" {
cpolicy, err := c.cpolLister.Get(name)
if err != nil {
return nil, err
}
return cpolicy, nil
} else {
policy, err := c.polLister.Policies(namespace).Get(name)
if err != nil {
return nil, err
}
return policy, nil
}
}
func (c *controller) listExceptions() ([]*kyvernov2beta1.PolicyException, error) {
if c.namespace == "" {
return c.polexLister.List(labels.Everything())
}
return c.polexLister.PolicyExceptions(c.namespace).List(labels.Everything())
}
func (c *controller) buildRuleIndex(key string, policy kyvernov1.PolicyInterface) (ruleIndex, error) {
polexList, err := c.listExceptions()
if err != nil {
return nil, err
}
slices.SortFunc(polexList, func(a, b *kyvernov2beta1.PolicyException) int {
if cmp := cmp.Compare(a.Namespace, b.Namespace); cmp != 0 {
return cmp
}
if cmp := cmp.Compare(a.Name, b.Name); cmp != 0 {
return cmp
}
return 0
})
index := ruleIndex{}
for _, rule := range autogen.ComputeRules(policy) {
for _, polex := range polexList {
if polex.Contains(key, rule.Name) {
index[rule.Name] = append(index[rule.Name], polex)
}
}
}
return index, nil
}
func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, namespace, name string) error {
policy, err := c.getPolicy(namespace, name)
if err != nil {
if !apierrors.IsNotFound(err) {
logger.Error(err, "unable to get the policy from policy informer")
return err
}
c.lock.Lock()
defer c.lock.Unlock()
delete(c.index, key)
return nil
}
ruleIndex, err := c.buildRuleIndex(key, policy)
if err != nil {
return err
}
c.lock.Lock()
defer c.lock.Unlock()
c.index[key] = ruleIndex
return nil
}

View file

@ -0,0 +1,5 @@
package exceptions
import "github.com/kyverno/kyverno/pkg/logging"
var logger = logging.WithName(ControllerName)

View file

@ -2,26 +2,27 @@ package utils
import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/utils/conditions"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
matched "github.com/kyverno/kyverno/pkg/utils/match"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// 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 []*kyvernov2beta1.PolicyException,
policyContext engineapi.PolicyContext,
logger logr.Logger,
) *kyvernov2beta1.PolicyException {
func MatchesException(polexs []*kyvernov2beta1.PolicyException, policyContext engineapi.PolicyContext, logger logr.Logger) *kyvernov2beta1.PolicyException {
gvk, subresource := policyContext.ResourceKind()
resource := policyContext.NewResource()
if resource.Object == nil {
resource = policyContext.OldResource()
}
for _, polex := range polexs {
err := matched.CheckMatchesResources(
match := checkMatchesResources(
resource,
polex.Spec.Match,
policyContext.NamespaceLabels(),
@ -29,8 +30,7 @@ func MatchesException(
gvk,
subresource,
)
// if there's no error it means a match
if err == nil {
if match {
if polex.Spec.Conditions != nil {
passed, err := conditions.CheckAnyAllConditions(logger, policyContext.JSONContext(), *polex.Spec.Conditions)
if err != nil {
@ -45,3 +45,132 @@ func MatchesException(
}
return nil
}
func checkMatchesResources(
resource unstructured.Unstructured,
statement kyvernov2beta1.MatchResources,
namespaceLabels map[string]string,
admissionInfo kyvernov1beta1.RequestInfo,
gvk schema.GroupVersionKind,
subresource string,
) bool {
if len(statement.Any) > 0 {
for _, rmr := range statement.Any {
if checkResourceFilter(rmr, resource, namespaceLabels, admissionInfo, gvk, subresource) {
return true
}
}
return false
} else if len(statement.All) > 0 {
for _, rmr := range statement.All {
if !checkResourceFilter(rmr, resource, namespaceLabels, admissionInfo, gvk, subresource) {
return false
}
}
return true
}
return false
}
func checkResourceFilter(
statement kyvernov1.ResourceFilter,
resource unstructured.Unstructured,
namespaceLabels map[string]string,
admissionInfo kyvernov1beta1.RequestInfo,
gvk schema.GroupVersionKind,
subresource string,
) bool {
if statement.IsEmpty() {
return false
}
return checkResourceDescription(statement.ResourceDescription, resource, namespaceLabels, gvk, subresource) &&
checkUserInfo(statement.UserInfo, admissionInfo)
}
func checkResourceDescription(
conditionBlock kyvernov1.ResourceDescription,
resource unstructured.Unstructured,
namespaceLabels map[string]string,
gvk schema.GroupVersionKind,
subresource string,
) bool {
if len(conditionBlock.Kinds) > 0 {
// Matching on ephemeralcontainers even when they are not explicitly specified is only applicable to policies.
if !matched.CheckKind(conditionBlock.Kinds, gvk, subresource, false) {
return false
}
}
if conditionBlock.Name != "" || len(conditionBlock.Names) > 0 {
resourceName := resource.GetName()
if resourceName == "" {
resourceName = resource.GetGenerateName()
}
if conditionBlock.Name != "" {
if !matched.CheckName(conditionBlock.Name, resourceName) {
return false
}
}
if len(conditionBlock.Names) > 0 {
noneMatch := true
for i := range conditionBlock.Names {
if matched.CheckName(conditionBlock.Names[i], resourceName) {
noneMatch = false
break
}
}
if noneMatch {
return false
}
}
}
if len(conditionBlock.Namespaces) > 0 {
if !matched.CheckNameSpace(conditionBlock.Namespaces, resource) {
return false
}
}
if len(conditionBlock.Annotations) > 0 {
if !matched.CheckAnnotations(conditionBlock.Annotations, resource.GetAnnotations()) {
return false
}
}
if conditionBlock.Selector != nil {
hasPassed, err := matched.CheckSelector(conditionBlock.Selector, resource.GetLabels())
if err != nil {
return false
} else {
if !hasPassed {
return false
}
}
}
if conditionBlock.NamespaceSelector != nil && resource.GetKind() != "Namespace" && resource.GetKind() != "" {
hasPassed, err := matched.CheckSelector(conditionBlock.NamespaceSelector, namespaceLabels)
if err != nil {
return false
} else {
if !hasPassed {
return false
}
}
}
return true
}
func checkUserInfo(userInfo kyvernov1.UserInfo, admissionInfo kyvernov1beta1.RequestInfo) bool {
if len(userInfo.Roles) > 0 {
if !datautils.SliceContains(userInfo.Roles, admissionInfo.Roles...) {
return false
}
}
if len(userInfo.ClusterRoles) > 0 {
if !datautils.SliceContains(userInfo.ClusterRoles, admissionInfo.ClusterRoles...) {
return false
}
}
if len(userInfo.Subjects) > 0 {
if !matched.CheckSubjects(userInfo.Subjects, admissionInfo.AdmissionUserInfo) {
return false
}
}
return true
}

View file

@ -161,7 +161,7 @@ func checkResourceDescription(
}
}
if len(conditionBlock.Namespaces) > 0 {
if !checkNameSpace(conditionBlock.Namespaces, resource) {
if !CheckNameSpace(conditionBlock.Namespaces, resource) {
errs = append(errs, fmt.Errorf("namespace does not match"))
}
}
@ -193,7 +193,7 @@ func checkResourceDescription(
return errs
}
func checkNameSpace(namespaces []string, resource unstructured.Unstructured) bool {
func CheckNameSpace(namespaces []string, resource unstructured.Unstructured) bool {
resourceNameSpace := resource.GetNamespace()
if resource.GetKind() == "Namespace" {
resourceNameSpace = resource.GetName()