Skip to content

Resident client

The Resident client is an optional local daemon. It keeps the identity and catalog current, assigns a loopback address to each visible resource, and regenerates native-client files when the catalog changes. Authorization is the same as for the explicit commands in the User guide.

Platforms: Linux x86-64 (the static musl binary from the release) and Windows x86-64 (the .msi installer, supplied on request). Windows differences are in Windows.

Access levels

access controls how an application reaches a local relay. resolver controls how names are published. The two are set independently.

Machine Starting level
Personal workstation basic; direct when a native client needs it
Managed desktop basic under the managed user
Shared host Linux: protected per-user basic/direct; other platforms: off
Server or non-interactive service explicit commands or workload identity
Container explicit commands
Level What it publishes Addressing requirement
off no local listeners; use explicit commands none
basic one listener per resource on 127.0.0.1, each on its own port none
direct one loopback address per resource; ports below 1024 are shifted by default no low-port bind privilege with the default offset
transparent a virtual interface and per-resource addresses Linux: CAP_NET_ADMIN. Windows: the gdsgate-client-net service and Wintun
auto the strongest level available; the default none; status --explain shows the chosen level and why

On Linux, local owner protection is enabled by default for all active levels. The kernel admits resource connections only from sockets created by the resident owner's OS UID. An ordinary up requests elevation once to install the system service; sudo gdsgate up needs no second elevation. The default low-port offset removes the bind restriction, while owner protection still needs CAP_NET_ADMIN. See Local owner protection.

On the client host:

gdsgate --config "$CLIENT_CONFIG" up
gdsgate --config "$CLIENT_CONFIG" status --explain

Expected result: up returns when the level is settled; status reports the level in use. A catalogued but denied resource keeps its address, name and status row; the gateway refuses the connection and reports the reason. gdsgate down removes the listeners and names.

Privileged ports at direct

The default [client.direct].privileged_port_offset = 10000 moves only ports 1–1023: SSH 22 becomes 10022, HTTP 80 becomes 10080, and 443 becomes 10443. A database on 5432 stays on 5432. This changes the local listener; the Connector still connects to the original resource port.

Set an explicit offset from 1023 to 64512, or 0 to preserve every protocol port. With 0, low ports bind only when the host permits it. Windows has no Unix low-port restriction; reservations and existing listeners can still refuse a bind. status, connection commands and generated native-client files show the actual local port. The offset does not apply to basic or transparent.

The transparent level

transparent routes traffic for the client's address pool through the daemon. Use it for applications that cannot be pointed at a local proxy. It changes host routing while the daemon runs, for every process on the host.

Capabilities

The daemon needs a virtual interface, an address pool, and permission to install routes. Linux: CAP_NET_ADMIN. Windows: the Wintun adapter held by the gdsgate-client-net service (see Windows). status --explain names what is missing.

Requirements

The address pool must not overlap the host LAN or another virtual network. Check basic first, then request transparent:

gdsgate --config "$CLIENT_CONFIG" up
gdsgate --config "$CLIENT_CONFIG" status --explain

Expected result: ACCESS and SERVING report transparent, and a request to a visible resource succeeds. On a missing capability or a pool conflict the daemon refuses or falls back to a lower level, and status shows the level in use. After down, the interface and routes are removed.

Tradeoffs

transparent reaches applications that ignore proxy variables and changes host routing. On Linux, local owner protection restricts resource traffic to the owner's UID. With protection disabled, or on other platforms, another local account that can reach an endpoint can use the owner's cluster identity.

Connection identity

The daemon holds the user's identity and obtains short-lived access certificates per resource. Authority still decides connect; request-aware lanes decide each operation. A stale or revoked certificate shows as an ACCESS refusal.

Shared machines

On Linux, keep local owner protection enabled and give each resident separate address pools and basic port ranges. Transparent residents also need distinct interface names. Name publication changes the host resolver; coordinate it separately. On platforms without owner protection, use off and explicit commands for a shared machine. When an account is retired, run down and remove its service, generated files and identity cache.

Local owner protection

[client].local_user_only defaults to enabled on Linux and disabled elsewhere. Setting it to true on an unsupported platform refuses startup. On Linux it requires the trusted filter command /usr/sbin/nft (or /sbin/nft) and CAP_NET_ADMIN; install the host's kernel filter package before running up. A failure to establish protection prevents publication. An endpoint that cannot be protected remains unbound. There is no automatic fallback to an unprotected listener.

