Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write cilium-agent-proxy in Go #11

Merged
merged 6 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ jobs:
echo "Tag v${{ inputs.tag }} already exists"
exit 1
fi
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push cilium-agent-proxy
chez-shanpu marked this conversation as resolved.
Show resolved Hide resolved
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ghcr.io/cybozu-go/cilium-agent-proxy:${{ inputs.tag }}
- uses: actions/setup-go@v4
with:
go-version-file: go.mod
Expand Down
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Build the manager binary
FROM ghcr.io/cybozu/golang:1.23-jammy AS builder

# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download

# Copy the go source
COPY cmd/cilium-agent-proxy/ cmd/cilium-agent-proxy/

# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o cilium-agent-proxy ./cmd/cilium-agent-proxy
chez-shanpu marked this conversation as resolved.
Show resolved Hide resolved

# Compose the manager container
FROM ghcr.io/cybozu/ubuntu:22.04
LABEL org.opencontainers.image.source=https://github.com/cybozu-go/network-policy-viewer

WORKDIR /
COPY bin/download/cilium /
chez-shanpu marked this conversation as resolved.
Show resolved Hide resolved
COPY --from=builder /work/cilium-agent-proxy /

ENTRYPOINT ["/cilium-agent-proxy"]
13 changes: 11 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ TOOLS_DIR := $(BIN_DIR)/download
CACHE_DIR := $(shell pwd)/cache

# Test tools
CILIUM_IMAGE_VERSION := 1.14.14.1
CILIUM_CLI := $(TOOLS_DIR)/cilium
CUSTOMCHECKER := $(TOOLS_DIR)/custom-checker
HELM := helm --repository-cache $(CACHE_DIR)/helm/repository --repository-config $(CACHE_DIR)/helm/repositories.yaml
STATICCHECK := $(TOOLS_DIR)/staticcheck
Expand All @@ -17,7 +19,7 @@ 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: $(CUSTOMCHECKER) $(STATICCHECK) ## Install necessary tools
setup: $(CILIUM_CLI) $(CUSTOMCHECKER) $(STATICCHECK) ## Install necessary tools
if ! which aqua; then \
echo 'setup needs aqua.'; \
exit 1; \
Expand All @@ -26,6 +28,13 @@ setup: $(CUSTOMCHECKER) $(STATICCHECK) ## Install necessary tools
$(HELM) repo add cilium https://helm.cilium.io/
$(HELM) repo update cilium

$(CILIUM_CLI):
mkdir -p $(TOOLS_DIR)
CONTAINER_ID=$$(docker run --detach --entrypoint pause ghcr.io/cybozu/cilium:$(CILIUM_IMAGE_VERSION)); \
docker cp $${CONTAINER_ID}:/usr/bin/cilium $(CILIUM_CLI); \
docker stop $${CONTAINER_ID}; \
docker rm $${CONTAINER_ID}
chez-shanpu marked this conversation as resolved.
Show resolved Hide resolved

$(CUSTOMCHECKER):
GOBIN=$(TOOLS_DIR) go install github.com/cybozu-go/golang-custom-analyzer/cmd/custom-checker@latest

Expand All @@ -42,7 +51,7 @@ clean:
.PHONY: build
build: ## Build network-policy-viewer
mkdir -p $(BIN_DIR)
go build -trimpath -ldflags "-w -s" -o $(BIN_DIR)/npv main.go
go build -trimpath -ldflags "-w -s" -o $(BIN_DIR)/npv ./cmd/npv

.PHONY: check-generate
check-generate:
Expand Down
7 changes: 7 additions & 0 deletions cmd/cilium-agent-proxy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/cybozu-go/network-policy-viewer/cmd/cilium-agent-proxy/sub"

func main() {
sub.Execute()
}
53 changes: 53 additions & 0 deletions cmd/cilium-agent-proxy/sub/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package sub

import (
"bytes"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"os/exec"
"path/filepath"
"strconv"
)

const (
ciliumPath = "/cilium"
)

func runCommand(path string, input []byte, args ...string) ([]byte, []byte, error) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := exec.Command(path, args...)
cmd.Stdout = stdout
cmd.Stderr = stderr
if input != nil {
cmd.Stdin = bytes.NewReader(input)
}
if err := cmd.Run(); err != nil {
_, file := filepath.Split(path)
return stdout.Bytes(), stderr.Bytes(), fmt.Errorf("%s failed with %s: stderr=%s", file, err, stderr)
}
return stdout.Bytes(), stderr.Bytes(), nil
}

func renderJSON(w http.ResponseWriter, path string, data []byte, status int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if _, err := w.Write(data); err != nil {
slog.Error("failed to write response", slog.String("path", path))
}
}

func renderError(w http.ResponseWriter, path string, message string, status int) {
slog.Info(message, slog.String("path", path), slog.Int("status", status))
ret := make(map[string]string)
ret["error"] = message
ret["status"] = strconv.Itoa(status)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(ret); err != nil {
slog.Error("failed to write response", slog.String("path", path))
}
}
26 changes: 26 additions & 0 deletions cmd/cilium-agent-proxy/sub/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package sub
chez-shanpu marked this conversation as resolved.
Show resolved Hide resolved

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "cilium-agent-proxy",
Short: "cilium-agent proxy",
Long: `cilium-agent proxy`,

RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
return subMain()
},
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
113 changes: 113 additions & 0 deletions cmd/cilium-agent-proxy/sub/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package sub

import (
"bytes"
"context"
"fmt"
"io"
"net"
"net/http"
"strconv"
)

const socketPath = "/var/run/cilium/cilium.sock"

var (
socketClient *http.Client
)

