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 all 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
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
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
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 /
chez-shanpu marked this conversation as resolved.
Show resolved Hide resolved
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
Loading