You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Audited across crates/, helm/, docker-compose.yml, examples/ and docs/.
Plane
TLS today
Can it be turned off?
Verify toggle
Custom CA
Metadata Raft peer
mandatory mTLS
no
no
yes — ca is a required path
Metadata admin
mandatory mTLS
no
no
yes
Replication (leader↔follower)
mandatory mTLS
no
no
yes
Native client (produce/fetch)
mandatory mTLS
no
no
yes
Observability (/metrics, /healthz, /readyz)
none — plaintext only
n/a (cannot be enabled)
n/a
n/a
S3 / object store
optional
yes — verify_tls
partial
system trust store
PostgreSQL
required for remote
yes, loopback only
sslmode
sslrootcert
Evidence:
TlsPaths { ca, cert, key } — all three are non-optional PathBuf (crates/vtop-node/src/config.rs).
meta.tls and data.replica_tls are non-optional fields.
data.native_tls is Option, but a leader/standalone rejects None at startup: .ok_or("leader/standalone requires native_tls")?. The Option exists only because a follower serves no clients — not to allow plaintext.
grep -rniE "insecure|skip_verify|danger_accept|no_verify|accept_invalid" crates/ returns nothing. There is no verification toggle anywhere in the cluster planes.
No production code path binds a cluster listener without TLS; every plaintext TcpListener::bind is in a test.
So: the four cluster planes are TLS-mandatory with no configuration surface, the observability plane is plaintext with no way to secure it, and only the object-store client has the knobs. That is the inverse of a coherent design, and it is scattered across 37 TLS construction sites.
One thing to decide before building, because it reverses a documented decision
mTLS-only is not an accident of implementation — it is a stated security property:
docs/SECURITY_MODEL.md: "The native broker transport in vtop-broker is TLS 1.3 mTLS only."
And the existing verify_tls flag deliberately refuses the "disable verification" case:
Honest scope: this flag does NOT disable certificate verification for https:// endpoints… A self-signed or private-CA endpoint needs its CA in the system trust store; skipping verification is deliberately unsupported.
That stance is defensible: on the cluster planes the certificate CN is the authorization identity — a metadata node id, a broker UUID, a client principal. Disabling verification there does not merely weaken confidentiality, it removes authentication and authorization, because there would be nothing left binding a connection to an identity. Fencing, lease ownership and admin authorization all rest on it.
So the request splits into two parts with very different risk:
Plaintext mode (TLS off entirely) — coherent, and worth having for local development, CI, and single-node evaluation. Needs an explicit, loud, refuse-by-default opt-in.
TLS on but verification off — on these planes this is not "TLS without checks", it is "authentication removed while looking encrypted". If it is wanted, it should be scoped narrowly (a named test-only mode that refuses to start unless the listener is loopback, or gated behind a build feature) rather than a general config flag, and SECURITY_MODEL.md has to be amended to say so.
My recommendation: build (1) properly, and for (2) support custom CA / private PKI as the answer to the real underlying need (self-signed certs in test environments), which the codebase already treats as the correct path. If a true no-verify mode is still wanted after that, do it as its own decision with the doc change attached.
Proposed design
One TransportSecurity type, resolved once from configuration and threaded through, replacing the scattered TlsPaths requirements:
mode: mtls | tls | plaintext # default mtls; plaintext must be opt-in
ca: <path> # custom/private CA, per plane
cert, key: <path> # required unless plaintext
client_auth: required | none # `tls` mode = server auth only
verify: full | ca-only # hostname verification; NOT a way to skip it
Non-negotiables that fall out of the audit:
Refuse, never warn, on a downgrade the operator did not ask for. This is the pattern validate_endpoint_scheme already sets, and it is the right one.
Plaintext must be visible at runtime: a startup warning naming the plane, and a metric or /readyz detail an operator can alert on. A cluster silently running plaintext is worse than one that refuses.
A plaintext plane must refuse to talk to a TLS peer and vice versa, with an error naming both sides. Half-configured is the state that produces unexplainable handshake failures.
Per plane, not global. Plaintext client access with mTLS replication is a legitimate shape; one global switch cannot express it.
Observability gains the same surface so /metrics can be TLS where required. Today it cannot be, which is a real gap in the opposite direction (security: metrics endpoint spawns an unbounded task per connection #78 notes it is unauthenticated by design, but "no option" is different from "off by default").
Environment-variable overrides for every field, matching the VTOP_S3_VERIFY_TLS precedent, so a container can be configured without rewriting a mounted file.
Deployment surfaces, all four
Bare metal — the config file is the source of truth; document a plaintext single-node quickstart, since there is currently no way to run the engine at all without minting certificates.
Docker / compose — docker-compose.yml does not deploy vtop-node at all today, so the cluster planes are unexercised there. A plaintext compose profile is the cheapest way to make the engine runnable in the lab.
Helm — tls.metaSecretName / tls.dataSecretName are required and the chart refuses to render without them (security: lab compose binds to all interfaces with default credentials #81, deliberately). Under a plaintext mode they must become conditional, and the refusal must still fire whenever mode is not plaintext.
Kubernetes — the per-pod certificate CN convention is load-bearing for identity; plaintext mode has to explicitly drop the SAN/CN requirements rather than leave them half-checked.
Acceptance
TransportSecurity resolved from config + env, one type, no per-plane ad-hoc requirements
plaintext runs a full cluster: metadata quorum, replication, produce/fetch, observability
Mixed plaintext/TLS between peers is refused with an error naming both sides
Custom CA honoured on every plane, verified against a private PKI in a test
Helm renders and deploys both modes; k8s-smoke.sh covers plaintext as a variant
Compose gains a runnable plaintext cluster profile
SECURITY_MODEL.md states the modes, the defaults, and what each gives up
Enabling or disabling TLS changes no other behaviour — the existing suites pass in both modes
Milestone v0.4.0 (hardening), since v0.3.0 is gated on the durability arc.
What is true today
Audited across
crates/,helm/,docker-compose.yml,examples/anddocs/.cais a required path/metrics,/healthz,/readyz)verify_tlssslmodesslrootcertEvidence:
TlsPaths { ca, cert, key }— all three are non-optionalPathBuf(crates/vtop-node/src/config.rs).meta.tlsanddata.replica_tlsare non-optional fields.data.native_tlsisOption, but a leader/standalone rejectsNoneat startup:.ok_or("leader/standalone requires native_tls")?. TheOptionexists only because a follower serves no clients — not to allow plaintext.grep -rniE "insecure|skip_verify|danger_accept|no_verify|accept_invalid" crates/returns nothing. There is no verification toggle anywhere in the cluster planes.TcpListener::bindis in a test.So: the four cluster planes are TLS-mandatory with no configuration surface, the observability plane is plaintext with no way to secure it, and only the object-store client has the knobs. That is the inverse of a coherent design, and it is scattered across 37 TLS construction sites.
One thing to decide before building, because it reverses a documented decision
mTLS-only is not an accident of implementation — it is a stated security property:
docs/SECURITY_MODEL.md: "Native client ↔ broker | TLS 1.3 with mandatory client certificates"docs/SECURITY_MODEL.md: "The native broker transport invtop-brokeris TLS 1.3 mTLS only."And the existing
verify_tlsflag deliberately refuses the "disable verification" case:That stance is defensible: on the cluster planes the certificate CN is the authorization identity — a metadata node id, a broker UUID, a client principal. Disabling verification there does not merely weaken confidentiality, it removes authentication and authorization, because there would be nothing left binding a connection to an identity. Fencing, lease ownership and admin authorization all rest on it.
So the request splits into two parts with very different risk:
SECURITY_MODEL.mdhas to be amended to say so.My recommendation: build (1) properly, and for (2) support custom CA / private PKI as the answer to the real underlying need (self-signed certs in test environments), which the codebase already treats as the correct path. If a true no-verify mode is still wanted after that, do it as its own decision with the doc change attached.
Proposed design
One
TransportSecuritytype, resolved once from configuration and threaded through, replacing the scatteredTlsPathsrequirements:Non-negotiables that fall out of the audit:
validate_endpoint_schemealready sets, and it is the right one./readyzdetail an operator can alert on. A cluster silently running plaintext is worse than one that refuses./metricscan be TLS where required. Today it cannot be, which is a real gap in the opposite direction (security: metrics endpoint spawns an unbounded task per connection #78 notes it is unauthenticated by design, but "no option" is different from "off by default").VTOP_S3_VERIFY_TLSprecedent, so a container can be configured without rewriting a mounted file.Deployment surfaces, all four
docker-compose.ymldoes not deployvtop-nodeat all today, so the cluster planes are unexercised there. A plaintext compose profile is the cheapest way to make the engine runnable in the lab.tls.metaSecretName/tls.dataSecretNamearerequiredand the chart refuses to render without them (security: lab compose binds to all interfaces with default credentials #81, deliberately). Under a plaintext mode they must become conditional, and the refusal must still fire whenevermodeis not plaintext.Acceptance
TransportSecurityresolved from config + env, one type, no per-plane ad-hoc requirementsplaintextruns a full cluster: metadata quorum, replication, produce/fetch, observabilityk8s-smoke.shcovers plaintext as a variantSECURITY_MODEL.mdstates the modes, the defaults, and what each gives upMilestone v0.4.0 (hardening), since v0.3.0 is gated on the durability arc.