Skip to content

Commit a9ab55b

Browse files
thomas-manginclaude
andcommitted
fix: resolve 8 P3 release-readiness findings (docs drift, web headers, resolve config)
Docs drift: README and status.md test counts updated from live data (10,400+ unit tests, 789 .ci files, 57 fuzz targets). Race detector claim narrowed to unit tests only. Subsystem wiring doc annotated as pre-migration with tense corrections. BFD architecture and guide docs updated from "skeleton" to implemented status. TACACS guide expanded from 4 to 8 test entries, single-connect and tooling notes updated. BGP API commands.md fixed watchdog syntax (next -> nhop set) with wire-mode not-yet-implemented note. Web security headers: addSecurityHeaders applied to 401 responses in both AuthMiddleware (unauthenticated) and LoginHandler (failed login), closing the gap where pre-login pages lacked clickjacking, CSP, HSTS, and cache-control headers. Resolve config path: DNS ResolverConfig carries ResolvConfPath, resolveSystemDNS reads from configured path instead of hardcoded /etc/resolv.conf, wired through newResolvers in hub startup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ba7c147 commit a9ab55b

10 files changed

Lines changed: 59 additions & 45 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Ze
22

3-
> **Pre-release** -- Ze is under active development and has not been released yet. The core BGP engine works and is extensively tested (8,000+ unit tests, 550+ functional tests, fuzz testing, chaos testing, interop tests against FRR, BIRD, and GoBGP), but some advanced features are still incomplete. APIs and config syntax may change.
3+
> **Pre-release** -- Ze is under active development and has not been released yet. The core BGP engine works and is extensively tested (10,400+ unit tests, 789 functional tests, 57 fuzz targets, chaos testing, interop tests against FRR, BIRD, and GoBGP), but some advanced features are still incomplete. APIs and config syntax may change.
44
55
Ze is an open-source network operating system for Linux. It speaks BGP, manages network interfaces, programs the FIB, and serves a config editor over SSH and a web UI. Everything beyond the core is a plugin. Plugins can be Go modules or external processes in any language. An MCP server can let AI assistants discover all its features (including plugins) and operate them directly.
66

@@ -54,10 +54,10 @@ If you are an ExaBGP user, we would love your feedback on the migration experien
5454

5555
| Type | Scope |
5656
|------|-------|
57-
| Unit tests | 18,000+ test functions |
57+
| Unit tests | 10,400+ test functions |
5858
| Linting | 26 linters |
59-
| Functional tests | Config parsing, wire encoding, plugin behavior |
60-
| Fuzz testing | All external input parsing |
59+
| Functional tests | 789 `.ci` files: config parsing, wire encoding, plugin behavior |
60+
| Fuzz testing | 57 targets covering external input parsing |
6161
| Chaos testing | Deterministic replay with [configurable scenarios](docs/guide/chaos-testing.md) |
6262

6363
### Deployment