func handleEndpoint(w http.ResponseWriter, r *http.Request) {
param := r.URL.Path[len("/v1/endpoint/"):]
if len(param) == 0 {
renderError(w, r.URL.Path, "failed to read endpoint ID", http.StatusBadRequest)
return
}

// Convert to number to avoid parameter injection
endpoint, err := strconv.Atoi(param)
if err != nil {
renderError(w, r.URL.Path, "failed to read endpoint ID", http.StatusBadRequest)
return
}

url := fmt.Sprintf("http://localhost/v1/endpoint/%d", endpoint)
resp, err := socketClient.Get(url)
if err != nil {
renderError(w, r.URL.Path, "failed to call Cilium API", http.StatusInternalServerError)
return
}

buf := new(bytes.Buffer)
io.Copy(buf, resp.Body)
renderJSON(w, r.URL.Path, buf.Bytes(), http.StatusOK)
}

func handleIdentity(w http.ResponseWriter, r *http.Request) {
param := r.URL.Path[len("/v1/identity/"):]
if len(param) == 0 {
renderError(w, r.URL.Path, "failed to read identity", http.StatusBadRequest)
return
}

// Convert to number to avoid parameter injection
identity, err := strconv.Atoi(param)
if err != nil {
renderError(w, r.URL.Path, "failed to read identity", http.StatusBadRequest)
return
}

url := fmt.Sprintf("http://localhost/v1/identity/%d", identity)
resp, err := socketClient.Get(url)
if err != nil {
renderError(w, r.URL.Path, "failed to call Cilium API", http.StatusInternalServerError)
return
}

buf := new(bytes.Buffer)
io.Copy(buf, resp.Body)
renderJSON(w, r.URL.Path, buf.Bytes(), http.StatusOK)
}

func handlePolicy(w http.ResponseWriter, r *http.Request) {
param := r.URL.Path[len("/policy/"):]
if len(param) == 0 {
renderError(w, r.URL.Path, "failed to read endpoint ID", http.StatusBadRequest)
return
}

// Convert to number to avoid parameter injection
endpoint, err := strconv.Atoi(param)
if err != nil {
renderError(w, r.URL.Path, "failed to read endpoint ID", http.StatusBadRequest)
return
}

stdout, _, err := runCommand(ciliumPath, nil, "bpf", "policy", "get", strconv.Itoa(endpoint), "-ojson")
if err != nil {
renderError(w, r.URL.Path, "failed to read BPF map", http.StatusInternalServerError)
return
}

renderJSON(w, r.URL.Path, stdout, http.StatusOK)
}

func subMain() error {
socketClient = &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
}

server := http.Server{
Addr: ":8080",
Handler: nil,
}

http.HandleFunc("/v1/endpoint/", handleEndpoint)
http.HandleFunc("/v1/identity/", handleIdentity)
http.HandleFunc("/policy/", handlePolicy)

return server.ListenAndServe()
}
7 changes: 7 additions & 0 deletions cmd/npv/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/cybozu-go/network-policy-viewer/cmd/npv/sub"

func main() {
sub.Execute()
}
2 changes: 1 addition & 1 deletion cmd/dump.go → cmd/npv/sub/dump.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package sub

import (
"bytes"
Expand Down
2 changes: 1 addition & 1 deletion cmd/helper.go → cmd/npv/sub/helper.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package sub

import (
"context"
Expand Down
8 changes: 4 additions & 4 deletions cmd/list.go → cmd/npv/sub/list.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package sub

import (
"context"
Expand Down Expand Up @@ -76,12 +76,12 @@ func parseDerivedFromEntry(input []string, direction string) derivedFromEntry {
func runList(ctx context.Context, w io.Writer, name string) error {
_, dynamicClient, client, err := createClients(ctx, name)
if err != nil {
return err
return fmt.Errorf("failed to create clients: %w", err)
}

endpointID, err := getPodEndpointID(ctx, dynamicClient, rootOptions.namespace, name)
if err != nil {
return err
return fmt.Errorf("failed to get pod endpoint ID: %w", err)
}

params := endpoint.GetEndpointIDParams{
Expand All @@ -90,7 +90,7 @@ func runList(ctx context.Context, w io.Writer, name string) error {
}
response, err := client.Endpoint.GetEndpointID(&params)
if err != nil {
return err
return fmt.Errorf("failed to get endpoint information: %w", err)
}

// The same rule appears multiple times in the response, so we need to dedup it
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go → cmd/npv/sub/root.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package sub

import (
"fmt"
Expand Down
4 changes: 4 additions & 0 deletions e2e/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ start:
--namespace kube-system \
--set image.pullPolicy=IfNotPresent \
--set ipam.mode=kubernetes

cd ..; docker build . -t cilium-agent-proxy:dev
kind load docker-image cilium-agent-proxy:dev

kustomize build testdata | kubectl apply -f -
$(MAKE) --no-print-directory wait-for-workloads

Expand Down
17 changes: 8 additions & 9 deletions e2e/testdata/cilium-agent-proxy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ spec:
securityContext:
fsGroup: 0
containers:
- image: ghcr.io/cybozu/envoy
name: envoy
command: ["envoy", "-c", "/etc/envoy/envoy-config.yaml"]
args: []
- image: ghcr.io/cybozu-go/cilium-agent-proxy
name: proxy
volumeMounts:
- name: cilium-socket
mountPath: /var/run/cilium
- name: envoy-config
mountPath: /etc/envoy
- name: bpf
mountPath: /sys/fs/bpf
securityContext:
capabilities:
drop:
Expand All @@ -32,6 +30,7 @@ spec:
- name: cilium-socket
hostPath:
path: /var/run/cilium
- name: envoy-config
configMap:
name: cilium-agent-proxy
- name: bpf
hostPath:
path: /sys/fs/bpf
type: DirectoryOrCreate
chez-shanpu marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading