High availability with etcd or Consul

Cluster

Several r3v3rs3 nodes can share one state in etcd or in the key-value store of Consul. Each node serves traffic with the same ports, proxies, access lists, certificates, ACME entries, admin accounts and settings. A change on one node reaches the other nodes without a restart.

This page is the reference. To build a cluster from nothing, follow High Availability: it covers every step from the store to the load balancer.

Architecture

The Settings page of the WebUI shows the state, the role and the applied revision of the node. GET /api/cluster/status returns the same data.

StateMeaning
disabledThe node does not use a cluster store.
syncingThe node reads the store for the first time.
syncedThe node applies the changes of the store.
degradedThe node lost the store. It serves the last applied state and rejects changes.

Set Up a Cluster

Four commands build a cluster: r3v3rs3 cluster keygen writes the encryption key, the [cluster] section of config.toml names the store on every node, r3v3rs3 cluster import copies the files of one node into an empty prefix, and r3v3rs3 start runs every node.

High Availability gives each step with its commands, the store install, the credentials and the load balancer.

Settings

Only config.toml sets these fields.

FieldDefaultDescription
enabledfalseTurns the cluster on.
backendetcdetcd or consul.
endpointsnoneThe HTTP API addresses of the store: http://<host>:<port>, https://<host>:<port> or unix://<path>. A connection error selects the next address.
username, passwordemptyThe etcd user. An empty user sends no credentials.
tokennoneThe Consul ACL token.
datacenteremptyThe Consul datacenter. Empty uses the datacenter of the agent.
prefixr3v3rs3Every key of the cluster starts with <prefix>/v1/. Several clusters can share one store with different prefixes.
node_namenoneThe name of the node. It is required, and each node needs a different name.
tlsnoneca_file verifies the store. Without it, the system root certificates verify the store. cert_file and key_file send a client certificate.
encryption_key_filesnoneThe key files. The first key encrypts. Every key decrypts.
lock_ttl15sThe TTL of the leader lock and of the node presence. etcd needs at least 1s, Consul at least 10s.
startup_timeout30sThe longest wait for the store when the node starts.
rate_limit_sync_interval1sHow often a node publishes its rate limit counts. The minimum is 100ms.
share_cachefalseStores cached responses in the store for the other nodes.
cache_max_value_size1048576The largest cached response in bytes that a node stores for the other nodes.

The admin API does not return password or token.

Keys in the Store

Every key starts with <prefix>/v1/.

KeyContentEncrypted
schemaThe version of the data layout. The import writes it last.no
state/configThe settings.yes
state/ports/<id>The ports.no
state/proxies/<id>The proxies.yes
state/access-lists/<id>The access lists with their password hashes and token digests.yes
state/certs/<kind>/<id>The certificates and their private keys.yes
state/acme/<id>The ACME entries with the account keys and the DNS provider credentials.yes
state/accounts/<hex name>The admin accounts.yes
state/cdnThe CDN IP ranges.no
state/challenges/http/<hex token>, state/challenges/tls-alpn/<hex domain>The ACME challenges that every node serves. The ACME server publishes their values.no
state/cache-purges/<proxy id>The time of the last cache purge of a proxy.no
lock/leaderThe leader lock, attached to the lease of the leader.yes
nodes/<hex name>The presence of a node, attached to its lease.no
acks/<hex name>The digest of the challenges that a node serves.no
sessions/<scope>/<SHA-256 of the token>The admin and proxy sessions. The store never holds a token.yes
ratelimit/<hex name>The rate limit counts of a node, with client IP addresses.yes
cache/<proxy id>/<SHA-256 of the cache key>A shared cached response.yes
audit/<YYYY-MM-DD>/<Unix ms>-<random>An audit log entry, below the key of its day.yes
notifyThe certificate events that the webhook got. Only the leader writes it.yes

Encryption

A node encrypts each value with AES-256-GCM before it writes the value. The KV key of the value is the associated data, so a value that somebody moves to another key does not decrypt. An encrypted value starts with the id of its key: the first 8 bytes of the SHA-256 digest of the key.

To replace a key:

  1. Create a new key file with r3v3rs3 cluster keygen.
  2. Put the new file first in encryption_key_files and keep the old file in the list. Do this on every node and restart the nodes, because a node reads the key files when it starts.
  3. Run r3v3rs3 cluster rekey --config-dir <dir> on one node. The command encrypts every value again with the first key and reports the values that it changed.
  4. Remove the old file from encryption_key_files on every node and restart the nodes.

Protect the store too. The encryption hides the values, but the key names show the ids of the ports, proxies and certificates.

etcd Permissions

The user of the nodes needs read and write access to the keys below the prefix:

$ etcdctl role add r3v3rs3
$ etcdctl role grant-permission --prefix=true r3v3rs3 readwrite r3v3rs3/
$ etcdctl user add r3v3rs3
$ etcdctl user grant-role r3v3rs3 r3v3rs3

With another prefix, grant the permission on <prefix>/. The nodes renew the authentication token when etcd revokes it.

Consul Permissions

The token of the nodes needs write access to the keys below the prefix and the permission to create sessions:

key_prefix "r3v3rs3/" {
  policy = "write"
}

session_prefix "" {
  policy = "write"
}
$ consul acl policy create -name r3v3rs3 -rules @r3v3rs3.hcl
$ consul acl token create -description "r3v3rs3 nodes" -policy-name r3v3rs3

The make test-cluster-e2e target runs the nodes with exactly these permissions.

Leader and ACME

The leader holds the lock/leader key with a lease of lock_ttl. It renews the lease three times in each lock_ttl. A store call of the leader waits one third of lock_ttl at most, so the leader gives up the lead before its lease can end in the store. When the leader stops, it releases the lock, and another node takes the lead at its next check.

For an HTTP-01 or TLS-ALPN-01 challenge, the leader writes the challenge to the store. Every node serves it. Each node writes the digest of the challenges that it serves to acks/. The leader waits up to 10 seconds until every present node reports the new challenges, and then asks the ACME server to validate.

Failure Modes

Sessions

The admin sessions and the sessions of the proxy authentication are in the store, so a session that one node starts is valid on every node. A logout removes the session for every node. A node reads the store for each request that carries a session. Without the store, a proxy session is not valid, and the admin API answers 503 cluster_unavailable.

Rate Limit Accuracy

A node does not read the store for each request. Each node counts the requests of each client and publishes the counts of the 2048 busiest clients of each limit at each rate_limit_sync_interval. A node adds the recent counts of the other nodes to its own counts. Counts that are older than three intervals, or older than 2 seconds when that is longer, do not count.

Shared Cache

With share_cache = true, a node queues each cached response that stays fresh for at least 60 seconds for the store. The node does not wait for the write. On a local miss, a node reads the store for at most 100 milliseconds.

Clocks

The nodes compare Unix times for the session expiry, the rate limit windows, the freshness of the rate limit counts and the expiry of shared responses. Keep the clocks of the nodes in sync with NTP. A clock that is ahead or behind by seconds changes the rate limit decisions and the cache expiry.