Client commands¶
Common global behavior is documented in the CLI hub. The complete client command family follows.
Client commands¶
Commands a user runs from their own machine. They read the client configuration
([endpoints], [client], [oidc]) plus the identity-token environment
variables.
Issued access certificates are cached under ~/.gdsgate/<cluster>/<user>/ and
reused while in scope and valid. If the cluster CA has rotated, or the
certificate was revoked, the cached certificate is rejected at session open. The
client recognises this, prints "cached access certificate was rejected … re-run
gdsgate login" and clears the stale cache, so a re-login, or a plain retry,
mints a fresh one.
gdsgate connect <host[:port]>¶
Fetches a cluster's settings and writes the client configuration from them. The first command run against a new cluster, and the only one that needs no configuration of its own.
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 (0600) and written atomically. Every later command reads
~/.gdsgate/config.toml when --config names nothing else.
Trust is settled before anything is written. The settings arrive over a
connection that cannot yet be verified, since the anchor that would verify it is
inside them, so a cluster's own claim is never the reason to believe it.
connect shows the fingerprint of the anchor on offer and waits for an answer,
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 that second answer. A gateway that
replays the real cluster's anchor, which is public, cannot complete that
handshake, because it has no certificate the real authority signed.
The operator publishes the fingerprint with
gdsgate 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 and nothing is verified, so development gateways on this host only. Conflicts with --fingerprint. |
--timeout <SECS> |
How long to wait for the gateway. Default 15. |
What it deliberately does not do:
- It carries no identity. The settings say where the cluster is and whom to
trust; nothing in them authenticates anybody, and
connectleaves your login and certificate cache untouched. Sign in afterwards withgdsgate login, orgdsgate machine-idfor a workload. - It does not overwrite. A configuration you may have edited is never replaced
without
--force. - It writes nothing on failure. A mismatched fingerprint, a declined confirmation, an unreachable gateway or a value the configuration would refuse all leave the filesystem as it was.
Values the cluster publishes are held to the same rules as hand-written ones: a
name zone must be a single label, address pools must parse, and a browser origin
must be a bare scheme://host[:port], HTTPS unless it is on this host. Anything
else is refused by name rather than written.
gdsgate login [--browser]¶
Authenticate to the cluster.
- With an
[oidc]provider configured,loginruns 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. --browserruns Authorization Code with PKCE over a loopback redirect, so the browser opens on the same machine.- Without
[oidc],loginconfirms reachability and a local-dev principal is used (GDSGATE_USER, defaultdev).
Headless and CI: skip login and supply the token through GDSGATE_ID_TOKEN.
gdsgate up performs the same sign-in itself when it finds no identity, so
login is a separate step only when you want it to be.
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 none, the same flow login runs, with --browser
for the browser instead of the device code. Without a terminal to sign in on, a
service or a pipe, it stops and names the command to run.
It returns the terminal. The client is started as a process of its own and up
returns once it is serving, so the next command in the same terminal is
gdsgate status. Exit code 0 means there is a client running; a client that died
or never answered is a non-zero exit with the end of its journal
(~/.gdsgate/gdsgated.log) on the screen. Starting a second one beside a running
client is refused, and the running one is left alone. gdsgate down stops it.
--foreground runs the client in this process instead and holds the terminal; an
interrupt then stops it, performing the same teardown down does. That is what
the shipped systemd units use, since a service manager supervises the process it
started, and what to watch a start with when one is going wrong.
gdsgate down¶
Asks a running resident client to stop, over its control socket. Finding none is a success, so a teardown script does not have to guard a call with nothing left to do.
gdsgate status [--json] [--explain]¶
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 issued ahead of the connections that need them · 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, and an unprivileged process cannot bind it
dev1 run `gdsgate step-up dev1 --action connect`, then connect
pg-demo declared, and no connector is serving it
# `gdsgate status --explain` prints each of these in full
The header is four lines. The first carries what decides whether anything works
at all; names and access are the two settings that can be half working;
state collects what you need only when you are asked for a path, the state file
and, at the transparent level, the interface and what it has refused. An
error line appears when the last poll or the last file write failed, and is
never folded into another line.
ACCESS is what stands between you and the resource: ok; step-up,
approval or mfa, meaning refused with one thing you can do to cure it, which
the line under the table names; denied, refused with only an administrator able
to change that; ?, the gateway could not be asked; -, it has not been.
SERVING is the other half of the same question, and the half a certificate
cannot answer: whether a connector was serving the resource when the catalog was
last read, as the gateway's routing table had it. A resource stays declared,
named and addressed after whatever served it goes away, so ACCESS ok on its own
never meant reachable. yes; no, declared with nothing behind it, where a
connection would be refused rather than relayed; -, nobody answered the
question, because the listing did not come through a gateway, which is not a
synonym for no.
A resource with no listener, a resource the gateway refuses, and a resource
nothing serves keep their rows and are repeated below the table with the reason
and the command that cures it, one line each. None of the three is offered a
command to copy: one printed there is one you run and watch fail, or, for the
unserved, watch hang. --explain prints the whole of every reason instead, and
adds which local process reached which resource; where there are more than a
handful, the folded form counts the rest instead of printing them.
--json prints the same for a script, with a connect string per resource and a
running flag; both a running and a stopped client exit zero. It is unaffected
by --explain, since the machine-readable form carries the whole of every reason
either way.
| Flag | Purpose |
|---|---|
--json |
Machine-readable output, always complete. |
--explain |
Print every reason under the table in full instead of folding it to a line. |
See Resident client for the whole picture.
gdsgate completion <bash|zsh|fish>¶
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
Besides commands and flags it completes resource names where one is expected
(gdsgate ssh <Tab>, gdsgate db proxy <Tab>). The names come from the resident
client's ~/.gdsgate/endpoints.json, so completing never calls the cluster and
never waits on a process: it is instant with the client stopped, and a machine
that has never run one offers no names.
gdsgate completion --resources prints those names, one per line. It is what the
generated script runs, and is rarely useful by hand.
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, with the private
half never leaving the host, exchanges the proof for a short-lived SPIFFE-named
certificate, and caches it where the resource commands look for credentials.
Prints the issued 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 |
Token-less renew for a provision-token workload: presents the stored certificate plus a proof of possession and rolls to a fresh key. A platform-token workload instead re-runs the bootstrap with a fresh token. |
--endpoint <url> |
Bootstrap listener URL. Defaults to [enroll].endpoint. |