Skip to content

Client commands

Commands a user runs on their own machine, Linux or Windows. They read the client configuration ([endpoints], [client], [oidc]) and the identity-token environment variables. Global flags are in the CLI hub.

~/.gdsgate below is %USERPROFILE%\.gdsgate on Windows (%HOME%\.gdsgate when HOME is set).

Access certificates are cached under ~/.gdsgate/<cluster>/<user>/ and reused while in scope and valid. After a CA rotation or a revocation, the cached certificate is rejected at session open: the client says the cached access certificate was rejected, asks for gdsgate login, and clears the cache. The next login or retry issues a new one.

gdsgate connect <host[:port]>

Fetches a cluster's settings and writes the client configuration from them. Run it first against a new cluster; it needs no configuration.

gdsgate connect gdsgate.example.com:50061
PROXY_FINGERPRINT="<approved-proxy-fingerprint>"
gdsgate connect gdsgate.example.com --fingerprint "$PROXY_FINGERPRINT"  # set from the approved bundle

The port defaults to 50061. What it writes:

Path Contents
~/.gdsgate/config.toml [endpoints].proxy_public, [client] (anchor path, server name), the addressing defaults the cluster publishes, and [proxy].public_origin if it has one
~/.gdsgate/transport-ca.pem the cluster's transport CA, which [client].transport_ca points at

Both are owner-only and written atomically. Later commands read ~/.gdsgate/config.toml unless --config names another file.

connect shows the fingerprint of the offered anchor and asks for confirmation, or compares it with --fingerprint. With neither a fingerprint nor a terminal it stops. There is no trust-on-first-use. Once the anchor is confirmed, connect fetches the settings again over a connection verified against it and writes those.

The operator publishes the fingerprint with gdsgate ca fingerprint:

gdsgate --config /etc/gdsgate/proxy.toml ca fingerprint

Flags:

Flag Effect
--fingerprint <SHA256> The expected anchor fingerprint. Separators and case ignored. A mismatch is an error. Required off a terminal.
--output <PATH>, -o Write elsewhere. The anchor goes in the same directory.
--force Replace files that already exist. Without it, an existing file is an error and nothing is written.
--sni <NAME> Server name to send and verify, overriding what the cluster advertises. For a front that routes by name.
--insecure Bootstrap from a gateway that serves no TLS. Nothing is encrypted or verified; for development gateways on this host only. Conflicts with --fingerprint.
--timeout <SECS> How long to wait for the gateway. Default 15.

connect does not sign in and leaves the login and certificate cache unchanged; sign in afterwards with gdsgate login, or gdsgate machine-id for a workload. On failure (fingerprint mismatch, declined confirmation, unreachable gateway, invalid value) nothing is written.

Published values are validated as hand-written ones are: a name zone is a single label, address pools must parse, and a browser origin is a bare scheme://host[:port], HTTPS unless on this host. An invalid value is refused by name.

gdsgate login [--browser]

Authenticate to the cluster.

  • With an [oidc] provider configured, login runs the device flow by default: prints a URL and a short code, waits for approval, then caches the obtained identity token under ~/.gdsgate/<cluster>/id_token, mode 0600. Later commands reuse it.
  • --browser runs Authorization Code with PKCE over a loopback redirect, so the browser opens on the same machine.
  • Without [oidc], login confirms reachability and a local-dev principal is used (GDSGATE_USER, default dev).

Headless and CI: skip login and supply the token through GDSGATE_ID_TOKEN.

gdsgate up signs in the same way when it finds no identity.

gdsgate up [--browser] [--foreground]

Starts the resident client: publishes a local address for every resource in your catalog, keeps the generated ssh / kubectl / environment configuration in step with it, and holds the identity both need.

Signs in first when there is no identity, as login does; --browser uses the browser flow. Without a terminal (a service, a pipe) it stops and names the command to run.