At basic and direct, the filter covers each exact IPv4 address and TCP port before the listener starts accepting. At transparent, it covers the resource pool before the interface opens, including application domains. Traffic arriving from another interface or forwarded from another network namespace is denied, even if that namespace uses the same numeric UID. The pool's reserved DNS address is excluded; it serves names, not resource sessions. The private control socket has its own peer-credential checks.

status reports local OS owner UID <UID> only; JSON includes local_owner_uid when enforcement is active. A clean stop closes listeners and the interface before removing their protection. After a forced termination, restrictive rules remain until the next start safely replaces that stopped process's policy, or sudo gdsgate service uninstall client removes it. A live resident's policy is never replaced by another startup.

The boundary is the socket creator's UID. Group membership grants no access. Processes under the owner's UID can use the resources; an owner can also pass an already authorized socket to another process. Root and processes able to change the kernel filter are outside this boundary. An administrator must retain the resident's tables when updating host firewall policy; flushing them removes enforcement.

Set local_user_only = false only when exposing the owner's resource relays to other local accounts is intended. Separate configurations and high ports alone do not provide an OS-user boundary.

Names

resolver Names
off none
hosts a marked block in the hosts file
system the daemon serves the zone; the system resolver is pointed at it
auto the strongest mode the host allows

A resolver fallback does not change the access level.

Name resolution per access level

Access resolver = off hosts system auto
off no names managed hosts entries if allowed explicit system resolver if configured hosts or off
basic no names managed hosts entries possible when the host routes queries to the daemon system, hosts, or off
direct no names managed hosts entries possible without transparent access system, hosts, or off
transparent no names managed hosts entries normally required for whole-host names system or hosts

system works at any level; transparent does not select it.

hosts

Requires write access to the hosts file.

gdsgate --config "$CLIENT_CONFIG" up
getent hosts RESOURCE_NAME

Expected result: the name maps to the assigned local address. With a missing or read-only hosts file, status shows names - or names ?; use resolver = system or the loopback addresses. down removes only the block this configuration wrote.

system

Requires the daemon's name server, a zone, and a system resolver that sends the zone's queries to it. Check both:

gdsgate --config "$CLIENT_CONFIG" status --explain
getent hosts RESOURCE_NAME

If the daemon answers but lookups fail, the system resolver still sends the zone elsewhere. Restore the previous resolver settings or switch to hosts.

Verifying name resolution

status --explain shows the resolver mode, its state, and any fallback reason. Look up one catalog name and one name outside the catalog; only the first resolves to a managed address. After down, neither does.

An application's own name

A web resource may declare a native_name, the upstream application's own host name. The Resident client intercepts it only when the user also lists the whole name in [client].intercept_names.

Start the Resident client and open the name in a browser or HTTP client. Expected result: the application receives its original host name, and audit records an httpRequest. A name in only one of the two lists is not intercepted. To stop interception, remove the name from intercept_names and run down.

Certificate root for intercepted names

Install the deployment's certificate root in the browser or client trust store before using interception. A trust warning means the root is missing. Remove the root when the deployment is retired.

Limitations

Interception changes DNS and TLS for the listed names on the whole host. A name absent from the catalog is not intercepted. Native names depend on the resolver mode and are unavailable while it has fallen back to off.

Status output

status reports identity, catalog, names, listener, access and serving states: ok passed, ? unknown (control plane unreachable), - not requested. Spacing may differ between releases:

identity  ok   catalog ok   names ok   access ok   serving ok

Start-up sequence

up validates settings, refuses to start over a running client, signs in when needed, starts the daemon, settles the interface and names, fetches the catalog, assigns addresses, generates native files, and reports the serving state. It waits up to 10 seconds for the daemon's socket and 3 seconds for names and the interface. In scripts, check status; do not rely on fixed sleeps.

Background execution

gdsgate --config "$CLIENT_CONFIG" up
gdsgate --config "$CLIENT_CONFIG" status
gdsgate --config "$CLIENT_CONFIG" down

up returns when the daemon reports its state. Expected result: SERVING ok. If a client is already running or the identity is refused, up fails and the running client is left unchanged; see status --explain.

The client journal

The journal records start-up decisions, fallbacks, listener changes and errors. It contains no private keys or provider tokens. With a malformed generated state file: stop the daemon, keep a copy of the file, check the configuration, start again.

--foreground

Use foreground mode to diagnose start-up or under a service manager:

gdsgate --config "$CLIENT_CONFIG" up --foreground

The command stays attached while the daemon serves; start-up errors print to the terminal. Interrupting it shuts down normally and removes managed state.

