Certificates¶
gdsgate does not request certificates from outside. It reads PEM files that an ACME client or a corporate CA writes, and for a gateway reached by IP address the cluster CA issues the registration certificate. This page lists the files each listener reads, three ways to produce them, and how clients, connectors and the Proxy come to trust them.
Listeners and keys¶
| Listener | Port | Keys | Without the keys |
|---|---|---|---|
Proxy, client-facing; connector registration with public_join |
50061 |
[proxy].public_cert_file, public_key_file |
Presents the Proxy's enrolled identity, signed by the cluster CA. Clients trust it through gdsgate connect --fingerprint. Connectors cannot register on it: the identity carries no name or address they dial. |
| Authority, registration | 50050 |
[authority].enroll_cert_file, enroll_key_file |
Cleartext h2c. Refused at start under pilot and production. |
| Proxy, browser origin | [proxy].web_addr |
[proxy].web_cert_file, web_key_file |
Listener off. Browser-trusted certificate required. |
| Proxy, clientless browser entry | [proxy].webapps_addr |
[proxy].webapps_cert_file, webapps_key_file |
Entry off. Wildcard *.<webapps_zone>, browser-trusted. |
Connector tunnels (50062, 50063) and the Authority control plane (50051)
use the cluster's own CA. Nothing on this page changes them.
One pair can serve the Proxy's client-facing listener and the Authority
registration listener when both answer to one name. Wherever the Proxy dials
Authority by another address (the loopback on one host, an internal address
on two), setup prepare --enroll-cert sets
[enroll].transport_sni to the
gateway name.
File modes: certificate 0644, key 0640 with group gdsgate (systemd
units) or owned by uid 10001 (the container image).
Modes¶
For an internal cluster or a pilot without a DNS name. The client-facing
listener presents the enrolled identity. The registration listener
presents a certificate the cluster CA issues. setup prepare with an
address in --domain and no --enroll-cert sets it up; no openssl:
gdsgate setup prepare --domain 203.0.113.10 --security-profile pilot \
--store-url postgres://gdsgate@127.0.0.1:5432/gdsgate \
--store-password-file /etc/gdsgate/store.password \
--oidc-issuer https://idp.example.com/realms/gdsgate --oidc-client-id gdsgate \
--join-relay
What the run writes for the registration listener:
| File | Content |
|---|---|
/var/lib/gdsgate/authority/enroll.pem |
The certificate for 203.0.113.10, 127.0.0.1 and enroll.gdsgate.internal, then the cluster CA. Valid 90 days. |
/var/lib/gdsgate/authority/enroll.key |
Its key, owner-only. |
/etc/gdsgate/transport-ca.pem |
The cluster CA, for the Proxy. |
/etc/gdsgate/authority.toml |
[authority]: enroll_cert_from_cluster_ca = true and the two paths above. |
/etc/gdsgate/proxy.toml |
[enroll]: endpoint = "https://127.0.0.1:50050", transport_ca = "/etc/gdsgate/transport-ca.pem". |
The run prints transport_ca.fingerprint: the SHA-256 of the cluster CA.
Record the approved value through a trusted channel, then set:
| Who | Trusts the registration listener by | Trusts the client-facing listener by |
|---|---|---|
| Proxy host | [enroll].transport_ca, written by setup prepare |
not applicable |
| Connector | gdsgate join ... --enroll-fingerprint "$CA_FINGERPRINT" --enroll-sni enroll.gdsgate.internal |
not applicable |
| Client | not applicable | gdsgate connect 203.0.113.10:50061 --fingerprint "$CA_FINGERPRINT" |
Connectors register through the
join relay at the address,
which --join-relay binds:
gdsgate join 203.0.113.10 --enroll-endpoint https://203.0.113.10:50064 --enroll-fingerprint "$CA_FINGERPRINT" --enroll-sni enroll.gdsgate.internal.
Use --enroll-sni enroll.gdsgate.internal to verify the common internal
registration name through an HA join relay. The approved CA pin is still
required. Without an explicit name the certificate is checked against
the dialled address; see
registration at an IP address
and connector routes.
Renewal: Authority issues the certificate again 30 days before it expires, at start and hourly while it runs. See issued by the cluster CA.
For a registration certificate from your own CA: the "Your own CA"
tab with IP:203.0.113.10 in subjectAltName, passed as
setup prepare --enroll-cert --enroll-key; connectors then join with
--enroll-ca.
For a gateway with a public DNS name. certbot writes the pair under
/etc/letsencrypt/live/gate.example.com/, which only root can read. A
deploy hook copies it where gdsgate reads it and asks the services to
re-read. gdsgate does not use port 80; certbot's standalone mode binds it
only while it runs.
Issue with HTTP-01, port 80 reachable from the internet:
Or with DNS-01, no inbound port (Cloudflare shown; other DNS plugins take the same form):
install -d -m 0700 /root/.secrets
printf 'dns_cloudflare_api_token = %s\n' "$CF_API_TOKEN" > /root/.secrets/cloudflare.ini
chmod 0600 /root/.secrets/cloudflare.ini
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d gate.example.com -m ops@example.com --agree-tos
Copy the pair for gdsgate, once, before setup prepare:
install -d -m 0750 -g gdsgate /etc/gdsgate/tls
install -m 0644 /etc/letsencrypt/live/gate.example.com/fullchain.pem /etc/gdsgate/tls/fullchain.pem
install -m 0640 -g gdsgate /etc/letsencrypt/live/gate.example.com/privkey.pem /etc/gdsgate/tls/privkey.pem
The hook for every renewal, /etc/letsencrypt/renewal-hooks/deploy/gdsgate,
mode 0755:
#!/bin/sh
# certbot deploy hook: copy the renewed pair to /etc/gdsgate/tls
set -e
[ "$RENEWED_LINEAGE" = /etc/letsencrypt/live/gate.example.com ] || exit 0
install -m 0644 "$RENEWED_LINEAGE/fullchain.pem" /etc/gdsgate/tls/fullchain.pem
install -m 0640 -g gdsgate "$RENEWED_LINEAGE/privkey.pem" /etc/gdsgate/tls/privkey.pem
systemctl reload gdsgate-authority gdsgate-proxy
Test it: RENEWED_LINEAGE=/etc/letsencrypt/live/gate.example.com
/etc/letsencrypt/renewal-hooks/deploy/gdsgate, then look for the reload
lines in the log.
| Key | Value |
|---|---|
[proxy].public_cert_file |
/etc/gdsgate/tls/fullchain.pem (setup prepare --tls-cert) |
[proxy].public_key_file |
/etc/gdsgate/tls/privkey.pem (--tls-key) |
[authority].enroll_cert_file |
/etc/gdsgate/tls/fullchain.pem (--enroll-cert) |
[authority].enroll_key_file |
/etc/gdsgate/tls/privkey.pem (--enroll-key) |
[enroll].transport_sni (Proxy) |
gate.example.com. Written by setup prepare when the Proxy dials Authority by another address. |
| Who | Trusts it by |
|---|---|
| Proxy host | the system CA bundle (ca-certificates) |
| Connector | the system CA bundle; gdsgate join needs no --enroll-ca |
| Client | the system CA bundle; gdsgate connect gate.example.com needs no --fingerprint and writes no trust anchor |
The certificate is valid for 90 days. certbot's timer renews it 30 days before expiry and runs the hook after each renewal.
For a corporate PKI. Request a server certificate with:
| Field | Value |
|---|---|
subjectAltName |
DNS:gate.example.com, the name clients and connectors dial. Add IP:<address> for every address a peer dials by number, for example IP:192.0.2.10 when the Proxy dials Authority by address. |
extendedKeyUsage |
serverAuth |
| Key | EC P-256 or RSA 2048 and up |
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-subj "/CN=gate.example.com" -keyout gate.key -out gate.csr \
-addext "subjectAltName=DNS:gate.example.com,IP:192.0.2.10" \
-addext "extendedKeyUsage=serverAuth"
The file gdsgate reads holds the leaf first, then each intermediate, and no root:
cat gate.pem issuing-ca.pem > /etc/gdsgate/tls/fullchain.pem
install -g gdsgate -m 0640 gate.key /etc/gdsgate/tls/privkey.pem
openssl verify -CAfile root.pem -untrusted issuing-ca.pem gate.pem
The keys are the ones in the Let's Encrypt tab.
| Who | Trusts it by |
|---|---|
| Proxy host | the root in the system store (as in the IP tab), or [enroll].transport_ca |
| Connector | the root in the system store, or gdsgate join ... --enroll-ca root.pem |
| Client, Linux | the root in the system store, or SSL_CERT_FILE=/path/root.pem |
| Client, Windows | certutil -addstore Root root.pem from an elevated prompt |
A client without the root stops at gdsgate connect; see
symptoms.
Separate public and private TLS in HA¶
Authority registration can use cluster-issued TLS in both IP-only and DNS
public modes. Each Proxy enrolls against the private Authority endpoint with
transport_ca=/etc/gdsgate/transport-ca.pem and
transport_sni=enroll.gdsgate.internal.
| Public entry | Proxy certificate | Connector registration trust |
|---|---|---|
IP-only join relay 50064 |
Enrolled Proxy identity; no external public pair | Approved cluster CA pin and enroll.gdsgate.internal |
DNS public_join on 50061 |
Operator-supplied public certificate and key | Public trust store or the operator's CA |
setup prepare does not create an external public pair at
/etc/gdsgate/tls/fullchain.pem and privkey.pem. Those paths are inputs
only when the chosen mode needs them. The cluster-issued registration pair
lives in Authority state and is renewed by Authority. Follow
Proxy preparation and Connector join.
Renewal and re-read¶
| Event | What gdsgate does |
|---|---|
| Certificate or key file replaced | Re-read within a second. New connections get the new pair; open ones continue. Log: listener certificate reloaded cert=<path> generation=<n>. |
systemctl reload gdsgate-proxy gdsgate-authority (sends SIGHUP) |
Re-reads now. Log: SIGHUP: re-reading the listener certificates (nothing else is reloaded). |
sc.exe control gdsgate-proxy paramchange on Windows (also gdsgate-authority) |
The same as SIGHUP. |
| Unreadable, malformed or mismatched pair | The pair in force stays. Log: listener certificate not reloaded; the one in force stays in force. |
A reload re-reads listener certificates only. Configuration, policy and listen addresses need a restart. Procedure: Renewing a listener certificate.
What each profile requires¶
| Check | dev |
pilot |
production |
Satisfied by |
|---|---|---|---|---|
registration-tls |
not checked | required | required | any mode: enroll_cert_file, enroll_key_file |
enroll-endpoint-tls |
not checked | required | required | https:// in every node's [enroll].endpoint |
proxy-public-tls |
not checked | required | required | any mode: an enrolled Proxy, or public_cert_file |
| Client-facing certificate from a public CA | optional | optional | recommended | Let's Encrypt, or your own CA installed on every client |
Symptoms¶
| Symptom | Cause | What to do |
|---|---|---|
Service log: the private key of <cert>: ... Permission denied (os error 13) |
The key is not readable by the service | Under /etc/gdsgate: gdsgate --config <file> service install <role> gives it to the gdsgate group. Elsewhere: chgrp gdsgate <key>; chmod 0640 <key>, and the directories above it searchable by the group. In a container, owner uid 10001. |
Proxy log: enrollment endpoint https://...: certificate refused (UnknownIssuer); checked for the name `...` against ... |
The Proxy does not trust the registration certificate | Install the CA in the Proxy host's system store or set [enroll].transport_ca. |
Proxy log: certificate refused (NotValidForName...) |
The name checked, shown in the message, is not in the certificate | Set [enroll].transport_sni to a name in the certificate. |
gdsgate join: certificate refused at https://<IP>:50064 |
The certificate does not carry IP:<IP>, or the connector lacks its CA |
Your own CA: reissue with the address in subjectAltName; pass --enroll-ca. Issued by the cluster CA: the address belongs in Authority's [endpoints]; gdsgate config check names what the certificate lacks, and a restart of Authority issues it again. |
gdsgate join: presents a chain from <sha256>, not from <sha256> |
Wrong --enroll-fingerprint; the token is unspent |
Take transport_ca.fingerprint from setup prepare, or gdsgate ca fingerprint on the Proxy host. |
gdsgate join: presents its certificate without the CA that issued it |
--enroll-fingerprint against a certificate from outside |
Pass --enroll-ca with that CA. |
gdsgate join: certificate refused at https://<IP>:50061 |
public_cert_file does not carry IP:<IP>, or the Proxy has none and presents its enrolled identity |
Add the address to the certificate, or join with --enroll-sni <the name on it>. No DNS name: prepare with --join-relay and join at 50064. |
gdsgate connect: published no transport CA anchor, and the certificate authorities this host trusts do not vouch for it either |
The client does not trust the CA of public_cert_file |
Install the root, or set SSL_CERT_FILE. |
gdsgate connect: trust anchor fingerprint mismatch: expected ..., gateway offered ... |
Wrong --fingerprint |
Take the value from gdsgate ca fingerprint on the Proxy host. |
Log: listener certificate not reloaded |
The new pair does not parse or the key does not match | openssl x509 -in <cert> -noout -pubkey and openssl pkey -in <key> -pubout must print the same key. |