rqlite adds mutual TLS to HTTP and node connections
rqlite 7.14.0 adds client certificate verification to both connection types. Its TLS refactoring makes the trust settings and the distinction from request authorization explicit.
An encrypted database connection still leaves a question about who may initiate it. A server certificate lets a client verify the server it reached. Requiring a trusted client certificate adds a check in the other direction. The rqlite 7.14.0 release, published on March 9, adds that mutual TLS support to both its HTTP connections and its traffic between nodes.
Those two connection types need separate treatment. An application contacts an HTTP endpoint to submit work, while nodes also communicate through the node transport. Protecting only the application-facing listener would leave the second connection type under a different policy. The release supplies independent flags and certificate settings for each, so a deployment can express both policies directly.
I value rqlite's relatively small operational footprint. Certificate authentication is useful within that model when the configuration remains explicit enough to inspect. The implementation in this release is worth reading because it shows exactly where the checks enter the connection path, and where their responsibility ends.
The certificate requirement is an explicit choice
The release's flag definitions introduce -http-verify-client for HTTPS and -node-verify-client for node communication. Both default to false. Supplying certificates enables the relevant TLS listener, but the additional verification flag determines whether that listener requires a certificate from its clients.
For HTTPS, the certificate and key options are -http-cert and -http-key. The CA option is -http-ca-cert. Their node equivalents are -node-cert, -node-key, and -node-ca-cert. Each set describes its own connection type. Configuring one set should not be taken as evidence that the other listener now requires mutual authentication.
The same definitions retain -node-encrypt, but describe it as ignored. Certificate and key settings now control node encryption. That matters when reading an existing startup command, because an old boolean switch can remain present without expressing the active policy. The paths to the certificate material and the verification flag are the relevant settings in this version.
Validation requires a certificate and key to be set together for each connection type. The error text still uses older names in places, while the registered flags use the names above. The implementation therefore offers a more reliable reference for a version-specific command than assuming every diagnostic repeats the current option spelling.
The HTTP server requires a verified certificate
The HTTP service receives the configured certificate paths and the HTTP client-verification flag from startup. If certificate or key material is absent, its listener follows the ordinary TCP path. With both present, it builds a TLS server configuration and calls tls.Listen.
The new TLS configuration package implements the certificate requirement. CreateServerConfig() loads the server key pair and, when supplied, creates a client CA pool from the CA file. When client verification is enabled, it sets Go's tls.RequireAndVerifyClientCert policy. The result requires the connecting client to provide a certificate that passes verification.
This is stronger than requesting a certificate and accepting the connection regardless of its validity. The policy is part of the TLS handshake configuration. An HTTP handler does not first receive an ordinary unauthenticated request and then decide whether the client remembered to attach certificate information in a header.
The configuration also sets a minimum TLS version of 1.2 by default. The existing -tls1011 compatibility flag permits the older versions by lowering that minimum. Its name makes the compatibility choice visible in the command, and it remains separate from whether a client certificate is mandatory.
A local node can enable both certificate checks with a command of the following form. The file paths are illustrative and assume that the certificate material has already been issued for the intended server and client uses.
rqlited \
-http-cert /etc/rqlite/http.pem \
-http-key /etc/rqlite/http-key.pem \
-http-ca-cert /etc/rqlite/http-ca.pem \
-http-verify-client \
-node-cert /etc/rqlite/node.pem \
-node-key /etc/rqlite/node-key.pem \
-node-ca-cert /etc/rqlite/node-ca.pem \
-node-verify-client \
/var/lib/rqlite
This example describes TLS policy for one node. It does not configure additional cluster members or issue certificates. Its ordinary listener defaults remain in effect because no bind or advertised addresses are supplied. A deployment that exposes those listeners elsewhere must also supply the appropriate address configuration and certificates.
A node is both a server and a client
Node communication needs a configuration that works in both directions. In the startup code, a configured node certificate selects tcp.NewTLSMux. That constructor receives the node certificate, key, CA file, and verification choices. Its multiplexer implementation wraps the listener with TLS and passes the TLS configuration into the dialer used by its transport layers.
The shared TLS constructor loads the key pair and places the supplied CA certificates into both RootCAs and ClientCAs. Those pools have different purposes. Outgoing connections need roots for verifying the remote server. Incoming connections need trusted issuers for verifying the connecting client. A node that participates in both roles needs both settings represented.
The constructor sets ClientAuth to RequireAndVerifyClientCert when mutual authentication is selected. Its certificate collection also supplies the credentials needed when the same node acts as a client. This explains why the change extends beyond adding a server-side boolean. The dialer must carry a configuration capable of presenting credentials and validating its peer.
The TCP dialer now accepts a complete TLS configuration. A nil configuration selects ordinary TCP. A non-nil configuration selects tls.DialWithDialer. Previously separate encryption and verification booleans could not carry all the certificate and CA information needed by the new behavior. Passing the configuration connects those settings to the actual outgoing connection.
Cluster joining has an HTTP connection too
A node joining the cluster also acts as an HTTP client. The startup code creates that client configuration from the HTTP certificate settings, including the key pair and CA file. The joiner receives it, so HTTPS cluster joining can participate in the same certificate-authentication policy as the server it contacts.
Pull request 1171 also changes the bootstrapper's handling of its supplied TLS configuration. The previous constructor began with a configuration that skipped server verification and replaced it when the caller supplied one. The new constructor takes the provided configuration directly. Both the bootstrap notification and joining code set up their HTTP transport using the selected configuration.
This is an important part of the release's broader TLS work. Supporting certificates on a listener is insufficient if the program's own client connections cannot use the matching credentials and trust roots. The joining path needs the same attention as steady-state communication, because a node must establish membership before it can contribute to the database.
I prefer this explicit separation of settings because it makes the scope of trust reviewable. The HTTP CA and node CA may have different operational ownership. The code does not require someone reading a command to infer that one listener's settings automatically apply to every other connection the process makes.
Certificate trust and request permissions differ
The startup code still loads the authentication file separately and supplies its credential store to the HTTP service. The -auth flag remains an independent option for authentication and authorization configuration. Nothing in the new TLS constructor maps certificate subjects onto that credential store's users or permissions.
Consequently, the supported claim is that mutual TLS restricts connections to peers with acceptable certificates. It does not establish a new certificate-to-SQL-permission mapping. A deployment that also needs request-level authorization must account for that separately, using the mechanisms the HTTP service actually implements.
Trust in a CA also defines the set of credentials that can pass the certificate check. Sharing an issuer is an operational decision about which peers belong in that set. The implementation loads a CA pool and asks TLS to verify certificates against it. It does not introduce a separate list of permitted node certificate subjects in this change.
One implementation detail deserves attention when checking a node's startup output. In this release's startNodeMux(), the log message says mutual TLS is disabled when NodeVerifyClient is true, and enabled when it is false. The value passed into the TLS multiplexer follows the flag directly, and the shared constructor uses that value to require client certificates. That particular log message therefore reverses the actual setting. The HTTP service's corresponding log message follows its verification flag correctly.
The changelog describes both mutual TLS support and corrections to TLS and CA configuration. Reading the connection constructors gives that description a concrete meaning. Each listener has its own certificate requirement, outgoing connections carry the relevant trust configuration, and HTTP joining uses the HTTP credentials. For an operator reading this release's configuration, those are the specific places where certificate authentication now applies.