Skip to content

Commit

Permalink
add matchLabel parsing for other netpol
Browse files Browse the repository at this point in the history
Signed-off-by: Matthias Bertschy <[email protected]>
  • Loading branch information
matthyx committed Jun 25, 2024
1 parent 3997fa5 commit 77f8980
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ jobs:
security-events: write
uses: kubescape/workflows/.github/workflows/go-basic-tests.yaml@main
with:
GO_VERSION: 1.19
GO_VERSION: 1.22
secrets: inherit
5 changes: 2 additions & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Release-Tag
on:
push:
branches: [ master, main ]
branches: [ master, main ]
paths-ignore:
- '**.md' ### Ignore running when .md files change
- '**.yaml' ### Ignore running when .yaml files change
Expand All @@ -17,7 +17,7 @@ jobs:
security-events: write
uses: kubescape/workflows/.github/workflows/go-basic-tests.yaml@main
with:
GO_VERSION: 1.19
GO_VERSION: 1.22
secrets: inherit
release:
needs: test
Expand All @@ -29,4 +29,3 @@ jobs:
- uses: rickstaa/action-create-tag@v1
with:
tag: "v0.0.${{ github.run_number }}"

103 changes: 48 additions & 55 deletions armometadata/k8sutils.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package armometadata

import (
"bytes"
"encoding/json"
"fmt"
"hash/fnv"
"path"
"slices"
"strings"

"github.com/armosec/utils-k8s-go/wlid"
Expand Down Expand Up @@ -126,67 +126,60 @@ func ExtractMetadataFromJsonBytes(input []byte) (Metadata, error) {
PodSelectorMatchLabels: map[string]string{},
}
// ujson parsing
var parent, subParent string
jsonPathElements := make([]string, 0)
err := ujson.Walk(input, func(level int, key, value []byte) bool {
switch level {
case 1:
if bytes.EqualFold(key, []byte(`"kind"`)) {
m.Kind = unquote(value)
}

if bytes.EqualFold(key, []byte(`"apiVersion"`)) {
m.ApiVersion = unquote(value)
}

// skip everything except metadata and spec
if !bytes.EqualFold(key, []byte(`"metadata"`)) && !bytes.EqualFold(key, []byte(`"spec"`)) {
return false
}

parent = unquote(key)
case 2:
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
subParent = unquote(key)

case 3:
// read annotations
if subParent == "annotations" {
m.Annotations[unquote(key)] = unquote(value)
}
// read labels
if subParent == "labels" {
m.Labels[unquote(key)] = unquote(value)
}

case 4:
// read ownerReferences
if subParent == "ownerReferences" {
m.OwnerReferences[unquote(key)] = unquote(value)
}

if subParent == "podSelector" {
m.PodSelectorMatchLabels[unquote(key)] = unquote(value)
}

if level > 0 {
jsonPathElements = slices.Replace(jsonPathElements, level-1, len(jsonPathElements), unquote(key))
}
jsonPath := strings.Join(jsonPathElements, ".")
switch {
case jsonPath == "kind":
m.Kind = unquote(value)
case jsonPath == "apiVersion":
m.ApiVersion = unquote(value)
case jsonPath == "metadata.creationTimestamp":
m.CreationTimestamp = unquote(value)
case jsonPath == "metadata.resourceVersion":
m.ResourceVersion = unquote(value)
case strings.HasPrefix(jsonPath, "metadata.annotations."):
m.Annotations[unquote(key)] = unquote(value)
case strings.HasPrefix(jsonPath, "metadata.labels."):
m.Labels[unquote(key)] = unquote(value)
case strings.HasPrefix(jsonPath, "metadata.ownerReferences.."):
m.OwnerReferences[unquote(key)] = unquote(value)
case m.ApiVersion == "cilium.io/v2" && strings.HasPrefix(jsonPath, "spec.endpointSelector.matchLabels."):
m.PodSelectorMatchLabels[unquote(key)] = unquote(value)
case m.ApiVersion == "networking.k8s.io/v1" && strings.HasPrefix(jsonPath, "spec.podSelector.matchLabels."):
m.PodSelectorMatchLabels[unquote(key)] = unquote(value)
case m.ApiVersion == "security.istio.io/v1" && strings.HasPrefix(jsonPath, "spec.selector.matchLabels."):
m.PodSelectorMatchLabels[unquote(key)] = unquote(value)
case m.ApiVersion == "projectcalico.org/v3" && jsonPath == "spec.selector":
m.PodSelectorMatchLabels = parseCalicoSelector(value)
}

return true
})
return m, err
}

func parseCalicoSelector(value []byte) map[string]string {
selector := map[string]string{}
for _, rule := range strings.Split(unquote(value), "&&") {
s := strings.Split(rule, "==")
if len(s) != 2 {
continue
}
k := strings.TrimSpace(s[0])
v := strings.TrimSpace(s[1])
// strconv.Unquote does not handle single quotes
if (strings.HasPrefix(v, "'") && strings.HasSuffix(v, "'")) ||
(strings.HasPrefix(v, "\"") && strings.HasSuffix(v, "\"")) {
v = v[1 : len(v)-1]
}
selector[k] = v
}
return selector
}

func unquote(value []byte) string {
buf, err := ujson.Unquote(value)
if err != nil {
Expand Down
61 changes: 61 additions & 0 deletions armometadata/k8sutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,33 @@ func TestExtractMetadataFromJsonBytes(t *testing.T) {
apiVersion: "spdx.softwarecomposition.kubescape.io/v1beta1",
podSelectorMatchLabels: map[string]string{},
},
{
name: "caliconetworkpolicy",
annotations: map[string]string{},
labels: map[string]string{},
ownerReferences: map[string]string{},
kind: "NetworkPolicy",
apiVersion: "projectcalico.org/v3",
podSelectorMatchLabels: map[string]string{"role": "database"},
},
{
name: "ciliumnetworkpolicy",
annotations: map[string]string{},
labels: map[string]string{},
ownerReferences: map[string]string{},
kind: "CiliumNetworkPolicy",
apiVersion: "cilium.io/v2",
podSelectorMatchLabels: map[string]string{"app": "frontend"},
},
{
name: "istionetworkpolicy",
annotations: map[string]string{},
labels: map[string]string{},
ownerReferences: map[string]string{},
kind: "AuthorizationPolicy",
apiVersion: "security.istio.io/v1",
podSelectorMatchLabels: map[string]string{"app": "myapi"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -254,3 +281,37 @@ func BenchmarkExtractMetadataFromJsonBytes(b *testing.B) {
_, _ = ExtractMetadataFromJsonBytes(input)
}
}

func Test_parseCalicoSelector(t *testing.T) {
tests := []struct {
name string
value []byte
want map[string]string
}{
{
name: "empty",
value: []byte(""),
want: map[string]string{},
},
{
name: "single",
value: []byte(`"role == 'database'"`),
want: map[string]string{"role": "database"},
},
{
name: "multiple",
value: []byte(`"role == 'database' && tier == 'frontend'"`),
want: map[string]string{"role": "database", "tier": "frontend"},
},
{
name: "real",
value: []byte(`"app.kubernetes.io/instance == 'kubescape' && app.kubernetes.io/name == 'operator' && tier == 'ks-control-plane'"`),
want: map[string]string{"app.kubernetes.io/instance": "kubescape", "app.kubernetes.io/name": "operator", "tier": "ks-control-plane"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, parseCalicoSelector(tt.value), "parseCalicoSelector(%v)", tt.value)
})
}
}
31 changes: 31 additions & 0 deletions armometadata/testdata/caliconetworkpolicy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"apiVersion": "projectcalico.org/v3",
"kind": "NetworkPolicy",
"metadata": {
"name": "allow-tcp-6379",
"namespace": "production"
},
"spec": {
"selector": "role == 'database'",
"types": [
"Ingress",
"Egress"
],
"ingress": [
{
"action": "Log",
"protocol": "TCP",
"source": {
"selector": "role == 'frontend'"
}
},
{
"action": "Deny",
"protocol": "TCP",
"source": {
"selector": "role == 'frontend'"
}
}
]
}
}
61 changes: 61 additions & 0 deletions armometadata/testdata/ciliumnetworkpolicy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"apiVersion": "cilium.io/v2",
"kind": "CiliumNetworkPolicy",
"metadata": {
"name": "untitled-policy"
},
"spec": {
"endpointSelector": {
"matchLabels": {
"app": "frontend"
}
},
"egress": [
{
"toEndpoints": [
{
"matchLabels": {
"io.kubernetes.pod.namespace": "kube-system",
"k8s-app": "kube-dns"
}
}
],
"toPorts": [
{
"ports": [
{
"port": "53",
"protocol": "UDP"
}
],
"rules": {
"dns": [
{
"matchPattern": "*"
}
]
}
}
]
},
{
"toEndpoints": [
{
"matchLabels": {
"app": "backend"
}
}
],
"toPorts": [
{
"ports": [
{
"port": "443"
}
]
}
]
}
]
}
}
32 changes: 32 additions & 0 deletions armometadata/testdata/istionetworkpolicy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"apiVersion": "security.istio.io/v1",
"kind": "AuthorizationPolicy",
"metadata": {
"namespace": "ns1",
"name": "anyname"
},
"spec": {
"selector": {
"matchLabels": {
"app": "myapi"
}
},
"action": "AUDIT",
"rules": [
{
"to": [
{
"operation": {
"methods": [
"GET"
],
"paths": [
"/user/profile/*"
]
}
}
]
}
]
}
}
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/armosec/utils-k8s-go

go 1.18
go 1.22.4

require (
github.com/armosec/armoapi-go v0.0.234
Expand All @@ -20,8 +20,6 @@ require (
require (
github.com/armosec/gojay v1.2.15 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-logr/logr v1.2.4 // indirect
Expand Down
Loading

0 comments on commit 77f8980

Please sign in to comment.