diff --git a/exceptions/comparator.go b/exceptions/comparator.go index da75dedf..c588301b 100644 --- a/exceptions/comparator.go +++ b/exceptions/comparator.go @@ -5,8 +5,9 @@ import ( "strings" "sync" - "github.com/kubescape/k8s-interface/k8sinterface" "github.com/kubescape/k8s-interface/workloadinterface" + "github.com/kubescape/opa-utils/objectsenvelopes" + "github.com/kubescape/opa-utils/objectsenvelopes/localworkload" "k8s.io/apimachinery/pkg/labels" ) @@ -46,9 +47,6 @@ func (c *comparator) compareResourceID(workload workloadinterface.IMetadata, res func (c *comparator) comparePath(workload workloadinterface.IMetadata, path string) bool { w := workload.GetObject() - if !k8sinterface.IsTypeWorkload(w) { - return false - } if val, ok := w["sourcePath"]; ok { if sourcePath, ok := val.(string); ok { @@ -61,9 +59,6 @@ func (c *comparator) comparePath(workload workloadinterface.IMetadata, path stri func (c *comparator) compareLabels(workload workloadinterface.IMetadata, attributes map[string]string) bool { w := workload.GetObject() - if !k8sinterface.IsTypeWorkload(w) { - return true - } workloadLabels := labels.Set(workloadinterface.NewWorkloadObj(w).GetLabels()) @@ -75,14 +70,8 @@ func (c *comparator) compareLabels(workload workloadinterface.IMetadata, attribu if !workloadLabels.Has(key) { return false } - found := false - for label, annotation := range workloadLabels { - if key == label && c.regexCompare(val, annotation) { - found = true - break - } - } - if !found { + value := workloadLabels.Get(key) + if !c.regexCompare(val, value) { return false } } @@ -91,11 +80,8 @@ func (c *comparator) compareLabels(workload workloadinterface.IMetadata, attribu } func (c *comparator) compareAnnotations(workload workloadinterface.IMetadata, attributes map[string]string) bool { - w := workload.GetObject() - if !k8sinterface.IsTypeWorkload(w) { - return true - } + w := workload.GetObject() workloadAnnotations := labels.Set(workloadinterface.NewWorkloadObj(w).GetAnnotations()) if len(workloadAnnotations) == 0 { return false @@ -105,14 +91,8 @@ func (c *comparator) compareAnnotations(workload workloadinterface.IMetadata, at if !workloadAnnotations.Has(key) { return false } - found := false - for label, annotation := range workloadAnnotations { - if key == label && c.regexCompare(val, annotation) { - found = true - break - } - } - if !found { + value := workloadAnnotations.Get(key) + if !c.regexCompare(val, value) { return false } } @@ -187,3 +167,26 @@ func (c *comparator) regexCompare(reg, name string) bool { return r.MatchString(name) } + +func isTypeWorkload(workload workloadinterface.IMetadata) bool { + switch objectsenvelopes.GetObjectType(workload.GetObject()) { + case workloadinterface.TypeBaseObject: + return true + case workloadinterface.TypeWorkloadObject: + return true + case workloadinterface.TypeListWorkloads: + return true + case localworkload.TypeLocalWorkload: + return true + default: + return false + } +} +func isTypeRegoResponseVector(workload workloadinterface.IMetadata) bool { + switch objectsenvelopes.GetObjectType(workload.GetObject()) { + case objectsenvelopes.TypeRegoResponseVectorObject: + return true + default: + return false + } +} diff --git a/exceptions/comparator_test.go b/exceptions/comparator_test.go new file mode 100644 index 00000000..7abd0369 --- /dev/null +++ b/exceptions/comparator_test.go @@ -0,0 +1,393 @@ +package exceptions + +import ( + "testing" + + "github.com/kubescape/k8s-interface/workloadinterface" + "github.com/kubescape/opa-utils/objectsenvelopes" + "github.com/kubescape/opa-utils/objectsenvelopes/localworkload" + + "github.com/stretchr/testify/assert" +) + +func TestComparator_compareNamespace(t *testing.T) { + c := &comparator{} // Assuming you have initialized the comparator instance + + tests := []struct { + name string + workload workloadinterface.IMetadata + namespace string + expected bool + }{ + { + name: "Workload kind is Namespace, regex match", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test-namespace", + }, + }, + ), + namespace: "test-namespace", + expected: true, + }, + { + name: "Workload kind is Namespace, regex does not match", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test-namespace", + }, + }, + ), + namespace: "different-namespace", + expected: false, + }, + { + name: "Workload kind is not Namespace, regex match", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + ), + namespace: "test-namespace", + expected: true, + }, + { + name: "Workload kind is not Namespace, regex does not match", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "different-namespace", + }, + }, + ), + namespace: "test-namespace", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, c.compareNamespace(tt.workload, tt.namespace)) + }) + } +} +func TestComparator_compareKind(t *testing.T) { + c := &comparator{} // Assuming you have initialized the comparator instance + + tests := []struct { + name string + workload workloadinterface.IMetadata + kind string + expected bool + }{ + { + name: "Kind matches", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + ), + kind: "Deployment", + expected: true, + }, + { + name: "Kind does not match", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + ), + kind: "Pod", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, c.compareKind(tt.workload, tt.kind)) + }) + } +} + +func TestComparator_compareName(t *testing.T) { + c := &comparator{} // Assuming you have initialized the comparator instance + + tests := []struct { + name string + workload workloadinterface.IMetadata + workloadName string + expected bool + }{ + { + name: "Name matches", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + ), + workloadName: "test", + expected: true, + }, + { + name: "Name does not match", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + ), + workloadName: "different", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, c.compareName(tt.workload, tt.workloadName)) + }) + } +} +func TestComparator_comparePath(t *testing.T) { + c := &comparator{} // Assuming you have initialized the comparator instance + + tests := []struct { + name string + workload workloadinterface.IMetadata + path string + expected bool + }{ + { + name: "Workload has sourcePath, regex match", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + "sourcePath": "/test/path", + }, + ), + path: "/test/path", + expected: true, + }, + { + name: "Workload has sourcePath, regex does not match", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + "sourcePath": "/test/path", + }, + ), + path: "/different/path", + expected: false, + }, + { + name: "Workload does not have sourcePath", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + ), + path: "/test/path", + expected: false, + }, + { + name: "Not a workload", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "kind": "Something", + "name": "test", + }, + ), + path: "/test/path", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, c.comparePath(tt.workload, tt.path)) + }) + } +} + +func TestIsTypeWorkload(t *testing.T) { + tests := []struct { + workload workloadinterface.IMetadata + name string + expected bool + }{ + { + name: "Workload type is WorkloadObject", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + ), + expected: true, + }, + { + name: "Workload type is ListWorkloads", + workload: workloadinterface.NewListWorkloadsObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "List", + "metadata": map[string]interface{}{ + "namespace": "test-namespace", + }, + "items": []interface{}{}, + }, + ), + expected: true, + }, + { + name: "Workload type is BaseObject", + workload: workloadinterface.NewBaseObject( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + }, + }, + ), + expected: true, + }, + { + name: "Workload type is LocalWorkload", + workload: localworkload.NewLocalWorkload( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + "sourcePath": "/test/path", + }, + ), + expected: true, + }, + { + name: "Workload type is not a recognized type", + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "objectType": "UnrecognizedType", + }, + ), + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, isTypeWorkload(tt.workload)) + }) + } +} + +func TestIsTypeRegoResponseVector(t *testing.T) { + tests := []struct { + workload workloadinterface.IMetadata + name string + expected bool + }{ + { + name: "Workload type is RegoResponseVectorObject", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "test", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + }, + }, + }, + }, + ), + expected: true, + }, + { + name: "Workload type is not RegoResponseVectorObject", + workload: workloadinterface.NewBaseObject( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + }, + }, + ), + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, isTypeRegoResponseVector(tt.workload)) + }) + } +} diff --git a/exceptions/designators_cache.go b/exceptions/designators_cache.go index 42a40f77..21fe0f01 100644 --- a/exceptions/designators_cache.go +++ b/exceptions/designators_cache.go @@ -1,9 +1,10 @@ package exceptions import ( - "github.com/armosec/armoapi-go/identifiers" "sync" + "github.com/armosec/armoapi-go/identifiers" + "github.com/kubescape/opa-utils/exceptions/internal/hashmap" ) @@ -13,8 +14,8 @@ type ( // We use a plain map with mutex instead of sync.Map, so we may preallocate // a few slots for designators. designatorCache struct { - mx sync.RWMutex innerMap map[portalDesignatorKey]identifiers.AttributesDesignators + mx sync.RWMutex } portalDesignatorKey struct { diff --git a/exceptions/exceptionprocessor.go b/exceptions/exceptionprocessor.go index 025ef594..81ef1a8a 100644 --- a/exceptions/exceptionprocessor.go +++ b/exceptions/exceptionprocessor.go @@ -1,9 +1,10 @@ package exceptions import ( - "github.com/armosec/armoapi-go/identifiers" "strings" + "github.com/armosec/armoapi-go/identifiers" + "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/opa-utils/objectsenvelopes" "github.com/kubescape/opa-utils/reporthandling" @@ -172,6 +173,18 @@ func (p *Processor) hasException(clusterName string, designator *identifiers.Por return false // cluster name does not match } + if isTypeRegoResponseVector(workload) { + if p.iterateRegoResponseVector(workload, attributes) { + return true + } + // otherwise, continue to check the base object + } + return p.metadataHasException(workload, attributes) + +} + +func (p *Processor) metadataHasException(workload workloadinterface.IMetadata, attributes identifiers.AttributesDesignators) bool { + if attributes.GetNamespace() != "" && !p.compareNamespace(workload, attributes.GetNamespace()) { return false // namespaces do not match } @@ -192,9 +205,20 @@ func (p *Processor) hasException(clusterName string, designator *identifiers.Por return false // paths do not match } - if len(attributes.GetLabels()) > 0 && !p.compareLabels(workload, attributes.GetLabels()) && !p.compareAnnotations(workload, attributes.GetLabels()) { - return false // labels nor annotations do not match + if isTypeWorkload(workload) && len(attributes.GetLabels()) > 0 { + if !p.compareLabels(workload, attributes.GetLabels()) && !p.compareAnnotations(workload, attributes.GetLabels()) { + return false // labels nor annotations do not match + } } + return true +} - return true // no mismatch found -> the workload has an exception +func (p *Processor) iterateRegoResponseVector(workload workloadinterface.IMetadata, attributes identifiers.AttributesDesignators) bool { + v := objectsenvelopes.NewRegoResponseVectorObject(workload.GetObject()) + for _, r := range v.GetRelatedObjects() { + if p.metadataHasException(r, attributes) { + return true + } + } + return false } diff --git a/exceptions/exceptionprocessor_test.go b/exceptions/exceptionprocessor_test.go index f43ea224..12590528 100644 --- a/exceptions/exceptionprocessor_test.go +++ b/exceptions/exceptionprocessor_test.go @@ -6,6 +6,7 @@ import ( "github.com/armosec/armoapi-go/identifiers" "github.com/kubescape/k8s-interface/workloadinterface" + "github.com/kubescape/opa-utils/objectsenvelopes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -193,9 +194,9 @@ func TestGetResourceExceptions(t *testing.T) { p := NewProcessor() testCases := []struct { - desc string - exceptionPolicy *armotypes.PostureExceptionPolicy workloadObj workloadinterface.IMetadata + exceptionPolicy *armotypes.PostureExceptionPolicy + desc string expectedExceptionsCount int }{ { @@ -274,14 +275,942 @@ func TestRegexCompare(t *testing.T) { assert.False(t, c.compareCluster("bla", "bez-minikube-25-10")) } -// func TestGetException(t *testing.T) { -// exceptionPolicies := []armotypes.PostureExceptionPolicy{*PostureExceptionPolicyAlertOnlyMock()} -// res1 := ListRuleExceptions(exceptionPolicies, "MITRE", "", "") -// if len(res1) != 1 { -// t.Errorf("expecting 1 exception") -// } -// res2 := ListRuleExceptions(exceptionPolicies, "", "hostPath mount", "") -// if len(res2) != 0 { -// t.Errorf("expecting 0 exception") -// } -// } +func TestHasException(t *testing.T) { + processor := NewProcessor() + + tests := []struct { + workload workloadinterface.IMetadata + designator *identifiers.PortalDesignator + name string + clusterName string + expected bool + }{ + { + name: "Test case: Missing attributes", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{}, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{}, + ), + expected: false, + }, + { + name: "Test case: Matching cluster name", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "cluster": "cluster1", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{}, + ), + expected: true, + }, + { + name: "Test case: Non-matching cluster name", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "cluster": "cluster2", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{}, + ), + expected: false, + }, + { + name: "Test case: Matching cluster name with regex", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "cluster": "cluster.*", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{}, + ), + expected: true, + }, + { + name: "Test case: Kind matches", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "kind": "Deployment", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "kind": "Deployment", + }, + ), + expected: true, + }, + { + name: "Test case: Name matches", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "name": "test-workload", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test-workload", + }, + }, + ), + expected: true, + }, + { + name: "Test case: Namespace matches", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "namespace": "test-namespace", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "namespace": "test-namespace", + }, + }, + ), + expected: true, + }, + { + name: "Test case: Kind matches with regex", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "kind": "Deploy.*", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "kind": "Deployment", + }, + ), + expected: true, + }, + { + name: "Test case 3: Name matches with regex", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "name": "test-.*", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test-workload", + }, + }, + ), + expected: true, + }, + { + name: "Test case 4: Namespace matches with regex", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "namespace": "test-.*", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "namespace": "test-namespace", + }, + }, + ), + expected: true, + }, + { + name: "Test case 5: Kind does not match", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "kind": "Service", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "kind": "Deployment", + }, + ), + expected: false, + }, + { + name: "Test case 6: Name does not match", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "name": "different-workload", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test-workload", + }, + }, + ), + expected: false, + }, + { + name: "Test case: Namespace does not match", + clusterName: "cluster1", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "namespace": "different-namespace", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "namespace": "test-namespace", + }, + }, + ), + expected: false, + }, + { + name: "Test case: Path matches", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "path": "/path/to/source", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "sourcePath": "/path/to/source", + }, + ), + expected: true, + }, + { + name: "Test case: Path matches with regex", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "path": "/path/.*/source", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "sourcePath": "/path/to/source", + }, + ), + expected: true, + }, + { + name: "Test case: Path does not match", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "path": "/path/to/source", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "sourcePath": "/path/to/dest", + }, + ), + expected: false, + }, + { + name: "Test case: Labels match", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": "val1", + "key2": "val2", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "labels": map[string]interface{}{ + "key1": "val1", + "key2": "val2", + }, + }, + }, + ), + expected: true, + }, + { + name: "Test case: Labels do not match", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": "val1", + "key2": "val2", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "labels": map[string]interface{}{ + "key1": "val1", + "key2": "val3", + }, + }, + }, + ), + expected: false, + }, + { + name: "Test case: Labels missing", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": "val1", + "key2": "val2", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "labels": map[string]interface{}{ + "key1": "val1", + }, + }, + }, + ), + expected: false, + }, + { + name: "Test case: Labels match regex", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": ".*", + "key2": ".*", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "labels": map[string]interface{}{ + "key1": "val1", + "key2": "val2", + }, + }, + }, + ), + expected: true, + }, + { + name: "Test case: Labels dont match regex", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": "val.*", + "key2": "val.*", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "labels": map[string]interface{}{ + "key1": "val1", + "key2": "bla2", + }, + }, + }, + ), + expected: false, + }, + { + name: "Test case: Annotations match", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": "val1", + "key2": "val2", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "annotations": map[string]interface{}{ + "key1": "val1", + "key2": "val2", + }, + }, + }, + ), + expected: true, + }, + { + name: "Test case: Annotations do not match", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": "val1", + "key2": "val2", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "annotations": map[string]interface{}{ + "key1": "val1", + "key2": "val3", + }, + }, + }, + ), + expected: false, + }, + { + name: "Test case: annotations missing", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": "val1", + "key2": "val2", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "annotations": map[string]interface{}{ + "key1": "val1", + }, + }, + }, + ), + expected: false, + }, + { + name: "Test case: annotations match regex", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": ".*", + "key2": ".*", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "annotations": map[string]interface{}{ + "key1": "val1", + "key2": "val2", + }, + }, + }, + ), + expected: true, + }, + { + name: "Test case: annotations dont match regex", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": "val.*", + "key2": "val.*", + }, + }, + workload: workloadinterface.NewWorkloadObj( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{ + "name": "test", + "annotations": map[string]interface{}{ + "key1": "val1", + "key2": "bla2", + }, + }, + }, + ), + expected: false, + }, + { + name: "Labels and annotations match in related object", + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "key1": "val.*", + "key2": "val.*", + }, + }, + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "test", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + "labels": map[string]interface{}{ + "key1": "val1", + "key2": "val2", + }, + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + "annotations": map[string]interface{}{ + "key1": "val1", + "key2": "val2", + }, + }, + }, + }, + }, + ), + expected: true, + }, + { + name: "Test case: Name matches in base object", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "base", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "name": "base", + }, + }, + expected: true, + }, + { + name: "Test case: Kind matches in base object", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "base", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "kind": "RegoResponseVector", + }, + }, + expected: true, + }, + { + name: "Test case: Name mismatches in base object", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "ServiceAccount", + "name": "base", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "name": "base-2", + }, + }, + expected: false, + }, + { + name: "Test case: Kind mismatches in base object", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "ServiceAccount", + "name": "base", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "kind": "RegoResponseVector", + }, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, processor.hasException(tt.clusterName, tt.designator, tt.workload)) + }) + } +} + +func TestProcessor_iterateRegoResponseVector(t *testing.T) { + p := NewProcessor() + + tests := []struct { + workload workloadinterface.IMetadata + designator *identifiers.PortalDesignator + name string + expected bool + }{ + { + name: "Labels match in one related object", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "test", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + "labels": map[string]interface{}{ + "app": "test-app", + }, + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "app": "test-app", + }, + }, + expected: true, + }, + { + name: "Labels match in one related object and mismatch in another related object", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "test", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + "labels": map[string]interface{}{ + "app": "different-app", + }, + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + "labels": map[string]interface{}{ + "app": "test-app", + }, + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "app": "test-app", + }, + }, + expected: true, + }, + { + name: "Annotations match in one related object", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "test", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + "annotations": map[string]interface{}{ + "app": "test-app", + }, + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "app": "test-app", + }, + }, + expected: true, + }, + { + name: "Annotations match in one related object and mismatch in another related object", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "test", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + "annotations": map[string]interface{}{ + "app": "different-app", + }, + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + "annotations": map[string]interface{}{ + "app": "test-app", + }, + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "app": "test-app", + }, + }, + expected: true, + }, + { + name: "Labels and Annotations do not match", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "test", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + "labels": map[string]interface{}{ + "app": "test-app", + }, + "annotations": map[string]interface{}{ + "app": "test-app", + }, + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + "labels": map[string]interface{}{ + "app": "test-app", + }, + "annotations": map[string]interface{}{ + "app": "test-app", + }, + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "app": "different-app", + }, + }, + expected: false, + }, + { + name: "Labels and Annotations are missing in related objects", + workload: objectsenvelopes.NewRegoResponseVectorObject( + map[string]interface{}{ + "kind": "RegoResponseVector", + "name": "test", + "relatedObjects": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "test-namespace", + }, + }, + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-2", + "namespace": "test-namespace", + }, + }, + }, + }, + ), + designator: &identifiers.PortalDesignator{ + DesignatorType: identifiers.DesignatorAttributes, + Attributes: map[string]string{ + "app": "test-app", + }, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, p.iterateRegoResponseVector(tt.workload, tt.designator.DigestPortalDesignator())) + }) + } +}