Context
SIGHUP currently reloads only the leaf server certificate via the dynamic GetCertificate callback. Two other pieces of TLS material are snapshotted at process start and stay frozen for the life of the process:
-
ca-cert / client CA pool. NewServerTLSConfig populates tls.Config.ClientCAs from a []byte slice fetched once at startup (see services/api/grpc/service.go::createServer). Rotating ca.crt to add or revoke a client CA and sending SIGHUP refreshes the server cert but leaves the boot-time ClientCAs in place. Newly authorised clients fail to authenticate; revoked CAs keep working. The cluster ends up half-rotated with no operator-visible signal that a full restart is required.
-
DKG client credentials. NewGRPCClientCredentials snapshots the client cert manager's tls.Config once at sender construction. That TransportCredentials value is closure-captured inside obtainConnection's puddle.NewPool constructor, so every cached pool keeps dialing peers with the original credentials. There is no path to refresh the outbound DKG identity — once the cert nears expiry, every handshake fails with "certificate has expired" while disk shows a valid replacement cert.
The operational consequence — "rotation requires a full Dirk restart, SIGHUP is not enough" — is now documented in docs/configuration.md, but documentation alone is a workaround, not a fix. A full restart drops every connected Vouch/validator and costs missed attestations.
Proposed refresh path
CA pool
Plumb tls.Config.VerifyPeerCertificate against a swappable *x509.CertPool held in an atomic.Pointer. Cert manager fires a reload hook → the pool is rebuilt from the fresh PEM → the next handshake reads the new pool through the verifier. Cleaner than rebuilding the tls.Config because verification happens per handshake; no connection teardown needed.
DKG client credentials
Harder. Requires draining the puddle connection pools and re-instantiating s.credentials once a new client cert is loaded. Gated on the client cert manager firing a reload hook (which today does not exist — the client cert manager has no SIGHUP wiring at all). Steps:
- Wire the client cert manager into the SIGHUP handler in
main.go.
- Add a reload callback on the sender that swaps
s.credentials and drains affected puddle pools.
- Confirm in-flight RPCs survive the drain.
Scope
One issue, two scopes (CA pool + DKG client credentials) — they share the "TLS material frozen at startup, no refresh path" root cause. Track separately if work splits naturally.
Refs
Follow-up to PR #91 (use-go-certmanager).
Context
SIGHUP currently reloads only the leaf server certificate via the dynamic
GetCertificatecallback. Two other pieces of TLS material are snapshotted at process start and stay frozen for the life of the process:ca-cert/ client CA pool.NewServerTLSConfigpopulatestls.Config.ClientCAsfrom a[]byteslice fetched once at startup (seeservices/api/grpc/service.go::createServer). Rotatingca.crtto add or revoke a client CA and sending SIGHUP refreshes the server cert but leaves the boot-timeClientCAsin place. Newly authorised clients fail to authenticate; revoked CAs keep working. The cluster ends up half-rotated with no operator-visible signal that a full restart is required.DKG client credentials.
NewGRPCClientCredentialssnapshots the client cert manager'stls.Configonce at sender construction. ThatTransportCredentialsvalue is closure-captured insideobtainConnection'spuddle.NewPoolconstructor, so every cached pool keeps dialing peers with the original credentials. There is no path to refresh the outbound DKG identity — once the cert nears expiry, every handshake fails with"certificate has expired"while disk shows a valid replacement cert.The operational consequence — "rotation requires a full Dirk restart, SIGHUP is not enough" — is now documented in
docs/configuration.md, but documentation alone is a workaround, not a fix. A full restart drops every connected Vouch/validator and costs missed attestations.Proposed refresh path
CA pool
Plumb
tls.Config.VerifyPeerCertificateagainst a swappable*x509.CertPoolheld in anatomic.Pointer. Cert manager fires a reload hook → the pool is rebuilt from the fresh PEM → the next handshake reads the new pool through the verifier. Cleaner than rebuilding thetls.Configbecause verification happens per handshake; no connection teardown needed.DKG client credentials
Harder. Requires draining the puddle connection pools and re-instantiating
s.credentialsonce a new client cert is loaded. Gated on the client cert manager firing a reload hook (which today does not exist — the client cert manager has no SIGHUP wiring at all). Steps:main.go.s.credentialsand drains affectedpuddlepools.Scope
One issue, two scopes (CA pool + DKG client credentials) — they share the "TLS material frozen at startup, no refresh path" root cause. Track separately if work splits naturally.
Refs
Follow-up to PR #91 (use-go-certmanager).