Repository navigation
feat: node → controller transport (ADR 0010) - #22
Merged
Merged
Conversation
…er receives (ADR 0010) Wires the node→controller inventory channel end to end at the transport layer; the per-workload join and payload land in the next slice. Node role: - New -controller-endpoint and -token-path flags. When an endpoint is set, each scan pass POSTs a nodescan.Report (node name, kept binaries, counters) to the controller, authenticated with a projected controller-audience ServiceAccount token read fresh per send (rotation). No endpoint → log-only, unchanged. - Delivery is best-effort: a failure is logged and the next pass retries; the controller rebuilds inventory from re-scans, so a lost report costs nothing. Controller: - internal/nodeintake: an HTTP receiver that verifies the bearer token locally via nodeauth (cluster JWKS, no TokenReview), decodes the report (size-capped, unknown fields and trailing data rejected), and hands it to a callback. The response is ack/error only — never configuration or commands (one-way, ADR 0001, one level down). For this slice the callback logs aggregates. - config.NodeIntake gates the receiver (off by default, so a controller-only install opens no port); the JWKS client reuses the API server's in-cluster REST transport. Manifest: the node DaemonSet gains a projected serviceAccountToken volume (audience-bound to the controller, tmpfs, rotated), a NODE_NAME downward-API env, and the -controller-endpoint arg. automountServiceAccountToken stays false: the default API token is still never mounted, and the controller-audience token cannot reach the API server. security.md: §2/§4/§5/§7/§9 updated for the new in-cluster flow — the controller's receiver, local token validation (no new RBAC verb), the projected controller-audience token that is not an API credential, and the honest cleartext-HTTP tradeoff. Unit tests: receiver auth/decoding (accept, missing/bad token, malformed, unknown fields, trailing data, oversize, wrong method); node shipper (bearer from token file, per-send rotation, non-2xx and missing-token errors). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Slice 3a: the node→controller inventory channel wired end to end at the transport layer. The per-workload join and the
go_inventorypayload land in the next slice (3b).Node role
-controller-endpoint/-token-pathflags. With an endpoint set, each scan pass POSTs anodescan.Report(node, kept binaries, counters) to the controller, authenticated with a projected controller-audience ServiceAccount token read fresh per send (rotation-safe). No endpoint → log-only, unchanged (existing node e2e and unit test stay green).Controller
internal/nodeintake: HTTP receiver that verifies the bearer token locally vianodeauth(cluster JWKS, noTokenReview), decodes the report (size-capped, unknown-fields + trailing-data rejected), hands it to a callback. Response is ack/error only — one-way (ADR 0001) one level down. This slice's callback logs aggregates.config.NodeIntakegates the receiver (off by default → controller-only installs open no port); the JWKS client reuses the API server's in-cluster REST transport.Manifest
Node DaemonSet gains a projected
serviceAccountTokenvolume (audience-bound, tmpfs, rotated), aNODE_NAMEdownward-API env, and the-controller-endpointarg.automountServiceAccountTokenstays false — the default API token is still never mounted, and the controller-audience token can't reach the API server. Validated by decoding with real k8s types.security.md
§2 / §4 / §5 / §7 / §9 updated: the controller receiver, local token validation (no new RBAC verb), the projected token that is not an API credential, and the honest cleartext-HTTP tradeoff (bounded, one-way, no-money — ADR 0008 blast-radius reasoning).
Tests
Receiver: accept, missing/bad token, malformed, unknown fields, trailing data, oversize, wrong method. Shipper: bearer from token file, per-send rotation, non-2xx and missing-token errors.
Note on e2e
Touches cluster-facing code (node manifest). I'll run
make node-e2elocally against kind before merge to confirm the manifest change doesn't break DaemonSet startup — pending Docker being up.🤖 Generated with Claude Code