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
- The user signs in to the identity provider and receives an identity
token (an OIDC
id_token). - The client calls
RequestAccesson the Proxy with the token and the target resource id. The Proxy forwards it to Auth. - 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
subbecomes the principal, thegroupsclaim 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). - 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.
- The client calls
OpenSessionpresenting 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
subbecomes the Cedar principal (User::"<sub>"), and thegroupsclaim 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
sshdand relays every channel: bytes, PTY requests,shell/exec, window-change events, exit status, stderr. Authentication to the downstreamsshdis 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_forwardandallow_remote_forwardpatterns. With no entries,-Lis restricted to loopback only and-Ris 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) orbind_host:bind_port(for-R) in the Cedar context. The policy decidessshForwardLocal/sshForwardRemotefor 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 namegdsgate.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 onAllow. 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 downstreamsshdin 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, andagentprocesses 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.