On Linux with systemd, up installs and starts a system service running as the original user with network capabilities for the default local owner protection. With local_user_only = false and a loopback level, a user service is available. An explicit access = transparent still needs the system service. Elevation is requested once when needed; sudo gdsgate up does not request it again. Without systemd it starts a detached process; Linux owner protection still requires a privileged launcher. On Windows it ensures the network helper is running, while the resident stays in the user's session.

up returns after the resident answers with catalog status. A repeated up reports an existing resident; sudo or an explicit transparent request can upgrade a resident running at a lower level. A failed start exits non-zero and names the service journal, or prints the detached process log. gdsgate down stops it; service uninstall client removes its service.

--foreground runs the client in this process and holds the terminal. An interrupt stops it with the same teardown as down, and so does losing the terminal: on Linux the terminal closing or its host killed, on Windows the console closing, logoff or shutdown. A multiplexer's detached window does not count. On Linux the shipped systemd unit runs --foreground. Use it to watch a failing start. On a terminal it also appends its output to the journal, without colour.

gdsgate down

Asks a running resident client to stop, over its control channel (a socket on Linux, a named pipe on Windows). Exits 0 when no client is running.

gdsgate status [--json] [--explain] [--refresh]

What the resident client is doing: the identity it holds, the level it settled on, when the catalog was last read, how names are served, how access is established and how a refusal reaches you, the last failure, and one row per resource with the command that reaches it.

identity  identity live for 42m · direct · catalog last read 14s ago
names     hosts · zone gds in /etc/hosts · 3 answering
access    certificates: pre-issued · notify: /usr/bin/notify-send
state     /home/me/.gdsgate/endpoints.json

resource     name             kind        address          access   serving  connect
pg-prod      pg-prod.gds      postgres    127.70.0.2:5432  ok       yes      psql 'postgres://pg-prod.gds'
dev1         dev1             ssh         -                step-up  yes      -
prodcluster  prodcluster.gds  kubernetes  127.70.0.4:6443  ok       yes      kubectl --context prodcluster
pg-demo      pg-demo.gds      postgres    127.70.0.5:5432  ok       no       -

dev1     port 22 is privileged → set `[client.direct].privileged_port_offset = 10000` and restart the...
dev1     access refused 1m ago → run `gdsgate step-up dev1 --action connect`, then connect
pg-demo  no connector is serving this resource → start or restart the Connector configured to serve `pg-demo`
# `gdsgate status --explain` prints each of these in full

The example uses Linux with [client.direct].privileged_port_offset = 0. The default offset is 10000, so SSH port 22 becomes local port 10022. On Windows the hosts file is C:\Windows\System32\drivers\etc\hosts.

Header lines:

Line Shows
identity identity lifetime, level, last catalog read
names how names are served
access how certificates are issued and how refusals are notified
state the state file and, at the transparent level, the interface and what it refused
error the last failed poll or file write; shown only then

ACCESS values:

Value Meaning
ok permitted
step-up, approval, mfa refused; the line under the table names the command that resolves it
denied refused; only an administrator can change it
? the gateway could not be asked
- not asked yet

SERVING is whether a connector served the resource at the last catalog read, according to the gateway's routing table. ACCESS ok alone does not mean reachable.

Value Meaning
yes served
no declared, nothing serving it; a connection is refused
- unknown: the listing did not come through a gateway

A resource with no listener, refused access, or no serving Connector has no connect command. Each problem appears under the table with its first action. The brief output shows up to five problems and counts the rest.

--explain shows each problem as separate fields. The recommended action comes first; alternatives are independent ways to address the same problem.

dev1: port 22 is privileged
  Fix:          set `[client.direct].privileged_port_offset = 10000` and
                restart the resident client
  Alternative:  `gdsgate ssh dev1`
  Alternative:  set `[client].access = "transparent"`; restart the resident
                client with interface privileges
  Cause:        could not bind 127.70.0.3:22: Permission denied (os error 13)

Fix is the first action, Alternative lists other actions, Cause lists individual facts, and Note reports any remaining usable access. The local connections section identifies observed processes; it does not authorize them.