On a terminal, output is also appended to ~/.gdsgate/gdsgated.log; its end shows whether shutdown finished after the window was closed. Under a service manager, output goes where the manager sends it.

Automatic authentication

With no usable identity, up runs the configured login. To use the browser:

gdsgate --config "$CLIENT_CONFIG" up --browser

A non-interactive service has no browser: use a workload identity. Do not put a user's token in a unit file.

Certificate prefetch

With warm_access on, the daemon requests access certificates during the catalog pass, and status shows which resources are ready to connect. A refusal is cached with its reason and not retried continuously. ACCESS ok means ready; ACCESS step-up or ACCESS denied means refused. After a step-up or policy change, the next catalog pass picks it up.

Address stability

Addresses stay the same while the client runs. An address freed by a removed resource is not reused immediately. On an address conflict, fix the pool and restart the client. Do not edit the state map by hand.

Resources removed from the catalog

When a resource leaves the catalog, the client immediately stops accepting new connections to its address and removes its generated entries and DNS names. The resource remains in status with listener state withdrawn during the retention period. status --refresh reads the catalog again; it does not clear records before their deadline. Use ls to see the current catalog and status --explain to inspect recent withdrawals. Open sessions end under their own revocation rules.

The age shown in status is time since withdrawal. Retention is [client.transparent].reuse_grace_secs, capped at 86400 seconds (one day). After that deadline, cleanup removes the record from endpoints.json, status and diagnostics. Cleanup runs before catalog reads and at least once per minute while waiting, including when identity or catalog access fails. A stopped client cleans up when started again. Returning within retention restores the same address.

Address reuse

Addresses and basic ports published during a run remain reserved in memory until that run stops, even after the removed resource's record expires. These anonymous reservations contain no resource names and are not written to disk. After a restart, expired assignments are removed before new addresses are allocated; the freed addresses and ports can be used again. Active resources keep their addresses.

Generated files

Generated files are owner-only, written atomically, and marked; the daemon replaces only its own content. They contain routing data and no credentials. SSH host keys stay in the native client's known_hosts.

To inspect them:

gdsgate --config "$CLIENT_CONFIG" status --json

Expected result: paths and resource states match the current catalog. After an interrupted update, stop the client and start it again.

SSH configuration

The Resident maintains ~/.gdsgate/ssh_config for visible SSH resources. Add this line once to ~/.ssh/config, before matching Host entries:

Include ~/.gdsgate/ssh_config

Then connect by resource name:

ssh RESOURCE_NAME

For one invocation, use ssh -F ~/.gdsgate/ssh_config RESOURCE_NAME. The generated entries carry the selected cluster configuration and host-key settings. SSH audit and recordings explains the session notice. Shell commands, SFTP and forwarding are covered in the User guide.

Kubernetes contexts

The generated Kubernetes contexts are connected to the current shell with:

source ~/.gdsgate/env
kubectl --context prod-cluster get pods

For one command, select the generated file explicitly:

kubectl --kubeconfig ~/.gdsgate/kube/prod-cluster.yaml get pods

The local Kubernetes listener speaks HTTP; its traffic crosses the authenticated gdsgate tunnel, and the connector connects to the backend API independently.

If kubectl reports that a context was not found, check KUBECONFIG in that shell and load the environment again. A Forbidden response requires checking both gdsgate kubeRequest policy and Kubernetes RBAC for the impersonated user and groups. See the Kubernetes user guide for the explicit relay commands.

Database and TCP environment

Load the generated environment in each shell that uses the resource listeners:

source ~/.gdsgate/env

It exports GDSGATE_<RESOURCE>_HOST and GDSGATE_<RESOURCE>_PORT for each bound resource. Resource IDs are uppercased; punctuation becomes underscores.

PGHOST and PGPORT are set when exactly one resource is bound for its database kind; MYSQL_HOST and MYSQL_TCP_PORT follow the same rule for MySQL. With several databases, use the resource-specific variables.

With one PostgreSQL database, psql can use the generated defaults:

psql

The published ports reflect the chosen access level, including the direct low-port offset. Reload the environment after changing the catalog or selected cluster. On Windows, load env.ps1 as described in Windows differences. See the User guide for databases and TCP clients.

SSH audit and recordings

SSH connections through a resource id, its published name or its Resident address reach the same Connector and use the same audit and recording policy. The # connected ... recording line is printed by the ProxyCommand for a resource id. For a connection directly to the Resident address, the SSH server sends the same session metadata as an authentication banner. The banner stays separate from command stdout and subsystem data; the SSH client's quiet mode can suppress it. This requires the resident, Proxy and Connector to support the native notice request; older components retain their previous behavior.

