TLS Certificates
Netsy uses mutual TLS (mTLS) for authentication for Nodes, Peers, and Clients.
Each Node requires a server certificate (for serving gRPC and the Elector/s3lect election health listener) and a client certificate (for Nodes connecting to Peers).
Presented client certificates embed a role of either peer or client, and Netsy authorises the connection based on that role.
Certificate Requirements
All TLS certificates in a Netsy cluster must be signed by the same Certificate Authority (CA).
Identity via URI SANs
A TLS certificate can carry Subject Alternative Names (SANs) — additional identifiers beyond the certificate’s subject line. SANs are commonly DNS names or IP addresses, but they can also be URIs. A URI SAN embeds a structured identifier inside the certificate itself, so the receiving side can parse it to learn who is connecting without relying on out-of-band configuration.
Netsy identifies certificates using a URI SAN with the format:
netsy://{cluster_id}/{role}/{identity}| Component | Description | Example |
|---|---|---|
cluster_id | Cluster ID (must match Node config) | my-cluster |
role | Certificate role | peer or client |
identity | Peer: node_id, Client: arbitrary identifier | node-1, my-app |
Peer certificates (Node-to-Node):
netsy://my-cluster/peer/node-1The identity component must match the node’s configured node_id.
Client certificates (external etcd clients):
netsy://my-cluster/client/kube-apiserverThe identity component is an arbitrary client identifier — no node_id matching is required.
Validation Rules
Both cluster_id and node_id must be:
- Lowercase alphanumeric characters and hyphens only
- No leading, trailing, or consecutive hyphens
- Maximum 32 characters
Server Certificate
Used by a Node on the Client API and Peer API gRPC servers. Must include:
- URI SAN:
netsy://{cluster_id}/peer/{node_id} - Subject Alternative Names (SANs): the advertise addresses and any literal hostnames/IPs that peers or clients actually dial for this Node (Client API, Peer API, and Elector/s3lect peer and health server)
- Key Usage: Digital Signature, Key Encipherment
- Extended Key Usage: TLS Web Server Authentication
At startup, Netsy validates that the certificate identity matches the configured node_id / cluster_id, and that the server certificate SANs cover the configured Client, Peer, and election advertise addresses.
Node Client Certificate
Used by a Node when connecting to other Peer gRPC servers. Must include:
- URI SAN:
netsy://{cluster_id}/peer/{node_id} - Key Usage: Digital Signature
- Extended Key Usage: TLS Web Client Authentication
Client API Client Certificates
External “etcd clients” are not cluster Nodes. When a Client connects to the Client API with mTLS, Netsy verifies the URI SAN has the client role and the correct cluster_id. Only peer certificates are required to match a Node’s node_id.
Generating Certificates with OpenSSL
The examples below use the following variables:
CLUSTER_ID="my-cluster"
NODE_ID="node-1"
DAYS=365
# Addresses for SANs (adjust to your environment)
CLIENT_ADDR_IP4="172.16.0.1"
CLIENT_ADDR_IP6="2001:db8::10"
PEER_ADDR_IP4="172.16.0.1"
PEER_ADDR_IP6="2001:db8::10"
ELECTOR_ADDR_IP4="172.16.0.1"
ELECTOR_ADDR_IP6="2001:db8::10"Note that if you want to use DNS names instead of IPs for the Client, Peer, and Elector (s3lect) addresses the examples below will need to be adjusted accordingly. Include SANs only for the actual addresses or hostnames that callers use.
1. Create the Cluster CA
Generate a CA certificate and key. This CA is shared across all Nodes in the cluster.
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes \
-key ca.key \
-sha256 \
-days $DAYS \
-out ca.crt \
-subj "/O=${CLUSTER_ID}/CN=${CLUSTER_ID}-ca"2. Generate the Node Server Certificate
Create a config file for the server certificate SANs:
cat > server.cnf <<EOF
[req]
distinguished_name = req_dn
req_extensions = v3_req
prompt = no
[req_dn]
CN = ${NODE_ID}
[v3_req]
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
IP.1 = ${CLIENT_ADDR_IP4}
IP.2 = ${CLIENT_ADDR_IP6}
IP.3 = ${PEER_ADDR_IP4}
IP.4 = ${PEER_ADDR_IP6}
IP.5 = ${ELECTOR_ADDR_IP4}
IP.6 = ${ELECTOR_ADDR_IP6}
IP.7 = 127.0.0.1
IP.8 = ::1
DNS.1 = localhost
URI.1 = netsy://${CLUSTER_ID}/peer/${NODE_ID}
EOFGenerate the server key, CSR, and signed certificate:
openssl genrsa -out server.key 4096
openssl req -new \
-key server.key \
-out server.csr \
-config server.cnf
openssl x509 -req \
-in server.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out server.crt \
-days $DAYS \
-sha256 \
-extensions v3_req \
-extfile server.cnf3. Generate the Node Client Certificate
Create a config file for the client certificate:
cat > client.cnf <<EOF
[req]
distinguished_name = req_dn
req_extensions = v3_req
prompt = no
[req_dn]
CN = ${NODE_ID}
[v3_req]
keyUsage = digitalSignature
extendedKeyUsage = clientAuth
subjectAltName = @alt_names
[alt_names]
URI.1 = netsy://${CLUSTER_ID}/peer/${NODE_ID}
EOFGenerate the client key, CSR, and signed certificate:
openssl genrsa -out client.key 4096
openssl req -new \
-key client.key \
-out client.csr \
-config client.cnf
openssl x509 -req \
-in client.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out client.crt \
-days $DAYS \
-sha256 \
-extensions v3_req \
-extfile client.cnf4. Verify Certificates
Verify the subject fields and SANs are correct:
# Check server certificate
openssl x509 -in server.crt -noout -subject -ext subjectAltName
# Expected: subject=CN=node-1
# Expected SAN: URI:netsy://my-cluster/peer/node-1, IP:..., DNS:localhost
# Check client certificate
openssl x509 -in client.crt -noout -subject -ext subjectAltName
# Expected: subject=CN=node-1
# Expected SAN: URI:netsy://my-cluster/peer/node-1
# Verify both are signed by the CA
openssl verify -CAfile ca.crt server.crt
openssl verify -CAfile ca.crt client.crtNetsy Configuration
Once certificates are generated, configure the Node. TLS cert paths and node identity are per-node settings (env vars), while cluster_id is a per-cluster setting (config file).
Per-node env vars:
export NETSY_NODE_ID="node-1"
export NETSY_TLS_CA_CERT=./certs/ca.crt
export NETSY_TLS_SERVER_CERT=./certs/server.crt
export NETSY_TLS_SERVER_KEY=./certs/server.key
export NETSY_TLS_CLIENT_CERT=./certs/peer.crt
export NETSY_TLS_CLIENT_KEY=./certs/peer.keyPer-cluster config file (see Configuration for the full reference):
{
"cluster_id": "my-cluster"
}Multi-Node Example
For a 3-node cluster, repeat steps 2–3 for each node with a different NODE_ID and the appropriate addresses. The CA (step 1) is created once and shared. All nodes use the same ca.crt and cluster config file.
# Node 1
NODE_ID="node-1" CLIENT_ADDR_V4="172.16.0.1" CLIENT_ADDR_V6="2001:db8::11" PEER_ADDR_V4="172.16.0.1" PEER_ADDR_V6="2001:db8::11" ELECTOR_ADDR_V4="172.16.0.1" ELECTOR_ADDR_V6="2001:db8::11"
# Node 2
NODE_ID="node-2" CLIENT_ADDR_V4="172.16.0.2" CLIENT_ADDR_V6="2001:db8::12" PEER_ADDR_V4="172.16.0.2" PEER_ADDR_V6="2001:db8::12" ELECTOR_ADDR_V4="172.16.0.2" ELECTOR_ADDR_V6="2001:db8::12"
# Node 3
NODE_ID="node-3" CLIENT_ADDR_V4="172.16.0.3" CLIENT_ADDR_V6="2001:db8::13" PEER_ADDR_V4="172.16.0.3" PEER_ADDR_V6="2001:db8::13" ELECTOR_ADDR_V4="172.16.0.3" ELECTOR_ADDR_V6="2001:db8::13"