GitHub Security Advisory: GHSA-v5mh-h5hx-7v92
Package: github.com/cloudnativelabs/kube-router (GO)
Severity: MODERATE (CVSS 6.3)
CWE: CWE-862: Missing Authorization
Vulnerable versions: <= 2.8.1
Patched in: 2.9.0

Summary: kube-router: GoBGP gRPC Admin Port Exposed on Node Primary IP Without Authentication, Allowing Cluster-Wide BGP Route Injection

Details:
## Summary

When the kube-router routing controller starts (`--run-router`), it binds the GoBGP gRPC management server to the node's primary IP (e.g., `192.168.1.10:50051`) in addition to `127.0.0.1:50051`. The default admin port is `50051` and the server is enabled by default with no TLS and no authentication. Any pod in the cluster can reach node IPs and therefore call the GoBGP gRPC API to inject arbitrary BGP routes, enumerate peer configurations, add unauthorized BGP neighbors, or withdraw legitimate routes. While kube-router's BGP export policy of `ROUTE_ACTION_REJECT` limits the attack surface to the local node's GoBGP RIB, an attacker can still impact local routing decisions.

## Details

The gRPC server is started unconditionally when `--run-router` is active. In `pkg/controllers/routing/network_routes_controller.go`, the `startBgpServer(true)` call at line 365 passes `grpcServer=true`, and the binding logic at lines 1057–1061 is:

```go
// pkg/controllers/routing/network_routes_controller.go:1057-1061
if grpcServer && nrc.goBGPAdminPort != 0 {
    nrc.bgpServer = gobgp.NewBgpServer(
        gobgp.GrpcListenAddress(net.JoinHostPort(nrc.krNode.GetPrimaryNodeIP().String(),
            strconv.FormatUint(uint64(nrc.goBGPAdminPort), 10)) + "," +
            fmt.Sprintf("127.0.0.1:%d", nrc.goBGPAdminPort)))
}
```

The default admin port is defined in `pkg/options/options.go`:

```go
// pkg/options/options.go:16
defaultGoBGPAdminPort uint16 = 50051
```

No `gobgp.GrpcOption` is passed, meaning the gRPC server is started with no TLS credentials and no authentication interceptor. The GoBGP gRPC API (`gobgpapi`) exposes write-capable RPCs:

- `AddPath` / `DeletePath` — inject or withdraw arbitrary BGP routes
- `AddPeer` / `DeletePeer` / `UpdatePeer` — add/remove/modify BGP neighbors
- `AddPolicy` / `DeletePolicy` — modify BGP routing policies
- `ListPeer` / `ListPath` — enumerate all BGP peer configs and routing table entries

kube-router runs as a DaemonSet with `hostNetwork: true`. This means the gRPC server is reachable at `<node-primary-ip>:50051` from any pod in the cluster — pod-to-node-IP connectivity is guaranteed by any Kubernetes-conformant CNI. The kube-router documentation in `docs/pod-toolbox.md` explicitly demonstrates cross-node usage: "To query a different node use `gobgp --host node02.mydomain`" — confirming the port is reachable across the cluster, but providing no guidance on restricting access.

## PoC

From any pod running in the cluster:

**Step 1 — Discover a node IP:**
```bash
# Using the Kubernetes API (available to all pods via service account)
curl -s -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
  https://kubernetes.default.svc/api/v1/nodes \
  --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  | grep -o '"internalIP":"[^"]*"' | head -1
# Expected output: "internalIP":"192.168.1.10"
```

**Step 2 — Connect to the GoBGP gRPC API and inject a blackhole route:**
```