TessariDBDocs
  • 0.34.2-betalatest
  • 0.34.1-beta
  • 0.34.0-beta
  • 0.33.4-beta
  • 0.33.3-beta
  • 0.33.2-beta
  • 0.33.1-beta
  • 0.33.0-beta
  • 0.32.0-beta

Encryption in transit

TLS 1.3 for clients when the node is given a certificate, a switch that requires it, mutual TLS between peers always, certificates that renew without a restart, a revocation every node obeys, and a carried request that never carries a password.

From 0.21.0-beta a node speaks TLS on every client surface when it is given a certificate, and the nodes of a cluster speak only mutual TLS to one another. Nothing here needs anything standing in front of the node.

Clients

tessaridb /var/lib/tessaridb --serve 0.0.0.0:9080 --http 0.0.0.0:8000 \
    --tls-cert node.pem --tls-key node.key

With a certificate chain and its key (PEM; or TESSARIDB_TLS_CERT and TESSARIDB_TLS_KEY), the wire port, HTTP and the WebSocket at /wire all speak TLS 1.3, and nothing else: there is no port that answers both ways, so a client that greets in the clear is refused at the handshake rather than answered in the clear, and a client limited to TLS 1.2 fails the handshake. One certificate serves both surfaces. Session tickets are off, so forward secrecy is not traded for resumption.

The key file must be readable by its owner alone (from 0.29.0-beta). A node refuses to start when --tls-key or --cluster-key names a file anybody else may read, and says which file and its mode — chmod 600 node.key is the fix. It is the rule a private SSH key follows: a key every account on the host can read authenticates the node to nobody who can log in to it. One allowance, for a secret an orchestrator mounts: a file owned by root may also be readable by its group (mode 640 or 440), so a node running as another user in that group can read it.

Over TLS every HTTP answer also carries Strict-Transport-Security, so a browser that reached the console once over TLS does not fall back to the clear. Every answer, with or without TLS, carries a content policy that keeps the console from being framed by another site, X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer.

Client TLS is opt-in, on a single node and on a cluster alike (from 0.23.0-beta). Without a certificate a node serves its clients in the clear and says so every time it starts, naming whether anybody beyond this machine can reach them:

tessaridb — clients in the clear, reachable beyond this machine: --tls-cert and --tls-key would encrypt them, and --require-client-tls refuses to start without them

INFO FOR NODE says the same under clients — { required: false, tls: false } — and the console shows a warning across the top of every page when it was reached in the clear from another machine. A password sent to such a node crosses the network as it was typed, so a node clients reach over a network you do not control is given a certificate.

Requiring TLS

A deployment whose policy forbids the clear starts its nodes with --require-client-tls (or TESSARIDB_REQUIRE_CLIENT_TLS=1). Such a node refuses to start without --tls-cert and --tls-key rather than serve one connection in the clear, and reports required: true. The switch is off by default; any value of the variable other than 1, 0 or empty is refused, so a misspelt yes cannot leave a policy unenforced.

Until 0.22.0-beta a node in a cluster refused to serve its clients in the clear unless --client-plaintext said so. The flag was accepted with a notice in 0.23.0-beta and is refused from 0.24.0-beta, as is TESSARIDB_CLIENT_PLAINTEXT=1: the clear is the default without a certificate, so drop either from a deployment that still carries it.

A console a browser trusts

A browser shows a warning page for a certificate it cannot chain to a root it trusts, which is every certificate a cluster's own authority issues. Three shapes avoid it:

  • Use the console from the machine itself, or over an SSH tunnel — ssh -L 8000:localhost:8000 db-1 and open http://localhost:8000. A browser treats localhost as a secure context, and nothing crosses a network in the clear.
  • Give the HTTP surface a publicly trusted certificate — one issued for the node's DNS name by a public authority, for example over ACME. The node re-reads its certificate files when they change, so a renewal needs no restart. Clients then need no authority file either: each trusts the operating system's store, and the prompt is given the system bundle, for example --tls-authority /etc/ssl/certs/ca-certificates.crt.
  • Put a TLS-terminating proxy in front of the HTTP port, with the node's HTTP bound to loopback (--http 127.0.0.1:8000) so nothing reaches it but the proxy.

The node never generates a certificate of its own: a self-signed one would bring back the same warning page, with a fingerprint to hand to every client.

Connecting over TLS

