Skip to content

Concepts

This page describes what gdsgate is, what each component does, how a request travels through it, and the security principles the design rests on. No configuration here — see Configuration for that.

Roles

gdsgate runs four roles. Three are long-lived services — Auth, Proxy, Agent — and the fourth is the client command a user runs from their own machine. Every role is the same binary; a subcommand selects which one this process is.

Role What it does
Auth The cluster authority. Holds the certificate authorities, evaluates the Cedar policy, issues short-lived access certificates, verifies identity tokens against the identity provider's signing keys, registers new nodes, and persists the tamper-evident audit log.
Proxy The public, client-facing entry point. Terminates the client's TLS, forwards the authorisation request to Auth, and relays an authorised session onto the agent that serves the resource. Stateless — it holds nothing but in-memory tunnel registrations.
Agent A sidecar next to the protected resources. Dials out to the Proxy, keeps a long-lived reverse tunnel open, and serves the resources declared in its configuration (SSH, PostgreSQL / MySQL, Kubernetes, MCP, raw TCP).
Client The user's gdsgate invocation: login, then ssh, db proxy, kube …, tcp proxy, mcp proxy — plus the proxy-ssh transport that native ssh drives through ProxyCommand.

A separate state store (PostgreSQL in production, in-memory SQLite for dev) backs Auth's audit log, the transport CA's private key, the persisted SSH certificate authorities, and the registration-token registry.

flowchart LR
  subgraph edge[Edge zone]
    client([Client\nlogin · ssh · psql · kubectl · redis-cli])
  end
  subgraph control[Control zone]
    proxy[Proxy]
    auth["Auth\nCA · Cedar policy · audit · store"]
    store[(State store\nPostgreSQL)]
  end
  subgraph protected[Protected zone]
    agent[Agent]
    db[(PostgreSQL / MySQL)]
    host["SSH host · TCP · K8s · MCP"]
  end
  idp([OIDC identity provider])

  client -- "public TLS / gRPC" --> proxy
  proxy -- "mTLS" --> auth
  auth --- store
  agent -- "reverse tunnel\n(mTLS, outbound)" --> proxy
  agent --- db
  agent --- host
  client -. "login (device flow / PKCE)" .-> idp
  auth -. "discovery + JWKS" .-> idp

How a request flows

