Skip to content

Commit

Permalink
Implement list command
Browse files Browse the repository at this point in the history
Signed-off-by: Daichi Sakaue <daichi-sakaue@cybozu.co.jp>
  • Loading branch information
yokaze committed Apr 5, 2024
1 parent c6cea5d commit fd3f37a
Show file tree
Hide file tree
Showing 13 changed files with 659 additions and 110 deletions.
59 changes: 10 additions & 49 deletions cmd/dump.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
package cmd

import (
"bytes"
"context"
"errors"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

var dumpOptions struct {
namespace string
}

func init() {
dumpCmd.Flags().StringVarP(&dumpOptions.namespace, "namespace", "n", "", "namespace of a pod")
rootCmd.AddCommand(dumpCmd)
}

Expand All @@ -37,54 +27,22 @@ var dumpCmd = &cobra.Command{
}

func runDump(ctx context.Context, name string) error {
config, err := rest.InClusterConfig()
clientset, dynamicClient, _, err := createClients(ctx, name)
if err != nil {
return err
}

clientset, _ := kubernetes.NewForConfig(config)
pod, err := clientset.CoreV1().Pods(dumpOptions.namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return err
}
node := pod.Spec.NodeName
proxy, err := clientset.CoreV1().Pods("kube-system").List(ctx, metav1.ListOptions{
FieldSelector: "spec.nodeName=" + node,
LabelSelector: "app.kubernetes.io/name=cilium-agent-proxy",
})
endpointID, err := getPodEndpointID(ctx, dynamicClient, rootOptions.namespace, name)
if err != nil {
return err
}
if len(proxy.Items) != 1 {
return errors.New("proxy not found")
}
proxyIP := proxy.Items[0].Status.PodIP

client, err := dynamic.NewForConfig(config)
proxyEndpoint, err := getProxyEndpoint(ctx, clientset, rootOptions.namespace, name)
if err != nil {
return err
}

gvr := schema.GroupVersionResource{
Group: "cilium.io",
Version: "v2",
Resource: "ciliumendpoints",
}
obj, err := client.Resource(gvr).Namespace(dumpOptions.namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return err
}

endpointID, found, err := unstructured.NestedInt64(obj.Object, "status", "id")
if err != nil {
return err
}
if !found {
return errors.New("endpoint not found")
}

url := fmt.Sprintf("http://%s:8080/v1/endpoint/%d", proxyIP, endpointID)
resp, err := http.Get(url)
resp, err := http.Get(proxyEndpoint + fmt.Sprintf("/v1/endpoint/%d", endpointID))
if err != nil {
return err
}
Expand All @@ -93,6 +51,9 @@ func runDump(ctx context.Context, name string) error {
if err != nil {
return err
}
fmt.Println(string(data))

var buf bytes.Buffer
json.Indent(&buf, data, "", " ")
fmt.Println(buf.String())
return nil
}
91 changes: 91 additions & 0 deletions cmd/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package cmd

import (
"context"
"errors"
"fmt"

"github.com/cilium/cilium/pkg/client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

func createClients(ctx context.Context, name string) (*kubernetes.Clientset, *dynamic.DynamicClient, *client.Client, error) {
config, err := rest.InClusterConfig()
if err != nil {
return nil, nil, nil, err
}

// Create Kubernetes Clients
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, nil, nil, err
}

dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, nil, nil, err
}

// Create Cilium Client
endpoint, err := getProxyEndpoint(ctx, clientset, rootOptions.namespace, name)
if err != nil {
return nil, nil, nil, err
}
ciliumClient, err := client.NewClient(endpoint)
if err != nil {
return nil, nil, nil, err
}

return clientset, dynamicClient, ciliumClient, err
}

func getProxyEndpoint(ctx context.Context, c *kubernetes.Clientset, namespace, name string) (string, error) {
targetPod, err := c.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", err
}
targetNode := targetPod.Spec.NodeName

pods, err := c.CoreV1().Pods("kube-system").List(ctx, metav1.ListOptions{
FieldSelector: "spec.nodeName=" + targetNode,
LabelSelector: rootOptions.proxySelector,
})
if err != nil {
return "", err
}
if num := len(pods.Items); num != 1 {
err := fmt.Errorf("failed to find cilium-agent-proxy. found %d pods", num)
return "", err
}

podIP := pods.Items[0].Status.PodIP
return fmt.Sprintf("http://%s:%d", podIP, rootOptions.proxyPort), nil
}

func getPodEndpointID(ctx context.Context, d *dynamic.DynamicClient, namespace, name string) (int64, error) {
gvr := schema.GroupVersionResource{
Group: "cilium.io",
Version: "v2",
Resource: "ciliumendpoints",
}

ep, err := d.Resource(gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return 0, err
}

endpointID, found, err := unstructured.NestedInt64(ep.Object, "status", "id")
if err != nil {
return 0, err
}
if !found {
return 0, errors.New("endpoint resource is broken")
}

return endpointID, nil
}
1 change: 1 addition & 0 deletions cmd/inspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package cmd
101 changes: 101 additions & 0 deletions cmd/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package cmd

