diff --git a/armometadata/k8sutils.go b/armometadata/k8sutils.go index 15b9476..21b4211 100644 --- a/armometadata/k8sutils.go +++ b/armometadata/k8sutils.go @@ -105,63 +105,87 @@ func LoadConfig(configPath string) (*ClusterConfig, error) { return config, err } +type Metadata struct { + Annotations map[string]string + Labels map[string]string + OwnerReferences map[string]string + CreationTimestamp string + ResourceVersion string + Kind string + ApiVersion string + PodSelectorMatchLabels map[string]string +} + // ExtractMetadataFromBytes extracts metadata from the JSON bytes of a Kubernetes object -func ExtractMetadataFromJsonBytes(input []byte) (error, map[string]string, map[string]string, map[string]string, string, string, string, string) { +func ExtractMetadataFromJsonBytes(input []byte) (Metadata, error) { // output values - annotations := map[string]string{} - labels := map[string]string{} - ownerReferences := map[string]string{} - creationTs := "" - resourceVersion := "" - kind := "" - apiVersion := "" + m := Metadata{ + Annotations: map[string]string{}, + Labels: map[string]string{}, + OwnerReferences: map[string]string{}, + PodSelectorMatchLabels: map[string]string{}, + } // ujson parsing - var parent string + var parent, subParent, subParent2 string err := ujson.Walk(input, func(level int, key, value []byte) bool { switch level { case 1: if bytes.EqualFold(key, []byte(`"kind"`)) { - kind = unquote(value) + m.Kind = unquote(value) } if bytes.EqualFold(key, []byte(`"apiVersion"`)) { - apiVersion = unquote(value) + m.ApiVersion = unquote(value) } - // skip everything except metadata - if !bytes.EqualFold(key, []byte(`"metadata"`)) { + // skip everything except metadata and spec + if !bytes.EqualFold(key, []byte(`"metadata"`)) && !bytes.EqualFold(key, []byte(`"spec"`)) { return false } + + parent = unquote(key) case 2: - // read creationTimestamp - if bytes.EqualFold(key, []byte(`"creationTimestamp"`)) { - creationTs = unquote(value) - } - // read resourceVersion - if bytes.EqualFold(key, []byte(`"resourceVersion"`)) { - resourceVersion = unquote(value) + if parent == "metadata" { + // read creationTimestamp + if bytes.EqualFold(key, []byte(`"creationTimestamp"`)) { + m.CreationTimestamp = unquote(value) + } + // read resourceVersion + if bytes.EqualFold(key, []byte(`"resourceVersion"`)) { + m.ResourceVersion = unquote(value) + } + } + // record parent for level 3 - parent = unquote(key) + subParent = unquote(key) + case 3: // read annotations - if parent == "annotations" { - annotations[unquote(key)] = unquote(value) + if subParent == "annotations" { + m.Annotations[unquote(key)] = unquote(value) } // read labels - if parent == "labels" { - labels[unquote(key)] = unquote(value) + if subParent == "labels" { + m.Labels[unquote(key)] = unquote(value) } + + subParent2 = unquote(key) + case 4: // read ownerReferences - if parent == "ownerReferences" { - ownerReferences[unquote(key)] = unquote(value) + if subParent == "ownerReferences" { + m.OwnerReferences[unquote(key)] = unquote(value) + } + + if subParent2 == "matchLabels" { + m.PodSelectorMatchLabels[unquote(key)] = unquote(value) } } return true }) - return err, annotations, labels, ownerReferences, creationTs, resourceVersion, kind, apiVersion + return m, err } func unquote(value []byte) string { diff --git a/armometadata/k8sutils_test.go b/armometadata/k8sutils_test.go index 0e4cab3..0b3c758 100644 --- a/armometadata/k8sutils_test.go +++ b/armometadata/k8sutils_test.go @@ -124,16 +124,41 @@ func BoolPtr(b bool) *bool { func TestExtractMetadataFromJsonBytes(t *testing.T) { tests := []struct { - name string - want error - annotations map[string]string - labels map[string]string - ownerReferences map[string]string - creationTs string - resourceVersion string - kind string - apiVersion string + name string + wantErr error + annotations map[string]string + labels map[string]string + ownerReferences map[string]string + creationTs string + resourceVersion string + kind string + apiVersion string + podSelectorMatchLabels map[string]string }{ + { + name: "networkpolicy_withoutmatching_labels", + annotations: map[string]string{}, + labels: map[string]string{}, + ownerReferences: map[string]string{}, + creationTs: "2023-11-16T10:12:35Z", + resourceVersion: "", + kind: "NetworkPolicy", + apiVersion: "networking.k8s.io/v1", + podSelectorMatchLabels: map[string]string{}, + }, + { + name: "networkpolicy_withmatching_labels", + annotations: map[string]string{}, + labels: map[string]string{}, + ownerReferences: map[string]string{}, + creationTs: "2023-11-16T10:12:35Z", + resourceVersion: "", + kind: "NetworkPolicy", + apiVersion: "networking.k8s.io/v1", + podSelectorMatchLabels: map[string]string{ + "role": "frontend", + }, + }, { name: "applicationactivity", annotations: map[string]string{ @@ -147,11 +172,12 @@ func TestExtractMetadataFromJsonBytes(t *testing.T) { "kubescape.io/workload-name": "storage", "kubescape.io/workload-namespace": "kubescape", }, - ownerReferences: map[string]string{}, - creationTs: "2023-11-16T10:15:05Z", - resourceVersion: "1", - kind: "ApplicationActivity", - apiVersion: "spdx.softwarecomposition.kubescape.io/v1beta1", + ownerReferences: map[string]string{}, + creationTs: "2023-11-16T10:15:05Z", + resourceVersion: "1", + kind: "ApplicationActivity", + apiVersion: "spdx.softwarecomposition.kubescape.io/v1beta1", + podSelectorMatchLabels: map[string]string{}, }, { name: "pod", @@ -178,10 +204,11 @@ func TestExtractMetadataFromJsonBytes(t *testing.T) { "name": "kubescape-549f95c69", "uid": "c0ff7d3b-4183-482c-81c5-998faf0b6150", }, - creationTs: "2023-11-16T10:12:35Z", - resourceVersion: "59348379", - kind: "Pod", - apiVersion: "v1", + creationTs: "2023-11-16T10:12:35Z", + resourceVersion: "59348379", + kind: "Pod", + apiVersion: "v1", + podSelectorMatchLabels: map[string]string{}, }, { name: "sbom", @@ -193,26 +220,28 @@ func TestExtractMetadataFromJsonBytes(t *testing.T) { "kubescape.io/image-id": "quay-io-kubescape-kubescape-sha256-608b85d3de51caad84a2bfe089ec", "kubescape.io/image-name": "quay-io-kubescape-kubescape", }, - ownerReferences: map[string]string{}, - creationTs: "2023-11-16T10:13:40Z", - resourceVersion: "1", - kind: "SBOMSPDXv2p3", - apiVersion: "spdx.softwarecomposition.kubescape.io/v1beta1", + ownerReferences: map[string]string{}, + creationTs: "2023-11-16T10:13:40Z", + resourceVersion: "1", + kind: "SBOMSPDXv2p3", + apiVersion: "spdx.softwarecomposition.kubescape.io/v1beta1", + podSelectorMatchLabels: map[string]string{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { input, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", tt.name)) assert.NoError(t, err) - got, annotations, labels, ownerReferences, creationTs, resourceVersion, kind, apiVersion := ExtractMetadataFromJsonBytes(input) - assert.Equal(t, tt.want, got) - assert.Equal(t, tt.annotations, annotations) - assert.Equal(t, tt.labels, labels) - assert.Equal(t, tt.ownerReferences, ownerReferences) - assert.Equal(t, tt.creationTs, creationTs) - assert.Equal(t, tt.resourceVersion, resourceVersion) - assert.Equal(t, tt.kind, kind) - assert.Equal(t, tt.apiVersion, apiVersion) + m, err := ExtractMetadataFromJsonBytes(input) + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.annotations, m.Annotations) + assert.Equal(t, tt.labels, m.Labels) + assert.Equal(t, tt.ownerReferences, m.OwnerReferences) + assert.Equal(t, tt.creationTs, m.CreationTimestamp) + assert.Equal(t, tt.resourceVersion, m.ResourceVersion) + assert.Equal(t, tt.kind, m.Kind) + assert.Equal(t, tt.apiVersion, m.ApiVersion) + assert.Equal(t, tt.podSelectorMatchLabels, m.PodSelectorMatchLabels) }) } } @@ -221,6 +250,6 @@ func BenchmarkExtractMetadataFromJsonBytes(b *testing.B) { input, err := os.ReadFile("testdata/applicationactivity.json") assert.NoError(b, err) for i := 0; i < b.N; i++ { - _, _, _, _, _, _, _, _ = ExtractMetadataFromJsonBytes(input) + _, _ = ExtractMetadataFromJsonBytes(input) } } diff --git a/armometadata/testdata/networkpolicy_withmatching_labels.json b/armometadata/testdata/networkpolicy_withmatching_labels.json new file mode 100644 index 0000000..caabc92 --- /dev/null +++ b/armometadata/testdata/networkpolicy_withmatching_labels.json @@ -0,0 +1,31 @@ +{ + "apiVersion": "networking.k8s.io/v1", + "kind": "NetworkPolicy", + "metadata": { + "creationTimestamp": "2023-11-16T10:12:35Z", + "name": "allow-frontend-backend", + "namespace": "default" + }, + "spec": { + "podSelector": { + "matchLabels": { + "role": "frontend" + } + }, + "policyTypes": ["Ingress"], + "ingress": [ + { + "from": [ + { + "podSelector": { + "matchLabels": { + "role": "backend" + } + } + } + ] + } + ] + } + } + \ No newline at end of file diff --git a/armometadata/testdata/networkpolicy_withoutmatching_labels.json b/armometadata/testdata/networkpolicy_withoutmatching_labels.json new file mode 100644 index 0000000..640dba3 --- /dev/null +++ b/armometadata/testdata/networkpolicy_withoutmatching_labels.json @@ -0,0 +1,23 @@ +{ + "apiVersion": "networking.k8s.io/v1", + "kind": "NetworkPolicy", + "metadata": { + "creationTimestamp": "2023-11-16T10:12:35Z", + "name": "allow-all-in-namespace", + "namespace": "default" + }, + "spec": { + "podSelector": {}, + "policyTypes": ["Ingress"], + "ingress": [ + { + "from": [ + { + "podSelector": {} + } + ] + } + ] + } + } + \ No newline at end of file