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
derphttp,magicsock,netcheck: add TLSConfigBypassesTLSDial opt-in flag
Today derphttp.Client.tlsConfig always passes the caller-supplied
TLSConfig through tlsdial.Config, which wraps it with a VerifyConnection
hook that runs system-root verification with a baked-in Let's Encrypt
fallback. tlsdial.Config also panics on base configs that already set
InsecureSkipVerify or VerifyConnection.
That contract works well when DERP is reachable directly over a publicly
trusted PKI, but it breaks for callers who legitimately need to perform
their own server verification — for example when DERP is fronted by a
reverse proxy that presents a non-publicly-trusted certificate, or when
authenticating with an mTLS framework that uses custom CAs / SPIFFE-style
identity / app-name verification (i.e. the standard Go pattern of
InsecureSkipVerify=true paired with a custom VerifyPeerCertificate).
This adds an opt-in TLSConfigBypassesTLSDial bool. When true (and
TLSConfig is non-nil), the supplied config is used as-is after a Clone +
ServerName fallback. tlsdial.Config is bypassed entirely. node-level
InsecureForTests is still honored; node.CertName (a tlsdial-specific
domain-fronting hook) is intentionally ignored on the bypass path —
callers bringing their own verifier are expected to encode any cert
pinning in their own VerifyPeerCertificate / VerifyConnection. The doc
comment also explicitly warns that with bypass=true the supplied
TLSConfig is the sole source of server verification.
Plumbing:
- derphttp.Client.TLSConfigBypassesTLSDial bool.
- magicsock.Conn stores the (TLSConfig, bypass) pair as a single
atomic.Pointer[derpTLSPair] so reconnects observe a coherent pair
and never see a custom-verifier config briefly paired with the
stale bypass=false (which would panic) nor a public config briefly
paired with a stale bypass=true (which would silently skip server
verification).
- SetDERPTLSConfig(cfg) is preserved (existing public API): it sets
cfg with bypass=false in one atomic store.
- SetDERPTLSConfigBypassesTLSDial(v) does a load-modify-store
(serialized via derpTLSConfigSetMu) that preserves the current cfg.
- SetDERPTLSConfigWithBypass(cfg, bypass) is the recommended setter
for callers that want both fields set together.
- magicsock/derp.go reads the pair atomically and propagates both
onto the constructed derphttp.Client.
- netcheck.Client gains DERPTLSConfigBypassesTLSDial alongside the
existing DERPTLSConfig field, propagated to the netcheck DERP
probe client so health checks / `coder netcheck` don't panic when
DERPTLSConfig is a custom-verifier config.
Backward compatible: bypass=false (the default) preserves the existing
behavior and existing callers see no change. Existing
SetDERPTLSConfig(cfg) callers continue to work; their effective bypass
state is false.
Test coverage:
- derp/derphttp/derphttp_test.go: TestTLSConfigBypassesTLSDial covers
the default path, the bypass path, ServerName behavior, the
nil-config fallback, and node.InsecureForTests under bypass.
- wgengine/magicsock/derp_tls_pair_test.go: TestSetDERPTLSConfigPair
covers the legacy setter, the bypass-flag setter preserving the
cfg, the combined setter, and a -race-friendly concurrent
writers/readers test that verifies the (cfg, bypass) atomic-pair
invariant.
0 commit comments