Skip to content

User guide

This page is for end users — engineers who need to reach a server, a database, a Kubernetes cluster, or a TCP service through gdsgate. You sign in once with gdsgate login and then use the same native tools you already know.

If you are setting up the cluster, read the Admin guide instead.

Prerequisites

  • The gdsgate binary on your $PATH. It is one self-contained executable — there is no separate CLI to install.
  • A client configuration file from your administrator. It points the binary at your cluster's public endpoint and (in production) at the cluster's transport CA so the public TLS is verified. See [endpoints] and [client] in Configuration.

A minimal client config:

profile = "prod-eu"

[endpoints]
proxy_public = "gdsgate.example.com:50061"

[client]
transport_ca = "/etc/gdsgate/transport-ca.pem"

[oidc]
issuer = "https://idp.example.com/realms/gdsgate"
client_id = "gdsgate"

Save it (anywhere readable, conventionally ~/.gdsgate/client.toml) and point every command at it with --config. Anywhere below where you see gdsgate …, read it as gdsgate --config ~/.gdsgate/client.toml ….

1 — Sign in

gdsgate login

With an identity provider configured, login runs the device flow: it prints a short URL and a one-time code, you approve it in a browser on any device, and the obtained identity token is cached on disk for the follow-up commands. The cache is keyed by the cluster's public endpoint so several clusters can be signed into independently.

Variants:

  • gdsgate login --browser runs the Authorization Code + PKCE flow against a loopback redirect — the browser opens on the same machine. Use this on a desktop where the IdP supports PKCE.
  • For headless / CI use, obtain a token out of band and export it; every command will pick it up:
export GDSGATE_ID_TOKEN="$( …obtain an id_token from your IdP… )"

Without an identity provider (a development cluster), login confirms reachability and a local development principal is used.

Who do I act as?

After a successful sign-in, the identity gdsgate carries is the subject (sub) of your identity token, with the groups claim as your group memberships. The policy in your cluster authorises by those — typically group-scoped. See Policy for details.

2 — List what you can reach

gdsgate ls

prints the resources the policy currently lets you view — one row per resource id, with kind, environment, and labels. The list is filtered: resources you have no view permission on are not shown.

Per-protocol variants restrict by kind:

gdsgate db ls            # only database resources
gdsgate kube ls          # only Kubernetes clusters
gdsgate tcp ls           # only raw-TCP resources
gdsgate mcp ls           # only MCP servers

3 — SSH

gdsgate routes native ssh through a ProxyCommand. Generate an OpenSSH client-config fragment once and append it to ~/.ssh/config:

gdsgate --config ~/.gdsgate/client.toml ssh-config '*.gds' >> ~/.ssh/config

That emits:

Host *.gds
    ProxyCommand /usr/local/bin/gdsgate --config /home/user/.gdsgate/client.toml proxy-ssh %h
    StrictHostKeyChecking accept-new
    UserKnownHostsFile ~/.gdsgate/known_hosts

The --config path is woven into the ProxyCommand so native ssh (which has no gdsgate flags of its own) reaches the same cluster you signed into. The host alias is the gdsgate resource id.

Then connect with plain ssh:

ssh deploy@web-01.gds

ssh runs gdsgate proxy-ssh web-01.gds under the hood. That request is authorised (sshConnect), the byte stream is relayed to the agent, and the agent terminates and records the session.

Interactive shell (PTY)

A native interactive shell works as on any host:

ssh deploy@web-01.gds
# vim, htop, tmux, Ctrl-C, window resize — all behave as expected

The agent allocates a proper PTY (the pty_request is honoured, live window-change events propagate to the spawned program).

Exec mode

A one-off command also works, with or without a PTY:

ssh deploy@web-01.gds 'systemctl status nginx'
ssh -tt deploy@web-01.gds 'top -bn1'   # -tt forces a PTY for exec

-L — local port forward

Open a local port that tunnels through the SSH session to a host:port the agent can reach from inside the protected zone:

ssh -fnNT -o ExitOnForwardFailure=yes \
    -L 6379:redis.internal:6379 \
    deploy@web-01.gds

redis-cli -p 6379 ping     # PONG

-L is gated by two layers (see Concepts):

  • The agent's operator declares which host:port targets are even reachable through this backend (allow_local_forward).
  • The Cedar policy decides per user and per session (sshForwardLocal).

A target denied by either layer closes the channel.

-R — remote port forward

Open a listener on the agent's side that tunnels back to a service on your machine:

ssh -R 127.0.0.1:8001:127.0.0.1:9999 \
    deploy@web-01.gds 'curl http://127.0.0.1:8001/health'

Bind to 127.0.0.1 explicitly

Without 127.0.0.1: in the -R spec, OpenSSH sends localhost as the bind host, which does not match an allow-list keyed on 127.0.0.1. The Cedar policy further restricts -R to loopback binds in the default deployment — naming the bind explicitly is the safe practice.

-R is gated the same two-layer way (allow_remote_forward and sshForwardRemote). With no operator allow-list, -R is disabled.