cmd/ze/hub/main.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,9 +1415,10 @@ func runOrchestratorWithData(store storage.Storage, configPath string, data []by
14151415
// and a Cymru resolver wired to it. Called once at hub startup.
14161416
func newResolvers(sc system.SystemConfig) *resolve.Resolvers {
14171417
cfg := resolveDNS.ResolverConfig{
1418-
Timeout: sc.DNSTimeout,
1419-
CacheSize: sc.DNSCacheSize,
1420-
CacheTTL: sc.DNSCacheTTL,
1418+
Timeout: sc.DNSTimeout,
1419+
ResolvConfPath: sc.ResolvConfPath,
1420+
CacheSize: sc.DNSCacheSize,
1421+
CacheTTL: sc.DNSCacheTTL,
14211422
}
14221423
if len(sc.NameServers) > 0 {
14231424
cfg.Server = sc.NameServers[0]

docs/architecture/api/commands.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,9 +493,13 @@ watchdog withdraw <name> [peer] # Withdraw all routes in pool from pee
493493

494494
Routes are tagged with a pool when announced:
495495
```bash
496-
update text next 10.0.0.1 nlri ipv4/unicast add prefix 1.0.0.0/24 watchdog set mypool
496+
update text nhop set 10.0.0.1 nlri ipv4/unicast add prefix 1.0.0.0/24 watchdog set mypool
497497
```
498498

499+
> **Note:** `watchdog set` in wire-mode updates is parsed but not yet
500+
> implemented. The watchdog announce/withdraw pool commands work
501+
> independently of this tagging.
502+
499503
### RIB Commands
500504

501505
```

docs/architecture/bfd.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
# BFD — Bidirectional Forwarding Detection
22

3-
**Status:** skeleton merged, NOT wired into the engine startup path. The plugin
4-
compiles, the codec and FSM are tested under `-race`, and a loopback engine
5-
test exercises the full three-way handshake. The `RunBFDPlugin` entry point
6-
is a no-op stub; opening real UDP sockets and exposing the Service over RPC
7-
lands in a follow-up commit.
3+
**Status:** implemented and wired. The plugin runs as an internal plugin with
4+
UDP transport, RFC 5880 FSM, detection timers, echo mode (RFC 5880 §6.8.9),
5+
and multi-hop support (RFC 5883). Static route next-hop tracking via the BFD
6+
Service is live. Session keying includes local address for multi-hop uniqueness.
87

98
## Source
109

docs/architecture/subsystem-wiring.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
# Subsystem Wiring: Reactor as ze.Subsystem
22

3+
> **Note (2026-04-24):** This document was written during the arch-0 migration.
4+
> The startup path has since evolved: the plugin server uses topological tier
5+
> ordering (`startup.go`), the coordinator owns config distribution, and the
6+
> reactor starts via the BGP plugin's `OnStarted` hook rather than direct
7+
> `LoadReactorWithPlugins`. The diagrams below describe the pre-migration state
8+
> and the planned target; the live code in `cmd/ze/hub/main.go` is authoritative.
9+
310
This document describes the migration from direct reactor startup to Engine-supervised
411
startup with Bus integration, completing the arch-0 component boundary work.
512

6-
## Current Architecture
13+
## Pre-Migration Architecture
714

8-
The reactor is created and started directly by `cmd/ze/hub/main.go`. It holds a
15+
The reactor was created and started directly by `cmd/ze/hub/main.go`. It held a
916
`*pluginserver.Server` and `*EventDispatcher` for plugin communication. The Engine,
10-
Bus, and Subsystem interface exist but are not wired into the startup path.
17+
Bus, and Subsystem interface existed but were not wired into the startup path.
1118

1219
```mermaid
1320
flowchart TB

docs/guide/bfd.md

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,11 @@ packet sets `RequiredMinEchoRxInterval` to the configured rate.
110110
Peers that see a non-zero advertisement learn that the local end
111111
is willing to reflect echo packets.
112112

113-
**Current coverage:** the YANG surface, wire advertisement, and
114-
session state plumbing ship in Stage 6. The actual echo transport
115-
(UDP 3785 socket, per-session TX scheduler, RX demux, RTT
116-
histogram, detection-time switchover, async slow-down) is tracked
117-
as `spec-bfd-6b-echo-transport`. Configurations written against
118-
the Stage 6 surface remain valid when the transport half lands.
119-
The `ze_bfd_echo_tx_packets_total` and `ze_bfd_echo_rx_packets_total`
120-
metric families are registered now so downstream alerting can
121-
reference them from day one, even though the counters stay at
122-
zero until the transport half lands.
113+
**Current coverage:** echo mode is fully implemented including the
114+
YANG surface, wire advertisement, per-session TX scheduler, RX
115+
demux with RTT measurement, echo detection timer, and async
116+
slow-down (RFC 5880 §6.8.9). The `ze_bfd_echo_tx_packets_total`
117+
and `ze_bfd_echo_rx_packets_total` Prometheus metrics are live.
123118

124119
### Authentication
125120

docs/guide/status.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ Over 40 built-in plugins covering protocol features, all address families, BFD,
7777

7878
| Type | Count |
7979
|------|-------|
80-
| Unit test functions | 18,000+ |
81-
| Functional test files (.ci) | 417 |
82-
| Fuzz targets | 121 |
80+
| Unit test functions | 10,400+ |
81+
| Functional test files (.ci) | 789 |
82+
| Fuzz targets | 57 |
8383
| Linters | 26 |
8484

85-
All tests run with the race detector enabled.
85+
Unit tests run with the race detector enabled (`-race`). Functional, browser, and compatibility suites do not currently use the race detector.
8686
<!-- source: Makefile -- ze-unit-test, ze-functional-test, ze-lint, ze-fuzz-test targets -->
8787

8888
## What Does NOT Work Yet

docs/guide/tacacs.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,18 @@ queued after `Stop()` are dropped silently.
115115

116116
## Verification
117117

118-
The four `.ci` tests in `test/plugin/` cover the main behaviours:
118+
The `.ci` tests in `test/plugin/` cover the main behaviours:
119119

120120
| Test | Asserts |
121121
|------|---------|
122122
| `tacacs-auth.ci` | TACACS+ PASS + priv-lvl 15 -> admin profile, no local fallback consulted |
123+
| `tacacs-author.ci` | TACACS+ command authorization PASS/FAIL with local fallback |
123124
| `tacacs-fallback.ci` | Server unreachable -> local bcrypt accepted, log shows `source=local` |
124125
| `tacacs-local-only.ci` | No `tacacs` block -> existing local-only auth path unchanged |
126+
| `tacacs-readonly.ci` | Read-only profile restricts write commands |
125127
| `tacacs-acct.ci` | `accounting true` -> mock receives ACCT START followed by STOP |
128+
| `tacacs-singleconnect.ci` | Single-connect mode TCP reuse |
129+
| `tacacs-show.ci` | `ze tacacs show` offline config display |
126130

127131
For ad-hoc verification, point the daemon at a real TACACS+ server and
128132
run any command via `ze cli -c "summary"` -- the daemon log tags the
@@ -145,12 +149,10 @@ local bcrypt user accepted the credentials.
145149
replaces them with `/* SECRET-DATA */`.
146150
- **VRF**: when the SSH server runs in a non-default VRF, TACACS+ TCP
147151
connections inherit the same VRF context.
148-
- **Single-connect mode** (RFC 8907 §4.4) is not negotiated today; every
149-
AUTHEN/AUTHOR/ACCT exchange opens its own TCP connection. This is
150-
compatible with every server we have tested.
151-
- **Operational tooling** (`ze show tacacs` per-server reachability +
152-
counters) is tracked in `plan/deferrals.md` against
153-
`spec-tacacs-observability`.
152+
- **Single-connect mode** (RFC 8907 §4.4) is tested via `tacacs-singleconnect.ci`.
153+
- **Operational tooling**: `ze tacacs show <config>` displays the parsed
154+
TACACS+ configuration offline. Runtime `ze show tacacs` per-server
155+
reachability and counters are tracked in `plan/deferrals.md`.
154156

155157
## RFC reference
156158

internal/component/resolve/dns/resolver.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ import (
1717

1818
// ResolverConfig holds DNS resolver configuration from YANG.
1919
type ResolverConfig struct {
20-
Server string // DNS server address (e.g., "8.8.8.8:53"). Empty uses system default.
21-
Timeout uint16 // Query timeout in seconds.
22-
CacheSize uint32 // Max cached entries. 0 disables caching.
23-
CacheTTL uint32 // Max cache TTL in seconds. 0 means use response TTL only.
20+
Server string // DNS server address (e.g., "8.8.8.8:53"). Empty uses system default.
21+
ResolvConfPath string // Path to resolv.conf (empty uses /etc/resolv.conf).
22+
Timeout uint16 // Query timeout in seconds.
23+
CacheSize uint32 // Max cached entries. 0 disables caching.
24+
CacheTTL uint32 // Max cache TTL in seconds. 0 means use response TTL only.
2425
}
2526

2627
// Resolver provides DNS query services to Ze components.
@@ -47,8 +48,11 @@ func NewResolver(cfg ResolverConfig) *Resolver {
4748
server = net.JoinHostPort(server, "53")
4849
}
4950
} else {
50-
// Resolve system default DNS server once at construction.
51-
server = resolveSystemDNS()
51+
resolvPath := cfg.ResolvConfPath
52+
if resolvPath == "" {
53+
resolvPath = "/etc/resolv.conf"
54+
}
55+
server = resolveSystemDNS(resolvPath)
5256
}
5357

5458
return &Resolver{
@@ -65,8 +69,8 @@ func NewResolver(cfg ResolverConfig) *Resolver {
6569
// resolveSystemDNS reads the system DNS server from /etc/resolv.conf.
6670
// Falls back to 8.8.8.8:53 (Google Public DNS) if the file is missing or empty,
6771
// so DNS resolution always works out of the box.
68-
func resolveSystemDNS() string {
69-
config, err := mdns.ClientConfigFromFile("/etc/resolv.conf")
72+
func resolveSystemDNS(resolvConfPath string) string {
73+
config, err := mdns.ClientConfigFromFile(resolvConfPath)
7074
if err != nil || len(config.Servers) == 0 {
7175
return "8.8.8.8:53"
7276
}

internal/component/web/auth.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ func AuthMiddleware(store *SessionStore, authenticator authz.Authenticator, logi
198198
}
199199

200200
// Unauthenticated: return 401 without WWW-Authenticate header.
201+
addSecurityHeaders(w)
201202
w.WriteHeader(http.StatusUnauthorized)
202203
loginRenderer(w, r)
203204
})
@@ -226,6 +227,7 @@ func LoginHandler(store *SessionStore, authenticator authz.Authenticator, loginR
226227
})
227228
if err != nil || !result.Authenticated {
228229
logger.Warn("login failed", "username", username, "remote", r.RemoteAddr)
230+
addSecurityHeaders(w)
229231
w.WriteHeader(http.StatusUnauthorized)
230232
loginRenderer(w, r)
231233

0 commit comments

Comments
 (0)