Audit includes session.open when the session opens and session.close when it ends. The recordings list shows closed sessions. After exiting SSH, check both with the same cluster configuration:

gdsgate --config "$CLIENT_CONFIG" authority audit list --target demo-ssh --since 5m
gdsgate --config "$CLIENT_CONFIG" authority recordings list --target demo-ssh --since 5m

Expected result: the closed session has a matching session id in audit and, when recording is enabled, a recording entry. The absence of a terminal message does not indicate that recording is disabled.

Shutdown

gdsgate --config "$CLIENT_CONFIG" down
gdsgate --config "$CLIENT_CONFIG" status --explain

Expected result: no Resident socket, managed listeners or managed names. down is idempotent. After a crash, the next up removes stale state it owns and reports what it could not remove. After a power loss, check the interface, routes, hosts block and resolver before starting transparent again.

Safe uninstall

  1. End active sessions.
  2. If a service unit runs the Resident client, stop and disable it. Run gdsgate down as the user and with the configuration that ran up.
  3. Check that status --explain shows no socket, listener, managed address or served name, and that the interface, routes, hosts block and resolver are back to their state before install.
  4. Remove the generated fragments, endpoint map, socket directory and identity cache at the paths the configuration names.
  5. Check that the resource names no longer resolve and nothing listens on their addresses.

Access refusals

A refused resource keeps its local address, name and status row; the gateway denies the connection. A refusal comes from identity, catalog visibility, connect policy, a per-request action, an expired certificate, or a host capability. The message names the operation, the cause and the next step.

Refusals in gdsgate status

Value Meaning
ACCESS denied policy or identity refused the resource
ACCESS step-up a one-time grant can allow it
ACCESS ? the control plane is unreachable; the cached result is shown
SERVING - no local listener was requested

Step-up grants are single-use

A step-up grant belongs to one principal, resource and action. Prefetch does not consume it. The next matching operation consumes it; a second operation is denied.

Desktop notifications

If the notification program is unavailable, the refusal is still in status and the journal.

Refusals inside an SSH connection

An SSH session can open while a later forward is denied. connect, sshForwardLocal, sshForwardRemote and host-key failures are separate decisions; the error names the channel that was refused.

Resolving access failures

Check in order: status --explain, identity freshness, catalog row, action name, local listener, backend reachability. gdsgate --no-remediate disables the interactive remediation prompt.

Reading gdsgate status

gdsgate --config "$CLIENT_CONFIG" status
gdsgate --config "$CLIENT_CONFIG" status --explain
gdsgate --config "$CLIENT_CONFIG" status --json
Field ok ? -
identity usable local identity control-plane freshness unknown not requested
catalog current catalog refresh uncertain not requested
names resolver serving resolver state uncertain names disabled
listener local listener bound bind status uncertain no listener requested
access policy/certificate usable cached decision only not warmed
serving request can reach its relay tunnel uncertain no resource served

Resources without an active connector

A catalog row can exist while no Connector serves it; status shows it as declared but not serving. The administrator brings up the Connector that serves the resource, or removes the row: see the Admin guide.

Status details

--explain shows each problem with Fix first, followed by independent Alternative actions, individual Cause fields and an optional Note. It also lists observed local connections. --json carries the same problems in diagnostics, with target, headline, cause, actions and note. The first action is the recommended fix. Existing status fields remain available.

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.

Terminal output colours ready resources green, problems yellow, policy denials red and withdrawn resources grey. NO_COLOR=1 disables colour; JSON, pipes and files contain no colour escapes. See the CLI reference for an example and the JSON fields.

Windows

The client runs as the signed-in user. The gdsgate-client-net service, running as SYSTEM, performs the two administrator operations: the NRPT rule that sends the zone to the client's name server, and the Wintun adapter of transparent. It serves one client at a time. It removes the rule and the adapter when the client disconnects (including when it is killed) and when the service stops. If the service stops, transparent addresses stop answering until the service is started and the client restarted.

Installing

The installer gdsgate-<version>-x86_64-pc-windows-msvc.msi puts gdsgate.exe in C:\Program Files\gdsgate, together with the Wintun library and the pseudoconsole host of the agent sandbox. It adds the directory to the system PATH and installs and starts gdsgate-client-net. From an elevated prompt:

msiexec /i gdsgate-<version>-x86_64-pc-windows-msvc.msi

