Concepts¶
Architecture reference: roles, request path, identity and trust model. Terms are in the Glossary; keys are in Configuration.
Roles¶
One binary runs five roles, selected by subcommand: four long-lived services (Authority, Proxy, Connector, Agent host) and the client. Every role runs on Linux and Windows. On Windows, an agent host starts but runs no agents: agents run on a Linux agent host only.
| Role | What it does |
|---|---|
| Authority | The cluster authority. Holds the certificate authorities, evaluates the Cedar policy, issues short-lived access certificates, verifies identity tokens against the provider's signing keys, registers new nodes, and persists the tamper-evident audit log. |
| Proxy | The public entry point. Terminates the client's TLS, forwards the authorization request to Authority, and relays an authorized session to the connector that serves the resource. Keeps no durable state: tunnel registrations, live sessions and signed-in browser sessions are in memory. |
| Connector | Runs next to the protected resources. Holds a reverse tunnel to the Proxy, over which every user session arrives, and calls Authority for per-session decisions. Serves SSH, PostgreSQL / MySQL, Kubernetes, MCP, model services, web applications, Windows desktops and raw TCP. |
| Agent host | Runs agents declared on Authority, each in its own cage (Landlock, namespaces, a system-call filter, cgroup limits) under its own identity. Linux only for running agents. Opens the agents' sessions through the Proxy like a client and publishes their faces (console, page, model API, tools) through a reverse tunnel like a connector. See Agent host. |
| Client | The user's gdsgate invocation: login, then ssh, db proxy, kube, web proxy, mcp proxy, rdp proxy, tcp proxy, delegate, plus the proxy-ssh transport that native ssh drives through ProxyCommand. gdsgate up starts the resident client, which keeps a local address and name for every resource in the catalog. |
The state store (PostgreSQL in production, SQLite in development) holds the audit log, the transport CA's private key, the SSH certificate authorities, the registration registry and the live resource inventory.
flowchart LR
subgraph edge[Edge zone]
client(["Client<br/>CLI · resident client<br/>ssh · psql · kubectl · browser"])
end
subgraph control[Control zone]
proxy[Proxy]
authority["Authority<br/>CA · Cedar policy · audit"]
store[("State store<br/>PostgreSQL")]
end
subgraph protected[Protected zone]
connector[Connector]
resources["SSH · databases · Kubernetes<br/>web · MCP · models · RDP · TCP"]
agents["Agent host<br/>caged agents"]
end
idp([OIDC identity provider])
client -- "public TLS / gRPC" --> proxy
proxy -- "mTLS" --> authority
authority --- store
connector -- "reverse tunnel<br/>(mTLS, outbound)" --> proxy
connector -- "mTLS: control-plane RPCs<br/>(direct or through the Proxy)" --> authority
connector --- resources
agents -- "agent sessions<br/>(public listener)" --> proxy
agents -- "faces<br/>(reverse tunnel)" --> proxy
agents -- "mTLS: declarations,<br/>identities, reports" --> authority
client -. "login (device flow / PKCE)" .-> idp
authority -. "discovery + JWKS" .-> idp
How a request flows¶
sequenceDiagram
actor U as User
participant IdP as IdP
participant P as Proxy
participant A as Authority
participant Ag as Connector
participant R as Resource
U->>IdP: log in (device flow / PKCE)
IdP-->>U: id_token (JWT)
U->>P: RequestAccess(id_token, resource)
P->>A: authorize(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, HTTP)
loop each request inside the session (Kubernetes, web, MCP, model, restricted DB)
Ag->>A: authorize the request
A-->>Ag: Allow or Deny, audited
Ag->>R: forward the allowed request
end
Ag-->>U: session bytes
sequenceDiagram
actor U as User
participant P as Proxy
participant A as Authority
participant Ag as Connector
participant R as Resource
U->>P: RequestAccess(id_token, resource)
P->>A: authorize
A-->>P: Allow → short-lived certificate
P-->>U: certificate
U->>P: OpenSession(resource, certificate)
P->>Ag: relay over the reverse tunnel
Ag->>R: connect or terminate
loop each request inside the session
Ag->>P: authorize the request (reverse tunnel)
P->>A: relay
A-->>P: Allow or Deny, audited
P-->>Ag: relay
Ag->>R: forward the allowed request
end
Ag-->>U: session bytes
The connector's only egress is the Proxy. See
authority_transport.
sequenceDiagram
actor V as Visitor
participant P as Proxy
participant H as Agent host
participant W as Agent
participant A as Authority
participant C as Connector
participant M as Model service
V->>P: open the agent's web face
P->>H: relay over the agent host's reverse tunnel
H->>A: authorize httpRequest for the visitor
A-->>H: Allow + signed statement of the visitor
H->>W: request + gdsgate-identity
W->>P: model call as the agent + gdsgate-identity
P->>C: relay
C->>A: llmCall for the agent and for the visitor
A-->>C: Allow only if both are allowed, audited with owner = visitor
C->>M: call (statement removed)
M-->>C: answer
C-->>W: answer, through the Proxy
W-->>V: page
The same statement is accepted by the MCP, web and Kubernetes lanes. See acting for the person who opened the page.
- The user signs in to the identity provider and receives an OIDC
id_token. - The client calls
RequestAccesson the Proxy with the token and the target resource id. The Proxy forwards it to Authority. - Authority verifies the token (signature against the provider's JWKS, issuer,
audience, expiry), maps the claims to an internal identity (
subbecomes the principal,groupsbecomes its Cedar group memberships), and evaluates the Cedar policy for(principal, action, resource). The action isconnectfor every protocol; the resource's entity type names the protocol:SshHost,Database,KubernetesCluster,TcpService,McpServer,WebApp,WebEgress,RdpHost. A model service has noconnect: each call is decided asllmCall. - On Allow, Authority issues a short-lived certificate scoped to that resource and writes the decision to the audit chain before returning it. On Deny it writes the denial and returns an error.
- The client calls
OpenSessionwith the certificate. The Proxy relays the session to the connector that registered the resource. The connector verifies the certificate against its trust bundle, then forwards the byte stream (TCP, database, Kubernetes) or terminates the protocol (SSH; HTTP for a model service or a web application).
The connector authorizes the session by the certificate. It does not see the identity token.
Five lanes authorize again inside the session, at the connector: every
Kubernetes API request, MCP tools/call, model call, web request, and
statement in a restricted database session. See
finer-grained authorization.
The reverse tunnel¶
Connectors accept no inbound connections. Each connector dials out to the Proxy's
internal listener and holds a multiplexed tunnel: gRPC by default, WebSocket as a
fallback, through an HTTP CONNECT proxy where egress requires one. The Proxy
routes an authorized session onto the tunnel of the connector that registered
the resource. The protected zone needs outbound access to the Proxy and no
inbound firewall rules.
The connector also holds an outbound mTLS channel to Authority for the decisions it makes during a session: downstream OpenSSH certificates (SSH model B), forward channels, Kubernetes, MCP and model calls, database roles and statement categories, and publishing its live inventory. Authority has no route to the resources.
Identity and authorization¶
Authentication is OIDC. Authority fetches the provider's discovery document and JSON Web Key Set at startup and verifies every identity token's signature, issuer, audience and expiry. Only asymmetric signing algorithms are accepted; the set is configurable. A built-in development issuer (HS256) is for loopback only.
The token's sub becomes the Cedar principal (User::"<sub>"); the groups
claim becomes its group memberships (Group::"<name>").
Authorization is Cedar. A policy is validated against the shipped Cedar schema when it loads. With no policy configured, Authority denies everything. See Policy.
Workload identity¶
A workload is a non-human client: a CI job, a service, or an autonomous agent.
Each workload holds its own short-lived certificate, authorized by the same Cedar
policy as a person. Workload identity is off by default and is enabled with the
[workload] section.
Bootstrap. gdsgate machine-id presents one of two proofs and receives a
certificate:
- A platform token. A GitLab CI job requests an id-token addressed to the gateway. Authority verifies it against the platform's published keys.
- A provision token, for a host with no platform issuer:
gdsgate authority create-token --role workload.
The key pair is generated locally; only the public key is sent.
Naming. The certificate carries a SPIFFE ID,
spiffe://<trust-domain>/<issuer>/…, and no per-resource scope. The path after
the issuer is templated from the proof's claims, for example
spiffe://gds.example/gitlab/project/infra/ref/main.
Authorization. Every connection is authorized against the Cedar Agent
principal: per connect on the SSH, PostgreSQL / MySQL, Kubernetes, MCP, web,
desktop and raw-TCP lanes, and per call on the model lane. For a desktop, the
permit also sets the Windows account and the allowed channels. A workload gets
no SSH jump-host onward certificates and no -L / -R forwards.
Owner. A workload may carry an owner: the human it acts for (from a claim such
as GitLab's user_login) or a static service account. The owner is recorded on
every action and is readable by policy.
Lifecycle. A workload bootstrapped from a provision token is kept in a registry
and renews with its current certificate and a proof of possession until
gdsgate authority revoke-workload. A workload bootstrapped from a platform
token has no registry row: it re-runs the bootstrap and is cut off by policy.
Certificates live for minutes in both cases.
Delegation¶
Delegation gives an agent part of a person's own access for a limited time. The
delegator names capabilities (db:read@prod-pg, ssh:connect@bastion,
kube:read@staging, mcp:search@corp); the gateway issues the agent a
short-lived certificate bound to the grant. Every action is attributed to both
the agent and the delegator.
Each capability is checked against the delegator's own policy when the grant is issued; a capability the policy would deny is refused.
Each action is checked against the grant and against policy. A
db:read@prod-pg grant keeps the agent read-only on that database whatever
policy allows. mcp:search@corp admits that one tool;
mcp:resource:changelog@corp admits that one resource and no prompt of the same
name. The grant is read on each operation: revocation or expiry applies from the
agent's next request.
Owner-wide revocation. gdsgate revoke --owner <user> (or --all for the
caller's own grants) invalidates every grant the owner issued before the
command, from each agent's next authorization check. Later grants are not
affected; the command can be repeated. Use it when de-provisioning a person.
A grant is presented in one of two ways:
- An agent holds its own certificate bound to the grant, written by
gdsgate delegate --bind-keyor served by--exec, and runs unattended. - The delegator narrows their own session: any command run with
--as <grant-id>is restricted to the grant's capabilities.
A program started with --exec:
| Property | How |
|---|---|
| Reaches its resources through local addresses | One address per capability in GDSGATE_ENDPOINTS; the certificate stays in the gdsgate process. A web-egress way out is set as HTTP_PROXY. See Endpoints. |
| Runs in a cage | --sandbox basic or strict. Linux: Landlock, namespaces, cgroup limits, and a system-call filter under strict. Windows: an AppContainer with no network capability and a Job object; no system-call filter. The properties are checked before the program runs and recorded as sandbox.claim. See Sandbox. |
| Is configured for a known agent client | An agent profile, chosen by the program's file name or --agent-profile, names the variable the client reads its model address from and where its MCP configuration goes. See Agent profiles. |
| Ends with the grant | The grant is revoked when the program exits. |
An ssh grant carries only the channels it names: ssh:connect opens the
session, ssh:forward and ssh:agent add port and agent forwarding.
gdsgate delegations lists the grants you own. A bundle written with
--bind-key is checked with delegation status and, for a --renewable
grant, rolled forward with delegation renew. --profile reads a grant's
flags from a saved row, [[delegation_profiles]].
Delegation is refused until the control keyring of
[security.integrity] holds the
key that seals each grant. See
User guide → Delegating access and
AI agents.
Agent host¶
An agent host runs long-lived agents declared on Authority (Linux only):
| Delegation | Agent host | |
|---|---|---|
| Declared by | the delegator, gdsgate delegate |
an operator, authority set-agent |
| Principal | the delegator's grant, attributed to both | the agent's own, Agent::"spiffe://<trust-domain>/agent/<project>/<id>" |
| Lifetime | the grant's TTL | until removed; access held on a lease the host renews |
| Rights | a subset of the delegator's | --can capabilities, then Cedar on every call |
| Reachable from outside | no | through faces: console (ssh), page (web), model API (llm), tools (mcp), stream (tcp) |
A web face declared identity = "signed" passes the agent a signed
statement of the visitor. An agent that forwards it on a model, MCP, web or
Kubernetes call acts for the visitor: the call is allowed only if both the
agent and the visitor are allowed, and its audit row names the visitor as
owner. See Agent host.
Finer-grained authorization¶
connect decides whether a session opens. Five lanes also decide every
operation inside the session, and each decision is audited:
| Lane | Action | What the rule can use |
|---|---|---|
| Kubernetes | kubeRequest on every API request |
Verb, resource kind, namespace, name. context.read_only is true for get, list, watch, log. |
| MCP | mcpCallTool on every tools/call; viewTools filters tools/list |
Tool name; a tool the operator marks destructive. |
| Model services | llmCall on every call, before the provider |
One model (LlmModel) or a service (LlmService); a forbid on a model overrides a permit on its service. Token counts are audited. |
| Databases, restricted mode | dbQuery on every statement |
Category: read, write, DDL, DCL. An unrestricted session is not checked per statement and still records the query log. |
| Web applications | httpRequest on every request |
context.route: the matched route name, "" when none matches. |
A web-egress resource opens at connect; httpRequest then decides each
request by the target it names. A CONNECT (https through the way out) is
decided on host and port and requires the web:tunnel capability. The
application sees the caller's credentials unchanged unless the backend
configures identity substitution. See
Policy → HTTP(S) application routes and
Configuration → Telling the application who came.
SSH backend models¶
A connector serves an SSH resource in one of two models:
- Model A, connector as SSH server. No downstream address is configured. The
session runs on the connector host under the connector's account and is
recorded in asciicast v2. On Windows the terminal is a ConPTY pseudoconsole
(Windows 10 1809 / Server 2019 or later) and the default shell is
%ComSpec%. - Model B, jump host. A downstream address is configured. The connector
terminates and records the session, opens its own SSH session to the
downstream
sshdand relays every channel. It authenticates with an OpenSSH user certificate Authority signs per connection.
-L and -R forwards work in both models, under
two-layer gating. -A works in model A only, under
the same gating.
Two-layer forward gating¶
Each ssh -L and ssh -R channel must pass two layers:
- Layer 1, per backend:
allow_local_forwardandallow_remote_forwardpatterns. With no entries,-Lis limited to loopback and-Ris disabled. A non-loopback target needs an explicit entry. - Layer 2, Cedar: at channel open the connector sends the requested
host:port(-L) orbind_host:port(-R) to Authority, which decidessshForwardLocal/sshForwardRemote.
A channel denied by either layer is refused and audited.
ssh -A uses the same two layers. Layer 1 is the per-backend switch
allow_agent_forward, off by default. Layer 2 is sshForwardAgent. When
refused, the session has no SSH_AUTH_SOCK. Any process that reaches the
forwarded agent signs as the client, root on the connector host included.
PKI and node registration¶
gdsgate runs four certificate authorities, all held by Authority:
| CA | Signs |
|---|---|
Transport CA (HostTls) |
X.509 identities for internal mutual TLS and the Proxy's public listener. Every leaf carries gdsgate.internal. TLS-terminating roles add auth.gdsgate.internal (control plane), proxy.gdsgate.internal and tunnel.gdsgate.internal (the Proxy's two listener groups), so a front can route them by server name on one address. |
User SSH CA (UserSsh) |
Short-lived access certificates issued on Allow. The Proxy and connectors trust the current generation and, during a rotation, the retiring one. |
Onward SSH CA (OnwardSsh) |
OpenSSH user certificates a connector presents to a downstream sshd (model B), one per connection. The connector does not hold the key. |
SPIFFE CA (Spiffe) |
Workload certificates. Carried in the trust bundle; rotates like the others. |
A standalone Proxy or Connector registers to get its credentials: an operator
creates a one-time token with gdsgate authority create-token; the node
presents it with a certificate-signing request on Authority's bootstrap
listener and receives a transport certificate and the trust bundle. The node
keeps its identity across restarts. The transport CA is kept in the state
store, so registered nodes stay trusted after an Authority restart.
Audit¶
Every privileged action is appended to a hash-chained audit log in the state store: authorization decisions on allow and deny, node registrations, administrative operations (CA rotation, policy change, JIT approval, session kill), and sessions (a recorded SSH session, the query-log tap on databases, Kubernetes requests, MCP tool calls). Each record links to the previous record's hash; tampering or a gap is detectable. A grant is not issued until its audit record is durable. Records export as canonical JSON, Splunk HEC, or CEF and syslog. See Operations.
Resource discovery¶
A request for a resource that is not in the catalog is refused before policy is evaluated. The catalog is filled two ways:
- Static. Entries under
[discovery]in Authority's config: id, kind, environment, labels. Policy reads these (resource.environment,resource.getTag("team")). - Live. A connector publishes the databases behind a PostgreSQL entry; each
appears in
gdsgate lsasDatabase::"<host>.<db>". An MCP backend's tools are published too. Tools do not appear ingdsgate ls; they filtertools/list(viewTools) and are authorized per call (mcpCallTool).
A live snapshot affects listings only. It does not permit a connection or call.
Each snapshot belongs to one connector: a connector that owns dev-pg cannot
publish for prod-pg. Stale entries drop out after a freshness window. Each
snapshot is sealed with an HMAC receipt whose key is outside the state store; a
row rewritten in the store is dropped. Database listings are filtered per
identity by the Cedar view action.
Just-in-time access¶
A policy can require approval before access. The user runs
gdsgate request-access, approvers run gdsgate approve, and then the policy
allows that user and resource. The number of approvers is the per-resource
threshold, else the per-environment threshold, else the global default. Requests
and approvals are audited.
Live sessions and termination¶
The Proxy keeps the sessions open through it in memory. gdsgate session list
lists them; gdsgate session kill ends one by id or by --principal /
--resource. Each kill is authorized by the Cedar killSession action and
audited.
Deployment shapes¶
- All-in-one (
gdsgate all). Every role in one process over one state store, for development and small single-node deployments. An external connector can register with it. - Multi-node. Separate
authority,proxy,connectorandagent-hostprocesses with mutual TLS between them; the production shape. Several Authority instances can share the store; HA elects one audit write-leader.
On Linux each role runs as a systemd unit; on Windows as a service registered
with gdsgate service install <role>. See the Admin guide.
Glossary¶
Roles and objects¶
| Term | Canonical meaning |
|---|---|
| Authority | The control-plane service that owns durable identity, policy, catalog, and audit state. |
| Proxy | The client-facing service that terminates the public access path and carries authorized traffic toward a Connector. |
| Connector | The data-plane service that reaches protected backends and offers them through the control-plane catalog. |
| Agent host | The service that runs declared agents in cages and publishes their faces. |
| Client | The gdsgate command-line side or a native client using the local access path. |
| resident client | The client started by gdsgate up: a local address and name for every catalog resource. |
| service role | A role implemented by a running Authority, Proxy, Connector, or Agent host process. |
| actor | The human, workload, delegated agent, or declared agent whose action is evaluated. |
| node | An enrolled Authority, Proxy, Connector, or Agent host instance with transport identity and lifecycle state. |
| face | An agent's endpoint published as a catalog resource. |
| catalog resource | The named, policy-visible target that a user or workload selects. |
| Connector backend | The Connector-side implementation and address that serves a catalog resource. |
| session | A live access lifecycle with an owner, resource, state, and close or kill evidence. |
Security and access¶
| Term | Canonical meaning | Boundary to state |
|---|---|---|
| authentication | Establishing who or what presents an acceptable identity. | It does not grant an action. |
| authorization | Evaluating whether an authenticated actor may perform an action on a resource in context. | It is not the same as network reachability. |
| admission | The connection or protocol gate that decides whether a request may enter a lane or session. | A successful admission does not imply every later request is allowed. |
| policy | The declared rules and evaluation model that produce authorization decisions. | A certificate or grant cannot bypass ordinary policy. |
| audit | Structured evidence of lifecycle, authorization, and operational decisions, with integrity rules where configured. | It is not content recording and does not prove facts outside the gateway path. |
| recording | Optional captured session or request content governed by a configured level and retention policy. | It is more sensitive than a decision row. |
| trust material | CA roots, transport anchors, certificates, keys, fingerprints, and related verification inputs. | It is not a user permission. |
| workload identity | The identity used by a non-human process, independently of a human login. | It is not automatically a delegation. |
| delegation | An owner-authorized, attenuated capability and lifecycle granted to a workload or agent. | It is not a broad copy of a human credential. |
Credential families¶
| Family | Use |
|---|---|
| OIDC identity | An identity-provider token used to establish a human or workload identity through the configured OIDC flow. |
| transport certificate | An enrolled node certificate used for service-to-service transport and renewal. |
| access certificate | A short-lived certificate or equivalent access artifact issued for a user-facing resource path. |
| workload certificate | A certificate representing a non-human workload identity and its lifecycle. |
| delegation certificate | An agent-bound workload identity associated with a delegated grant. The certificate alone is not the capability. |
| onward credential | A credential minted for a downstream connection, such as a Connector-originated backend identity. |
| web credential | A browser or clientless access credential, cookie, or session artifact used only in the web path. |
| IdP MFA | Multi-factor authentication performed by the configured identity provider. |
| gateway step-up | A gateway-controlled factor or challenge required by an access policy after initial authentication. |
| factor-specific step-up | A step-up requirement tied to a particular factor or assurance condition. |
| one-time grant | A grant intended to be consumed once, with its own expiry and replay behavior. |
| JIT approval | Just-in-time approval that creates or enables a narrowly scoped access request after an approver decision. |
Abbreviations¶
| Abbreviation | First-use expansion |
|---|---|
| mTLS | mutual Transport Layer Security |
| OIDC | OpenID Connect |
| JIT | just in time |
| SPIFFE | Secure Production Identity Framework for Everyone |
| TOFU | trust on first use |
| SSRF | server-side request forgery |
| SNI | Server Name Indication |
| MCP | Model Context Protocol |
| OTLP | OpenTelemetry Protocol |
| Cedar | the policy language and authorization engine used by gdsgate |