From 8f5dc4dce22dd5caecde85c3360882088fdbf24c Mon Sep 17 00:00:00 2001 From: Daichi Sakaue Date: Fri, 11 Oct 2024 17:29:32 +0900 Subject: [PATCH] Refactor implementation Signed-off-by: Daichi Sakaue --- cmd/list.go | 37 +++++++++-- cmd/root.go | 2 + e2e/Makefile | 15 ++++- e2e/list_test.go | 100 +++++++++++++++++------------ e2e/testdata/policy/README.md | 15 +++++ e2e/testdata/policy/l3.yaml | 20 ++++++ e2e/testdata/policy/l4.yaml | 115 ++++++++++++++++++++++++++++++++++ e2e/utils_test.go | 8 ++- 8 files changed, 263 insertions(+), 49 deletions(-) create mode 100644 e2e/testdata/policy/README.md create mode 100644 e2e/testdata/policy/l4.yaml diff --git a/cmd/list.go b/cmd/list.go index 03a5861..0610a96 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -5,12 +5,14 @@ import ( "encoding/json" "fmt" "io" + "sort" "strconv" "strings" "text/tabwriter" "github.com/cilium/cilium/api/v1/client/endpoint" "github.com/spf13/cobra" + "golang.org/x/exp/maps" ) func init() { @@ -40,6 +42,22 @@ type derivedFromEntry struct { Name string `json:"name"` } +func compareDerivedFromEntry(x, y *derivedFromEntry) bool { + if x.Direction != y.Direction { + return strings.Compare(x.Direction, y.Direction) < 0 + } + if x.Kind != y.Kind { + return strings.Compare(x.Kind, y.Kind) < 0 + } + if x.Namespace != y.Namespace { + return strings.Compare(x.Namespace, y.Namespace) < 0 + } + if x.Name != y.Name { + return strings.Compare(x.Name, y.Name) < 0 + } + return false +} + func parseDerivedFromEntry(input []string, direction string) derivedFromEntry { val := derivedFromEntry{ Direction: direction, @@ -77,22 +95,27 @@ func runList(ctx context.Context, w io.Writer, name string) error { return err } - policyList := make([]derivedFromEntry, 0) + policySet := make(map[derivedFromEntry]struct{}) ingressRules := response.Payload.Status.Policy.Realized.L4.Ingress for _, rule := range ingressRules { for _, r := range rule.DerivedFromRules { - policyList = append(policyList, parseDerivedFromEntry(r, directionIngress)) + entry := parseDerivedFromEntry(r, directionIngress) + policySet[entry] = struct{}{} } } egressRules := response.Payload.Status.Policy.Realized.L4.Egress for _, rule := range egressRules { for _, r := range rule.DerivedFromRules { - policyList = append(policyList, parseDerivedFromEntry(r, directionEgress)) + entry := parseDerivedFromEntry(r, directionEgress) + policySet[entry] = struct{}{} } } + policyList := maps.Keys(policySet) + sort.Slice(policyList, func(i, j int) bool { return compareDerivedFromEntry(&policyList[i], &policyList[j]) }) + switch rootOptions.output { case OutputJson: text, err := json.MarshalIndent(policyList, "", " ") @@ -103,9 +126,11 @@ func runList(ctx context.Context, w io.Writer, name string) error { return err case OutputSimple: tw := tabwriter.NewWriter(w, 0, 1, 1, ' ', 0) - _, err := tw.Write([]byte("DIRECTION\tKIND\tNAMESPACE\tNAME\n")) - if err != nil { - return err + if !rootOptions.noHeaders { + _, err := tw.Write([]byte("DIRECTION\tKIND\tNAMESPACE\tNAME\n")) + if err != nil { + return err + } } for _, p := range policyList { _, err := tw.Write([]byte(fmt.Sprintf("%v\t%v\t%v\t%v\n", p.Direction, p.Kind, p.Namespace, p.Name))) diff --git a/cmd/root.go b/cmd/root.go index 3451d32..d97d6a8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,6 +17,7 @@ var rootOptions struct { proxySelector string proxyPort uint16 output string + noHeaders bool } func init() { @@ -24,6 +25,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&rootOptions.proxySelector, "proxy-selector", "app.kubernetes.io/name=cilium-agent-proxy", "label selector to find the proxy pods") rootCmd.PersistentFlags().Uint16Var(&rootOptions.proxyPort, "proxy-port", 8080, "port number of the proxy endpoints") rootCmd.PersistentFlags().StringVarP(&rootOptions.output, "output", "o", OutputSimple, "output format") + rootCmd.PersistentFlags().BoolVar(&rootOptions.noHeaders, "no-headers", false, "stop printing header") } var rootCmd = &cobra.Command{} diff --git a/e2e/Makefile b/e2e/Makefile index b2f73e5..d80f725 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -33,22 +33,33 @@ start: $(MAKE) --no-print-directory wait-for-workloads run-test-pod-%: + @# https://github.com/orgs/aquaproj/discussions/2964 + @echo Hello | yq > /dev/null cat testdata/template/ubuntu.yaml | \ yq '.metadata.name = "$*"' | \ yq '.spec.selector.matchLabels = {"test": "$*"}' | \ - yq '.spec.template.metadata.labels = {"test": "$*"}' | \ + yq '.spec.template.metadata.labels = {"test": "$*", "group": "test"}' | \ kubectl apply -f - .PHONY: install-test-pod install-test-pod: $(MAKE) --no-print-directory run-test-pod-self $(MAKE) --no-print-directory run-test-pod-l3-ingress-explicit-allow - $(MAKE) --no-print-directory run-test-pod-l3-ingress-no-rule + $(MAKE) --no-print-directory run-test-pod-l3-ingress-implicit-deny $(MAKE) --no-print-directory run-test-pod-l3-ingress-explicit-deny $(MAKE) --no-print-directory run-test-pod-l3-egress-implicit-deny $(MAKE) --no-print-directory run-test-pod-l3-egress-explicit-deny + + $(MAKE) --no-print-directory run-test-pod-l4-ingress-explicit-allow-any + $(MAKE) --no-print-directory run-test-pod-l4-ingress-explicit-allow-tcp + $(MAKE) --no-print-directory run-test-pod-l4-ingress-explicit-deny-any + $(MAKE) --no-print-directory run-test-pod-l4-ingress-explicit-deny-udp + $(MAKE) --no-print-directory run-test-pod-l4-egress-explicit-deny-any + $(MAKE) --no-print-directory run-test-pod-l4-egress-explicit-deny-tcp $(MAKE) --no-print-directory wait-for-workloads + kubectl apply -f testdata/policy/l3.yaml + kubectl apply -f testdata/policy/l4.yaml .PHONY: install-policy-viewer install-policy-viewer: diff --git a/e2e/list_test.go b/e2e/list_test.go index 2f64dee..949b5f5 100644 --- a/e2e/list_test.go +++ b/e2e/list_test.go @@ -1,28 +1,10 @@ package e2e import ( - "encoding/json" - "fmt" - "reflect" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) -func testJson(g Gomega, target []byte, expected string) { - var t, e interface{} - err := json.Unmarshal(target, &t) - g.Expect(err).NotTo(HaveOccurred(), "actual: %s", target) - - err = json.Unmarshal([]byte(expected), &e) - g.Expect(err).NotTo(HaveOccurred(), "expected: %s", expected) - - if !reflect.DeepEqual(t, e) { - err := fmt.Errorf("compare failed. actual: %s, expected: %s", target, expected) - g.Expect(err).NotTo(HaveOccurred()) - } -} - func testList() { cases := []struct { Selector string @@ -30,42 +12,80 @@ func testList() { }{ { Selector: "test=self", - Expected: `[{ - "direction": "EGRESS", - "kind": "CiliumNetworkPolicy", - "namespace": "default", - "name": "l3-egress" - }]`, + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +EGRESS,CiliumNetworkPolicy,default,l3-egress +EGRESS,CiliumNetworkPolicy,default,l4-egress +INGRESS,CiliumNetworkPolicy,default,l3-baseline`, }, { Selector: "test=l3-ingress-explicit-allow", - Expected: `[{ - "direction": "INGRESS", - "kind": "CiliumNetworkPolicy", - "namespace": "default", - "name": "l3-ingress-explicit-allow" - }]`, + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-ingress-explicit-allow`, }, { - Selector: "test=l3-ingress-no-rule", - Expected: `[]`, + Selector: "test=l3-ingress-implicit-deny", + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline`, }, { Selector: "test=l3-ingress-explicit-deny", - Expected: `[{ - "direction": "INGRESS", - "kind": "CiliumNetworkPolicy", - "namespace": "default", - "name": "l3-ingress-explicit-deny" - }]`, + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-ingress-explicit-deny`, + }, + { + Selector: "test=l3-egress-implicit-deny", + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline`, + }, + { + Selector: "test=l3-egress-explicit-deny", + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline`, + }, + { + Selector: "test=l4-ingress-explicit-allow-any", + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l4-ingress-explicit-allow-any`, + }, + { + Selector: "test=l4-ingress-explicit-allow-tcp", + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l4-ingress-explicit-allow-tcp`, + }, + { + Selector: "test=l4-ingress-explicit-deny-any", + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l4-ingress-explicit-deny-any`, + }, + { + Selector: "test=l4-ingress-explicit-deny-udp", + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l4-ingress-explicit-deny-udp`, + }, + { + Selector: "test=l4-egress-explicit-deny-any", + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline`, + }, + { + Selector: "test=l4-egress-explicit-deny-tcp", + Expected: `EGRESS,CiliumNetworkPolicy,default,l3-baseline +INGRESS,CiliumNetworkPolicy,default,l3-baseline`, }, } It("should list applied policies", func() { for _, c := range cases { podName := onePodByLabelSelector(Default, "default", c.Selector) - result := runViewerSafe(Default, nil, "list", "-o=json", podName) - testJson(Default, result, c.Expected) + result := runViewerSafe(Default, nil, "list", "-o=json", "--no-headers", podName) + result = sedSafe(Default, jqSafe(Default, result, "-r", ".[] | [.direction, .kind, .namespace, .name] | @csv"), `s/\"//g`) + Expect(string(result)).To(Equal(c.Expected), "compare failed. selector: %s, actual: %s, expected: %s", c.Selector, string(result), c.Expected) } }) } diff --git a/e2e/testdata/policy/README.md b/e2e/testdata/policy/README.md new file mode 100644 index 0000000..884a997 --- /dev/null +++ b/e2e/testdata/policy/README.md @@ -0,0 +1,15 @@ +# NetworkPolicy Configuration for Test Pods + +| Target | From self (Egress) | To pod (Ingress) | +|-|-|-| +| l3-ingress-explicit-allow | allow | allow | +| l3-ingress-implicit-deny | allow | - | +| l3-ingress-explicit-deny | allow | deny | +| l3-egress-implicit-deny | - | - | +| l3-egress-explicit-deny | deny | - | +| l4-ingress-explicit-allow-any | allow | allow (L4) | +| l4-ingress-explicit-allow-tcp | allow | allow (L4) | +| l4-ingress-explicit-deny-any | allow | deny (L4) | +| l4-ingress-explicit-deny-udp | allow | deny (L4) | +| l4-egress-explicit-deny-any | deny (L4) | - | +| l4-egress-explicit-deny-tcp | deny (L4) | - | diff --git a/e2e/testdata/policy/l3.yaml b/e2e/testdata/policy/l3.yaml index ccc3433..15c42ef 100644 --- a/e2e/testdata/policy/l3.yaml +++ b/e2e/testdata/policy/l3.yaml @@ -1,5 +1,22 @@ apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy +metadata: + name: l3-baseline +spec: + endpointSelector: + matchLabels: + k8s:group: test + ingressDeny: + - fromEndpoints: + - matchLabels: + k8s:test: scapegoat + egressDeny: + - toEndpoints: + - matchLabels: + k8s:test: scapegoat +--- +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy metadata: name: l3-egress spec: @@ -10,6 +27,9 @@ spec: - toEndpoints: - matchLabels: k8s:test: l3-ingress-explicit-allow + - toEndpoints: + - matchLabels: + k8s:test: l3-ingress-no-rule - toEndpoints: - matchLabels: k8s:test: l3-ingress-implicit-deny diff --git a/e2e/testdata/policy/l4.yaml b/e2e/testdata/policy/l4.yaml new file mode 100644 index 0000000..3ef0d76 --- /dev/null +++ b/e2e/testdata/policy/l4.yaml @@ -0,0 +1,115 @@ +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: l4-egress +spec: + endpointSelector: + matchLabels: + k8s:test: self + egress: + - toEndpoints: + - matchLabels: + k8s:test: l4-ingress-explicit-allow-any + toPorts: + - ports: + - port: "53" + - toEndpoints: + - matchLabels: + k8s:test: l4-ingress-explicit-allow-tcp + toPorts: + - ports: + - port: "8080" + protocol: TCP + - toEndpoints: + - matchLabels: + k8s:test: l4-ingress-explicit-deny-any + toPorts: + - ports: + - port: "53" + - toEndpoints: + - matchLabels: + k8s:test: l4-ingress-explicit-deny-udp + toPorts: + - ports: + - port: "161" # SNMP (UDP) + protocol: UDP + egressDeny: + - toEndpoints: + - matchLabels: + k8s:test: l4-egress-explicit-deny-any + toPorts: + - ports: + - port: "53" + - toEndpoints: + - matchLabels: + k8s:test: l4-ingress-explicit-deny-tcp + toPorts: + - ports: + - port: "8080" + protocol: TCP +--- +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: l4-ingress-explicit-allow-any +spec: + endpointSelector: + matchLabels: + k8s:test: l4-ingress-explicit-allow-any + ingress: + - fromEndpoints: + - matchLabels: + k8s:test: self + toPorts: + - ports: + - port: "53" +--- +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: l4-ingress-explicit-allow-tcp +spec: + endpointSelector: + matchLabels: + k8s:test: l4-ingress-explicit-allow-tcp + ingress: + - fromEndpoints: + - matchLabels: + k8s:test: self + toPorts: + - ports: + - port: "8080" + protocol: TCP +--- +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: l4-ingress-explicit-deny-any +spec: + endpointSelector: + matchLabels: + k8s:test: l4-ingress-explicit-deny-any + ingressDeny: + - fromEndpoints: + - matchLabels: + k8s:test: self + toPorts: + - ports: + - port: "53" +--- +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: l4-ingress-explicit-deny-udp +spec: + endpointSelector: + matchLabels: + k8s:test: l4-ingress-explicit-deny-udp + ingressDeny: + - fromEndpoints: + - matchLabels: + k8s:test: self + toPorts: + - ports: + - port: "161" # SNMP (UDP) + protocol: UDP diff --git a/e2e/utils_test.go b/e2e/utils_test.go index b89dcb9..00e4dcb 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -50,6 +50,12 @@ func jqSafe(g Gomega, input []byte, args ...string) []byte { return []byte(strings.TrimSpace(string(stdout))) } +func sedSafe(g Gomega, input []byte, args ...string) []byte { + stdout, stderr, err := runCommand("sed", input, args...) + g.Expect(err).NotTo(HaveOccurred(), "input: %s, stdout: %s, stderr: %s", string(input), stdout, stderr) + return []byte(strings.TrimSpace(string(stdout))) +} + func runViewer(input []byte, args ...string) ([]byte, []byte, error) { args = append([]string{"exec", "deploy/ubuntu", "--", policyViewerPath}, args...) return kubectl(input, args...) @@ -65,6 +71,6 @@ func onePodByLabelSelector(g Gomega, namespace, selector string) string { data := kubectlSafe(g, nil, "get", "pod", "-n", namespace, "-l", selector, "-o=json") count, err := strconv.Atoi(string(jqSafe(g, data, "-r", ".items | length"))) g.Expect(err).NotTo(HaveOccurred()) - g.Expect(count).To(Equal(1)) + g.Expect(count).To(Equal(1), "namespace: %s, selector: %s", namespace, selector) return string(jqSafe(g, data, "-r", ".items[0].metadata.name")) }