import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"

"github.com/cilium/cilium/api/v1/client/endpoint"
"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(listCmd)
}

var listCmd = &cobra.Command{
Use: "list",
Short: "list network policies applied to a pod",
Long: `List network policies applied to a pod`,

Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runList(context.Background(), args[0])
},
}

const (
directionEgress = "EGRESS"
directionIngress = "INGRESS"
)

type derivedFromEntry struct {
Direction string `json:"direction"`
Kind string `json:"kind"`
Namespace string `json:"namespace"`
Name string `json:"name"`
}

func parseDerivedFromEntry(input []string, direction string) derivedFromEntry {
val := derivedFromEntry{
Direction: direction,
}
for _, s := range input {
switch {
case strings.Contains(s, "k8s:io.cilium.k8s.policy.derived-from"):
val.Kind = strings.Split(s, "=")[1]
case strings.Contains(s, "k8s:io.cilium.k8s.policy.namespace"):
val.Namespace = strings.Split(s, "=")[1]
case strings.Contains(s, "k8s:io.cilium.k8s.policy.name"):
val.Name = strings.Split(s, "=")[1]
}
}
return val
}

func runList(ctx context.Context, name string) error {
_, dynamicClient, client, err := createClients(ctx, name)
if err != nil {
return err
}

endpointID, err := getPodEndpointID(ctx, dynamicClient, rootOptions.namespace, name)
if err != nil {
return err
}

params := endpoint.GetEndpointIDParams{
Context: ctx,
ID: strconv.FormatInt(endpointID, 10),
}
response, err := client.Endpoint.GetEndpointID(&params)
if err != nil {
return err
}

policyList := make([]derivedFromEntry, 0)

ingressRules := response.Payload.Status.Policy.Realized.L4.Ingress
for _, rule := range ingressRules {
for _, r := range rule.DerivedFromRules {
policyList = append(policyList, parseDerivedFromEntry(r, directionIngress))
}
}

egressRules := response.Payload.Status.Policy.Realized.L4.Egress
for _, rule := range egressRules {
for _, r := range rule.DerivedFromRules {
policyList = append(policyList, parseDerivedFromEntry(r, directionEgress))
}
}

text, err := json.MarshalIndent(policyList, "", " ")
if err != nil {
return err
}

fmt.Println(string(text))
return nil
}
1 change: 1 addition & 0 deletions cmd/reach.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package cmd
14 changes: 14 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ import (
"github.com/spf13/cobra"
)

var rootOptions struct {
namespace string
proxySelector string
proxyPort uint16
output string
}

func init() {
rootCmd.PersistentFlags().StringVarP(&rootOptions.namespace, "namespace", "n", "default", "namespace of a pod")
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", "json", "output format")
}

var rootCmd = &cobra.Command{}

func Execute() {
Expand Down
4 changes: 4 additions & 0 deletions e2e/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ install-policy-viewer:
$(KUBECTL) cp $(CILIUM_POLICY) $${PODNAME}:/tmp/; \
$(KUBECTL) exec $${PODNAME} -- chmod +x /tmp/cilium-policy

.PHONY: test
test:
go test -v -race . -ginkgo.v -ginkgo.fail-fast

.PHONY: stop
stop:
$(KIND) delete cluster
15 changes: 15 additions & 0 deletions e2e/dump_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package e2e

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func testDump() {
It("should dump endpoint content", func() {
podName := onePodByLabelSelector(Default, "default", "test=self")
ret := runViewerSafe(Default, nil, "dump", podName)
state := jqSafe(Default, ret, "-r", ".status.state")
Expect(string(state)).To(Equal("ready"))
})
}
31 changes: 31 additions & 0 deletions e2e/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package e2e

import (
"strconv"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func testList() {
It("should show rules applied to self", func() {
podName := onePodByLabelSelector(Default, "default", "test=self")
result := runViewerSafe(Default, nil, "list", podName)

count, err := strconv.Atoi(string(jqSafe(Default, result, "-r", "length")))
Expect(err).NotTo(HaveOccurred())
Expect(count).To(Equal(1))

dir := string(jqSafe(Default, result, "-r", ".[0].direction"))
Expect(dir).To(Equal("EGRESS"))

kind := string(jqSafe(Default, result, "-r", ".[0].kind"))
Expect(kind).To(Equal("CiliumNetworkPolicy"))

ns := string(jqSafe(Default, result, "-r", ".[0].namespace"))
Expect(ns).To(Equal("default"))

name := string(jqSafe(Default, result, "-r", ".[0].name"))
Expect(name).To(Equal("l3-egress"))
})
}
Loading

0 comments on commit fd3f37a

Please sign in to comment.