Authority and Proxy on separate hosts¶
This recipe uses administrator-chosen manual resource IDs. Keep manual enrollment for its Connector examples. For automatic backends and Authority-assigned names, use publish and migrate resources.
Authority on an internal host, the Proxy on a public host or in a DMZ,
connectors beside the resources. Profile: pilot.
| Host | Addresses | Runs |
|---|---|---|
| Authority host | 192.0.2.10 (internal) |
Authority, PostgreSQL |
| Proxy host | 203.0.113.10 (public, gate.example.com), 192.0.2.11 (internal) |
Proxy |
| Connector hosts | anywhere with a route to the Proxy | connectors |
Before you start¶
| Item | How to check | Reference |
|---|---|---|
| Everything in the single-host checklist, per host | VPS: before you start | |
| Inbound ports open on each host before you install. The administrator opens them; without them the Proxy cannot register and connectors cannot join | from the peer, after start: nc -vz 192.0.2.10 50051, nc -vz 203.0.113.10 50061 |
Open the ports |
Proxy host reaches Authority on 50050 and 50051 |
from the Proxy host: nc -vz 192.0.2.10 50051 after Authority starts |
Traffic |
| Registration certificate trusted by the Proxy at the address it dials | openssl s_client -connect 192.0.2.10:50050 -servername gate.example.com -verify_return_error < /dev/null prints Verify return code: 0 (ok) |
Certificates |
| A way to copy one file from the Authority host to the Proxy host | scp, or a secret store |
Traffic¶
| From | To | Port | Carries |
|---|---|---|---|
| clients | Proxy | 50061 |
sessions, gdsgate connect, sign-in settings |
| connectors | Proxy | 50062 (50063 WebSocket fallback) |
reverse tunnels |
connectors, tunneled |
Proxy | 50061 |
registration, forwarded to Authority; renewal, relayed to Authority |
connectors, tunneled with --join-relay |
Proxy | 50064 |
registration and renewal, relayed to Authority |
connectors, direct |
Authority | 50050, 50051 |
registration, renewal, control plane |
| Proxy | Authority | 50050 |
the Proxy's own registration; relayed registration |
| Proxy | Authority | 50051 |
control plane; relayed renewal and tunneled control plane |
| Authority | PostgreSQL | 5432 |
cluster state |
| Authority | identity provider | 443 |
discovery, signing keys |
| clients | identity provider | 443 |
sign-in |
What each port carries: Endpoints.
Open the ports¶
Open the ports before you install. Each host's firewall is the
administrator's responsibility: setup prepare, service install and
--start do not change it. While a port is closed, the steps that cross it
fail: the Proxy's registration at Authority, gdsgate join on a connector
host, gdsgate connect on a workstation.
| Host | Open to every address | Open to the Proxy host only |
|---|---|---|
| Proxy host | 50061, 50062, 50063; 50064 with --join-relay |
|
| Authority host | 50050, 50051; the connector networks as well on the direct route |
Proxy host, per firewall:
| Firewall | Command |
|---|---|
| ufw | ufw allow 50061:50063/tcp |
| firewalld | firewall-cmd --permanent --add-port=50061-50063/tcp && firewall-cmd --reload |
nftables, the inet filter table of /etc/nftables.conf |
nft insert rule inet filter input tcp dport 50061-50063 accept, and the same rule in /etc/nftables.conf |
| Windows, elevated PowerShell | New-NetFirewallRule -DisplayName gdsgate-proxy -Direction Inbound -Protocol TCP -LocalPort 50061,50062,50063 -Action Allow |
Authority host: the nftables table below. On Windows:
New-NetFirewallRule -DisplayName gdsgate-authority -Direction Inbound -Protocol TCP -LocalPort 50050,50051 -RemoteAddress 192.0.2.11 -Action Allow.
Nothing dials the Proxy host from Authority's side. Firewall on the Authority host (nftables):
nft -f - <<'EOF'
table inet gdsgate {
chain input {
type filter hook input priority 0; policy accept;
iifname "lo" accept # this host: the admin CLI
tcp dport { 50050, 50051 } ip saddr 192.0.2.11 accept # the Proxy
tcp dport { 50050, 50051 } drop
}
}
EOF
Without the lo rule the administrative commands on the Authority host hang:
they dial 192.0.2.10 from 192.0.2.10. For the direct route add the
connector networks to the accept rule. Persist the table in
/etc/nftables.conf.
Connector routes¶
Port purpose and exposure: Listener ports.
tunneled (recommended) |
tunneled, IP address only |
direct |
fronted |
|
|---|---|---|---|---|
| Connector egress | Proxy only | Proxy only | Proxy and Authority | one address: a front, or the Proxy's client port |
| Registration | https://gate.example.com:50061 |
https://203.0.113.10:50064 (join relay) |
https://192.0.2.10:50050 |
https://gate.example.com:50061 |
| Renewal | 50061, server name gdsgate.internal, at most public_renewal_max_connections (default 64) at once |
join relay 50064 |
Authority 50051 |
the registration address, server name gdsgate.internal |
| Control-plane calls | inside the reverse tunnel | inside the reverse tunnel | Authority 50051 |
through the front, server name auth.gdsgate.internal |
setup prepare |
no flag | --join-relay |
--no-public-join |
no flag |
| Authority file | [proxy].public_join = true, [connector].authority_transport = "tunneled" |
[endpoints].proxy_join, authority_transport = "tunneled" |
[endpoints].authority is an address connectors reach |
as tunneled |
| Proxy file | [proxy].public_join = true |
[endpoints].proxy_join |
nothing | a front routing by server name, or [proxy].public_join = true |
| Connector file | written by gdsgate join |
written by gdsgate join |
written by gdsgate join |
written by gdsgate join --route fronted: the route, [endpoints].authority and [enroll].renew_endpoint = the registration address |
| Use | connectors anywhere; Authority unreachable from them | the same, for a gateway without a DNS name | connectors inside the internal network | one egress address; the Proxy's client port as front carries at most public_admin_max_connections (default 8) connectors |
Authority tells each node the route at registration
(what Authority tells a joining node).
On 50061 a connector checks the Proxy's public_cert_file for the name or
address it dials. A connector that registers through the join relay at an IP
address passes --enroll-ca with the CA of the registration certificate,
which names that address:
registration at an IP address.
public_join forwards the registration it terminates over TLS when the
Proxy's [enroll].endpoint is https:// and transport_sni names the
certificate (transport_ca for a private CA); setup prepare writes both.
Key reference: authority_transport,
proxy_join,
public_join.
Configure¶
The commands below use the tunneled route and a Let's Encrypt pair for
gate.example.com on both hosts. For your own CA, one certificate with
DNS:gate.example.com serves both.
On the Authority host:
-
PostgreSQL, the configuration directory, the store password and the certificate files: VPS steps 1–3.
-
The configuration:
gdsgate setup prepare --domain gate.example.com --security-profile pilot \ --role authority --authority-bind 192.0.2.10 \ --store-url postgres://gdsgate@127.0.0.1:5432/gdsgate \ --store-password-file /etc/gdsgate/store.password \ --enroll-cert /etc/gdsgate/tls/fullchain.pem --enroll-key /etc/gdsgate/tls/privkey.pem \ --oidc-issuer https://idp.example.com/realms/gdsgate --oidc-client-id gdsgate cat >> /etc/gdsgate/authority.toml <<'EOF' [audit] export_path = "/var/lib/gdsgate/authority/audit.jsonl" EOF gdsgate --config /etc/gdsgate/authority.toml service install authority gdsgate config check /etc/gdsgate/authority.toml --role authority--authority-bindis the address Authority binds and the address it gives the Proxy. With--no-public-joinit is also the address connectors are told, and a wildcard is refused.service installgives thegdsgategroup the files the configuration names under/etc/gdsgate. For a gateway without a DNS name, add--join-relayhere and in step 5.Result, the keys that differ from the single host:
[endpoints] authority = "192.0.2.10:50051" # bound; reachable from the Proxy authority_enroll = "192.0.2.10:50050" # bound; reachable from the Proxy proxy_public = "gate.example.com:50061" # told to joining nodes; where connectors renew proxy_internal = "gate.example.com:50062" proxy_ws = "gate.example.com:50063" -
Start, take the administrative identity, and issue the Proxy's token:
systemctl start gdsgate-authority gdsgate --config /etc/gdsgate/authority.toml authority identities bootstrap --role admin \ --output-dir /etc/gdsgate/admin cat >> /etc/gdsgate/authority.toml <<'EOF' [admin] identity_dir = "/etc/gdsgate/admin" endpoint = "https://192.0.2.10:50051" EOF umask 077 gdsgate --config /etc/gdsgate/authority.toml authority publication set manual gdsgate --config /etc/gdsgate/authority.toml authority tokens create --role proxy > proxy.tokenCopy
proxy.tokento the Proxy host and delete it here. It expires in one hour (--ttlchanges that).
On the Proxy host:
-
The group, the directory, the certificate files (the Let's Encrypt tab on this host), and the token:
-
The configuration:
gdsgate setup prepare --domain gate.example.com --security-profile pilot \ --role proxy --authority-host 192.0.2.10 \ --enroll-cert /etc/gdsgate/tls/fullchain.pem \ --tls-cert /etc/gdsgate/tls/fullchain.pem --tls-key /etc/gdsgate/tls/privkey.pem \ --oidc-issuer https://idp.example.com/realms/gdsgate --oidc-client-id gdsgate--enroll-certnames the certificate Authority's registration listener presents; the file is not read here. The Proxy dials192.0.2.10and the certificate namesgate.example.com, sosetup preparewrites[enroll].transport_sni = "gate.example.com".Registration certificate for an internal name only, for example
authority.internal:Where Setting Proxy, after this step gdsgate setup change --set proxy:enroll.transport_sni=authority.internalConnectors Through public_join(:50061) no flag is needed. Through the join relay:gdsgate join ... --enroll-endpoint https://gate.example.com:50064 --enroll-sni authority.internal, plus--enroll-cafor a private CA. -
Check and start:
gdsgate --config /etc/gdsgate/proxy.toml service install proxy gdsgate config check /etc/gdsgate/proxy.toml --role proxy systemctl start gdsgate-proxyResult. The three listeners bind every interface, as on the single host:
[endpoints] # proxy_public, proxy_internal, proxy_ws: 0.0.0.0:50061 to 0.0.0.0:50063 authority = "192.0.2.10:50051" # dialled authority_enroll = "192.0.2.10:50050" # where registration is forwarded [proxy] public_cert_file = "/etc/gdsgate/tls/fullchain.pem" public_key_file = "/etc/gdsgate/tls/privkey.pem" public_join = true # connectors register and renew on 50061 [enroll] endpoint = "https://192.0.2.10:50050" # the Proxy's own registration transport_sni = "gate.example.com" # the name on the certificate state_dir = "/var/lib/gdsgate/proxy-state" [oidc] issuer = "https://idp.example.com/realms/gdsgate" client_id = "gdsgate" [security] profile = "pilot" [telemetry] metrics_listen = "127.0.0.1:9090"
Connectors: VPS, a connector on another host, tunneled
tab. The catalogue row and the token come from the Authority host.
Start¶
Steps 3 and 6 above.
From an elevated PowerShell prompt, files under %ProgramData%\gdsgate.
Authority host: step 2 up to setup prepare, with --start; it starts
gdsgate-authority, takes the administrative identity and writes the
Proxy token to proxy.token. It does not write the audit export. Add
it, restart Authority and check the file:
Add-Content C:\ProgramData\gdsgate\authority.toml "`n[audit]`nexport_path = 'C:\ProgramData\gdsgate\state\authority\audit.jsonl'"
Restart-Service gdsgate-authority
gdsgate config check C:\ProgramData\gdsgate\authority.toml --role authority
Expected: 0 error(s), 0 warning(s).
Proxy host: put the token alone in %ProgramData%\gdsgate\proxy.token
and run step 5 with --start. Once it reports the gateway started, the
Proxy has registered and holds its identity; remove the token:
Verify¶
| Command | Expected | If not: cause → fix |
|---|---|---|
Authority host: ss -ltn \| grep 5005 |
192.0.2.10:50050, 192.0.2.10:50051 |
127.0.0.1 → --authority-bind missing. |
Authority host: curl -s 127.0.0.1:9090/readyz |
ready |
journalctl -u gdsgate-authority -n 30. |
Proxy host: journalctl -u gdsgate-proxy -n 30 |
enrolled and persisted a new transport identity, proxy serving node registration on the public listener ... forward="TLS" (proxy serving the join relay with --join-relay), proxy serving gRPC (mTLS (enrolled), public TLS from the configured certificate) |
certificate refused (...); checked for the name ... → the name or trust named there; a certificate for an internal name: step 5. connect to the enrollment endpoint failed with a timeout → the firewall drops 192.0.2.11. |
Proxy host: curl -s 127.0.0.1:9090/readyz |
ready |
as above |
Authority host: gdsgate --config /etc/gdsgate/authority.toml authority nodes list |
proxy gdsgate-proxy ... active |
No row → the Proxy has not registered; the Proxy log names why. |
Connector host: gdsgate join gate.example.com ... --enroll-endpoint https://gate.example.com:50061 |
ok: joined gate.example.com as conn-1 · role connector, then auth tunneled at none, renew https://gate.example.com:50061 |
UNAVAILABLE → the Proxy cannot reach or verify the registration listener; the Proxy log names why. Otherwise see VPS verify. |
Authority host: gdsgate --config /etc/gdsgate/authority.toml authority offers list |
demo-ssh ssh ... yes |
Connector log: journalctl -u gdsgate-connector. |
A host outside 192.0.2.11: nc -vz -w 3 192.0.2.10 50051 |
timeout | Connected → the firewall rule is not loaded: nft list table inet gdsgate. |
Workstation: gdsgate connect gate.example.com |
ok: wrote ~/.gdsgate/config.toml |
See Certificates. |
After these checks pass, complete the access checks from a client workstation.
Profile¶
pilot, the same matrix rows as the single host. The
registration listener crosses a network here, so registration-tls and
enroll-endpoint-tls apply in every profile you choose.