Skip to content

feat: secure the NATS control plane - #194

Merged
paullatzelsperger merged 5 commits into
mainfrom
feature/s1-secure-nats-control-plane
Oct 30, 2026
Merged

paullatzelsperger merged 5 commits into
mainfrom
feature/s1-secure-nats-control-plane

Conversation

@paullatzelsperger

@paullatzelsperger paullatzelsperger commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What this PR changes

Every message on the NATS bus is trusted by CFM components, yet the bus was unauthenticated and unencrypted by default. This PR hardens the control plane, one commit per change:

  • Refuse anonymous NATS in production mode. Components fail to start when nats.auth.method is unset or none unless nats.auth.allowAnonymous=true. Development and debug modes are unchanged.
  • TLS and mutual TLS. New nats.tls.enabled, nats.tls.ca, nats.tls.cert and nats.tls.key options, integration-tested against a broker that requires mTLS.
  • Only apply responses for orchestrations TM started. TM records the pending manifest on the participant profile and ignores responses that do not match it, including replays. PM now links auto-compensation orchestrations to the deploy they compensate (compensatedManifestId) so TM can accept them.
  • Least-privilege permissions. docs/developer/nats/ documents securing the bus and ships a sample NATS authorization block with one user per component. Two e2e tests run the deploy, dispose and key-event flows against that file.

Notes for reviewers

  • Upgrade: participant profiles that are mid-orchestration when TM is upgraded have no recorded manifest, so their responses will be ignored.
  • Orchestration agents (not only PM) publish orchestration responses, so they keep that permission; see "Known limitations" in the docs.
  • PM's KV index watcher acks core $KV.<bucket>.> messages, which sends +ACK to the KV writer's reply inbox. The sample permissions deny it; it should go away when the watcher moves to a JetStream consumer.

🤖 Generated with Claude Code

paullatzelsperger and others added 4 commits October 30, 2026 12:25
Every message on the NATS bus is trusted, so connecting without
authentication must be an explicit choice. In production mode,
components now fail to start when nats.auth.method is unset or "none"
unless nats.auth.allowAnonymous is set to true. Development and debug
modes keep allowing anonymous connections.

Tests that run the production launchers against an unsecured broker
opt in via the new flag.

Refs: S1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q43ySpqd18vfAt3S6pk7UR
Connections to the broker could not be encrypted because nats.Connect
was called without any TLS option. Components now read
nats.tls.enabled, nats.tls.ca, nats.tls.cert and nats.tls.key: setting
any of them enables TLS, a CA file replaces the system trust store, and
a client certificate and key enable mutual TLS. File paths are checked
at startup so misconfiguration fails fast.

The NATS test fixture gains SetupNatsContainerWithConfig to start a
broker with arbitrary server configuration and files, which the new
integration tests use to verify mTLS against a real server.

Refs: S1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q43ySpqd18vfAt3S6pk7UR
The Tenant Manager applied any orchestration response whose
correlationId matched a participant profile, so anyone on the bus could
mark deployments active, overwrite VPA state data, or replay old
responses.

TM now records the manifest ID and type of the orchestration it starts
on the participant profile (cfm.orchestration.pending) and ignores
responses that do not match it. A caller-supplied value for that
property is overwritten on deploy. The record is cleared once a
successful response is applied, so duplicates and replays are dropped.

Auto-compensation orchestrations are started by the Provision Manager,
not TM, so PM now records which orchestration a compensation
compensates and includes it in the response as compensatedManifestId.
TM accepts a dispose response that compensates its pending deploy. The
manifest field is not deserialized, so external callers cannot claim to
compensate an orchestration.

Refs: S1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q43ySpqd18vfAt3S6pk7UR
Authentication alone still lets every component publish anything on the
bus. Adds docs/developer/nats with a guide to securing the control plane
(authentication, TLS, per-component users and known limitations) and a
sample NATS authorization block giving the Tenant Manager, Provision
Manager, an orchestration agent, the key management agent and
IdentityHub only the subjects they need. Only TM may start
orchestrations and only IdentityHub may publish key pair events.

Two e2e tests run the deploy, dispose and key event flows against the
sample file and check that forbidden publishes are rejected, so the
documentation stays in sync with the subjects the code uses.

Refs: S1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q43ySpqd18vfAt3S6pk7UR
@paullatzelsperger
paullatzelsperger force-pushed the feature/s1-secure-nats-control-plane branch from 21ecabe to 56c4318 Compare October 30, 2026 11:26
Test_VerifyAutoCompensation only waited for PM and then called TM, which
fails with "connection refused" when TM's HTTP server is not listening
yet. Wait for TM as well in the compensation tests and in
verifyDeployAndDispose.

waitTManager shadowed err, so it never reported a timeout. Fix that and
pause between attempts in both wait helpers instead of spinning.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@paullatzelsperger
paullatzelsperger merged commit c3c1cd4 into main Oct 30, 2026
5 checks passed
@paullatzelsperger
paullatzelsperger deleted the feature/s1-secure-nats-control-plane branch October 30, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants