One Linux host (VPS)¶
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 and Proxy on one Linux host with a public address, PostgreSQL on the
same host, connectors on other hosts. Profile: pilot.
| Placeholder | Meaning |
|---|---|
gate.example.com |
The name clients and connectors dial. Resolves to 203.0.113.10. |
203.0.113.10 |
The host's public address. |
https://idp.example.com/realms/gdsgate |
The OIDC issuer. |
Before you start¶
| Item | How to check | Reference |
|---|---|---|
| Linux with systemd, a root shell | systemctl --version |
service install |
gdsgate at /usr/local/bin/gdsgate |
gdsgate --version |
Installation |
Inbound ports open before you install, per the connector route. The administrator opens them; without them join and connect from other hosts fail |
from outside, after start: nc -vz 203.0.113.10 50061 |
Open the ports |
| The name resolves to the host (DNS modes) | getent hosts gate.example.com prints 203.0.113.10 |
Certificates |
| Certificate files for the chosen mode | openssl x509 -in /etc/gdsgate/tls/fullchain.pem -noout -subject -ext subjectAltName |
Certificates |
OIDC client gdsgate: public client, device authorization grant on; loopback redirect URI http://127.0.0.1:<port>/callback for gdsgate login --browser |
curl -s https://idp.example.com/realms/gdsgate/.well-known/openid-configuration prints JSON |
[oidc], gdsgate login |
PostgreSQL on the host, database and role gdsgate |
psql postgres://gdsgate@127.0.0.1:5432/gdsgate -c 'select 1' |
State store |
Group gdsgate for the service accounts |
getent group gdsgate |
service install |
production only: wrapped keyrings |
setup prepare --security-profile production --dry-run names what is missing |
[security.integrity] |
Ports¶
Purpose, dialer, and exposure per port: Listener
ports. On this host, 50050/50051
stay on the loopback unless the route is direct.
What each port carries: Endpoints.
Open the ports¶
Open the ports before you install. The host 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: gdsgate join on a connector host, gdsgate connect on a
workstation.
| Route | Open to every address | Open to connector addresses only |
|---|---|---|
tunneled |
50061, 50062, 50063 |
|
tunneled, IP address only |
50061, 50062, 50063, 50064 |
|
direct |
50061, 50062, 50063 |
50050, 50051 |
fronted |
50061, 50062, 50063 |
The tunneled route:
| 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 |
For 50064 add it to the same command. For 50050 and 50051 on the
direct route, allow the connector networks only:
the Authority host's rule. A network firewall in front
of the host, such as a cloud security group, needs the same ports.
Connector routes¶
A connector reaches Authority by one of three routes. The route decides which ports face the connectors' network.
| Route | Ports open on the host | What to set | When |
|---|---|---|---|
tunneled (recommended) |
50061, 50062, 50063 |
setup prepare, no flag: [proxy].public_join = true on both files, [connector].authority_transport = "tunneled" in Authority's. Connector: gdsgate join --enroll-endpoint https://gate.example.com:50061 writes the rest. |
A public host with a DNS name. Authority listens on the loopback. |
tunneled, IP address only |
50061, 50062, 50063, 50064 |
setup prepare --join-relay: [endpoints].proxy_join on both files, authority_transport = "tunneled" in Authority's, the registration certificate from the cluster CA. Connector: --enroll-endpoint https://203.0.113.10:50064 --enroll-fingerprint "$CA_FINGERPRINT" --enroll-sni enroll.gdsgate.internal (registration at an IP address). |
A public host without a DNS name. |
direct |
50050, 50051, 50061, 50062, 50063 |
setup prepare --authority-bind 203.0.113.10 --no-public-join. Connector: --enroll-endpoint https://203.0.113.10:50050. |
Connectors in a network you trust. Allow 50050, 50051 from connector addresses only. |
fronted |
50061, 50062, 50063 |
setup prepare, no flag. Connector: gdsgate join --enroll-endpoint https://gate.example.com:50061 --route fronted writes the route, [endpoints].authority and [enroll].renew_endpoint for that address. |
A front that routes by TLS server name. At most public_admin_max_connections (default 8) connectors reach Authority at once. |
On 50061 a connector checks the certificate of --tls-cert for the name or
address it dials. Renewal there is relayed to Authority unterminated, at most
public_renewal_max_connections
(default 64) at once; registration is limited per source address by
public_join_rate_per_ip (default 5 per second, burst 10). Clients on the
same port are served as before.
Full reference: split deployment, routes.
Configure¶
-
PostgreSQL on the host:
-
The configuration directory and the store password. Supply the same password in a protected file named
store.password: -
Certificate files, per Certificates:
Mode Files Let's Encrypt /etc/gdsgate/tls/fullchain.pem,/etc/gdsgate/tls/privkey.pem, and the deploy hookYour own CA the same two files; the root in the host's system store IP address only none: setup preparehas the cluster CA issue the registration certificate -
Write the configurations. Run with
--dry-runfirst: theto preparesection lists what the files will not satisfy; anx:line is refused at start.gdsgate setup prepare --domain gate.example.com --security-profile pilot \ --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 \ --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 \ --dry-runThen the same command without
--dry-run. For thedirectroute add--authority-bind 203.0.113.10 --no-public-join.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 --dry-runThen the same command without
--dry-run. For thedirectroute replace--join-relaywith--authority-bind 203.0.113.10 --no-public-join: Authority binds that address and the Proxy dials it there.The run also has the cluster CA issue the registration certificate to
/var/lib/gdsgate/authority/enroll.pemand writes the cluster CA to/etc/gdsgate/transport-ca.pemfor the Proxy. Authority renews the certificate. See Certificates, IP address only. Windows: the Windows tab, IP address only.Each run writes the configurations, the seed policy, the two keyrings and the systemd units, and prints
transport_ca.fingerprint. Flags:setup prepare. -
The audit export, and the services:
gdsgate setup change --authority-config /etc/gdsgate/authority.toml \ --set authority:audit.export_path=/var/lib/gdsgate/authority/audit.jsonl gdsgate --config /etc/gdsgate/authority.toml service install authority gdsgate --config /etc/gdsgate/proxy.toml service install proxyservice installwrites and enables each unit and gives thegdsgategroup/etc/gdsgateand every file the configuration names in it, keys included. -
The files as written, comments shortened:
/etc/gdsgate/authority.toml:store_url = "postgres://gdsgate@127.0.0.1:5432/gdsgate" # cluster state store_password_file = "/etc/gdsgate/store.password" # password, read at connect [security] profile = "pilot" # checks applied at start [security.integrity] control_keyring = "/etc/gdsgate/control.keys" # seals control rows audit_keyring = "/etc/gdsgate/audit.keys" # seals the audit chain [endpoints] authority = "127.0.0.1:50051" # control plane, bound authority_enroll = "127.0.0.1:50050" # registration, bound proxy_public = "gate.example.com:50061" # told to joining nodes proxy_internal = "gate.example.com:50062" # told to joining nodes proxy_ws = "gate.example.com:50063" # told to joining nodes [telemetry] metrics_listen = "127.0.0.1:9090" # /readyz, /healthz, /metrics [proxy] public_join = true # nodes renew at proxy_public [connector] authority_transport = "tunneled" # route told to connectors [authority] enroll_cert_file = "/etc/gdsgate/tls/fullchain.pem" # registration listener TLS enroll_key_file = "/etc/gdsgate/tls/privkey.pem" [policy] path = "/etc/gdsgate/policy.cedar" # seed, read while the store has no policy [oidc] issuer = "https://idp.example.com/realms/gdsgate" client_id = "gdsgate" [audit] export_path = "/var/lib/gdsgate/authority/audit.jsonl" # off-cluster copy for a log shipper/etc/gdsgate/proxy.toml. The three public listeners bind every interface; the file holds them in[endpoints]beside the two Authority addresses:Key Value Listener proxy_public0.0.0.0:50061clients; connector registration and renewal proxy_internal0.0.0.0:50062connector tunnels proxy_ws0.0.0.0:50063connector tunnels, WebSocket [endpoints] # proxy_public, proxy_internal, proxy_ws: the table above authority = "127.0.0.1:50051" # dialled authority_enroll = "127.0.0.1:50050" # where registration is forwarded [proxy] public_cert_file = "/etc/gdsgate/tls/fullchain.pem" # what clients and connectors see public_key_file = "/etc/gdsgate/tls/privkey.pem" public_join = true # connectors register and renew on 50061 [enroll] endpoint = "https://127.0.0.1:50050" # the Proxy's own registration transport_sni = "gate.example.com" # name verified on that certificate state_dir = "/var/lib/gdsgate/proxy-state" # enrolled identity [oidc] issuer = "https://idp.example.com/realms/gdsgate" # advertised to gdsgate connect client_id = "gdsgate" [security] profile = "pilot" [telemetry] metrics_listen = "127.0.0.1:9091"/etc/gdsgate/authority.tomldiffers in the addresses told to joining nodes, in[authority], and has the join relay in place of[proxy]:[endpoints] authority = "127.0.0.1:50051" authority_enroll = "127.0.0.1:50050" proxy_public = "203.0.113.10:50061" proxy_internal = "203.0.113.10:50062" proxy_ws = "203.0.113.10:50063" proxy_join = "203.0.113.10:50064" # where nodes register and renew [authority] enroll_cert_from_cluster_ca = true # issued from the cluster CA, renewed by Authority enroll_cert_file = "/var/lib/gdsgate/authority/enroll.pem" enroll_key_file = "/var/lib/gdsgate/authority/enroll.key"/etc/gdsgate/proxy.toml; the listeners as in the DNS tab plusproxy_joinon port50064, the join relay; no[proxy]table:[endpoints] # proxy_public, proxy_internal, proxy_ws, proxy_join: 0.0.0.0, every interface authority = "127.0.0.1:50051" authority_enroll = "127.0.0.1:50050" [enroll] endpoint = "https://127.0.0.1:50050" transport_ca = "/etc/gdsgate/transport-ca.pem" # the cluster CA, written by setup prepare 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:9091"Key reference: top level,
[endpoints],[enroll],[proxy],[authority],[audit]. -
Check Authority's file:
Expected:
0 error(s), 0 warning(s). The Proxy's file is checked once its token file is installed (Start). Seeconfig check.
Start¶
systemctl start gdsgate-authority
gdsgate --config /etc/gdsgate/authority.toml authority identities bootstrap --role admin \
--output-dir /etc/gdsgate/admin
gdsgate setup change --authority-config /etc/gdsgate/authority.toml \
--set authority:admin.identity_dir=/etc/gdsgate/admin \
--set authority:admin.endpoint=https://127.0.0.1:50051
(umask 077
gdsgate --config /etc/gdsgate/authority.toml authority tokens create \
--role proxy > /etc/gdsgate/proxy.token)
chgrp gdsgate /etc/gdsgate/proxy.token
chmod 0640 /etc/gdsgate/proxy.token
gdsgate setup change --proxy-config /etc/gdsgate/proxy.toml \
--set 'proxy:enroll.token=${file:/etc/gdsgate/proxy.token}'
gdsgate --config /etc/gdsgate/proxy.toml service install proxy
gdsgate config check /etc/gdsgate/proxy.toml --role proxy
systemctl start gdsgate-proxy
With --authority-bind 203.0.113.10, [admin].endpoint is
https://203.0.113.10:50051. The administrative identity lasts one day;
the same identities bootstrap --role admin command renews it. The Proxy spends the token
on its first start and keeps its identity in /var/lib/gdsgate/proxy-state.
After readiness, remove the spent token reference and file:
gdsgate setup change --proxy-config /etc/gdsgate/proxy.toml \
--set proxy:enroll.token=
rm /etc/gdsgate/proxy.token
systemctl restart gdsgate-proxy
Readiness must recover from the persisted identity without another token.
Every service install checks the shared state parent at 0755; role
state remains private. For a working-directory refusal see
service permissions.
Step 4 with --start in place of --dry-run installs and starts both
services, bootstraps the admin identity and enrolls Proxy. Configure audit
export separately and remove the spent enrollment input after verification.
It reports the
gateway started once the Proxy's /readyz answers, and otherwise fails
with the last lines the Proxy logged.
From an elevated PowerShell prompt, with the files under
%ProgramData%\gdsgate. Step 4 with --start:
gdsgate setup prepare --domain gate.example.com --security-profile pilot `
--store-url postgres://gdsgate@127.0.0.1:5432/gdsgate `
--store-password-file C:\ProgramData\gdsgate\store.password `
--enroll-cert C:\ProgramData\gdsgate\tls\fullchain.pem --enroll-key C:\ProgramData\gdsgate\tls\privkey.pem `
--tls-cert C:\ProgramData\gdsgate\tls\fullchain.pem --tls-key C:\ProgramData\gdsgate\tls\privkey.pem `
--oidc-issuer https://idp.example.com/realms/gdsgate --oidc-client-id gdsgate `
--start
The firewall rule with the join relay:
New-NetFirewallRule -DisplayName gdsgate-proxy -Direction Inbound -Protocol TCP `
-LocalPort 50061,50062,50063,50064 -Action Allow
gdsgate setup prepare --domain 203.0.113.10 --security-profile pilot `
--store-url postgres://gdsgate@127.0.0.1:5432/gdsgate `
--store-password-file C:\ProgramData\gdsgate\store.password `
--oidc-issuer https://idp.example.com/realms/gdsgate --oidc-client-id gdsgate `
--join-relay --start
The registration certificate is in
C:\ProgramData\gdsgate\state\authority\enroll.pem, the cluster CA
for the Proxy in C:\ProgramData\gdsgate\transport-ca.pem. The run
prints transport_ca.fingerprint for the connector's
--enroll-fingerprint.
--start registers and starts gdsgate-authority, takes the
administrative identity, writes the Proxy token to proxy.token, and
starts gdsgate-proxy. It does not write the audit export of step 5.
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). Certificate reload:
sc.exe control gdsgate-proxy paramchange.
A connector on another host¶
This recipe uses manual catalog IDs. On the Authority host, select manual publication for new Connector identities, then declare the row and issue a token:
gdsgate --config /etc/gdsgate/authority.toml authority publication set manual
gdsgate --config /etc/gdsgate/authority.toml authority resources set demo-ssh \
--kind ssh --onward-auth local
(umask 077
gdsgate --config /etc/gdsgate/authority.toml authority tokens create --role connector \
> connector.token)
On the connector host, as root, with connector.token copied there.
For the IP-only modes, set the approved fingerprint from Authority:
The explicit configuration and state paths below are for a system service. For a home-directory foreground installation and two Proxy connections, use the HA Connector procedure.
gdsgate join gate.example.com --name conn-1 \
--enroll-endpoint https://gate.example.com:50061 --route tunneled \
--serve demo-ssh=ssh --recording central \
--output /etc/gdsgate/connector.toml --state-dir /var/lib/gdsgate/connector < connector.token
gdsgate --config /etc/gdsgate/connector.toml service install connector
systemctl start gdsgate-connector
rm connector.token
With your own CA and the root not in the connector host's store, add
--enroll-ca /etc/gdsgate/root.pem. At the host's address,
--enroll-endpoint https://203.0.113.10:50061 when the certificate
carries IP:203.0.113.10, or with --enroll-sni gate.example.com when
it does not.
Prepared with --join-relay. The fingerprint is
transport_ca.fingerprint from step 4:
gdsgate join 203.0.113.10 --name conn-1 \
--enroll-endpoint https://203.0.113.10:50064 --route tunneled \
--enroll-fingerprint "$CA_FINGERPRINT" --enroll-sni enroll.gdsgate.internal \
--serve demo-ssh=ssh --recording central \
--output /etc/gdsgate/connector.toml --state-dir /var/lib/gdsgate/connector < connector.token
gdsgate --config /etc/gdsgate/connector.toml service install connector
systemctl start gdsgate-connector
rm connector.token
The certificate is checked against the address: registration at an IP address.
Authority prepared with --authority-bind 203.0.113.10 --no-public-join
and no --enroll-cert. The fingerprint is transport_ca.fingerprint
from step 4:
gdsgate join 203.0.113.10 --name conn-1 \
--enroll-endpoint https://203.0.113.10:50050 --route direct \
--enroll-fingerprint "$CA_FINGERPRINT" --enroll-sni enroll.gdsgate.internal \
--serve demo-ssh=ssh --recording central \
--output /etc/gdsgate/connector.toml --state-dir /var/lib/gdsgate/connector < connector.token
gdsgate --config /etc/gdsgate/connector.toml service install connector
systemctl start gdsgate-connector
rm connector.token
From an elevated PowerShell prompt:
Get-Content -Raw connector.token | gdsgate join gate.example.com --name conn-1 `
--enroll-endpoint https://gate.example.com:50061 --serve demo-ssh=ssh
gdsgate service install connector
sc.exe start gdsgate-connector
Remove-Item connector.token
IP address only: gdsgate join 203.0.113.10 with the
--enroll-endpoint and --enroll-fingerprint of the tab
"tunneled, IP address only".
join writes /etc/gdsgate/connector.toml and the identity in
/var/lib/gdsgate/connector; on Windows C:\ProgramData\gdsgate\connector.toml
and C:\ProgramData\gdsgate\state\connector. Reference: gdsgate join,
[connector].
Verify¶
| Command | Expected | If not: cause → fix |
|---|---|---|
systemctl is-active gdsgate-authority gdsgate-proxy |
active twice |
journalctl -u gdsgate-authority -n 30; see the rows below. |
curl -s 127.0.0.1:9090/readyz; curl -s 127.0.0.1:9091/readyz |
ready twice |
cannot bind `[telemetry].metrics_listen` = 127.0.0.1:9091: Address in use in the log → another process has that port; stop it, or change metrics_listen. |
journalctl -u gdsgate-authority -n 30 |
auth service serving gRPC ... enroll_mode="TLS from the configured certificate" |
the private key of ...: Permission denied (os error 13) → the key is outside /etc/gdsgate; copy it there and run service install authority again. |
journalctl -u gdsgate-proxy -n 30 |
enrolled and persisted a new transport identity, then proxy serving gRPC |
enrollment endpoint https://127.0.0.1:50050: certificate refused (...); checked for the name `...` against ... → the name or the trust named there; see Certificates. |
gdsgate --config /etc/gdsgate/authority.toml doctor |
ok registration listener TLS · ... |
The failing line names the key. |
gdsgate config check /etc/gdsgate/authority.toml --role authority |
0 error(s), 0 warning(s) |
[audit-export] warning → step 5; on Windows, the Add-Content of the Windows tab. audit export file: the service cannot write it → export_path outside /var/lib/gdsgate/authority (Windows: %ProgramData%\gdsgate\state\authority). |
journalctl -u gdsgate-authority \| grep 'audit export' |
audit export file open (JSON-lines) path=/var/lib/gdsgate/authority/audit.jsonl |
Read-only file system → as the row above. |
journalctl -u gdsgate-proxy \| grep 'node registration on the public listener' |
forward="TLS" |
forward="cleartext" → the Proxy's [enroll].endpoint is http://. No line → prepared with --join-relay or --no-public-join. |
gdsgate join ... on the connector host |
ok: joined gate.example.com as conn-1 · role connector |
certificate refused → the connector host does not trust the certificate on 50061 (--enroll-ca), or it does not name the name or address dialled. UNAVAILABLE → the Proxy cannot reach or verify the registration listener; the Proxy log names why. |
tunneled: the same join output |
cluster settings: ... auth tunneled at none, renew https://gate.example.com:50061 |
renew https://gate.example.com:50064 → prepared with --join-relay. |
fronted: the same join output |
--route fronted: Authority gate.example.com:50061, renewal https://gate.example.com:50061 |
Line missing → --route fronted was not passed, and renewal goes to Authority's own address; run join again with --route fronted --if-missing. |
gdsgate --config /etc/gdsgate/authority.toml authority nodes list |
rows admin, proxy, connector, state active |
No connector row → the join output names the failure. |
gdsgate --config /etc/gdsgate/authority.toml authority offers list |
demo-ssh ssh ... yes |
No row → journalctl -u gdsgate-connector; Read the reason: not_in_catalog needs a catalogue row; mode_not_confirmed needs its SSH mode; gate_not_offered needs a capability receipt. See startup diagnostics. |
On a workstation: gdsgate connect gate.example.com (DNS name) or gdsgate connect 203.0.113.10:50061 --fingerprint "$CA_FINGERPRINT" |
ok: wrote ~/.gdsgate/config.toml · gateway <version> |
See Certificates. |
On the workstation: gdsgate setup check --role client |
ok: trust anchor ...: the file --fingerprint wrote, or this host's trust store for a public certificate |
? no trust anchor is configured → run gdsgate connect first. |
gdsgate login |
-> to sign in, open https://idp.example.com/realms/gdsgate/device and enter code ... |
Error from the provider → the OIDC client in Before you start. |
If login uses the development issuer, check [oidc] on Authority and Proxy, restart changed roles,
then refresh the client configuration with gdsgate connect --force and sign
in again. If login succeeds but later calls return invalid identity token,
check matching issuer, client audience and validation on every Authority.
Use the same client configuration for login and resource commands. Follow the
OIDC diagnostics for grants, claims and polling failures.
After these checks pass, complete the access checks from a client workstation.
Profile¶
pilot. These pages satisfy the profile matrix
rows profile-declared, store-postgres, registration-tls,
enroll-endpoint-tls, proxy-public-tls, proxy-internal-mtls,
connector-enrolled, audit-export, keyrings-distinct and
open-secret-file. For production add oidc-provider (already set here),
keyrings-named with wrapped keyrings, and key-file-access; run
setup prepare --security-profile production --dry-run for the list.