User guide¶
This page is for a person or workload that already has a deployment supplied by
an administrator. It takes one resource from trust to a native client, records
the positive proof and the nearest refusal, and leaves a safe next step. The
Quickstart is the shortest authority-1 to proxy-1 lab
journey. Use the Admin guide to create that deployment.
The supported release is the version printed by the supplied binary. Record the
output of gdsgate --version beside the deployment's compatibility decision.
The examples below use the split lab topology: Client to Proxy, Proxy to
Authority for decisions, Proxy to Connector for data, and Connector to
lab-postgres. lab-read is the positive database fixture and lab-denied is
visible but denied. The fixture also names authority-1, proxy-1,
connector-1, and alice@example.test.
Prerequisites¶
Reader: an end user or workload owner. The deployment administrator supplies a Linux x86_64 statically linked musl binary, the Proxy address, the transport CA fingerprint, the client configuration location, and the identity-provider instructions. No public download or hosted console is assumed.
Set shell variables on the client host. Values are supplied by the deployment owner, not guessed from this page:
: "${PROXY_ADDR:?set the administrator-supplied Proxy address}"
: "${PROXY_FINGERPRINT:?set the published transport CA fingerprint}"
: "${CLIENT_CONFIG:?set a client configuration path}"
SSH_FRAGMENT="${SSH_FRAGMENT:-./gdsgate-ssh-fragment.conf}"
SSH_CONFIG="${SSH_CONFIG:-$SSH_FRAGMENT}"
The default SSH paths use the generated fragment directly with ssh -F. If
the deployment instead includes that fragment from another owner-controlled
SSH configuration, set SSH_CONFIG to that configuration before continuing.
Keep the private key, identity cache, and any provider token on this host. Do not paste a token into a command argument or commit a generated file.
0. Connect to the cluster¶
Actor: the end user on the client host. Prerequisites: the variables above and the fingerprint verified out of band. Action:
Expected result: the command writes the client settings and transport trust
anchor and exits successfully. It does not authenticate the user. A repeat
with an existing file is refused unless --force is explicit.
Positive verification:
Nearest negative test: omit --fingerprint in a non-interactive shell. The
command must refuse first contact instead of trusting the answer. If the
fingerprint differs, stop, remove only the untrusted output, and ask the owner
to publish the correct value. If the Proxy is unavailable, fix the route or
endpoint and repeat the same trust check. The --insecure option is for a
development process on the same host only; it removes encryption and peer
verification and is not a recovery for a network deployment.
Security boundary: connect establishes server trust, not user identity. The
fingerprint is the evidence that did not originate in the exchange. Cleanup:
retain the trust anchor with its deployment record; remove it only when the
owner has rotated the CA and supplied a replacement. Next: sign in.
1. Sign in¶
Actor: a human using the client host, or a workload owner choosing a workload identity. Human action:
Expected result: the default device flow prints a short verification location
and a one-time code. Approve the request with the identity provider, then the
client stores the resulting identity for this cluster. On a desktop, use
gdsgate --config "$CLIENT_CONFIG" login --browser for the Authorization Code
with PKCE loopback flow.
Positive verification:
The command returns the resources visible to the signed-in principal. The
nearest negative is a token with no permitted group: login can succeed while
ls is empty and a connection is denied. That is a policy result, not proof
that the Proxy is down. Ask the policy owner to inspect view and connect
separately. For a workload, use workload identity
and its short-lived certificate instead of placing a human token in a job.
Security boundary: identity-provider claims become the principal and groups; Cedar policy remains the authority for each action. Cleanup: let the token or certificate expire and remove the local cache when retiring this client. Next: list resources.
2. List what you can reach¶
Actor: the signed-in principal on the client host. Action:
Expected result: one row per catalog resource for which view is allowed,
including its kind, environment, labels, and address information. A resource
can be connectable but omitted when policy grants connect without view.
Use the kind filters when the next native client needs one protocol:
gdsgate --config "$CLIENT_CONFIG" db ls
gdsgate --config "$CLIENT_CONFIG" kube ls
gdsgate --config "$CLIENT_CONFIG" tcp ls
gdsgate --config "$CLIENT_CONFIG" mcp ls
Positive verification in the split lab is a visible lab-read row and a
visible lab-denied row. The nearest negative is an empty list after login:
check the identity, endpoint, and view policy before changing a connector.
Catalog resolution happens before policy evaluation, so a misspelled resource
is not a policy denial. Cleanup: no files are changed. Next: choose an explicit
relay below, or start the Resident client.
Choose the access shape before continuing:
| Need | Use |
|---|---|
| One task, a script, or the clearest failure boundary | the explicit SSH, database, Kubernetes, TCP, MCP, model, or web command below |
| Repeated native-client use under one user account | Resident basic or direct, after reviewing its generated state |
| An application that cannot be pointed at a loopback relay | Resident transparent, only with host-owner approval and a non-overlapping address pool |
| A shared host, server, container, or non-interactive service | an explicit command or reviewed workload/service identity; do not leave a human Resident identity behind |
Start with one explicit command. Adopt Resident only after the native request, denial, cleanup, and ownership boundary are understood.
3. SSH¶
Actor: a user on the client host. Supported topology: a catalogued ssh
resource served by connector-1 or an equivalent Connector. Command host: the
client host. Generate a dedicated native-client fragment:
The default SSH_CONFIG above points directly at this generated fragment. To
use an existing SSH configuration instead, include the fragment before its
matching host entries and set SSH_CONFIG to that configuration. The generated
fragment carries a ProxyCommand, the chosen client config, and the gdsgate
host-key boundary. Do not append a second generated fragment.
Expected result: the native command opens a session and the Authority audit
contains a connect decision and a session close when it ends:
Positive verification is the remote prompt or command result plus the session
row visible to an authorized audit reader. The nearest negative is a Cedar
denial on SshHost::"RESOURCE_NAME"; no downstream SSH connection is made.
If the denial is curable, run the named gdsgate step-up RESOURCE_NAME or
gdsgate request-access RESOURCE_NAME --reason 'ticket-1234', then retry. If it is not
curable, ask the policy owner. A changed host key is a separate native host-key
failure and must be checked against the owner, not bypassed with a broad trust
setting.
Interactive shell¶
Input is the normal SSH command. Expected result: an interactive PTY, resize,
and interrupt work while the session remains open. Proof is the session ID and
close row. A refusal before the prompt is either the connect policy or native
host-key verification. Recover by checking each layer separately. Cleanup is
exit; the connector closes the session and emits its final audit event.
Running a command¶
The exact command is sent to the selected host. Positive proof is its exit status and the recorded session. A command that is denied inside a policy-aware downstream path must not be described as an SSH connect denial. Re-run only after correcting the command or its policy. Cleanup is automatic on process exit.
-L: local port forward¶
Prerequisite: policy must grant sshForwardLocal and the resource's forward
allow-list must contain the requested destination. Action:
Expected result: the local listener opens only after the session decision and the requested destination is permitted. Positive proof is the listener and its audit event. The nearest negative is a policy or allow-list denial before any forward bytes reach the Connector. Remove the listener by ending SSH; do not leave a background process with an inherited grant.
-A: agent forwarding¶
Prerequisite: the backend must set allow_agent_forward = true and policy must
grant sshForwardAgent. Both are off by default, and neither connect nor
sshForwardLocal implies them. Action:
Expected result: the session's programs see SSH_AUTH_SOCK and sign with the
client's keys through it. Positive proof is that variable inside the session and
the sshForwardAgent audit event. The nearest negative is a refusal, where the
Connector creates no socket and the variable is absent from the session's
environment. Weigh it before granting: while the session lives, anything that
reaches the socket authenticates as you wherever your keys are trusted. That is
the shell, what it starts, and root on the Connector host.
-R: remote port forward¶
The actor and prerequisites are the same, with sshForwardRemote and the
remote bind allow-list. Use an explicit loopback bind when the remote service
must not be exposed to that host's network:
Expected result and evidence are the remote listener plus the audited forward. The nearest negative is a denied remote bind. Recovery is a narrower bind or a policy request. Cleanup is ending SSH and confirming the remote listener is gone.
SFTP¶
Use the same generated fragment:
The actor owns the files and the command host is the client. Positive proof is the transfer result and the session close. The nearest negative is an SSH connect denial or a downstream file permission error. Keep the two errors distinct, and close the SFTP session when the transfer finishes.
Agent forwarding (-A)¶
Agent forwarding extends the native SSH trust boundary to the remote host. Use it only when policy and the resource owner explicitly permit it:
The security consequence is that a process on the remote host can ask the forwarded agent to sign while the session is open. The nearest negative is the forward policy refusing the channel. Prefer a short-lived, purpose-specific credential, then close the session and remove any agent socket reference.
Next: database relay, or use the Resident client when native names and generated files are needed for several resources.
4. Databases (PostgreSQL / MySQL)¶
Actor: a database user on the client host. Supported kinds are postgres,
mysql, and the Connector backend cockroach when the catalog advertises its
PostgreSQL wire resource as postgres. The command host is the client. Start a
loopback relay and point the native client at the printed local port:
gdsgate --config "$CLIENT_CONFIG" db proxy lab-read --listen 127.0.0.1:5433 &
DB_PID=$!
trap 'kill "$DB_PID" 2>/dev/null || true' EXIT
psql -h 127.0.0.1 -p 5433 -c 'select count(*) from lab_table;'
Expected result in the split lab is count 3. The Proxy makes the connect
decision; the Connector may make a per-statement dbQuery decision when the
backend is configured for it. Positive evidence is the query result and the
corresponding session/query audit rows. The nearest negative is:
Expected result: a policy denial naming Database::"lab-denied", with no
backend connection. A direct connection to the fixture backend must fail at the
network boundary, not be used as an alternative authorization test. Recovery:
ask for the typed connect or dbQuery grant, or run the named step-up and
retry. Cleanup: stop the relay and remove its trap; do not preserve a database
password in a shell history or generated config.
Next: Kubernetes or policy database patterns.
5. Kubernetes¶
Actor: a cluster operator on the client host. Supported version: use the
Kubernetes client version pinned by the deployment owner and record it with
gdsgate --version; the generated exec credential and per-request path are
version-sensitive. A Connector backend of kind kubernetes is required.
Create the local proxy and kubeconfig for the selected catalog resource. Keep the generated credential short-lived and local:
KUBECONFIG="${KUBECONFIG:-./gdsgate-kubeconfig.yaml}"
gdsgate --config "$CLIENT_CONFIG" kube login KUBE_RESOURCE > "$KUBECONFIG"
gdsgate --config "$CLIENT_CONFIG" kube proxy KUBE_RESOURCE --listen 127.0.0.1:6443 &
KUBE_PID=$!
trap 'kill "$KUBE_PID" 2>/dev/null || true' EXIT
kubectl --kubeconfig "$KUBECONFIG" get --raw=/version
Expected result: the exec plugin obtains a short-lived credential and the
request reaches the intended API through the local listener. Every API request
is evaluated as kubeRequest, narrowed by verb, resource kind, namespace,
name, subresource, and the policy marker. Positive evidence is the response,
the request decision, and the session close/audit row where a session is held.
The nearest negative is a request with an unapproved verb or namespace. It
returns 403 before the backend request. Port-forward is refused unless the
backend sets allow_port_forward; when enabled it is an opaque audited byte
channel and not a replayable typed request. exec and attach have their own
upgrade and recording limits. Recovery is a narrower request or a policy
step-up, not a broad cluster grant. Cleanup: stop the proxy, delete temporary
kubeconfig files, and let generated credentials expire. Next: TCP and web.
6. TCP, Redis, web¶
Actor: a user on the client host. A tcp backend is an opaque byte relay, not
an assertion that gdsgate can inspect its protocol. Start it on loopback:
gdsgate --config "$CLIENT_CONFIG" tcp proxy RESOURCE_NAME --listen 127.0.0.1:6390 &
TCP_PID=$!
trap 'kill "$TCP_PID" 2>/dev/null || true' EXIT
redis-cli -p 6390 ping
Expected result: the native client receives its normal response and the audit
records the connect and byte counts. The nearest negative is a connect
denial, which must occur before a downstream dial. Recovery is a typed policy
grant or a corrected resource ID. Cleanup is stopping the relay.
The web kind is a named upstream application and the web-egress kind is a
separate policy-controlled forward/CONNECT lane. Do not substitute one for the
other. Next: MCP.
7. MCP servers¶
Actor: an MCP client owner on the client host. Start the proxy:
gdsgate --config "$CLIENT_CONFIG" mcp proxy MCP_RESOURCE --listen 127.0.0.1:8765 &
MCP_PID=$!
trap 'kill "$MCP_PID" 2>/dev/null || true' EXIT
gdsgate mcp bridge 127.0.0.1:8765
Expected result: tools/list contains only the tools visible to the principal,
and an allowed tools/call, resources/read, or prompts/get reaches the
server. Static allowed_tools remains available; Cedar per-tool enforcement is
enabled only when the backend sets enforce_tool_policy. Positive evidence is
the JSON-RPC result and mcpCallTool, mcpReadResource, or mcpGetPrompt
audit row. The nearest negative is a denied tool, resource, or prompt. It must
return a JSON-RPC error before the backend is called. Recovery is a narrower
policy change, an allowed tool name, or a step-up. Cleanup: stop the proxy and
remove client-specific endpoint files.
Security boundary: listing visibility and invocation are separate decisions; seeing a server does not grant every tool. Next: model services.
8. Model services¶
Actor: a model client or delegated agent owner. A catalogued llm resource is
not a session relay: the Connector reads the model name from each HTTP request
body and evaluates llmCall for each call. The gateway-held credential, if the
backend uses substitution, never enters the client or sandbox.
Positive action is an allowed model call through the configured gateway. Proof
is the response, model/token counters, and the llmCall audit row. The nearest
negative is an unlisted model or a revoked delegation; the call is refused
before it leaves the Connector. Recovery is a policy grant to the exact model,
or a new short-lived delegation. Cleanup is revocation or expiry. See AI
agents for the containment decision table and
model policy for typed rules.
9. Web applications¶
Actor: a browser or HTTP client on the client host. For a named web resource,
start the local proxy and use the printed address:
Expected result: the proxy prints a loopback address and an allowed route
returns the application response. A route denial is an HTTP 403 from the
gateway; an application 403 proves only that the upstream application denied
the request. Positive evidence is the response and the httpRequest audit row.
The nearest negative is an unauthorized path or method. Recovery is a typed
route policy change or step-up. Cleanup is stopping the proxy and closing the
browser session.
Reaching the outside through a web-egress resource¶
web-egress is not a named application. The client sends a policy-controlled
forward request; a DNS answer is dialled only where the address is a host on the
public internet, so private, loopback, link-local, and metadata targets are
refused before the egress connection, and so is any other range set aside for a
purpose. A range the network genuinely routes is named in routed_ranges. Positive proof is an allowed
external response and an egress audit row. The nearest negative is a private or
metadata address and must fail closed. Recovery is a permitted destination
approved by the network owner. Cleanup is stopping the relay; never copy a
provider credential into the request body.
Reaching an application from a browser with nothing installed¶
This is an administrator-provided hosted route, if the deployment exposes one. The user must receive its exact origin and trust instructions out of band. Do not invent a public console name or URL. Expected result is the same route decision and audit row as the local proxy. A missing or expired browser identity is the nearest negative; use the provider login and retry. Cleanup is sign-out and expiry according to the deployment owner.
Reaching an application by its own name¶
The Resident client can intercept a catalogued native_name only when the user
also opts into that whole name in [client].intercept_names. Both facts are
required. Positive proof is the browser's original name and a matching route
audit row. The nearest negative is a name not in the local opt-in or catalog,
which must resolve normally rather than being hijacked. Recover by checking the
two lists, then stop the Resident client to remove interception. See Resident
client.
10. Just-in-time access¶
Actor: a user who has a reason but no standing action. Prerequisite: the denied
target action offers an approval remedy and the deployment has an approver
path. Creating the request is not a separate Cedar action; approval is gated by
approveRequest. Request on the client host:
gdsgate --config "$CLIENT_CONFIG" request-access RESOURCE_NAME \
--action connect --reason 'ticket or task identifier' --ttl 900
gdsgate --config "$CLIENT_CONFIG" requests
Expected result: a queued request with an ID. An approver runs gdsgate approve
REQUEST_ID through an authorized route. Positive proof is the approval audit
event and the subsequent connect. The nearest negative is a request denied by
policy or an expired TTL. Recovery is a new, narrower request; an old request
must not be reused. Cleanup is expiry and session termination. See policy JIT
patterns.
11. Multi-factor (MFA) and step-up¶
Login freshness and a one-time gateway step-up are different channels. The
identity provider establishes the login claim; gdsgate step-up RESOURCE_NAME
establishes a grant bound to one action and resource:
Use --code only when the deployment enrolled a local TOTP factor. The default
passkey flow opens the browser. Expected result: the next matching request is
allowed once. Positive evidence is the successful request and its step-up audit
row. The nearest negative is a second identical request, which is denied because
the grant was spent. Recovery is a fresh step-up with the same narrow scope.
Cleanup is expiry; there is no reusable credential to archive. Next: sessions.
12. Managing your sessions¶
Actor: a principal authorized to view or terminate sessions. On the client host:
gdsgate --config "$CLIENT_CONFIG" session list
gdsgate --config "$CLIENT_CONFIG" session kill SESSION_ID
Expected result: session list shows only sessions policy permits you to see;
session kill ends the selected one and records the action. Positive evidence
is the close event and the downstream connection ending. The nearest negative
is a session outside your killSession scope, which is denied without touching
it. Recovery is to choose an ID from the current list, not a stale log. Cleanup
is automatic when the session exits. For audit verification, use What the
audit log captures.
13. Delegating access to an agent¶
Use the AI agents page for the threat model, model channel, and sandbox boundary. The human actor creates the narrow grant on the client host:
gdsgate --config "$CLIENT_CONFIG" delegate \
--can mcp:search@tools-dev \
--can llm:call@models-dev \
--ttl 900 --sandbox strict --model gateway \
--agent-profile PROFILE_NAME \
--exec ./agent-wrapper -- --single-search-and-single-model-call
Run gdsgate delegate --list-agent-profiles to discover the profiles this
installation ships and the source and review date for each one. The profile
name must be verified in the local configuration and its external documentation
review record. Expected result: the child receives only the
agent-bound capability while it runs; the grant is revoked when it exits.
Positive evidence is delegation.create, sandbox.claim, the allowed tool and
model rows, and a denial after revocation. The nearest negative is an unlisted
tool or model, or a direct model channel under a sandbox, which must be refused
before launch. Cleanup is automatic for --exec; a --bind-key grant requires
an explicit revoke and removal of its mode-0600 directory.
What the audit log captures¶
The Authority audit chain separates authentication, policy decisions, session activity, delegation, and administrative events. A positive row names the principal, resource kind, action, outcome, and session or request identifier where the lane supplies one. A refusal row names the nearest boundary and must not be treated as evidence that a backend was contacted. Authorized operators verify the chain off-band from a separate workstation whose configuration opens the audit store directly and names the anchor key:
Expected result is a chain and anchor verification summary. The audit is not a secret store: tokens, private keys, passwords, and prompt bodies are not a substitute for a credential vault. See Operations for the exhaustive event and export reference owned by the operations documentation.
Troubleshooting¶
Check layers in this order: binary version, client trust, identity, catalog visibility, policy action, local relay, then backend. Keep the nearest failure message and its request ID when asking an administrator for help.
| Symptom | Evidence to collect | Safe recovery |
|---|---|---|
| First contact refuses | published fingerprint and offered fingerprint | stop and ask the deployment owner to reconcile them |
Login succeeds but ls is empty |
principal subject, groups, and endpoint | ask for view on the intended typed resource |
connect is denied |
resource kind, action, request ID | use the named step-up/JIT path or ask for policy review |
| Local relay opens but native client fails | relay output and native client error | check local port, generated config, and host-key/TLS trust separately |
Kubernetes request is 403 |
verb, kind, namespace, name, subresource | request one typed kubeRequest, not cluster-wide access |
| MCP tool is absent | tools/list output and tool name |
check allowed_tools, enforce_tool_policy, and viewTools |
| Agent loses access after exit | delegation ID and close event | expected for --exec; revoke and clean a bind-key bundle if one remains |
Never work around a denial by dialing a backend directly. If the source or deployment does not identify a supported version or endpoint, record that ambiguity and ask the owner; do not fill it with a public URL or credential.