Every client verifies the node — the certificate chain against an authority you give it (or the operating system's store), and the name in the certificate against the host you dialled, a DNS name or an IP address. None of them has a switch that skips either check, and a failed handshake is an error of its own that is never retried, because nothing about a second attempt at the same node would differ.

clienttrusting an authority
the prompttessaridb --at db.example:9080 --user ada --tls-authority ca.pem (or TESSARIDB_TLS_AUTHORITY)
RustTls::trusting_pem(&pem)?, then Client::connect_tls(addr, &tls)
Pythontessaridb.tls_context("ca.pem"), passed as tls=
JavaScriptconnect({ …, tls: { ca } }) on the wire; the HTTP client uses the runtime's store
Gotessaridb.TrustPEM(pem), then DialTLS or NewHTTPClientTLS
KotlinTrust.fromPem(bytes), passed to connect or HttpSurface

The prompt trusts only the authority it is given and does not read the operating system's store. The clients that follow a redirect check each node a redirect sends them to the same way.

Between the nodes of a cluster

Peers speak their own protocol on their own port, and the connection is mutually authenticated TLS: each side presents a certificate issued by the cluster's authority for its node identifier, and the handshake does not complete against any other node. That is why a seed names a node as well as an address — an address alone is not something a node can dial.

tessaridb /var/lib/tessaridb --serve 0.0.0.0:9080 \
    --tls-cert node.pem --tls-key node.key \
    --cluster-credential db-3.pem --cluster-key db-3.key \
    --cluster-authority cluster-ca.pem \
    --cluster-address db-3.internal:9000 \
    --seed 9f2c4e1a70bb43d5a1c6e2f480937d55@db-1.internal:9000

The five cluster options are given together or not at all: told some of them a node refuses to start rather than serving with credentials nobody checked. A client certificate cannot be used as a peer's — the two are different certificate roles and the wrong one is refused at the handshake.

Renewing a certificate

A node re-reads its peer and client certificate files every two seconds, by their content, and presents a renewed pair from the next connection on; open connections finish on the one they started with. Nothing is restarted and no statement is run.

A pair that does not belong together — the certificate written and the key not yet — is refused, the previous certificate stays in use, and the node says so once; it judges the files again when they change. The cluster's authority is not reloaded: trusting a new root is a restart, planned with both roots in place.

INFO FOR NODE reports what the node presents at the moment of asking, so a renewal shows on the next report:

{"certificates": [{"surface": "peers",   "fingerprint": "0f1e2d3c…", "expires": "2027-01-04T00:00:00Z"},
                  {"surface": "clients", "fingerprint": "9a8b7c6d…", "expires": "2026-12-01T00:00:00Z"}]}

tessari_tls_certificate_expires_seconds{surface} on /metrics is the same date as a number, shown to a caller who may read the node's topology.

Revoking one

A certificate that leaked, or a machine retired before its certificate expired, is a member the cluster should stop admitting. Which certificates are refused is a cluster-wide fact, so it is a statement and a replicated row:

REVOKE CERTIFICATE '0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0';
  • The value is the certificate's SHA-256 fingerprint, 64 hexadecimal digits. The colon-separated, upper-case form openssl x509 -noout -fingerprint -sha256 prints is taken too; both are stored in lower case. Anything else is refused where it was written.
  • From the moment the row reaches a node, that node refuses the certificate in both directions of its peer link — at its door, and when it dials. Every node holds the row whatever it is subscribed to, and applies it within a few seconds of it arriving.
  • A connection already open ends too. A follower holds one connection to its leader for as long as both run, so both ends ask again before every frame, and the stream ends once its certificate — or its node — is refused.
  • Only an operator of the store may revoke, and the administration trail records who did. There is no un-revoke: a revoked certificate is replaced by a new one.

INFO FOR NODE lists the revoked fingerprints under cluster.revoked. An expired certificate needs no statement: no handshake accepts one, and a stream opened before it expired ends when it does.

Removing a whole node is DROP REPLICA, which refuses that node at the handshake whatever certificate it presents — see Adding a node.

A request carried for a client never carries its password

A node that cannot answer a request, and has a peer link to one that can, carries the request there for a caller that cannot follow a redirect itself. It travels under an assertion the carrying node signs with its peer key — who asked, the node asked, the script and its parameters, a single-use nonce and an expiry of at most thirty seconds. The answering node checks the signature against the certificate the same connection proved, refuses an assertion it has seen before, and checks the account against its own catalog, so a changed password or a dropped user no longer matches. Statements that change users, grants, membership or the failover policy are never carried. The routing is in Reads, routing and staleness.

A sign-in is not carried at all: a clustered node asks the leader whether a name may try a password and reports how the try went, so the cluster has one sign-in budget, not one per node.

What the probes say

/health, /ready and /metrics take no credential, so a supervisor need not hold one — and so they say nothing about the cluster. /health and /ready answer liveness and readiness; /metrics without a credential carries no peer, member, address or leadership figure, which needs a caller who may read INFO FOR NODE.

What it does not cover

  • The disk. TLS protects the traffic; the store's files and its backups are Encryption at rest.
  • A machine you do not control. A node holds its private keys in memory and in the files you gave it; anybody who can read those can be that node.
  • The certificate authority. Issuing, storing and rotating the authority that signs peer certificates is yours to run; the node only checks against it.
Encryption in transit — TessariDB