Skip to content

Commit

Permalink
Implement list command
Browse files Browse the repository at this point in the history
Signed-off-by: Daichi Sakaue <[email protected]>
  • Loading branch information
yokaze committed Apr 4, 2024
1 parent 7cd4efb commit c07d37b
Show file tree
Hide file tree
Showing 16 changed files with 691 additions and 131 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
- name: Setup tools
if: steps.cache-tools.outputs.cache-hit != 'true'
run: make setup
- name: Run lint
run: make lint
- name: Run environment
run: make start
working-directory: e2e
Expand Down
50 changes: 30 additions & 20 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ BIN_DIR := $(shell pwd)/bin
TOOLS_DIR := $(BIN_DIR)/download
CILIUM_CLI_VERSION := 0.16.4
HELM_VERSION := 3.14.3
JQ_VERSION := 1.7.1
KIND_VERSION := 0.22.0
KUBECTL_VERSION := 1.29.3
KUSTOMIZE_VERSION := 5.3.0
YQ_VERSION := 4.43.1

# Test tools
CILIUM := $(TOOLS_DIR)/cilium
CUSTOMCHECKER := $(TOOLS_DIR)/custom-checker
HELM := $(TOOLS_DIR)/helm
JQ := $(TOOLS_DIR)/jq
KUBECTL := $(TOOLS_DIR)/kubectl
KUSTOMIZE := $(TOOLS_DIR)/kustomize
STATICCHECK := $(TOOLS_DIR)/staticcheck
Expand All @@ -25,47 +28,54 @@ help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

.PHONY: setup
setup: $(CILIUM) $(HELM) $(KUBECTL) $(KUSTOMIZE) $(YQ) ## Install necessary tools
setup: $(CILIUM) $(HELM) $(JQ) $(KUBECTL) $(KUSTOMIZE) $(YQ) ## Install necessary tools
GOBIN=$(TOOLS_DIR) go install sigs.k8s.io/kind@v$(KIND_VERSION)
GOBIN=$(TOOLS_DIR) go install honnef.co/go/tools/cmd/staticcheck@latest
GOBIN=$(TOOLS_DIR) go install github.com/cybozu-go/golang-custom-analyzer/cmd/custom-checker@latest
$(HELM) repo add cilium https://helm.cilium.io/
$(HELM) repo update cilium

$(CILIUM):
$(TOOLS_DIR):
mkdir -p $(TOOLS_DIR)

$(CILIUM): $(TOOLS_DIR)
wget -qO - https://github.com/cilium/cilium-cli/releases/download/v${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz | tar zx -O cilium > $@
chmod +x $@

$(HELM):
mkdir -p $(TOOLS_DIR)
$(HELM): $(TOOLS_DIR)
wget -qO - https://get.helm.sh/helm-v$(HELM_VERSION)-linux-amd64.tar.gz | tar zx -O linux-amd64/helm > $@
chmod +x $@

$(KUBECTL):
mkdir -p $(TOOLS_DIR)
$(JQ): $(TOOLS_DIR)
wget -qO $@ https://github.com/jqlang/jq/releases/download/jq-$(JQ_VERSION)/jq-linux-amd64
chmod +x $@

$(KUBECTL): $(TOOLS_DIR)
wget -qO $@ https://storage.googleapis.com/kubernetes-release/release/v$(KUBECTL_VERSION)/bin/linux/amd64/kubectl
chmod +x $@

$(KUSTOMIZE):
mkdir -p $(TOOLS_DIR)
$(KUSTOMIZE): $(TOOLS_DIR)
wget -qO - https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv$(KUSTOMIZE_VERSION)/kustomize_v$(KUSTOMIZE_VERSION)_linux_amd64.tar.gz | tar zx -O kustomize > $@
chmod +x $@

$(YQ):
mkdir -p $(TOOLS_DIR)
$(YQ): $(TOOLS_DIR)
wget -qO $@ https://github.com/mikefarah/yq/releases/download/v$(YQ_VERSION)/yq_linux_amd64
chmod +x $@

.PHONY: build
build:
mkdir -p $(BIN_DIR)
go build -o $(BIN_DIR)/cilium-policy main.go

.PHONY: clean
clean:
rm -rf $(BIN_DIR)

.PHONY: test
test:
if find . -name go.mod | grep -q go.mod; then \
$(MAKE) test-go; \
fi
##@ Development

.PHONY: build
build: ## Build cilium-policy-viewer
mkdir -p $(BIN_DIR)
go build -o $(BIN_DIR)/cilium-policy main.go

.PHONY: lint
lint: ## Run lint tools
go vet ./...
test -z "$$(gofmt -s -l . | tee /dev/stderr)"
$(STATICCHECK) ./...
test -z "$$($(CUSTOMCHECKER) -restrictpkg.packages=html/template,log ./... 2>&1 | tee /dev/stderr)"
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
Loading

0 comments on commit c07d37b

Please sign in to comment.