Skip to content

Commit

Permalink
Write cilium-agent-proxy in Go (#11)
Browse files Browse the repository at this point in the history
* Move CLI entry point
* Write cilium-agent-proxy in Go
* Return policy information from cilium-agent-proxy
* Push cilium-agent-proxy on release
* Reflect comments
* Renamed sub to app

Signed-off-by: Daichi Sakaue <[email protected]>
  • Loading branch information
yokaze authored Oct 24, 2024
1 parent a5f5537 commit 940c56a
Show file tree
Hide file tree
Showing 18 changed files with 304 additions and 83 deletions.
18 changes: 17 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,27 @@ jobs:
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup tools
run: make setup
run: make setup download-cilium-cli
- name: Run code check
run: make check-generate
- name: Run lint
run: make lint
- 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 cilium-agent-proxy
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
load: true
push: false
tags: cilium-agent-proxy:dev
- name: Run environment
working-directory: e2e
run: |
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ jobs:
echo "Tag v${{ inputs.tag }} already exists"
exit 1
fi
- name: Download Cilium CLI
run: make download-cilium-cli
- 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
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
26 changes: 26 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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/
COPY Makefile Makefile

# Build
RUN make build-proxy

# 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 /
COPY --from=builder /work/bin/cilium-agent-proxy /

ENTRYPOINT ["/cilium-agent-proxy"]
16 changes: 15 additions & 1 deletion 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 @@ -26,6 +28,13 @@ setup: $(CUSTOMCHECKER) $(STATICCHECK) ## Install necessary tools
$(HELM) repo add cilium https://helm.cilium.io/
$(HELM) repo update cilium

.PHONY: download-cilium-cli
download-cilium-cli:
mkdir -p $(TOOLS_DIR)
CONTAINER_ID=$$(docker run --rm --detach --entrypoint pause ghcr.io/cybozu/cilium:$(CILIUM_IMAGE_VERSION)); \
docker cp $${CONTAINER_ID}:/usr/bin/cilium $(CILIUM_CLI); \
docker stop $${CONTAINER_ID}

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

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

.PHONY: build-proxy
build-proxy: ## Build cilium-agent-proxy
mkdir -p $(BIN_DIR)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-w -s" -o $(BIN_DIR)/cilium-agent-proxy ./cmd/cilium-agent-proxy

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

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/app/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package app

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/app/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package app

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/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/app"

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

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

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

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/app/root.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package app

import (
"fmt"
Expand Down
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/app"

func main() {
app.Execute()
}
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
Loading

0 comments on commit 940c56a

Please sign in to comment.