Expected result: in a new prompt, gdsgate --version answers and gdsgate doctor reports state.client_net and sandbox.console as OK. A warning names the problem: the service is not running, or it cannot create an adapter. A newer installer upgrades in place and keeps the service. Uninstalling (Settings, or msiexec /x) removes the service and the files and keeps C:\ProgramData\gdsgate.

What the service enables

Level Without the service With the service With the service and wintun.dll
basic, direct works, no names names through NRPT names through NRPT
transparent unavailable unavailable works, names through NRPT

Under auto, the client picks the strongest level available.

Differences from Linux

  • resolver = system gives names at direct and basic too. Port 53 needs no privilege.
  • resolver = hosts writes C:\Windows\System32\drivers\etc\hosts directly. It works only for a client started from an elevated prompt.
  • The environment file is env.ps1. Load it with . ~\.gdsgate\env.ps1.
  • The control channel and the per-resource SSH agents are named pipes. The generated ssh_config points IdentityAgent at the pipe. With SSH_AUTH_SOCK unset, the client forwards to the Windows OpenSSH agent (\\.\pipe\openssh-ssh-agent).
  • Notifications are Windows notifications, shown on behalf of Windows PowerShell.

Verifying on Windows

gdsgate up
gdsgate status
Resolve-DnsName RESOURCE.gds
Get-DnsClientNrptPolicy

Expected result: status shows names system. The name resolves to the resource's address, and the NRPT policy lists the zone with the client's name server. After gdsgate down, Get-DnsClientNrptPolicy no longer lists the zone, and Get-NetAdapter gdsgate0 finds nothing.

Runtime files and state

The client configuration, identity cache, resident socket, endpoint map and generated fragments live at the paths the configuration names. Private keys are mode 0600; generated files are owner-only. The endpoint map is regenerated as needed.

Running under sudo

sudo gdsgate up resolves the original caller through SUDO_UID. It uses that person's configuration, identity, state directory and control socket. On a host with systemd it installs and starts gdsgate-client@<user>.service, running as that person with CAP_NET_ADMIN and CAP_NET_BIND_SERVICE. It does not invoke sudo a second time. A root login without an original caller uses root's own state.

As a service

On Linux with a reachable systemd manager, up installs, enables and starts a service automatically. With the default owner protection, up uses gdsgate-client@<user>.service and asks for elevation once; sudo gdsgate up already has that elevation. With protection explicitly disabled and a loopback level, up can use gdsgate-client.service in the user's service directory. Explicit transparent still needs a system unit. The system unit retains the user's identity and disables an earlier user unit so two daemons do not claim the same socket.

For explicit installation and removal:

# Default protected system unit, preserving the original user's state:
sudo gdsgate --config "$CLIENT_CONFIG" service install client --start
sudo gdsgate service uninstall client

service install client without --start only installs and enables the unit. The unit invokes this binary with up --foreground; foreground mode never installs another unit. A repeated up reports a working resident. down stops it and tears down its networking; uninstall also disables the unit, retaining configuration and identity. Without systemd, up uses a detached process; Linux owner protection still needs a privileged launcher, such as sudo gdsgate up, and refuses startup without the required capability.

Service logs are in journalctl --user -u gdsgate-client.service or journalctl -u gdsgate-client@<user>.service. On a terminal, up --foreground continues to log to ~/.gdsgate/gdsgated.log.

Names under the system unit

For resolver = auto or system, installing a system unit grants its user the resolver actions for DNS servers, domains and link teardown when the host has a policy agent. The grant is removed by uninstall. The system unit has no general permission to replace /etc/hosts. The resolver grant applies to all network links the resolver authorizes; it is not restricted to the resident's interface. On shared machines, review this host-wide permission before installation. Use the system resolver with transparent access, or configure host DNS routing separately. status --explain reports whether names are being served; addresses can work even when names do not.

On Windows, up ensures the client network helper is installed and started, requesting elevation when necessary. The resident stays in the signed-in user's session; see Windows.

Completion

Generate shell completion (no connection to Authority):

gdsgate completion bash > "$COMPLETION_FILE"

Resource names complete from the local Resident state; with the daemon stopped the list is empty.

Settings

Keys used on this page: access, resolver, warm_access, step_up_on_denial, the transparent address pool, and intercept_names. See Configuration. After editing the loaded file, run gdsgate doctor --role client and gdsgate reload. Select another cluster with gdsgate contexts use PATH; the running resident reloads it.

Changes to addressing, the interface, resolver or local isolation require a restart. Reload refuses these changes before replacing live routes. See the reload behaviour table.