sequenceDiagram
  actor U as User
  participant IdP as IdP
  participant P as Proxy
  participant A as Auth
  participant Ag as Agent
  participant R as Resource

  U->>IdP: log in (device flow / PKCE)
  IdP-->>U: id_token (JWT)
  U->>P: RequestAccess(id_token, resource)
  P->>A: authorise(id_token, resource, context)
  A->>A: verify token (JWKS), map claims, evaluate Cedar
  A-->>P: Allow → short-lived certificate (or Deny)
  P-->>U: certificate
  U->>P: OpenSession(resource, certificate)
  P->>Ag: relay over the reverse tunnel
  Ag->>Ag: verify certificate against the trust bundle
  Ag->>R: connect (TCP / DB / kube) or terminate (SSH)
  Ag-->>U: session bytes
  1. The user signs in to the identity provider and receives an identity token (an OIDC id_token).
  2. The client calls RequestAccess on the Proxy with the token and the target resource id. The Proxy forwards it to Auth.
  3. Auth verifies the token (RS256 signature against the IdP's JWKS, the issuer, the audience, and the expiry), maps the claims to an internal identity (the sub becomes the principal, the groups claim becomes its Cedar group memberships), and evaluates the Cedar policy for (principal, action, resource). The action comes from the resource's kind (sshConnect, dbConnect, k8sAccess, tcpConnect).
  4. On Allow, Auth issues a short-lived certificate scoped to that resource and records the decision in the audit chain before returning it. On Deny, it records the denial and returns an error.
  5. The client calls OpenSession presenting the certificate. The Proxy relays the session onto the agent that registered the resource. The Agent verifies the certificate against its trust bundle, then either forwards the byte stream (TCP / DB / Kubernetes) or terminates the protocol (SSH) and runs the requested shell / exec / SFTP itself.

The data-plane session is authorised by the certificate, not the identity token — so the agent never needs to see or trust the IdP.

The reverse tunnel

Agents are not reachable from the control plane or the edge. Instead, each agent dials out to the Proxy's internal listener and keeps a multiplexed tunnel open. gdsgate prefers gRPC for the tunnel and falls back to a WebSocket (and through an HTTP CONNECT proxy when egress is locked down). The Proxy routes an authorised session for a resource onto the tunnel of the agent that registered it.

This is what makes "everything through the proxy" structural: the protected zone has no inbound rules — only outbound egress to the Proxy. A backend is reachable only by the agent that fronts it.

Identity and authorisation

  • Authentication is OIDC. Auth fetches the identity provider's discovery document and JSON Web Key Set (JWKS) at startup and verifies every identity token's RS256 signature, the issuer, the audience, and the expiry. A built-in development issuer (HS256) exists for loopback / unit tests only.
  • Claim mapping turns the verified token into an internal identity: the token's sub becomes the Cedar principal (User::"<sub>"), and the groups claim becomes the principal's group memberships (Group::"<name>"). Policies match by these and by resource attributes.
  • Authorisation is Cedar. gdsgate ships a Cedar schema describing the entity types, actions, and context fields, so policies are validated against a known shape at load time. Without a configured policy, Auth runs a deny-all bootstrap policy. See Policy for the schema and patterns.

Two SSH backend models

For an SSH resource an agent can serve, the agent supports two models:

  • Model A — agent-terminated, recording. No downstream address. The agent is the SSH server: it terminates the client's SSH session, spawns a PTY under its own operating-system user, and records the terminal stream in asciicast v2. This is the node-recording model: the session executes where the agent runs.
  • Model B — jump host. A downstream address is configured. The agent terminates the client's SSH session (to record it), then opens its own SSH session to a real downstream sshd and relays every channel: bytes, PTY requests, shell / exec, window-change events, exit status, stderr. Authentication to the downstream sshd is by an OpenSSH user certificate that Auth signs per connection.

-L (local port forward) and -R (remote port forward) work in both models, gated by a two-layer scheme.

Two-layer forward gating

ssh -L and ssh -R open extra channels for proxying TCP through the SSH session. To stop those channels from turning an agent into an arbitrary internal proxy, gdsgate gates each one twice:

  • Layer 1 — operator allow-list, per backend. On each SSH backend the operator declares allow_local_forward and allow_remote_forward patterns. With no entries, -L is restricted to loopback only and -R is disabled altogether — both the conservative defaults. Any non-loopback target needs an explicit entry. This is the SSRF border: what is even reachable through this backend at all.
  • Layer 2 — Cedar policy, per user / session. The agent calls Auth at channel-open time with the requested host:port (for -L) or bind_host:bind_port (for -R) in the Cedar context. The policy decides sshForwardLocal / sshForwardRemote for this principal in this session.

Both layers must say allow. Either denies → the channel is refused and the decision is audited.

PKI and node registration

gdsgate runs three certificate authorities, all held by Auth:

  • Transport CA (HostTls). Issues the X.509 identities every node uses for internal mutual TLS and the Proxy's public TLS listener. All leaves carry the shared name gdsgate.internal, so peers verify by that name independently of the host's DNS.
  • User SSH CA (UserSsh). Signs the short-lived access certificates Auth issues on Allow. The Proxy and agents trust this CA's bundle (current + retiring during a rotation).
  • Onward SSH CA (OnwardSsh). Signs the OpenSSH user certificates the agent presents to a downstream sshd in the model-B jump-host path. The agent never holds this CA's private key — it asks Auth for a fresh certificate per connection.

A standalone Proxy or Agent starts with no internal credentials. It registers with Auth: an operator generates a one-time registration token with gdsgate auth create-token, the joining node presents it over Auth's plaintext bootstrap listener along with a certificate-signing request, and Auth returns a signed transport certificate plus the trust bundle. From then on every control-plane hop is mutually authenticated, and the node persists its identity to reuse across restarts. Auth persists the transport CA to the state store, so a restart keeps already-registered nodes trusted.

Audit

Every privileged action is appended to a hash-chained audit log in the state store:

  • every authorisation decision (allow and deny),
  • every node registration,
  • every administrative operation (CA rotation, policy change, JIT approval),
  • every session (SSH session recorded, query log tap on databases, MCP tool calls).

Each record links to the previous record's hash, so any tampering or gap is detectable. Auth refuses to hand out a grant whose audit record could not first be made durable — persist before grant. Records export in canonical JSON, Splunk HEC, or CEF / syslog for SIEM ingestion. See Operations.

Just-in-time access

For sensitive resources Cedar can require a JIT approval before access is granted: the user runs gdsgate request-access, one or more approvers run gdsgate approve, and only then does the policy evaluate to Allow for that user / resource pair. The number of approvers required follows a cascade: a per-resource threshold beats a per-environment threshold beats the global default. JIT decisions are themselves audited.

Deployment shapes

  • All-in-one (gdsgate all). Every role in one process, sharing one state store. The simplest path: useful for local development and for a small single-node deployment. The same persistent state store underpins registration of external nodes, so an external agent can still join an all-in-one cluster.
  • Multi-node. Separate auth, proxy, and agent processes that register for transport identities and run mutual TLS between them — the production shape. Auth scales for read load through multiple instances sharing the store; HA elects one audit write-leader at a time.

See Admin guide for both shapes end-to-end.

Design principles in one paragraph

Everything through the proxy. Identity once, then certificates everywhere. The protected zone has no inbound rules. Persist before grant. Short TTLs do the revocation. One Cedar policy speaks for every protocol. Native clients keep working unchanged. The same binary runs every role and nothing else needs to be installed.