An unsuccessful access check is reported as access check failed, with the original error under Cause. Possible causes include a Connector without a route to the resource or a transport failure. When the catalog and access check both report a missing Connector, one diagnostic includes the original access error and the Connector action. Independent access errors and policy refusals remain separate diagnostics.

Resources are ordered by availability: ready first, then resources needing attention, then unavailable resources. Unchecked access, failed checks, missing local listeners and required user actions need attention. Policy denials, missing Connectors and catalog withdrawals are unavailable. Within each group, resource IDs are sorted without case sensitivity, with the exact ID breaking ties. The table, --explain and JSON use the same order. Diagnostics for one resource stay together; resolver problems appear first.

In a terminal, ready resources are green, problems needing action are yellow, policy denials are red, and withdrawn resources are grey. Diagnostic headings use the same colour as their resource row. Only the target name is bold; the problem description after the colon uses normal weight. Set NO_COLOR=1 to disable colour. Files, pipes and JSON contain no colour escapes.

--json retains the status fields, including running, resource connect, listener.reason and resolver.detail. The top-level diagnostics array contains every problem shown by --explain, including access refusals and resources without a serving Connector. Its entries have these fields:

Field Meaning
target Resource ID or names for resolver diagnostics.
headline Short problem description.
cause Array of individual facts.
actions Recommended action first, followed by independent alternatives.
note Remaining usable access, or null.

--explain does not change JSON. A stopped client reports running: false and diagnostics: []. A running client without problems also has an empty diagnostics array. Both running and stopped status reports exit zero.

Flag Purpose
--json Machine-readable output, always complete.
--explain Print every reason under the table in full, unfolded.
--refresh Ask the client to read the catalog now, wait up to 15 seconds for the read, then report. Two reads are at least 5 seconds apart.

See Resident client for the whole picture.

gdsgate completion <bash|zsh|fish|powershell>

Emits a completion script for the shell.

gdsgate completion bash > ~/.local/share/bash-completion/completions/gdsgate
gdsgate completion zsh  > ~/.local/share/zsh/site-functions/_gdsgate
gdsgate completion fish > ~/.config/fish/completions/gdsgate.fish

In PowerShell, load it from the profile:

Add-Content $PROFILE 'gdsgate completion powershell | Out-String | Invoke-Expression'

Besides commands and flags it completes resource names where one is expected, of the kinds the command takes: SSH servers after gdsgate ssh, databases after gdsgate db proxy, every resource after gdsgate request-access. The names come from the resident client's endpoints.json in ~/.gdsgate/; completion does not contact the cluster. A machine that has never run the resident client offers no names.

gdsgate completion --resources [--for ssh|db|kube|tcp|mcp|web] prints those names, one per line; the generated script runs it.

gdsgate machine-id [--token <jwt> | --provision-token <t> | --renew]

Bootstrap or renew a workload identity, the machine counterpart of login. Generates a key pair locally, exchanges a proof for a short-lived SPIFFE-named certificate, and caches it where the resource commands look for credentials. The private key stays on the host. Prints the SPIFFE ID, its owner and groups if any, and the validity.

Flag Purpose
--token <jwt> A platform id-token addressed to the gateway, for example --token "$GITLAB_OIDC" from a GitLab id_tokens: job.
--provision-token <t> An admin-minted provision token (gdsgate authority create-token --role workload), the fallback for a host with no platform issuer.
--renew Renew a provision-token workload without a token: presents the stored certificate with a proof of possession and moves to a new key. A platform-token workload runs the bootstrap again with a new token.
--endpoint <url> Bootstrap listener URL. Defaults to [enroll].endpoint.
# In a GitLab CI job (id_tokens: configured for the gateway audience)
gdsgate machine-id --token "$GDSGATE_OIDC"
gdsgate db proxy prod-db --listen 127.0.0.1:5433 &
psql -h 127.0.0.1 -p 5433