From 143ea93b4cdb8e21c376e594eac360ee3a8c2042 Mon Sep 17 00:00:00 2001 From: alex <8968914+acpana@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:09:53 -0700 Subject: [PATCH] fix: ns exclusion audit from cache (#3129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alex Pana <8968914+acpana@users.noreply.github.com> Signed-off-by: alex <8968914+acpana@users.noreply.github.com> Signed-off-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> --- pkg/audit/manager.go | 11 +++++ pkg/audit/manager_test.go | 98 +++++++++++++++++++++++++++++++++++++++ pkg/fakes/fixtures.go | 63 +++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 pkg/fakes/fixtures.go diff --git a/pkg/audit/manager.go b/pkg/audit/manager.go index 4cfa2d99f40..fa7813de209 100644 --- a/pkg/audit/manager.go +++ b/pkg/audit/manager.go @@ -508,6 +508,17 @@ func (am *Manager) auditFromCache(ctx context.Context) ([]Result, []error) { ns = nil } + excluded, err := am.skipExcludedNamespace(&obj) + if err != nil { + am.log.Error(err, "Unable to exclude object namespace for audit from cache %v %s/%s", obj.GroupVersionKind().String(), obj.GetNamespace(), obj.GetName()) + continue + } + + if excluded { + am.log.V(logging.DebugLevel).Info("excluding object from audit from cache %v %s/%s", obj.GroupVersionKind().String(), obj.GetNamespace(), obj.GetName()) + continue + } + au := &target.AugmentedUnstructured{ Object: obj, Namespace: ns, diff --git a/pkg/audit/manager_test.go b/pkg/audit/manager_test.go index 8f4a2e8e8e8..6ab50afc5b3 100644 --- a/pkg/audit/manager_test.go +++ b/pkg/audit/manager_test.go @@ -1,17 +1,115 @@ package audit import ( + "context" "os" "reflect" "testing" + constraintclient "github.com/open-policy-agent/frameworks/constraint/pkg/client" + "github.com/open-policy-agent/frameworks/constraint/pkg/client/drivers/rego" + configv1alpha1 "github.com/open-policy-agent/gatekeeper/v3/apis/config/v1alpha1" + "github.com/open-policy-agent/gatekeeper/v3/pkg/controller/config/process" + "github.com/open-policy-agent/gatekeeper/v3/pkg/fakes" + "github.com/open-policy-agent/gatekeeper/v3/pkg/target" + "github.com/open-policy-agent/gatekeeper/v3/pkg/wildcard" "github.com/pkg/errors" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" ) +func Test_auditFromCache(t *testing.T) { + podToReview := fakes.Pod(fakes.WithNamespace("test-namespace-1")) + podGVK := podToReview.GroupVersionKind() + testAuditCache := fakeCacheListerFor([]schema.GroupVersionKind{podGVK}, []client.Object{podToReview}) + + driver, err := rego.New() + require.NoError(t, err) + client, err := constraintclient.NewClient(constraintclient.Targets(&target.K8sValidationTarget{}), constraintclient.Driver(driver)) + require.NoError(t, err) + + _, err = client.AddTemplate(context.Background(), fakes.DenyAllRegoTemplate()) + require.NoError(t, err, "adding denyall constraint template") + _, err = client.AddConstraint(context.Background(), fakes.DenyAllConstraint()) + require.NoError(t, err, "adding denyall constraint") + + tests := []struct { + name string + processExcluder *process.Excluder + wantViolation bool + }{ + { + name: "obj excluded from audit", + processExcluder: processExcluderFor([]string{"test-namespace-1"}), + }, + { + name: "obj not excluded from audit", + processExcluder: processExcluderFor([]string{}), + wantViolation: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + am := &Manager{ + processExcluder: tc.processExcluder, + auditCache: testAuditCache, + opa: client, + } + + results, errs := am.auditFromCache(context.Background()) + require.Len(t, errs, 0) + + if tc.wantViolation { + require.Len(t, results, 1) + } else { + require.Len(t, results, 0) + } + }) + } +} + +func fakeCacheListerFor(gvks []schema.GroupVersionKind, objsToList []client.Object) *CacheLister { + k8sclient := fake.NewClientBuilder().WithObjects(objsToList...).Build() + fakeLister := fakeWatchIterator{gvksToList: gvks} + + return NewAuditCacheLister(k8sclient, &fakeLister) +} + +type fakeWatchIterator struct { + gvksToList []schema.GroupVersionKind +} + +func (f *fakeWatchIterator) DoForEach(listFunc func(gvk schema.GroupVersionKind) error) error { + for _, gvk := range f.gvksToList { + if err := listFunc(gvk); err != nil { + return err + } + } + + return nil +} + +func processExcluderFor(ns []string) *process.Excluder { + processExcluder := process.New() + for _, n := range ns { + processExcluder.Add([]configv1alpha1.MatchEntry{ + { + ExcludedNamespaces: []wildcard.Wildcard{wildcard.Wildcard(n)}, + Processes: []string{"audit"}, + }, + }) + } + + return processExcluder +} + func Test_newNSCache(t *testing.T) { tests := []struct { name string diff --git a/pkg/fakes/fixtures.go b/pkg/fakes/fixtures.go new file mode 100644 index 00000000000..29f5cff9ed1 --- /dev/null +++ b/pkg/fakes/fixtures.go @@ -0,0 +1,63 @@ +package fakes + +import ( + "github.com/open-policy-agent/frameworks/constraint/pkg/apis/constraints" + templatesv1beta1 "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1beta1" + "github.com/open-policy-agent/frameworks/constraint/pkg/core/templates" + "github.com/open-policy-agent/gatekeeper/v3/pkg/target" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func DenyAllRegoTemplate() *templates.ConstraintTemplate { + return &templates.ConstraintTemplate{ + TypeMeta: metav1.TypeMeta{ + APIVersion: templatesv1beta1.SchemeGroupVersion.String(), + Kind: "ConstraintTemplate", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "denyall", + }, + Spec: templates.ConstraintTemplateSpec{ + CRD: templates.CRD{ + Spec: templates.CRDSpec{ + Names: templates.Names{ + Kind: "denyall", + }, + }, + }, + Targets: []templates.Target{{ + Target: target.Name, + Code: []templates.Code{{ + Engine: "Rego", + Source: &templates.Anything{ + Value: map[string]interface{}{"rego": ` +package goodrego + +violation[{"msg": msg}] { + msg := "denyall" +}`}, + }, + }}, + }}, + }, + } +} + +func DenyAllConstraint() *unstructured.Unstructured { + return ConstraintFor("denyall") +} + +func ConstraintFor(kind string) *unstructured.Unstructured { + u := &unstructured.Unstructured{} + + u.SetGroupVersionKind(schema.GroupVersionKind{ + Group: constraints.Group, + Version: "v1beta1", + Kind: kind, + }) + u.SetName("constraint") + + return u +}