From deaa8cde32ad65e57edc1ad5186d41531de5851b Mon Sep 17 00:00:00 2001 From: Becky Huang Date: Fri, 19 Feb 2021 05:56:42 -0800 Subject: [PATCH] Allow excluding namespaces via labels #1078 --- apis/config/v1alpha1/config_types.go | 1 + apis/config/v1alpha1/zz_generated.deepcopy.go | 12 ++++ pkg/controller/config/process/excluder.go | 71 +++++++++++++++---- pkg/controller/sync/sync_controller.go | 23 ++++-- pkg/util/obj_info.go | 8 +++ 5 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 pkg/util/obj_info.go diff --git a/apis/config/v1alpha1/config_types.go b/apis/config/v1alpha1/config_types.go index b6b7e2be436..6f1bde4acb3 100644 --- a/apis/config/v1alpha1/config_types.go +++ b/apis/config/v1alpha1/config_types.go @@ -63,6 +63,7 @@ type SyncOnlyEntry struct { type MatchEntry struct { ExcludedNamespaces []string `json:"excludedNamespaces,omitempty"` + NamespaceSelectors []*metav1.LabelSelector `json:"namespaceSelectors,omitempty"` Processes []string `json:"processes,omitempty"` } diff --git a/apis/config/v1alpha1/zz_generated.deepcopy.go b/apis/config/v1alpha1/zz_generated.deepcopy.go index d839cbb1b03..3fd408e0b79 100644 --- a/apis/config/v1alpha1/zz_generated.deepcopy.go +++ b/apis/config/v1alpha1/zz_generated.deepcopy.go @@ -20,6 +20,7 @@ limitations under the License. package v1alpha1 import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -145,6 +146,17 @@ func (in *MatchEntry) DeepCopyInto(out *MatchEntry) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.NamespaceSelectors != nil { + in, out := &in.NamespaceSelectors, &out.NamespaceSelectors + *out = make([]*v1.LabelSelector, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + } if in.Processes != nil { in, out := &in.Processes, &out.Processes *out = make([]string, len(*in)) diff --git a/pkg/controller/config/process/excluder.go b/pkg/controller/config/process/excluder.go index 1eca727bc51..e1033c9ee22 100644 --- a/pkg/controller/config/process/excluder.go +++ b/pkg/controller/config/process/excluder.go @@ -1,7 +1,12 @@ package process import ( + "github.com/open-policy-agent/gatekeeper/pkg/util" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "reflect" + logf "sigs.k8s.io/controller-runtime/pkg/log" "sync" configv1alpha1 "github.com/open-policy-agent/gatekeeper/apis/config/v1alpha1" @@ -20,9 +25,12 @@ const ( Star = Process("*") ) +var log = logf.Log.WithName("controller").WithValues("kind", "Config") + type Excluder struct { mux sync.RWMutex excludedNamespaces map[Process]map[string]bool + namespaceSelectors map[Process][]labels.Selector } var allProcesses = []Process{ @@ -33,6 +41,7 @@ var allProcesses = []Process{ var processExcluder = &Excluder{ excludedNamespaces: make(map[Process]map[string]bool), + namespaceSelectors: make(map[Process][]labels.Selector), } func Get() *Excluder { @@ -42,6 +51,7 @@ func Get() *Excluder { func New() *Excluder { return &Excluder{ excludedNamespaces: make(map[Process]map[string]bool), + namespaceSelectors: make(map[Process][]labels.Selector), } } @@ -50,21 +60,28 @@ func (s *Excluder) Add(entry []configv1alpha1.MatchEntry) { defer s.mux.Unlock() for _, matchEntry := range entry { - for _, ns := range matchEntry.ExcludedNamespaces { - for _, op := range matchEntry.Processes { + var processes []Process + for _, op := range matchEntry.Processes { + if Process(op) == Star { + processes = allProcesses + break + } + processes = append(processes, Process(op)) + } + for _, p := range processes { + for _, ns := range matchEntry.ExcludedNamespaces { // adding excluded namespace to all processes for "*" - if Process(op) == Star { - for _, o := range allProcesses { - if s.excludedNamespaces[o] == nil { - s.excludedNamespaces[o] = make(map[string]bool) - } - s.excludedNamespaces[o][ns] = true - } + if s.excludedNamespaces[p] == nil { + s.excludedNamespaces[p] = make(map[string]bool) + } + s.excludedNamespaces[p][ns] = true + } + for _, ns := range matchEntry.NamespaceSelectors { + selector, e := metav1.LabelSelectorAsSelector(ns) + if e == nil { + s.namespaceSelectors[p] = append(s.namespaceSelectors[p], selector) } else { - if s.excludedNamespaces[Process(op)] == nil { - s.excludedNamespaces[Process(op)] = make(map[string]bool) - } - s.excludedNamespaces[Process(op)][ns] = true + log.Error(e, "illegal namespaceSelectors format") } } } @@ -75,12 +92,13 @@ func (s *Excluder) Replace(new *Excluder) { s.mux.Lock() defer s.mux.Unlock() s.excludedNamespaces = new.excludedNamespaces + s.namespaceSelectors = new.namespaceSelectors } func (s *Excluder) Equals(new *Excluder) bool { s.mux.RLock() defer s.mux.RUnlock() - return reflect.DeepEqual(s.excludedNamespaces, new.excludedNamespaces) + return reflect.DeepEqual(s.excludedNamespaces, new.excludedNamespaces) && reflect.DeepEqual(s.namespaceSelectors, new.namespaceSelectors) } func (s *Excluder) IsNamespaceExcluded(process Process, obj runtime.Object) (bool, error) { @@ -92,9 +110,32 @@ func (s *Excluder) IsNamespaceExcluded(process Process, obj runtime.Object) (boo return false, errors.Wrapf(err, "Failed to get accessor for %s - %s", obj.GetObjectKind().GroupVersionKind().Group, obj.GetObjectKind().GroupVersionKind().Kind) } - if obj.GetObjectKind().GroupVersionKind().Kind == "Namespace" && obj.GetObjectKind().GroupVersionKind().Group == "" { + if util.IsNamespace(obj) { return s.excludedNamespaces[process][meta.GetName()], nil } return s.excludedNamespaces[process][meta.GetNamespace()], nil } + +func (s *Excluder) IsNamespaceSelectorExcluded(process Process, obj runtime.Object, ns *corev1.Namespace) (bool, error) { + s.mux.RLock() + defer s.mux.RUnlock() + + meta, err := meta.Accessor(obj) + if err != nil { + return false, errors.Wrapf(err, "Failed to get accessor for %s - %s", obj.GetObjectKind().GroupVersionKind().Group, obj.GetObjectKind().GroupVersionKind().Kind) + } + for _, selector := range s.namespaceSelectors[process] { + switch { + case util.IsNamespace(obj): // if the object is a namespace, namespace selector matches against the object + if selector.Matches(labels.Set(meta.GetLabels())) { + return true, nil + } + case meta.GetNamespace() == "": + // cluster scoped + case selector.Matches(labels.Set(ns.Labels)): + return true, nil + } + } + return false, nil +} diff --git a/pkg/controller/sync/sync_controller.go b/pkg/controller/sync/sync_controller.go index 56b70c1ae6a..ab3253ec21d 100644 --- a/pkg/controller/sync/sync_controller.go +++ b/pkg/controller/sync/sync_controller.go @@ -17,6 +17,8 @@ package sync import ( "context" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "strings" "sync" "time" @@ -187,7 +189,8 @@ func (r *ReconcileSync) Reconcile(ctx context.Context, request reconcile.Request } // namespace is excluded from sync - isExcludedNamespace, err := r.skipExcludedNamespace(instance) + isExcludedNamespace, err := r.skipExcludedNamespace(ctx, instance) + // TODO return reconcile.Result{} if err != nil { log.Error(err, "error while excluding namespaces") } @@ -245,13 +248,25 @@ func (r *ReconcileSync) Reconcile(ctx context.Context, request reconcile.Request return reconcile.Result{}, nil } -func (r *ReconcileSync) skipExcludedNamespace(obj *unstructured.Unstructured) (bool, error) { +func (r *ReconcileSync) skipExcludedNamespace(ctx context.Context, obj *unstructured.Unstructured) (bool, error) { isNamespaceExcluded, err := r.processExcluder.IsNamespaceExcluded(process.Sync, obj) if err != nil { return false, err } - - return isNamespaceExcluded, err + if isNamespaceExcluded { + return true, nil + } + ns := &corev1.Namespace{} + if !util.IsNamespace(obj) { + if err = r.reader.Get(ctx, types.NamespacedName{Name: obj.GetNamespace()}, ns); err!= nil { + return false, err + } + } + isNamespaceExcluded, err = r.processExcluder.IsNamespaceSelectorExcluded(process.Sync, obj, ns) + if err != nil { + return false, err + } + return isNamespaceExcluded, nil } func NewMetricsCache() *MetricsCache { diff --git a/pkg/util/obj_info.go b/pkg/util/obj_info.go new file mode 100644 index 00000000000..77143837726 --- /dev/null +++ b/pkg/util/obj_info.go @@ -0,0 +1,8 @@ +package util + +import "k8s.io/apimachinery/pkg/runtime" + +func IsNamespace(obj runtime.Object) bool { + return obj.GetObjectKind().GroupVersionKind().Kind == "Namespace" && + obj.GetObjectKind().GroupVersionKind().Group == "" +} \ No newline at end of file