Skip to content
Open
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ jobs:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install eBPF build dependencies
run: sudo apt-get update && sudo apt-get install -y clang llvm

- name: Build binary
run: task build

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ task test:e2e # Kind cluster lifecycle test
task lint # golangci-lint; lint-fix applies safe auto-fixes
```

There is no production release image build in this repo (`task docker-build` and the release workflow were removed after the shared image was found to advertise `galactic-router` without ever building it — see [docs/agents/ARCHITECTURE.md](docs/agents/ARCHITECTURE.md#known-constraints)). `containers/galactic-cni/Dockerfile` exists solely for `task test:e2e`.
Production images are built by `.github/workflows/publish.yaml`: `publish-galactic-cni-image` and `publish-galactic-router-image` each build and push their own image (`ghcr.io/datum-cloud/galactic-cni`, `ghcr.io/datum-cloud/galactic-router`) from their respective `containers/*/Dockerfile`, and `publish-kustomize-bundles` pushes `config/` as an OCI Kustomize bundle with each job's real published tag stamped in. This replaced the old single-image `release.yaml`, which built one shared image that advertised `galactic-router` without ever building it — see [docs/agents/ARCHITECTURE.md](docs/agents/ARCHITECTURE.md#cicd) for that history. `containers/galactic-cni/Dockerfile` is used by both `task test:e2e` and `publish.yaml`.

**Before every PR:** `task ci` (lint → build → test:unit → test:e2e).

Expand Down
25 changes: 24 additions & 1 deletion Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ tasks:
- GOOS=linux go vet ./...

ci:
desc: Run the full CI pipeline (lint, build, test:unit, test:e2e)
desc: Run the full CI pipeline
cmds:
- task: lint
- task: build
Expand Down Expand Up @@ -77,6 +77,29 @@ tasks:
build:
desc: Build binaries
deps: [fmt, vet]
cmds:
- task: build:ebpf
- task: build:binaries

build:ebpf:
desc: >-
Regenerate the eBPF uSID datapath
run: once
cmds:
- |
if ! command -v clang >/dev/null 2>&1; then
echo "ERROR: clang is required to build the eBPF uSID datapath" >&2
echo "(internal/plumbing/ebpf/prog/usid.c, via bpf2go)." >&2
echo "Install it, e.g.:" >&2
echo " Fedora/RHEL: sudo dnf install clang llvm" >&2
echo " Debian/Ubuntu: sudo apt install clang llvm" >&2
echo "then re-run 'task build:ebpf' (or 'task build')." >&2
exit 1
fi
- go generate ./internal/plumbing/ebpf/prog/...

build:binaries:
internal: true
vars:
VERSION:
sh: git describe --tags --always --dirty 2>/dev/null || echo "dev"
Expand Down
4 changes: 3 additions & 1 deletion cmd/galactic-cni/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,17 @@ func newInitCommand() *cobra.Command {

func newRunCommand() *cobra.Command {
var grpcHealthPort int
var metricsPort int

runCmd := &cobra.Command{
Use: "run",
Short: "Lightweight run loop to refresh credentials and run gRPC health server",
RunE: func(cmd *cobra.Command, args []string) error {
return installer.Run(cmd.Context(), grpcHealthPort)
return installer.Run(cmd.Context(), grpcHealthPort, metricsPort)
},
}
runCmd.Flags().IntVar(&grpcHealthPort, "grpc-health-port", 5180, "gRPC health check port")
runCmd.Flags().IntVar(&metricsPort, "metrics-port", 9091, "Prometheus metrics HTTP port")
return runCmd
}

Expand Down
49 changes: 49 additions & 0 deletions config/cni/daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,27 @@ spec:
# placeholder, not a published tag.
image: ghcr.io/datum-cloud/galactic-cni:latest
command: ["/galactic-cni", "run"]
# --- PRIVILEGE EXPANSION (Milestone 3.1 of
# .local/implementation-plan-ebpf-xdp-usid-datapath.md) ---
# This container previously ran with allowPrivilegeEscalation:
# false, no added capabilities, and no bpffs mount, because it
# only refreshed credentials and served gRPC health. It now also
# hosts the eBPF/TC-BPF uSID datapath's load/attach/pin control
# daemon (design plan .local/plan-ebpf-xdp-usid-datapath.md §5.4
# option (a), §9 "Privileges" -- deliberately not a footnote,
# per that section). BPF/NET_ADMIN below and the bpf-fs
# volumeMount are required unconditionally by the binary's BPF
# loader dependencies even though the datapath itself stays
# inert until GALACTIC_CNI_ENABLE_EBPF_DATAPATH=true is set
# (default off, design plan §8 Phase 0) -- so this grant takes
# effect on every node running this manifest regardless of
# whether the flag is ever flipped on.
securityContext:
runAsUser: 0
capabilities:
add:
- BPF
- NET_ADMIN
allowPrivilegeEscalation: false
resources:
requests:
Expand All @@ -86,6 +105,8 @@ spec:
mountPath: /host/var/lib/galactic
- name: galactic-log
mountPath: /host/var/log/galactic
- name: bpf-fs
mountPath: /sys/fs/bpf
livenessProbe:
grpc:
port: 5180
Expand Down Expand Up @@ -113,3 +134,31 @@ spec:
hostPath:
path: /var/log/galactic
type: DirectoryOrCreate
- name: bpf-fs
# The host's bpffs mount (design plan §4.4/§9: "All maps pinned
# under /sys/fs/bpf/galactic/"). credential-refresh pins the
# eBPF uSID datapath's maps under a galactic/ subdirectory of
# this mount so a container restart reuses the maps already
# pinned there instead of recreating them empty (pinned-map
# continuity). `Directory` (not DirectoryOrCreate): bpffs must
# already be mounted at this path by the host/kubelet node setup
# for pinning to actually work -- creating a plain directory
# here if it were missing would silently produce a regular
# filesystem path instead of a real bpf filesystem, and pinning
# would then fail at load time with a real, actionable error
# instead of appearing to succeed.
#
# SECURITY NOTE: this mounts the host's entire /sys/fs/bpf, not
# just the galactic/ subtree this container actually uses --
# a Kubernetes hostPath volume can't mount a subdirectory that
# doesn't exist yet (galactic/ is created by this container on
# first pin, not present beforehand), so this container has
# read/write visibility into every other pinned BPF object any
# other process on this host has placed under bpffs, not just
# its own. This is an accepted, largely inherent tradeoff of
# bpffs pinning on this platform, not an oversight -- call it
# out explicitly to a security reviewer rather than let it pass
# as a footnote.
hostPath:
path: /sys/fs/bpf
type: Directory
11 changes: 11 additions & 0 deletions config/fabric/daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,21 @@ spec:
# and brings up the node's lo address, both of which galactic-router
# depends on before it can start — so this must tolerate NotReady the
# same way a CNI plugin does, or it never gets scheduled early enough.
# It must also tolerate the route-reflector role's own
# galactic.datumapis.com/node=control:NoSchedule taint -- the affinity
# below already targets that label, but without this toleration the
# matching taint silently keeps this DaemonSet off the node entirely,
# so the route reflector's lo address (and BGP_LOCAL_ADDRESS
# auto-detection) never gets configured and galactic-router-control
# crashloops forever.
tolerations:
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoSchedule
- key: galactic.datumapis.com/node
operator: Equal
value: control
effect: NoSchedule
# Opt-in only, same as galactic-cni and galactic-router: runs on every
# node labeled either for regular tenant traffic or for the
# galactic-router route-reflector role, since both need underlay
Expand Down
16 changes: 16 additions & 0 deletions containers/galactic-cni/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ ARG SPDX_LICENSE=AGPL-3.0-or-later
ARG GIT_URL=https://github.com/datum-cloud/galactic

WORKDIR /workspace

# clang/LLVM builds the eBPF/TC-BPF uSID datapath (internal/plumbing/ebpf/
# prog/usid.c) via bpf2go, invoked by `go generate` below -- a build-time
# only dependency; the final runtime images below embed the resulting
# compiled object via go:embed in the galactic-cni binary itself and need
# no eBPF toolchain of their own (design plan
# .local/plan-ebpf-xdp-usid-datapath.md §6; Milestone 5.2 of
# .local/implementation-plan-ebpf-xdp-usid-datapath.md).
RUN apt-get update && apt-get install -y --no-install-recommends clang llvm linux-libc-dev \
&& rm -rf /var/lib/apt/lists/*

# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
Expand All @@ -21,6 +32,11 @@ RUN go mod download
COPY cmd/ cmd/
COPY internal/ internal/

# Regenerate the eBPF uSID datapath's compiled object and Go bindings from
# usid.c -- always, not just when the committed usid_bpfel.o/usid_bpfeb.o
# are stale, matching `task build:ebpf`'s own semantics (Taskfile.yaml).
RUN go generate ./internal/plumbing/ebpf/prog/...

# Build CNI plugin
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build \
-ldflags "-s -w \
Expand Down
16 changes: 16 additions & 0 deletions deploy/containerlab/resources/cni/daemonset-patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,19 @@ spec:
- name: credential-refresh
image: galactic-cni:latest
imagePullPolicy: Never
env:
# Every lab node is dual-homed: eth0 carries the IPv6 default
# route but only reaches the Kind/ContainerLab management
# bridge (kubectl/API-server traffic), while eth1 is the
# dedicated point-to-point link to the transit fabric (tr1-4)
# that actual SRv6-encapsulated VPC traffic arrives on.
# ResolveInterfaces' auto-detection (default-IPv6-route
# heuristic, internal/plumbing/ebpf/attach/interfaces.go) picks
# eth0 here since it's ambiguous between the two -- confirmed
# live by tcpdump: cross-region SRv6 packets arrive on eth1 but
# the eBPF usid_ingress filter was only ever attached to eth0,
# so decapsulation never ran and every VPC ping between sites
# silently blackholed. This override forces the correct
# interface for this topology; see docs/cni/configuration.md.
- name: GALACTIC_CNI_EBPF_INTERFACES
value: eth1
Loading