SFTP

sftp works on a model-A SSH resource (it speaks the SFTP subsystem to the agent, which runs an SFTP server bound to the agent host's filesystem):

sftp deploy@web-01.gds
sftp> put report.tar.gz /tmp/

Agent forwarding (-A)

ssh -A exposes your ssh-agent to the remote command through a per-channel Unix socket, and an auth-agent@openssh.com reverse channel back to your local agent:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh -A deploy@web-01.gds 'ssh-add -l'

4 — Databases (PostgreSQL / MySQL)

Run a localhost relay and point your native client at it:

gdsgate db proxy prod-db --listen 127.0.0.1:5433 &
psql -h 127.0.0.1 -p 5433 -U app -d app
# or:
mysql -h 127.0.0.1 -P 5433 -u app -p app

Each connection through the listener is authorised independently (dbConnect) and relayed to the database over the proxy. The agent forwards it to the real server while tapping the client→server stream for the structured query log (recorded into the audit chain).

You can also authorise once up front:

gdsgate db login prod-db        # mints and caches the access certificate

5 — Kubernetes

kubectl integrates through an exec credential plugin so you keep using your existing kubeconfig workflow. Emit a kubeconfig fragment and merge it:

gdsgate kube login prod-cluster --server https://127.0.0.1:6443 \
    > ~/.kube/gdsgate-prod-cluster
KUBECONFIG=~/.kube/config:~/.kube/gdsgate-prod-cluster kubectl config view --flatten > ~/.kube/config.new
mv ~/.kube/config.new ~/.kube/config

Then run a localhost Kubernetes relay and point kubectl at it:

gdsgate kube proxy prod-cluster --listen 127.0.0.1:6443 &
kubectl --context prod-cluster get pods -A

On each kubectl call, the bundled exec plugin runs gdsgate kube credentials prod-cluster for a short-lived credential. The gdsgate Kubernetes listener resolves the identity and impersonates you to the real API server (the client's Authorization and Impersonate-* headers are stripped and replaced with the resolved identity). The agent authenticates to the cluster API with its own service-account token and CA bundle — you cannot choose who you impersonate.

6 — TCP, Redis, web

Any raw TCP resource (Redis, HTTP, internal services) is reached the same way as a database — a localhost relay:

gdsgate tcp proxy redis-cache --listen 127.0.0.1:6390 &
redis-cli -p 6390 ping

Or, for clients that speak HTTP:

gdsgate tcp proxy internal-api --listen 127.0.0.1:8088 &
curl http://127.0.0.1:8088/healthz

Authorisation is tcpConnect and the byte stream is relayed verbatim.

7 — MCP servers

An MCP (Model Context Protocol) server is reached the same way as a TCP service, but with one extra check at the agent: tools/call requests are gated against the agent's allowed_tools allow-list, and every tool call is audited.

gdsgate mcp proxy tools-mcp --listen 127.0.0.1:8765 &
# point your MCP client at http://127.0.0.1:8765

A denied tool returns a JSON-RPC error before the call reaches the backend.

8 — Just-in-time access

For sensitive resources the cluster policy can require a JIT approval: you request access, one or more approvers confirm it, and only then does the policy evaluate to Allow for that resource and identity.

Request:

gdsgate request-access prod-db \
    --reason "Investigating PROD-1234 — read-only on orders" \
    --ttl 3600

This prints a request id and records the request in the audit log. The configured approvers run:

gdsgate requests             # list pending requests
gdsgate approve <request-id>

Once enough distinct approvers have confirmed (the threshold follows the [approvals] cascade), your access certificate request for that resource will be granted by the policy for the remaining TTL. Both the request and each approval are audited.

Reading the audit you leave behind

Every step above is policy-checked and persisted to the cluster's tamper-evident audit log: the authorisation decision, the issued certificate, the opened session, the SFTP file operations or the SQL query stream or the Kubernetes call or the MCP tool invocation. Your administrator can export the log to a SIEM — see Operations.

Troubleshooting

Symptom Likely cause What to check
gdsgate ls empty, but gdsgate ssh works The cluster catalog has not declared the resource for listing; access is still authorised. Ask the administrator to add a [[discovery.resources]] entry — see Configuration → [discovery].
command failed: permission denied on request-access / ssh Cedar policy denied this principal for this resource. Check group membership in the IdP, and resource environment / labels.
proxy-ssh session failed: transport error ~/.ssh/config calls gdsgate proxy-ssh without --config, so the wrong cluster is dialled. Regenerate the fragment with gdsgate --config <path> ssh-config--config is then woven in.
REMOTE HOST IDENTIFICATION HAS CHANGED! on the second SSH session A stale entry in known_hosts from before the agent had a persistent SSH host key. Remove the entry (ssh-keygen -R <alias>) and reconnect — the agent's host key is now stable across sessions.
redis-cli: Protocol error, got "S" as reply type byte on tcp proxy The resource id is actually an SSH backend; the agent answered with the SSH banner. Use the right resource id for the protocol (redis-cache, not web-01).