Administration commands¶
Authority command reference. Global flags, output channels, environment overlays and exit codes are on the CLI hub.
authority proxy setup connector agent-host service client-net all connect join login machine-id up down status completion ls request-access approve requests delegate delegations revoke delegation mfa step-up ssh ssh-config proxy-ssh db kube rdp tcp mcp web ca license session doctor config
auto gateway certificate emergency store
gdsgate authority¶
Runs the Authority service: control-plane gRPC listener (mutual TLS once the cluster is registered), bootstrap registration listener (plaintext, authenticated by join token), policy engine, certificate authorities, audit chain.
Reads: store_url, [endpoints], [policy], [oidc], [ca_rotation],
[ha], [discovery], [approvals].
Sub-actions fall into three groups:
| Group | Sub-actions | Route |
|---|---|---|
| Administrative (gRPC, decided by Cedar) | create-token, the five rotate-*-ca commands, list-nodes, list-offered, revoke-node, delete-node, forget-user, import-node, list-workloads, revoke-workload, list-resources, set-resource, remove-resource |
gateway (a person) or certificate (control-plane certificate); see --route. |
| Personal (gRPC, caller from identity token only) | policy commands against a running cluster, recordings |
gateway. policy pull and policy push also work on the emergency socket. |
Direct-store (on the Authority host, against store_url) |
bootstrap-admin, the deprecated init-* key commands, verify-audit, audit list, migrate, onward-ca-pub / onward-ca-pubs |
None. audit list also accepts --route store. |
policy validate and policy template touch neither the store nor the
network and take no route. bootstrap-provisioner needs no store credentials
and no prior identity: it obtains one at the enrollment listener and renews it
over mTLS.
Declaring from a document¶
set-agent, set-resource and set-scope accept a TOML document with
--from <file>; - reads the standard input. The table name is the object
kind:
[[agent]]
id = "tool-bot"
node = "agents-01"
program = "/usr/local/bin/tool-bot"
can = ["llm:call@models-prod"]
[agent.cage]
profile = "basic"
[[agent.faces]]
name = "tool-bot-tools"
kind = "mcp"
source = "port"
port = 9000
tools = ["read_ticket", "search_kb"]
gdsgate --config operator-workstation.toml authority set-agent --from agents.toml
# updated tool-bot on agents-01 (running)
Each table is an array of tables. Keys are the flag names with dashes replaced by underscores, and defaults are the flag defaults. A face's tools are nested in the face.
Objects the file does not name are not changed. Remove an object with
remove-agent, remove-resource or remove-scope.
--declaration on list-agents, list-resources and list-scopes prints the
same form:
gdsgate --config operator-workstation.toml authority list-agents --declaration > agents.toml
# edit agents.toml
gdsgate --config operator-workstation.toml authority set-agent --from agents.toml
An agent's document includes version, the version of the declaration when it
was printed. set-agent --from sends it, and the write is refused if the
declaration changed since; see concurrent edits.
For a document printed on another cluster, pass --ignore-version.
The lease and the creation time are not printed and are not accepted. Catalog rows a connector published into a boundary are not printed.
| Refused | Condition |
|---|---|
| Unknown key | The document contains a key this build does not define. |
| Two kinds in one file | The document contains tables of more than one kind. |
| Empty document | The document declares no object. |
| Duplicate name | Two objects in the document have the same name. |
Flags beside --from |
A declaring flag is set together with --from. Flags left at their defaults are ignored. |
Two identities may hold [admin].identity_dir: gdsgate-admin, issued by
bootstrap-admin, and the narrower
gdsgate-provisioner, issued by
bootstrap-provisioner. The commands
are the same under both; the policy grants each group different actions. See
Policy → The starting policy.
--route <auto|gateway|certificate|emergency|store>¶
Selects the route an administrative sub-action uses to reach Authority.
Accepted before or after any authority sub-action; a sub-action the route
does not serve refuses it by name.
| Value | The call travels | The caller is | The audit row names |
|---|---|---|---|
gateway |
the gateway's client-facing listener | you, by the identity token your provider issued | you, and the address you called from |
certificate |
a direct dial to [admin].endpoint |
the control-plane certificate in [admin].identity_dir |
that certificate's subject |
emergency |
the socket on the Authority host | whoever can open the socket | gdsgate-break-glass, with admin_channel=emergency-socket |
store |
the local persistent store | the operating-system account that can open it | no audit row; only audit list accepts this route |
| Value | Use | Requires |
|---|---|---|
gateway |
A person. Cedar decides over you and your provider groups; context.src_ip is your address. Disabling your account at the provider removes the access. |
gdsgate login and a policy granting your group the action. The starting policy grants four of the six. |
certificate |
A machine (a control plane on a timer); a person when the gateway is unreachable (top rung of the lock-out ladder). | [admin] with a control-plane identity. |
emergency |
Policy admits neither of the above. Carries policy pull, policy push, list-nodes, list-workloads, list-resources, set-resource, remove-resource; refuses the rest by name. See Admin guide → Lock-out and recovery. |
Running on the Authority host. |
store |
authority audit list only; reads local audit rows. Refused on every other sub-action. verify-audit is also local and has no route flag. |
Access to the store. |
auto (default) takes certificate when the configuration has an [admin]
section naming a control-plane identity, else gateway. A machine configured
only as a client (gdsgate connect) takes gateway.
# a person, on their own machine: through the gateway, as themselves
gdsgate authority list-nodes
# a control plane, on the workstation configuration: straight at Authority
gdsgate --config provisioner.toml authority list-nodes
# either configuration, saying which route out loud
gdsgate --config provisioner.toml authority --route gateway list-nodes
The policy commands against a running cluster (pull, push, edit,
approve-edit, pending-edits, history, diff, revert) and recordings
have no certificate form.
Under auto they take gateway; --route certificate is refused with a
message. policy pull and policy push also work over emergency.
--operator <NAME>¶
Names the person a call is made for, on
--route certificate. Used by a provisioning
control plane (CN=gdsgate-provisioner) acting for a person its own log knows.
gdsgate --config provisioner.toml authority --operator anna.ivanova@provider.example \
revoke-node nodes-abc123
The value is stored in a separate audit field, printed as unverified, and takes no part in policy decisions. Use the identifier your own log names the person by.
| Rule | On violation |
|---|---|
Only on --route certificate. |
Refused by the command line, and by Authority. |
Up to 128 bytes of ASCII letters, digits, space, or . _ - @ + '; no leading or trailing space; must not start with gdsgate- (any case). |
InvalidArgument; the call does nothing. |
| Peer is a control-plane identity. | PermissionDenied. |
Sub-action writes an audit row: create-token, the five rotate-*-ca commands, revoke-node, delete-node, forget-user, revoke-workload, import-node, list-resources, set-resource, remove-resource, audit list. |
Refused on any other sub-action. |
audit list is audited on both outcomes. list-nodes and list-offered write
a row only when refused, and take no --operator. --route store records
nothing and takes no --operator.
gdsgate authority init --profile <dev|pilot|production> [--output <PATH>] [--store-url <URL>] [--store-password-file <PATH>] [--oidc-issuer <URL>] [--oidc-client-id <ID>] [--control-keyring <REFERENCE>] [--audit-keyring <REFERENCE>] [--kms-key <NAME=KIND:COMMAND>] [--if-missing] [--dry-run] [--json] [--approve-plan <DIGEST>]¶
First run of an Authority. Writes the configuration, puts the two integrity keyrings in place, and prints one JSON object to stdout. Non-interactive.
The run prints its plan first; --dry-run stops there. A dry run checks each
keyring reference and creates nothing, so a profile that refuses to create a
keyring in the open refuses at the dry run.
gdsgate authority init --profile pilot --output /etc/gdsgate/authority.toml --dry-run
# + file:/etc/gdsgate/control.keys — the control keyring is created in the open; …
# ! restoring the files afterwards does not undo this
# + file:/etc/gdsgate/authority.toml — the Authority configuration (profile pilot)
# ? the store this configuration names can be opened — …
# plan digest 5008bbe3065bc874
--approve-plan <digest> refuses unless the plan is the one that digest names.
Exit codes are the setup contract; --if-missing
keeps its own 3.
gdsgate authority init --profile pilot \
--output /etc/gdsgate/authority.toml \
--store-url postgres://gdsgate@db.example.internal/gdsgate
{
"configuration": "/etc/gdsgate/authority.toml",
"outcome": "written",
"profile": "pilot",
"transport_ca": { "fingerprint": "3f9c1a2b4d5e6f708192a3b4c5d6e7f8" },
"activation": { "epoch": 2, "stamped": true },
"keyrings": [
{
"keyring": "control",
"source": "file /etc/gdsgate/control.keys",
"created": true,
"keys": ["a1b2c3d4e5f60718293a4b5c6d7e8f90"]
},
{
"keyring": "audit",
"source": "file /etc/gdsgate/audit.keys",
"created": true,
"keys": ["d4e5f60718293a4b5c6d7e8f90a1b2c3"]
}
]
}
The summary holds no key material: fingerprints, references as written, the profile and a path.
| Flag | Default | Purpose |
|---|---|---|
--profile <dev\|pilot\|production> |
required | Deployment profile. See [security]. |
--output <PATH> |
/etc/gdsgate/authority.toml on Linux; %ProgramData%\gdsgate\authority.toml on Windows |
Where the configuration is written. |
--store-url <URL> |
unset | Written into store_url. Use a postgres:// URL outside a laptop; put the password in store_password_file. |
--store-password-file <PATH> |
unset | Written into store_password_file. Read when connecting; the password stays out of the URL, the plan and logs. |
--oidc-issuer <URL> |
unset | Written into [oidc].issuer. Unset: identity tokens are verified with the built-in development issuer, whose secret every gdsgate binary carries; production refuses to start on it. |
--oidc-client-id <ID> |
unset | Written into [oidc].client_id. |
--control-keyring <REFERENCE> |
unset | Existing control keyring, in secret-reference notation. Unset: init creates one beside the configuration, except under --profile production. |
--audit-keyring <REFERENCE> |
unset | Existing audit keyring; same rules. |
--kms-key <NAME=KIND:COMMAND> |
none | External key, written into [[kms_keys]]. Repeatable. |
--if-missing |
off | Existing configuration: print its summary and exit 3. Without it: refuse, exit 1. Nothing is overwritten in either mode. |
dev and pilot create a missing keyring with one active root key.
production refuses to create a keyring on disk; name an existing one with
--control-keyring / --audit-keyring, or create it in a key service.
--kms-key declares an external key; <name> is what a
${kms.<name>+file:PATH} reference in --control-keyring or --audit-keyring
points at. The flag writes only the exec kind:
<name>=exec:<program> <argument...>, split on whitespace (an argument with a
space needs a wrapper script). Declare native key services in the
configuration file; see [[kms_keys]].
With a ${kms.<name>+...} reference, production creates the keyring inside the key
service.
init opens store_url and brings its schema up to date, as
authority migrate does, then persists the
transport CA and prints its fingerprint. Without --store-url,
transport_ca.fingerprint is null and the summary gives the reason.
A new cluster is stamped with the current
activation epoch in the same pass.
activation.epoch is the stored epoch; activation.stamped says whether this
run set it.
| Store | Epoch |
|---|---|
| Empty (no catalogue, no enrolled nodes) | Stamped with the current epoch by init, or by Authority at its first start (logged). |
| Holds resources or enrolled nodes | Unchanged; the summary gives the reason. Raise it with authority activate. |
Only migrated with authority migrate |
Not stamped. |
At epoch 2 every ssh row in the catalogue declares its onward mode
(gdsgate authority set-resource <id> --kind ssh --onward-auth
local|certificate|client_key); a row that declares none is not served.
gdsgate authority set-profile --profile <dev|pilot|production> --changed-by <NAME>¶
Changes the deployment profile of an installed cluster. Rewrites the
[security].profile line in the loaded configuration (comments and ordering
are kept) and records the change on the audit chain.
gdsgate --config authority.toml authority set-profile \
--profile pilot --changed-by anna.ivanova
# authority.toml: `[security].profile` is now "pilot" (was "dev").
# Restart Authority for it to apply. The change is on the audit chain.
| Flag | Default | Purpose |
|---|---|---|
--profile <dev\|pilot\|production> |
required | The profile to declare. |
--changed-by <NAME> |
required | Person the change is made for. Recorded on the audit row as asserted, unverified. |
The profile decides whether the store may be a file, whether a listener may be
plaintext, whether the all-in-one process starts, and whether
keys accept-loss is allowed; see
[security]. The audit row is written
before the file. The new profile applies at the next restart; the command does
not restart Authority. Exits 3 when the profile is already the requested one.
--operator does not apply here.
gdsgate authority bootstrap-admin --output-dir <DIR> [--ttl <secs>] [--if-missing]¶
With an empty --output-dir: opens the store directly, issues an admin-role
provisioning token, registers with it, and writes cert.pem, key.pem and
ca.pem. This is the only direct-store admin path. With a live identity in the
directory: renews it over mTLS to [admin].endpoint; no second identity is
issued and no token is spent.
gdsgate --config authority.toml authority bootstrap-admin \
--output-dir /var/lib/gdsgate/admin --ttl 300
Copy the three files to the operator workstation and point its config at them
([admin].identity_dir).
| Flag | Default | Purpose |
|---|---|---|
--output-dir <DIR> |
required | Where cert.pem, key.pem and ca.pem are written. Required. |
--ttl <secs> |
300 |
Validity of the bootstrap provisioning token. It is spent at once; matters only if registration is interrupted. |
--if-missing |
off | Keep an existing identity this cluster accepts and exit 3; no token, no renewal, no file written. Without it, a live identity is renewed over mTLS. |
With --if-missing the command is safe to repeat:
gdsgate --config authority.toml authority bootstrap-admin \
--output-dir /var/lib/gdsgate/admin --if-missing
# admin identity already present at /var/lib/gdsgate/admin (ttl 21h), nothing to do
Without --if-missing, a usable identity is renewed. A missing, expired,
untrusted or otherwise unusable one is replaced through the bootstrap path, and
the reason is printed. A renewal failure on a usable identity is an error; to
force a new identity, remove the directory.
An identity is usable (and kept by --if-missing) when all of these hold;
otherwise the run prints the failed condition and issues a new one:
cert.pem,key.pemandca.pemare readable;cert.pemparses and is within its validity window;- more than a quarter of the certificate's lifetime remains;
- the certificate was issued by a CA this cluster trusts now;
ca.pemcarries the CA the cluster serves with;key.pemmatchescert.pem.
The issued identity lives 24 hours, or [transport].cert_ttl_secs when set.
gdsgate authority bootstrap-provisioner --output-dir <DIR> [--token <TOKEN>] [--if-missing]¶
Obtains the provisioning control plane's mTLS identity on the first run and
renews it on later runs. Writes cert.pem, key.pem and ca.pem to
--output-dir, the files [admin].identity_dir
reads. authority sub-actions then run as CN=gdsgate-provisioner with what
the policy grants Group::"gdsgate-provisioner".
The store is not opened; no database credentials are needed. The first run
spends a one-time --role provisioner token at the enrollment listener; later
runs renew over Authority.RenewIdentity.
# First run, on the provisioning control plane.
gdsgate --config provisioner.toml authority bootstrap-provisioner \
--output-dir /var/lib/gdsgate/provisioner \
--token "$(gdsgate --config operator-workstation.toml \
authority create-token --role provisioner --ttl 300)"
# Every run after that — no token is read.
gdsgate --config provisioner.toml authority bootstrap-provisioner \
--output-dir /var/lib/gdsgate/provisioner --if-missing
| Flag | Default | Purpose |
|---|---|---|
--output-dir <DIR> |
required | Where cert.pem, key.pem and ca.pem are written and read back. Required. |
--token <TOKEN> |
[enroll].token |
One-time token from authority create-token --role provisioner, for the first run only. Not read once --output-dir holds an identity. |
--if-missing |
off | Keep an identity with more than five minutes left (or more than a quarter of its lifetime, when that is less) and exit 3. Without it, every run renews; a renewal replaces the identity and its registry row. |
Endpoints: [enroll].endpoint for the first registration;
[enroll].renew_endpoint, else [endpoints].authority, for renewals. With an
empty --output-dir and no enrollment endpoint, the run fails with a message.
Once --output-dir holds an identity, a failed renewal is an error and no
token is spent. To replace a broken identity, remove --output-dir and run
again with a fresh token. Two control planes registering at once end with one
identity: the second finds the token spent.
The command prints the identity path, its validity, and the
[admin] identity_dir line for this host's configuration.
gdsgate authority create-token --role {proxy|connector|admin|workload|provisioner|agent-host} [--ttl <secs>]¶
Generates a one-time registration token for one node of the given role. Prints the token to stdout; logs go to stderr.
| Flag | Default | Purpose |
|---|---|---|
--role |
required | proxy, connector, admin, workload, provisioner or agent-host. Required. An admin identity is normally issued by bootstrap-admin. |
--ttl <secs> |
3600 |
Validity window before the token expires. |
On --route certificate the configuration needs [admin].endpoint and
[admin].identity_dir.
Decided by Cedar issueJoinToken over the identity the token creates:
EnrolledNode::"gdsgate-connector", EnrolledNode::"gdsgate-proxy",
EnrolledNode::"gdsgate-admin", and so on. The starting policy grants the
provisioner connector, proxy, workload and agent-host. A refusal prints
permission denied: policy denied admin action and exits non-zero. See
Policy → Administrative actions.
gdsgate authority rotate-ca¶
Requests a User SSH CA rotation. Authority (the leader in HA) runs the paced
double-signing rotation. Works regardless of [ca_rotation].enabled. The
request is kept in the store until a rotation of that CA starts, so one made
while Authority is stopped runs on the first check after it starts. The same
holds for the other rotate-* commands.
gdsgate authority rotate-onward-ca¶
Requests an Onward SSH CA rotation. This CA signs the OpenSSH user
certificates a connector presents to a downstream sshd in the model-B
jump-host path. Same paced rotation as rotate-ca. Add the new public key to
TrustedUserCAKeys on every downstream sshd.
gdsgate authority rotate-host-tls-ca¶
Requests a HostTls (transport) CA rotation. Same paced double-signing rotation
as rotate-ca. Nodes pick up the new trust bundle on their next
mTLS renew. Set [ca_rotation].propagation_secs
to at least the renew interval. HostTls has no automatic schedule.
gdsgate authority rotate-spiffe-ca¶
Requests a rotation of the SPIFFE CA, which signs
workload certificates. Same paced rotation
as rotate-ca. Connectors and proxies pick up the new SPIFFE bundle on their
next mTLS renew.
gdsgate authority rotate-jwt-ca¶
Requests a rotation of the JWT-signing CA, which signs the Gdsgate-Identity
assertion sent to an application when a kind = "web" backend sets
identity = "signed". Same paced rotation as rotate-ca. Manual only. Set
propagation_secs to at least the time applications cache the key set the
Proxy publishes at /.well-known/gdsgate/jwks.json. See
Configuration → Telling the application who came.
gdsgate authority list-nodes¶
Prints every enrolled node, active and revoked, one per row.
Columns: id, role, cn, fingerprint, enrolled, state. enrolled is
UTC, YYYY-MM-DD HH:MM:SS; state is active or revoked <time>.
fingerprint is the first 16 characters, or the whole digest under --wide.
The second line appears for a node that set
[enroll].node_name: a name the node claims,
unverified. See Naming a node.
Pass the id to authority revoke-node.
gdsgate --config operator-workstation.toml authority list-nodes
# id role cn fingerprint enrolled state
# n-4f21a9 proxy gdsgate-proxy 9f2c1d7b3e4a5061 2026-09-12 08:41:07 active
# n-77c0e3 connector gdsgate-connector 1b8e44a0c5d92f37 2026-09-12 08:44:19 active
# calls itself edge-frankfurt (claimed by the node, unverified)
--json prints one object per node, with times as Unix seconds and the whole
fingerprint.
--health shows the health each node last reported through the periodic
check (see [doctor]). Columns: id, role,
ok, info, warn, fail, reported; --wide adds fingerprint.
Degraded checks follow each row, indented, worst first. A node missing from
--health has not reported since Authority last started.
gdsgate --config operator-workstation.toml authority list-nodes --health
# id role ok info warn fail reported
# n-4f21a9 proxy 11 0 1 0 42s ago
# warn store-latency · p99 write 412ms over the last minute
Both forms are decided by Cedar viewNodes over EnrolledNode::"*", as is
authority list-workloads. A refusal
prints permission denied: policy denied admin action and exits non-zero. Only
refusals are audited.
gdsgate authority list-offered¶
Prints what connectors say they serve, one per row.
Columns: node, resource, kind, offered, declared. offered is UTC,
YYYY-MM-DD HH:MM:SS.
A connector states the resources it serves each time its reverse tunnel opens.
declared=no needs an operator: the catalog has no row for the resource and
nothing is routed to it until one is declared with
authority set-resource.
Resource id, kind and the indented node name are claimed by the connector,
unverified. The node id, timestamp and declared come from the cluster.
declared=yes means the catalog has a row for the resource id. It says
nothing about the node: a row is listed until ten minutes after that node last
opened a tunnel, whether the node is still running or not.
| Row | Cause | Action |
|---|---|---|
| An old node after a re-join, next to the new one | its offer, from the last tunnel it opened; its registry row is still active |
authority revoke-node <old id>: its offers go at once, and the new node's tunnel is admitted. Without it the offer goes ten minutes after the old node's last tunnel, and the Proxy drops the new node's tunnel until then. |
| A node that stopped | the same | none: the row goes after ten minutes |
A backend added to [[connector.backends]]
appears after the connector restarts.
gdsgate --config operator-workstation.toml authority list-offered
# node resource kind offered declared
# n-77c0e3 db-orders_prod postgres 2026-09-12 08:44:21 yes
# n-77c0e3 host-db7 ssh 2026-09-12 08:44:21 no
--json prints one object per offer, with offered_at_unix as Unix seconds.
Decided by Cedar viewNodes, as
authority list-nodes is.
gdsgate authority revoke-node <id>¶
Revokes an enrolled node: it can no longer renew its transport leaf over mTLS.
Prints revoked on first success, already revoked on a re-run, unknown
(exit non-zero) if no such row exists. The revocation is sealed with the
revocation key of the control keyring (see
[security.integrity]); a
database rollback does not undo it.
Every proxy drops the node's reverse tunnel at once and ends the sessions over it. Its resources stop being routable, and its next registration is refused.
The issued leaf stays chain-valid until not_after. For a service other than a
gdsgate listener that trusts the leaf, rotate the HostTls CA.
NODE_ID="<id-from-list-nodes>"
gdsgate --config operator-workstation.toml authority revoke-node "$NODE_ID"
gdsgate authority delete-node <id>¶
Removes the registry row of a node revoked at least seven days ago. Prints
deleted on success; anything else exits non-zero:
| Printed | Meaning |
|---|---|
deleted |
The row is gone. |
not revoked |
The node is still active; run revoke-node first. |
too recent |
Revoked, but less than seven days ago. The message names the second from which the row may go. |
unknown |
No such row; also printed by a re-run of a successful delete. |
The HMAC revocation receipt is kept; the id stays revoked. The removal is
audited as identity.delete, with the reason when refused. Join and revocation
rows stay on the chain.
Decided by Cedar revokeNode on the node's EnrolledNode entity, the same
action as revoke-node; a policy cannot grant one without the other.
NODE_ID="<id-from-list-nodes>"
gdsgate --config operator-workstation.toml authority delete-node "$NODE_ID"
gdsgate authority forget-user <subject>¶
Removes a person's display name from the user directory that
display_name_claims fills. Audit listings
show the subject again. The audit chain never held the name and is not changed.
A later sign-in records the name again; to stop keeping names at all, set
display_name_claims = [].
| Printed | Exit | Meaning |
|---|---|---|
forgotten |
0 | The name is gone. |
unknown |
1 | No name was kept for the subject; a re-run prints this. |
<subject> is actor_verified in audit list --json.
Gated by Cedar disableUser on User::"<subject>". Written to the audit chain
as user_name.forget (AUT-0401) on every outcome, the refusal included.
gdsgate --config operator-workstation.toml authority audit list --actor alice@example.com --json --limit 1 |
jq -r .actor_verified
# 8f3a1c2e-5b7d-4e19-a0c6-3d2b1f4e5a67
gdsgate --config operator-workstation.toml authority forget-user 8f3a1c2e-5b7d-4e19-a0c6-3d2b1f4e5a67
# forgotten
gdsgate authority import-node --role <ROLE> --cert <PATH>¶
Registers a node whose transport leaf was issued by a release older than the
per-node revocation registry. Reads the leaf PEM, checks that its CN is
gdsgate-{role}, and registers the SHA-256 of its SubjectPublicKeyInfo under
the admin identity from [admin].identity_dir.
Prints imported <id> on the first run and existing <id> (exit 0, no
duplicate row) on a re-run. Audited as identity.import (AdminAction) with the
admin CN as actor.
After import the node renews normally. Without import, renewals from a pre-registry leaf are refused.
gdsgate --config operator-workstation.toml authority import-node \
--role connector \
--cert /var/lib/gdsgate/connector.cert.pem
Nodes enrolled by the current release are registered at enrollment and need no import.
gdsgate authority list-workloads¶
Prints every registered workload (provision-token machines tracked for token-less renewal), one per row.
Columns: spiffe id, owner, fingerprint, joined, state, cert until.
state is active or revoked, and cert until is the leaf's expiry on an
active row and the revocation time on a revoked one. fingerprint is the first
16 characters, or the whole digest under --wide.
Platform-token workloads keep no row and are not listed. Decided by Cedar
viewNodes over EnrolledNode::"*", as
authority list-nodes is.
gdsgate --config operator-workstation.toml authority list-workloads
# spiffe id owner fingerprint joined state cert until
# spiffe://gds.dev/ns/prod/sa/importer platform-ci 0a71cc93b5e28d4f 2026-09-01 11:02:44 active 2026-09-21 14:10:00
--json prints one object per workload, with times as Unix seconds.
gdsgate authority revoke-workload <spiffe-id>¶
Revokes a registered workload. Prints revoked on first success; a re-run or
an unknown id reports a no-op. The revocation is sealed with the revocation key
of the control keyring (see
[security.integrity]); a
database rollback does not undo it.
Every proxy ends the workload's open sessions at once. Its next connection and next renewal are refused on every lane. Other sessions on the same connector tunnel continue.
The issued certificate stays chain-valid until its not_after. To invalidate
certificates fleet-wide, rotate the SPIFFE CA.
Platform-token workloads keep no registry row and cannot be revoked here; deny them in policy.
WORKLOAD_SPIFFE_ID="spiffe://gds.example/provision/8f3c1234" # from `list-workloads`
gdsgate --config operator-workstation.toml authority revoke-workload \
"$WORKLOAD_SPIFFE_ID"
gdsgate authority list-resources¶
Prints the whole resource catalog,
one row per declared resource, in id order, including resources nobody may
reach. gdsgate ls shows what the caller may open.
Columns: id, kind, project, env, serving, hostname, port. A field
the row does not declare is -; defaults are not resolved (see
set-resource; gdsgate ls shows the
resolved name and port). An empty catalog prints the header and (none).
gdsgate --config operator-workstation.toml authority list-resources
# id kind project env serving hostname port
# db-orders_prod postgres payments prod yes orders 5433
# host-db1 ssh payments prod yes - -
--wide adds the columns the table leaves out: aliases, native name,
parent, approvers, ssh profile, web profile, onward auth, pinned,
scope, lease until, labels. Labels are sorted by key; aliases keep
declaration order. Columns do not depend on terminal width.
--json prints one object per resource. An undeclared field is absent (a row
with no port has no port field). A row a connector wrote into a scope carries
scope and lease_until (Unix seconds). An empty catalog prints nothing.
--declaration prints the declarations set-resource --from accepts, without
cluster-added fields and without rows connectors published into a scope. See
declaring from a document.
gdsgate --config operator-workstation.toml authority list-resources --json
# {"id":"host-db1","kind":"ssh","project":"payments","environment":"prod","serving":"yes","port":22}
serving says whether a connector's tunnel is registered for the resource at
that moment. It is filled on --route gateway and
absent on the direct routes. An absent serving means unknown.
list-resources, set-resource and remove-resource are decided by Cedar
manageCatalog over the resource's entity and audited on both outcomes. A
refusal names the manageCatalog action and the policy to permit it in, and
exits non-zero.
gdsgate authority list-serving¶
Prints the resources a connector's tunnel is registered for at that moment, one
id per row under a resource header. An empty answer prints the header and
(none). --json prints one {"resource":"<id>"} object per row, and nothing
when nothing is served.
The call goes to the gateway's internal listener ([endpoints].proxy_internal,
override with --endpoint) with the control-plane certificate from
[admin].identity_dir. The gateway refuses a peer whose CN is not a
control-plane role of this cluster. The client-facing listener is not used.
For a person, the same answer is under serving in
list-resources --route gateway --json.
gdsgate authority set-resource <id> --kind <KIND>¶
Declares a resource in the catalog or rewrites an existing one. Prints created
or updated and the row as written, in list-resources shape.
A rewrite replaces the whole row: a label or alias omitted on the second run is removed.
gdsgate --config operator-workstation.toml authority set-resource db-orders_prod \
--kind postgres --project payments --env prod \
--hostname orders --alias orders-db --port 5433 \
--label tier=gold --parent host-db1 --min-approvers 2
# created db-orders_prod postgres project=payments env=prod hostname=orders aliases=orders-db port=5433 min_approvers=2 labels=tier=gold parent=host-db1
| Flag | Default | Purpose |
|---|---|---|
--kind |
required | ssh, postgres, mysql, kubernetes (or k8s), tcp, mcp, llm, web, web-egress, rdp. A CockroachDB backend is catalogued as postgres; cockroach is a backend kind in [[discovery.resources]] and [[connector.backends]] only. |
--project |
default |
Owning project. |
--environment / --env |
dev |
Environment; matched by the [approvals] per-environment cascade and by policies. |
--label <KEY=VALUE> |
not set | Label; repeatable. Labels become the resource's Cedar entity tags. |
--parent <ID> |
not set | Parent resource id, for example a database's host server. Unset: top-level. |
--min-approvers <N> |
0 |
Resource-level JIT approval threshold, the narrowest level of the approvals cascade. 0: [approvals] decides. |
--hostname <NAME> |
not set | Name clients address the resource by. Unset: the resource id, when it is a legal name. |
--alias <NAME> |
not set | Additional name, same rules; repeatable. |
--port <N> |
0 |
Port clients dial. 0: the kind's well-known port. |
--pin-tool-descriptors |
off | mcp only. Pins the server's tool descriptors; see Pinning tool descriptors. A write without the flag turns pinning off. Refused on other kinds. |
--ssh-profile <PROFILE> |
not set | ssh only. general: anything the policy allows. git: the four recognised git commands only; a shell, any other exec and every subsystem are refused before policy. Unset reads as general and shows as unset in list-resources. A write without it clears it. Refused on other kinds or other values. |
--onward-auth <MODE> |
not set | ssh only. local: session on the connector's own host, no further connection. certificate: jump host with the onward certificate Authority issues per connection. client_key: jump host authenticated with the person's key through their agent. Unset reads as the resource's current behavior. A connector whose file names a different mode stops serving the resource. |
--web-profile <PROFILE> |
not set | web only. general: anything the policy allows. git: recognised git operations only; everything else is refused before policy. Unset reads as general. Refused on other kinds or other values. |
--native-name <DOMAIN> |
not set | web only. The application's own domain (app.example.com), as in [[connector.backends]] url. At least two labels; unique in the same namespace as --hostname and --alias. A resident client answers to it only when its user lists it in [client].intercept_names. Refused on other kinds. See Resident client → An application's own name. |
--from <PATH> |
not set | Read the declaration from a TOML document; - reads standard input. Not combined with other declaring flags. See declaring from a document. |
Addressing follows the same rules as hostname, aliases and port in
[[discovery.resources]]:
- A name is one DNS label: 1 to 63 bytes of letters, digits and
-, not starting or ending with-, folded to lowercase. An invalid name is refused. - The default name is the resource id when the id is a legal label. An id with
_or.(db-orders_prod) has no default name and needs--hostnameto be addressable. - Names are unique across the catalog: hostnames, aliases, native names and id-derived names share one namespace. A taken name is refused, naming its holder. To move a name, remove it from the holder first.
- The port defaults by kind: ssh 22, postgres 5432, cockroach 26257, mysql 3306,
kubernetes 6443, web 80.
tcp,mcp,llmandweb-egressmust declare one. This is the port clients connect on; the backend address is set on the connector.
Refused before sending: an unknown kind; a label that is not key=value, has
an empty key or value, or repeats a key. Refused by Authority: a --parent
naming the resource itself.
A resource also declared in [[discovery.resources]] is overwritten from the
file at the next Authority start. Keep [discovery] empty on a cluster whose
catalog is managed with this command.
gdsgate authority activate --epoch <n>¶
Switches on a gate, cluster-wide. Irreversible.
| Option | Default | Purpose |
|---|---|---|
--epoch <N> |
required | 1: Cedar action sshProgram is evaluated for every program an SSH session runs (shell, exec, subsystem). 2: an SSH resource's onward_auth is read from the catalog only; SSH resources without it are not served. A value below the current epoch is refused. |
--force |
off | Proceed despite the readiness shortfalls named, for example a node that can no longer be reached. The override is written to the audit chain. |
Before epoch 1, sshProgram is not evaluated. Write sshProgram rules before
activating epoch 1; without them every SSH program is denied.
Epoch 1 readiness: every node that serves an SSH resource and has renewed its
activation lease recently must report support for the gate. Otherwise the call
is refused, naming the nodes, and nothing changes. A node that has never
renewed is listed as not ready. Upgrade it, revoke it with
authority revoke-node, or pass --force.
Epoch 2 readiness: every SSH resource names an onward_auth; the refusal lists
those that do not. Run gdsgate --config <authority-config> doctor first: the
state.resource.modes lines show each SSH resource, the catalog's value, and
what each serving connector proposes. Set missing values with
authority set-resource --onward-auth.
After activation:
| Event | Result |
|---|---|
| Activation | Live SSH sessions on every connector end; tunnels drop and reconnect; callers reconnect. |
| A node registers without a receipt for the gate (new, returning, or rolled back to an older binary) | Served no SSH resource; reason gate_not_offered. |
| Epoch 2, SSH resource without confirmed mode | Withheld; reason mode_not_confirmed. |
| Connector's epoch answer expires | It stops serving SSH, ends its SSH sessions with the reason, and reopens its tunnel. |
Connector sees an epoch below the highest in <state_dir>/activation.json |
Refused. |
Connector serving SSH cannot read or write activation.json |
Does not start. |
Authority issues a one-time receipt on each activation-lease renewal and spends
it at the registration that presents it. A freshly started connector registers
once without a receipt, renews, and registers again: the log shows a second
tunnel a second later. The first tunnel stays open with no resource bound when
every resource was withheld for gate_not_offered; a registration refused for
another reason is dropped.
A compromised proxy can spend a relayed receipt and leave the connector with
gate_not_offered. It cannot be served in the connector's place: a tunnel is
admitted on the connector's own mutual-TLS leaf.
To correct a mistaken activation, write the rules the gate requires.
gdsgate authority remove-resource <id>¶
Removes a resource from the catalog. Prints removed <id> or
no such resource <id>; exits 0 in both cases.
Removal closes access and removes the connector binding and published inventory
in one transaction; see
Admin guide → What removing a resource does.
A resource still declared in [[discovery.resources]] comes back at the next
Authority start.
A row a connector wrote inside a scope carries scope=<prefix> (its scope)
and lease_until=<unix seconds> (lease expiry). A declared row carries neither.
gdsgate authority list-scopes¶
Prints every auto-scope, one per row, with its live rows against max_live.
Rows with an expired lease are not counted.
Columns: prefix, node, kinds, onward, env, project, rows,
lease, fallback, state. --wide adds labels and accepts. onward
lists the onward modes an ssh target may name; - when the scope admits no
ssh.
gdsgate --config operator-workstation.toml authority list-scopes
# prefix node kinds onward env project rows lease fallback state
# test- kube-conn-1 postgres,ssh certificate test qa 42/2000 900s parent enabled
A scope whose state is disabled accepts no publishes and holds no rows.
--json prints one object per scope. --declaration prints what set-scope
--from takes back; see declaring from a document.
gdsgate authority set-scope <prefix> --node <ID> --kind <KIND> --env <ENV>¶
Creates or rewrites an auto-scope. The named connector writes catalog rows inside it; see discovery configuration.
A rewrite replaces the whole declaration; omitted values revert to defaults. A prefix that overlaps another scope or covers a declared resource is refused.
gdsgate --config operator-workstation.toml authority set-scope test- \
--node kube-conn-1 --kind postgres --kind ssh --project qa --env test \
--label origin=k8s-auto --accept-label-key app --max-live 2000 --lease-secs 900
# created test- node=kube-conn-1 kinds=postgres,ssh env=test project=qa rows=0/2000 lease=900s fallback=parent labels=origin=k8s-auto accepts=app
| Flag | Default | Purpose |
|---|---|---|
--node <ID> |
required | Enrolled node id (from authority list-nodes) of the connector allowed to publish here, matched by key fingerprint. |
--kind <KIND> |
required | Kind a target may be published as; repeatable. A snapshot with another kind is refused. |
--project <NAME> |
default |
Project written on every row in this scope. |
--environment / --env |
required | Environment written on every row in this scope. Not taken from the source. |
--label <KEY=VALUE> |
not set | Label written on every row; overrides a source label with the same key. Repeatable. |
--accept-label-key <KEY> |
not set | Label key accepted from the source; repeatable. Other keys are dropped; pin_src_ip is always dropped. |
--max-live <N> |
1000 |
Maximum live rows in the scope, including unexpired rows from earlier snapshots. A snapshot that would exceed it is refused in full. A scope declared in the configuration file is rewritten from [[discovery.auto_scopes]] at every Authority start; set the value there too. |
--lease-secs <N> |
900 |
How long a row stays after the snapshot that carried it. |
--fallback <parent\|deny> |
parent |
Meaning of an absent row. parent: existing rules decide (a database sub-resource is authorized through its host). deny: a target without a row is not served. |
--ssh-host-key <annotation\|tofu> |
annotation |
Source of an ssh target's host-key fingerprint. annotation: the gdsgate.io/ssh-host-key annotation; a target without it is not served. tofu: the first key seen is accepted for the lease. |
--onward-auth <local\|certificate\|client-key> |
not set | Onward mode an ssh target may name; repeatable. Required with --kind ssh, refused without it. A snapshot naming another mode is refused in full. |
--from <PATH> |
not set | Read the declaration from a TOML document; - reads standard input. Not combined with other declaring flags. See declaring from a document. |
--disabled |
off | Create the scope without accepting publishes. Disabling a scope that holds rows deletes them. |
gdsgate authority remove-scope <prefix>¶
Removes an auto-scope and its rows, printing how many rows were deleted. Prints
no such boundary <prefix> when there was none; exits 0 in both cases.
gdsgate --config operator-workstation.toml authority remove-scope test-
# removed test- (rows dropped: 42)
To stop publishes and keep the scope declared, use
set-scope --disabled; its rows are deleted.
gdsgate authority list-agents [--node <id>]¶
Lists declared agent runs and the state each agent host last reported. Decided
by Cedar viewAgents.
Columns: agent, node, project, env, state, seen, cage, can,
faces, lease. --wide adds program, groups, limits, on expiry and
generation.
state is the declared state. seen is the state the agent host last
reported, with its age.
can lists the capabilities the agent may use. faces lists the published
faces as <published name>=<source>; - means none.
gdsgate --config operator-workstation.toml authority list-agents --node agents-01
# agent node project env state seen cage can faces lease
# support-bot agents-01 crm prod running running 4s ago strict db:read@pg-crm,llm:call@models-prod support-bot-console=pty 14m
# tool-bot agents-01 crm prod running exited×7 2s ago strict db:read@pg-crm tool-bot-tools=port 13m
seen |
Meaning |
|---|---|
- |
No report from the host yet, or the host stopped reporting. |
running |
The process is running and its endpoints are open. |
frozen |
The lease expired with on expiry = freeze: the process runs, and all its access is closed. |
exited |
The process exited without a stop request. |
failed |
The declaration says running; the process is not: the start failed, or the lease expired with on expiry = stop. The start error is in the agent host's log. |
stopped |
Declared stopped and stopped. |
exited×7 is the number of consecutive unrequested exits. (old) marks a
report about an earlier version of the declaration, or about a removed agent
with the same name. After a host stops reporting, its last report stays in the
column with a growing age.
seen is - for every agent when the Proxy relaying the call is older than
Authority. Upgrade the gateways with Authority, or use --route certificate.
expired in the lease column means no host renews the lease. The declaration
is kept.
--json prints one object per agent: the faces in full (name, kind,
source, and port or path), the lease expiry, and the host report as
seen, seen_at_unix, seen_generation, seen_incarnation, seen_exits and
seen_exited_at_unix. The report fields are absent when there is no report.
incarnation is the declaration's incarnation.
--declaration prints the declarations in the form set-agent --from reads,
with version and without the lease. See declaring from a
document.
| Flag | Default | Purpose |
|---|---|---|
--node <id> |
every node | Only the runs declared for one agent host. |
gdsgate authority set-agent <id> --node <id> --program <path> --can <capability>¶
Creates or replaces an agent declaration. Decided by Cedar declareAgent.
Agents run on Linux agent hosts only; an agent host on Windows starts but runs
no agents.
gdsgate --config operator-workstation.toml authority set-agent support-bot \
--node agents-01 --project crm --program /usr/local/bin/support-bot \
--can db:read@pg-crm --can llm:call@models-prod \
--group agents-support --memory-max 2G --cpu-quota 200%
# declared support-bot on agents-01 (running)
The agent host applies the declarations it holds. Starting, stopping, granting a resource and removing one are all changes to the declaration.
| Flag | Default | Purpose |
|---|---|---|
--node <id> |
required | Agent host that runs the agent, from list-nodes. |
--project <id> |
default |
Project: the Cedar parent of the agent's principal. |
--environment <env> |
prod |
Environment of the agent. Alias --env. |
--group <name> |
none | Cedar group of the agent's principal. Repeatable. |
--can <capability> |
required unless a face is declared | Capability the agent may use, e.g. db:read@pg-crm. Repeatable. A declaration without --can and without --face is refused. |
--program <path> |
required | Absolute path of the program. |
--arg <value> |
none | Program argument. Repeatable. |
--face <spec> |
none | Published face, <kind>:<source>[:<where>][=<name>]. Repeatable. See faces. |
--tool <face>=<tool> |
none | Tool an mcp face allows. Repeatable. An mcp face with no tool is refused. |
--input-request <face>=<ask> |
none | Server-to-client request the face's server may make: sampling/createMessage, elicitation/create, roots/list. Repeatable. |
--from <PATH> |
not set | Read the declaration from a TOML document; - reads the standard input. See declaring from a document. |
--lease-secs <n> |
900 |
Lease duration. The host renews the lease while the agent runs. |
--on-expiry <what> |
freeze |
On lease expiry: freeze closes the agent's access and keeps the process; stop ends the process. |
--sandbox <profile> |
strict |
Cage profile: none, basic or strict. Under none the agent still runs in its own network namespace, where its endpoints are. The run's record lists the restrictions applied. |
--memory-max <size> |
none | Memory limit, e.g. 2G. |
--tasks-max <n> |
none | Process count limit. |
--cpu-quota <percent> |
none | CPU limit; 100 is one core. |
--stopped |
off | Declare the agent stopped. |
--if-version <version> |
not set | Write only if the declaration is at this version (<generation>:<incarnation> from list-agents --declaration), or new if it must not exist yet. |
--ignore-version |
off | With --from: ignore version in the document. |
Editing a declaration does not renew the lease.
Concurrent edits¶
set-agent replaces the whole declaration. A write that names the version it
was edited from is refused if the declaration changed since:
gdsgate --config operator-workstation.toml authority list-agents --declaration --node agents-01 > agents.toml
# version = "4:0f2e9a61-5b7c-4d1e-9a3f-2c8b1d4e6f70"; edit agents.toml
gdsgate --config operator-workstation.toml authority set-agent --from agents.toml
A write without a version is accepted or refused according to
[authority].agent_declaration_version
(optional or required). A new declaration needs no version. switch-agent
and remove-agent take no version.
| Error contains | Cause | What to do |
|---|---|---|
changed after this edit was made from it |
The declaration was rewritten, switched, or removed and declared again after the version was read. | Print the declaration again and repeat the edit. |
is already declared |
new was given for an existing agent. |
Edit the existing declaration. |
is not declared |
The version refers to a removed agent. | Declare it without a version. |
agent_declaration_version = "required" |
No version was given for an existing agent. | Use the document from list-agents --declaration, or pass --if-version. |
Faces¶
A face publishes one endpoint of the agent as a catalog row: a source inside the cage and the resource kind it is published as. An agent can have several faces.
gdsgate --config operator-workstation.toml authority set-agent support-bot \
--node agents-01 --program /usr/local/bin/support-bot --can llm:call@models-prod \
--face ssh:pty \
--face web:port:8443=support-bot-console \
--face mcp:port:9000=support-bot-tools \
--tool support-bot-tools=read_ticket \
--tool support-bot-tools=search_kb
| Source | Where | Serves | Note |
|---|---|---|---|
pty |
nothing | ssh |
The agent's console. One per agent. |
port |
port number | web, llm, mcp, tcp |
A port the agent listens on inside its cage. |
unix |
path | web, llm, mcp, tcp |
A socket in the agent's workspace. Refused with --sandbox strict. |
stdio is refused as a source. Run an MCP server on a port or a socket.
Without =<name> the name is <agent>-<kind>, then <agent>-<kind>-2 for the
second face of the same kind. The catalog row name is the boundary's
id_prefix followed by the face name.
A web or llm face¶
Clients reach the face like any web or llm resource; each request is
decided by httpRequest or llmCall. The agent listens inside its network
namespace and is not reachable from the host network.
Requests reach the application with Host: <row>.invalid, where <row> is the
catalog row name. The name does not resolve.
Settings of a web or llm face:
| Setting | On a face |
|---|---|
| Routes | none named |
| Permit window | five seconds |
| Audit | sampled |
| Request body | no ceiling |
Caller's Authorization |
passed through, unless the face states identity (below) |
| WebSocket and other upgrades | admitted, and decided by httpRequest with context.upgrade |
Models an llm face serves |
not listed: Cedar decides each call |
Provider credential on an llm face |
none |
Traffic between the agent host and the agent is plain HTTP inside the host.
Telling a web face who opened it¶
With identity = "signed" on a web face, each request to the agent carries a
gdsgate-identity header: a short-lived assertion signed by Authority with the
caller's name, groups and the face. The caller's Authorization header is
removed.
identity |
Meaning |
|---|---|
none (default) |
No identity header. |
signed |
A signed assertion on every request. web faces only. |
headers |
Refused on a face. |
Acting for the person who opened the page¶
The agent forwards the assertion it received, unchanged, on its own call. The
call is allowed only if both the agent and the person may make it. The call's
audit row has owner set to the person and the detail keys on_behalf_of (the
person) and arrived_at (the face the assertion was issued for).
| Resource kind | Where the agent puts the assertion | Audit row |
|---|---|---|
llm |
gdsgate-identity request header |
llm.call |
mcp |
params._meta["gdsgate/identity"] of tools/call, resources/read, prompts/get or tools/list. The backend needs enforce_tool_policy. tools/list returns the tools both may see. |
mcp.tool_call, mcp.resource_read, mcp.prompt_get |
web, web-egress |
gdsgate-identity request header. Such requests do not use the permit window. |
http.request, written for every such request |
kubernetes |
gdsgate-identity request header, including exec, attach and port-forward |
kube.request |
SSH, TCP and database resources do not accept an assertion.
The assertion is not passed to the model provider, the application, the MCP
server or the Kubernetes API server. The _meta entry is removed from the MCP
message and the message is re-encoded.
| Symptom | Cause | What to do |
|---|---|---|
403 or a JSON-RPC error on a call the agent can make without the assertion |
The person may not make the call. | Grant the person the action, or call without the assertion. |
JSON-RPC error naming enforce_tool_policy |
The MCP backend has no per-call decision. | Set enforce_tool_policy = true on the backend. |
JSON-RPC error naming gdsgate/identity |
The _meta entry is not a non-empty string. |
Pass the assertion as received. |
403, row reason = assertion_for_another_resource |
The assertion was issued for a face this agent does not serve, or for a face name two agents on the node share. | Forward only the assertion received on the agent's own face; give each face a unique name. |
403, row reason = assertion_not_for_a_person |
The assertion is about a workload or about a person acting under a delegation (--as). |
None: only a person acting for themselves can be acted for. |
403, row reason = assertion_not_verifiable |
Not signed by this cluster, or expired. An assertion is valid for at most 60 seconds and not past the visitor's certificate. | Forward the assertion as received, without delay. |
400 from the model, web or Kubernetes lane |
The header is empty, not text, or repeated. | Send it once, as received. |
403 naming a per-request decision, from the web or Kubernetes lane |
The backend has no per-request gate. | Serve the resource from a connector with an Authority client. |
403 with a hint naming the field, or a refused tools/list |
Authority is older than this release. | Upgrade Authority. |
Tools on an mcp face¶
--tool <face>=<tool> lists the tools a caller may invoke through the face.
Calls to other tools are refused. A face with no tool is refused at
declaration. Each call is also decided by Cedar mcpCallTool; both must allow.
--input-request <face>=<ask> allows one of the three server-to-client
requests. None are allowed by default.
Refused ports and sockets¶
A port from 14500 to 14500 + <resources> + 8 is reserved by the cage for the
agent's endpoints and is refused as a face port. A unix face is refused with
--sandbox strict; use a port or --sandbox basic.
gdsgate authority switch-agent <id> [--stop]¶
Starts or stops a declared agent. Changes only the declared state. Decided by
Cedar switchAgent (separate from declareAgent).
gdsgate --config operator-workstation.toml authority switch-agent support-bot --stop
# support-bot is now stopped
Prints was already stopped when the agent is already stopped.
gdsgate authority remove-agent <id>¶
Removes an agent declaration. Decided by Cedar declareAgent. Prints
no agent run is declared under <id> when there was none; exits 0 in both
cases.
To stop an agent and keep its declaration, use
switch-agent --stop.
After a removal, credentials issued to the agent are refused. An agent declared again under the same name gets new credentials. Moving an agent to another host invalidates the credentials on the previous host.
What an edit of a running agent does:
| Edit | Effect |
|---|---|
| Capability added, groups, faces | Applied to the running process. Open sessions stay. |
Capability removed, or a verb narrowed (db:write to db:read) |
The process is restarted. Its open sessions end. |
| Program, arguments, cage profile, limits | The process is restarted. Its open sessions end. |
gdsgate authority onward-ca-pub¶
Prints the active Onward SSH CA public key in OpenSSH wire format, one line, the
form TrustedUserCAKeys expects.
gdsgate --config operator-workstation.toml authority onward-ca-pub \
> /etc/ssh/gdsgate-onward-ca.pub
gdsgate authority onward-ca-pubs¶
Prints every persisted Onward SSH CA public key (active, retiring, candidate),
one OpenSSH line each, newest generation first. Use it for the downstream
sshd's TrustedUserCAKeys; certificates signed by the retiring generation
keep verifying during rotation.
gdsgate --config operator-workstation.toml authority onward-ca-pubs \
> /etc/ssh/gdsgate-onward-ca-bundle.pub
gdsgate authority keys¶
Sub-actions over the two
integrity keyrings and the CA
signing seeds. None prints key material: output is fingerprints, states,
counts, public keys and references as written.
sync create-token prints a secret first-boot permission on stdout; redirect
that command to a protected file.
| Sub-action | Does |
|---|---|
migrate-legacy |
Imports the five [audit].*_key_path keys into the rings. |
wrap-ca-seeds |
Wraps stored CA signing seeds under a wrapping key. |
prune-ca-keys |
Removes private keys of unused CA generations. |
status |
Lists keys per ring, their use, and rows naming them. |
verify |
Checks each key against its fingerprint. |
backup-manifest |
Lists what a database backup needs beside it. |
accept-loss |
Records a key as lost. |
rotate |
Moves a ring to a new key. |
checkpoint-keys |
Prints the public keys that verify signed checkpoints. |
sync create-token |
Issues an expiring owner-bound Authority bootstrap permission. |
sync root-status |
Shows root epoch, preparation ID and retained-owner acknowledgements. |
sync prepare-root |
Prepares an independent root and an explicit retained membership. |
sync activate-root |
Commits the prepared root after every retained owner acknowledges it. |
sync cancel-root |
Abandons a preparation using a retained owner's independent credentials. |
sync wrap-root |
Migrates this host's root to its declared wrapping provider. |
gdsgate authority keys sync root-status|prepare-root|activate-root|cancel-root¶
Run on a provisioned Authority as the local administrator, using its protected root, receiver credentials and shared store. Root rotation changes the credential that encrypts distributed integrity material. Existing control and audit keys remain available for verification.
gdsgate --config /etc/gdsgate/authority.toml authority keys sync prepare-root
gdsgate --config /etc/gdsgate/authority.toml authority keys sync root-status
gdsgate --config /etc/gdsgate/authority.toml authority keys sync activate-root
| Command | Input | Result |
|---|---|---|
prepare-root |
Optional repeated --withdraw <OWNER>; retain all current owners by default |
Signed preparation with independent deliveries to retained owners. Requires at least two retained owners and retains the initiating owner. |
root-status |
None | Current root epoch and material revision; pending preparation ID, initiator and waiting/acknowledged per retained owner. No secret material. |
activate-root |
None; run on the initiating Authority | Atomically commits the successor root and membership after all required signed durable ACKs. Running retained Authorities install it automatically. |
cancel-root |
None; run on any retained Authority | Signed cancellation advances material revision without changing root or membership. Replicas discard the cancelled preparation journal. |
An offline retained member blocks activation. Restore it and wait for polling, or cancel and prepare an explicitly reviewed retained set. If the initiating Authority is lost, a retained owner can cancel, then prepare a new transition as initiator. Timeouts never remove members or cancel a preparation.
During preparation, ordinary keyring edits, membership additions and new bootstrap permissions are refused. A committed withdrawal stops the removed Authority; restarting it with its old credential is refused. Remove it from caller failover lists and service management separately. Root withdrawal does not revoke previously known CA keys, control/audit keys or DB/KMS credentials. See the root rotation procedure.
gdsgate authority keys sync create-token¶
Run on a provisioned Authority host as the local administrator. Requires
ha.keyring_sync.enabled, its protected
distribution root and access to the shared store. The command writes a secret
permission to stdout. It creates no service or local identity on the joining
host.
umask 077
gdsgate --config /etc/gdsgate/authority.toml authority keys sync create-token \
--owner authority-b --ttl-secs 600 > /etc/gdsgate/authority-bootstrap.token
| Flag | Default | Contract |
|---|---|---|
--owner <NAME> |
Required | Stable ha.owner of the joining Authority; 1–128 ASCII letters, digits, _ or -. |
--peer <HTTPS-URL> |
Unset | Required for a new owner; publishes its individual private control-plane address as a mandatory member. An existing owner's address must match. |
--ttl-secs <SECONDS> |
600 |
Permission lifetime, from 60 to 3600 seconds. |
Expected: exit 0 and a nonempty file with mode 0600. Failed validation or
store access produces an error. A membership addition takes effect before
grant issuance; if issuance fails, the new member still participates in the
rotation acknowledgement requirement. Inspect membership before retrying.
Transfer the file through a protected channel, make it readable by the joining Authority service and remove transfer copies. A different owner or nonce cannot reuse the permission after consumption. The same persisted request can recover a lost response while the permission remains valid. The permission's expiry or removal of its file does not revoke an Authority that has already bootstrapped. Complete Authority bootstrap, then remove the one-time input after readiness and restart checks pass.
gdsgate authority keys migrate-legacy¶
Brings the keys named by the five [audit].*_key_path parameters into the two
keyrings [security.integrity]
names, as records that verify rows and never seal them.
Nothing is re-sealed and no key is generated. Each key keeps its fingerprint; existing rows keep verifying. Until a root is added to a ring, new rows are sealed under the key files; keep the five parameters set.
The five parameters are deprecated. To retire them: run this command, add an
active key to each ring with keys rotate <ring> --prepare
and --activate, then remove the parameters from the configuration.
A second run adds nothing and changes no file.
Refused: a key whose ring is not named in the configuration; a ring given as
${env:...} or ${exec:...} (no file to write).
gdsgate authority keys wrap-ca-seeds¶
Puts every CA signing seed already in the store under the wrapping key
[authority].ca_kms_key
names.
With ca_kms_key set, new seeds are written wrapped; existing ones stay as
written until this command runs. It can run against a serving cluster. Each row
is converted separately; an interrupted run leaves a mix, which is read
correctly (each row's seed column records its form). A second run changes
nothing.
Refused when [authority].ca_kms_key names no key, or when a row is wrapped
under a different key.
gdsgate authority keys prune-ca-keys [--delete]¶
Removes the private keys of CA generations nothing will use again.
2 CA generation(s) can be removed:
user-ssh generation 1 (window closed 412d ago)
user-ssh generation 2 (window closed 380d ago)
Nothing was removed. Run this again with `--delete` to remove them; there is no
command that puts one back.
A rotation keeps the retired generation's
private row in ca_private_key; only this command removes it. Under
[authority].ca_kms_key
each stored row costs a key-service call at every start.
A generation is listed when all hold: its validity window closed more than thirty days ago, no rotation role names it, it is not one of its authority's two newest generations, and the trust registry no longer publishes it.
Without --delete the command only lists. With it, each removal is audited as
ca.key_pruned (CAS-0201) before the row is deleted, naming the authority,
the generation and when its window closed. A removed key cannot be restored.
A second run finds nothing to remove.
gdsgate authority keys status¶
Prints what each keyring holds: every key by fingerprint, what it may still be used for, and how many rows in the store still name it. No key material.
control file /etc/gdsgate/control.keys
active 3f9c1a2b4d5e6f708192a3b4c5d6e7f8 root, every domain derived from it
4 rows name it: access_request 2, step_up_grant 2
2 of 2 replicas hold it
verify_only a1b2c3d4e5f60718293a4b5c6d7e8f90 approval key, taken as it stands
no row names it
replicas: auth-1 confirmed 12s ago, auth-2 confirmed 47s ago
audit file /etc/gdsgate/audit.keys
active d4e5f60718293a4b5c6d7e8f90a1b2c3 root, every domain derived from it
812 rows name it: audit_anchor 812
2 of 2 replicas hold it
replicas: auth-1 confirmed 12s ago, auth-2 confirmed 47s ago
A key is either a root (derives every domain's working key) or a single
domain's key taken as it stands (the form after keys migrate-legacy). The row
count comes from the key_id column of audit_anchor, access_request,
step_up_grant, revocation_receipt, owner_revocation, delegation_grant,
backend_inventory and mcp_tool_pin. A key can be retired once the count is
zero.
Each Authority instance confirms the fingerprints it holds once a minute. The
replica line under a key appears once at least one replica has confirmed that
ring; before that the output reads replicas: nothing published (no Authority
has recorded the fingerprints it holds). A replica silent for ten minutes
leaves the count and is shown on the replicas: line with its last
confirmation; it returns when it confirms again. keys rotate --activate and a
production start-up check against the same set.
Keys declared lost with keys accept-loss appear in a trailing
declared lost: block: fingerprint, keyring, when recorded, who accepted it,
row count.
gdsgate authority keys verify¶
Recomputes each key's fingerprint from its material and compares it with the recorded one, and checks that no fingerprint is in both keyrings. Reads the keyring files only; works with the store unreachable.
control file /etc/gdsgate/control.keys
3f9c1a2b4d5e6f708192a3b4c5d6e7f8 root, every domain derived from it its own key
audit file /etc/gdsgate/audit.keys
d4e5f60718293a4b5c6d7e8f90a1b2c3 root, every domain derived from it its own key
every key is the key its fingerprint names.
Run it after editing a keyring file by hand. Material replaced under an
unchanged fingerprint makes every row the original key sealed fail
verification. verify exits non-zero and lists every failing record.
gdsgate authority keys backup-manifest¶
Prints what must be kept with a database backup. Does not open the store.
control file /etc/gdsgate/control.keys
covered by: nothing (the source holds the material as it stands)
3f9c1a2b4d5e6f708192a3b4c5d6e7f8 active root, every domain derived from it, created at 1755000000
audit file /etc/gdsgate/audit.keys
covered by: nothing (the source holds the material as it stands)
d4e5f60718293a4b5c6d7e8f90a1b2c3 active root, every domain derived from it, created at 1755000000
a database restored without these keyrings cannot be verified: every sealed row names the key that sealed it, and a key nothing holds cannot answer for its rows.
a keyring in the same backup as the database protects nothing: whoever holds that backup holds the rows and the key that seals them, and can write rows that verify.
the public keys above are the exception: publish them with the dump. They check the chain's signed checkpoints and seal nothing, so whoever holds them can verify the journal and forge no part of it.
covered by: nothing means the keyring material is not wrapped. The three
closing lines print on every run. See
Admin guide → Keeping keyrings out of the database
backup.
gdsgate authority keys accept-loss --key-id <FINGERPRINT> --accepted-by <NAME>¶
Records that one key is lost. Rows it sealed are then read as unverifiable (previously: forged). The record is sealed by the ring's active key and written to the audit chain.
gdsgate --config authority.toml authority keys accept-loss \
--key-id 3f9c1a2b4d5e6f708192a3b4c5d6e7f8 --accepted-by anna.ivanova
| Flag | Default | Purpose |
|---|---|---|
--key-id <FINGERPRINT> |
required | Lost fingerprint as rows carry it: 32 hex characters. One key per run; keys status prints the fingerprints. |
--accepted-by <NAME> |
required | Person accepting the loss. Recorded on the audit row as asserted, unverified. |
| Refused when | What to do |
|---|---|
[security].profile = "production" |
Change the profile with set-profile first. |
| A keyring still holds the fingerprint | The key is not lost. |
| No row names the fingerprint | Nothing to accept. |
| Rows of both keyrings name it | Check the fingerprint. |
What changes, by table:
| Row | After the loss is accepted |
|---|---|
revocation_receipt (and the equivalent workload-identity row) |
Still reads as revoked. A receipt sealed under a key now declared lost is no longer read as tampering. |
access_request, step_up_grant, delegation_grant |
Refused, as any unverifiable row is; the request, step-up, or delegation has to be made again. |
backend_inventory, mcp_tool_pin |
Discarded, as any unverifiable row is; the connector republishes them. |
audit_anchor |
Left in place. verify-audit reports each stretch of chain it cannot read and continues. |
See Admin guide → When a keyring is lost, including the loss of a whole ring.
gdsgate authority keys rotate <control|audit> <--prepare|--activate|--retire>¶
Moves a keyring to a new key in three steps:
| Step | Does | Refused when |
|---|---|---|
--prepare |
Adds a new key as prepared; the active key stays. Then distribute the ring and restart every Authority (a ring is read at start-up). |
|
--activate |
New rows are sealed under the prepared key. | A confirming replica has not published that it holds the key. |
--retire |
Removes the old key. | A row still names it. Always refused for the audit ring. |
--prepare --kind signing creates the Ed25519 key that signs audit
checkpoints; --kind hmac (default) creates the sealing key. Each kind rotates
separately; --activate replaces the active key of its own kind only. The
control ring takes no signing key.
See Admin guide → Rotating a keyring.
gdsgate authority keys checkpoint-keys¶
Prints the public keys that verify the audit chain's signed checkpoints, one line per signing key in the ring, in any state. Not secret; keep the output with the database dump.
With that file and a dump,
verify-audit --checkpoint-keys verifies
the journal without key material. See
Admin guide → Verifying the journal without a
keyring.
Deprecated key commands¶
Deprecated
The five init-* commands below and the [audit].*_key_path parameters
they write for are deprecated. They still run, print a warning on stderr,
and are hidden from --help.
A new deployment keeps these keys in the keyrings of
[security.integrity],
which authority init creates. A deployment
with key files brings them into the keyrings with
authority keys migrate-legacy.
gdsgate authority init-anchor [--path <PATH>] [--if-missing] (deprecated)¶
Generates a 32-byte HMAC anchor key and writes it hex-encoded to --path, or
to [audit].anchor_key_path. The file is mode 0o600 on Linux; on Windows its
access list is the owner and SYSTEM. An existing file is never overwritten.
Set [audit].anchor_key_path to the path in Authority's configuration and
back the file up. Without it, anchored events cannot be verified.
| Flag | Default | Purpose |
|---|---|---|
--path <PATH> |
[audit].anchor_key_path |
Where to write the key file. |
--if-missing |
off | Existing key file: reported, exit 3. Without it: refused, exit 1. Nothing is overwritten in either mode. |
gdsgate --config authority.toml authority init-anchor \
--path /var/lib/gdsgate/authority/anchor.key --if-missing
# HMAC anchor key already present at /var/lib/gdsgate/authority/anchor.key, nothing to do
The file is created exclusively. Of two runs racing on one path, the second reports "already present".
When [audit].anchor_key_path names a variable or command in
secret-reference notation, the command
refuses; create that key where it is stored, or pass --path to write a file.
The four commands below take the same two flags and behave the same way.
gdsgate authority init-revocation-key [--path <PATH>] [--if-missing] (deprecated)¶
Generates a 32-byte HMAC revocation-receipt key, as init-anchor does. Set
[audit].revocation_key_path to it; revoke-node then writes a sealed receipt
that a database rollback does not undo.
gdsgate --config authority.toml authority init-revocation-key \
--path /var/lib/gdsgate/authority/revocation.key
gdsgate authority init-approval-key [--path <PATH>] [--if-missing] (deprecated)¶
Generates a 32-byte HMAC key for JIT access requests, as init-anchor does.
Set [audit].approval_key_path to it; approved request rows are then sealed
against direct database edits.
gdsgate --config authority.toml authority init-approval-key \
--path /var/lib/gdsgate/authority/approvals.key
gdsgate authority init-inventory-key [--path <PATH>] [--if-missing] (deprecated)¶
Generates a 32-byte HMAC key for live-discovery inventory receipts, as
init-anchor does. Set [audit].inventory_key_path to it; each published
backend inventory then carries a receipt that detects a direct rewrite of the
row. Needed only with live resource discovery.
gdsgate --config authority.toml authority init-inventory-key \
--path /var/lib/gdsgate/authority/inventory.key
gdsgate authority init-delegation-key [--path <PATH>] [--if-missing] (deprecated)¶
Generates a 32-byte HMAC key that seals each
delegation grant, as init-anchor does. Set
[audit].delegation_key_path to it. Needed only with delegation; unset,
gdsgate delegate is refused.
gdsgate --config authority.toml authority init-delegation-key \
--path /var/lib/gdsgate/authority/delegation.key
gdsgate authority verify-audit [--checkpoint-keys <FILE>] [--keyring <REFERENCE>]¶
Opens the store directly, replays the hash chain, and checks every row in
audit_anchor against audit_log with the anchor key (the audit keyring, or
the deprecated [audit].anchor_key_path). Exits non-zero on any integrity
failure. Run it from a workstation that holds the key, separate from the
Authority host.
gdsgate --config authority.toml authority verify-audit
# audit chain ok (chain + anchors + checkpoints): 12483 rows, 4127 anchors, 61 checkpoints
--checkpoint-keys <FILE> is for an external reviewer. With the file
keys checkpoint-keys printed, the
run replays the chain and checks each signed checkpoint by signature; no
keyring or anchor key is needed. Without the flag, checkpoints are checked
against the public keys of the audit keyring when this host names one.
gdsgate --config reviewer.toml authority verify-audit --checkpoint-keys checkpoint-keys.txt
# audit chain ok (chain + checkpoints): 12483 rows, 0 anchors, 61 checkpoints
--keyring <REFERENCE> is for an operator holding the audit keyring. It takes
the secret-reference notation
of [security.integrity].audit_keyring,
including a wrapped ${kms.<name>+file:PATH} declared in
[[kms_keys]], and overrides that
setting for this run. Anchors are checked against its keys (in place of
[audit].anchor_key_path) and checkpoints against its public keys.
With both flags, --checkpoint-keys checks the checkpoints and --keyring
checks the anchors. Only the audit ring is accepted; a control ring is refused.
After keys accept-loss, an anchor
stretch sealed under the lost key is reported; it does not fail the run:
not verified: serials 4..=9 (6 anchors) were sealed under key 3f9c1a2b4d5e6f708192a3b4c5d6e7f8, declared lost at 1755000000
The summary line comes first; everything outside a reported stretch was verified.
gdsgate authority audit list [filters]¶
Prints audit chain rows, newest first.
| Route | Behavior |
|---|---|
gateway, certificate |
Decided by Cedar viewAudit; the read is audited. |
store |
Opens this host's database directly (needs store_url); no running Authority needed. |
| default | store when store_url is set, else gateway. |
The default output is seven columns (time in UTC, event class, action, code,
actor, target, outcome) with further fields on indented branch lines. --json
prints one object per row with millisecond time. An empty result prints the
header and (none).
gdsgate --config authority.toml authority audit list --class admin-action --limit 3
# # time class action code actor(verified) target outcome
# 2025-07-29 09:20:04 AdminAction catalog.manage CAT-0101 gdsgate-provisioner Database::"db-orders_prod" Success
# |- operation=create resource_kind=postgres
# `- claimed operator (asserted by the caller, unverified): anna.ivanova@provider.example
# 2025-07-29 09:19:50 AdminAction revokeNode gdsgate-admin EnrolledNode::"gdsgate-connector" Success
# 2025-07-29 09:00:01 CaOperation ca.rotate CAS-0101 gdsgate-admin CertificateAuthority::"user-ssh" Allow
The actor comes from a certificate or token. A person is printed by the
display name Authority keeps for the subject (see
display_name_claims); a subject without
one is printed as stored. --json carries both.
CODE is a stable event identifier: SES-0101 is a session opening in every
release. Codes are never reused or renamed. The cell is empty when the writer
records no code and on rows sealed before codes existed.
--node takes the node id that list-nodes,
import-node,
revoke-node and
list-offered use. Rows Authority sealed
for its own events and rows sealed before the node column existed match
nothing.
--work <name> selects rows about one agent: sessions opened to its faces and
sessions it opened. When one agent calls another's face, the row is filed under
the called agent; the caller is in actor. Matches across certificate renewals
and restarts.
An Authority older than --code, --node or --work ignores the filter. The
command then filters the returned rows locally and prints one warning per
filter to stderr (also under --quiet):
--node was not applied by this Authority, which is older than the filter; 2 of the rows it returned did not match and were left out here
Column widths are computed per page, and each page has its own heading.
--route store returns one page.
Branch lines under a row:
| Branch line | Present when |
|---|---|
Detail as key=value pairs, one line |
The row has a detail with fields (a ClientAssertion row lists nine sandbox properties; a database row carries the statement). |
| Claimed operator, marked unverified | A control plane named a person. |
submitted by node: <id> |
The row carries a node. |
on behalf of: <owner> |
The row was made on someone's behalf (display name where known). |
work: <name> |
The row is about an agent-host work. |
Branch lines are dimmed on a colour terminal. Output to a file or pipe, or with
NO_COLOR set, has no escape sequences.
--json carries the detail unescaped as the chain holds it; truncated says
whether the server cut it.
gdsgate authority audit list --limit 200 --json |
jq -r 'select(.detail != "") | [.time_unix_ms, .action, .detail] | @tsv'
In the text listing a branch line is four spaces, `- or |-, then the
field:
Columns are space-padded, not tab-separated; cut -f does not read them.
ClientAssertion holds unverifiable statements a client made about its own
machine, such as the sandbox a delegated run reports.
The actor is verified; the assertion is not.
CaOperation holds CA rotations and their
refusals only. Certificates a CA signs are recorded under the class of what was
signed.
| Flag | Default | Purpose |
|---|---|---|
--class <CLASS> |
every class | One of authentication, authorization, ca-operation, session-activity, admin-action, capability-grant, client-assertion, resource-integrity. Matches the class the record was sealed with; older records keep their original class. Use --code to match across both. |
--actor <ID> |
any | Exact match on the verified actor, or on a display name (matches every subject with that name). |
--action <PREFIX> |
any | Action prefix: enroll., session., policy.. Rows keep the spelling they were sealed with; --code matches across spellings. |
--code <CODE> |
any | Exact match on the event code, for example SES-0101. Rows without a code match nothing. |
--target <ID> |
any | Exact match on the target. |
--node <ID> |
any | Exact match on the enrolled node that delivered the row (list-nodes id). Rows without a node match nothing. |
--since <WHEN> |
no floor | Inclusive floor on event time: an interval back from now (90s, 30m, 2h, 7d) or a date (2026-09-21, midnight UTC). |
--since-ms <MS> |
no floor | The same floor as a Unix-millisecond instant. Conflicts with --since. |
--limit <N> |
50 |
Cap on rows. 0 prints the whole chain (--route store only). Over a network, pages are fetched until the cap is met. |
--json |
off | One JSON object per row. |
Use --json for scripts:
gdsgate --config authority.toml authority audit list --json --limit 1
# {"serial":682,"time_unix_ms":1785974336000,"class":"AdminAction","action":"audit.view",
# "code":"AUD-0101","actor_verified":"anna.ivanova@provider.example",
# "target":"AuditLog::\"chain\"","outcome":"Allow","detail":"{\"page_size\":1}",
# "truncated":false,"submitter":"gdsgate-authority"}
The object is the same on every route.
| Field | Value |
|---|---|
class, outcome |
An unknown code is printed as class(200). |
truncated |
true when the server cut the detail. |
code, submitter_node |
Absent when the row has none. |
submitter_node |
Node that delivered the row; submitter is its role. |
actor_name, owner_name |
Display names of actor_verified and owner; not part of the chain. |
work |
Agent-host work. |
Hashes are not included. The asserted operator name has no filter; filter on
--actor. This command does not verify the chain; use
verify-audit.
gdsgate authority migrate [--check]¶
Brings the store's schema up to date and exits. Opens store_url directly; no
running Authority needed. A second run applies nothing.
gdsgate --config authority.toml authority migrate
# applied 3 migration(s); store schema up to date
gdsgate --config authority.toml authority migrate
# store schema up to date — nothing to apply
| Flag | Default | Purpose |
|---|---|---|
--check |
off | Report only. Exits non-zero if migrations are pending. Run before a rollout. |
gdsgate --config authority.toml authority migrate --check
# store schema has 2 pending migration(s): m0036_owner_revocation, m0037_delegation_grant_root_owner
With store_auto_migrate = false, a service
start does not migrate; run this command as a rollout step.
A store with migrations this binary does not know was migrated by a newer
build. migrate (both modes), a service start and gdsgate doctor refuse it;
run the newer build on that host.
gdsgate authority policy template¶
Prints the starting policy for a new cluster on stdout. Runs offline.
| Principal | Granted |
|---|---|
Group::"gdsgate-admin" (CN of the admin mTLS certificate) |
The six provisioning actions: account lifecycle, CA rotation, the catalogue, join tokens, the node registry. |
Group::"gdsgate-provisioner" (CN=gdsgate-provisioner) |
The catalogue, the registry, join tokens for three named roles. |
Group::"admins" (from the identity provider) |
Listing, sessions, databases, MCP tools, approvals, recordings, policy editing, the catalogue and the registries, through the gateway. |
The template also carries the break-glass rule every later push is checked
against. A second team group, SSH -L / -R and onward certificates are
commented examples.
SSH is granted as connect plus sshProgram (evaluated for every shell,
exec and subsystem from epoch 1). Narrow it
with conditions on the rule (context.program_kind, git fields); the template
shows an example. sshUseClientKey is commented; grant it when a resource with
onward_auth = "client_key" is declared.
Without an external provisioning system, delete the
Group::"gdsgate-provisioner" block.
Rename admins to a group your identity provider emits, set
[policy].path to the file and start Authority.
While the store holds no policy version, the file is seeded as version 1. See
Policy → The starting policy.
gdsgate authority policy validate [<FILE>] [-o <FILE>] [--rule-ids]¶
Strict-validates a Cedar policy against gdsgate's schema and exits non-zero on any error. Runs offline.
gdsgate --config operator-workstation.toml authority policy validate
gdsgate --config operator-workstation.toml authority policy validate ./new-policy.cedar
Without <FILE>, validates [policy].path from the config.
A policy using the vocabulary retired in v0.8.23 (sshConnect, dbConnect,
tcpConnect, k8sAccess, k8sReadOnly, the entity type Server) is migrated
by the cluster on push and on load. This command migrates a copy, reports
policy OK after migration, exits zero, and lists every change and everything
to review. --output <FILE> writes the migrated text.
A rule over TcpService::"<id>" for an MCP backend needs the catalog to
migrate; it is left unchanged and named in the output. The cluster completes it
on push.
Anything else invalid fails.
--rule-ids lists the rules under the names policy.decision records use in
deciding. A rule's name is a digest of its text and does not change when
other statements are added or removed:
policy OK: ./policy.cedar
34 rules, as an audit row names them:
02726249cc2326ce policy0 permit(principal in Group::"gdsgate-break-glass", action == Action::"editPolicy", resource);
48bc8dac906e102c policy1 permit(principal in Group::"gdsgate-admin", action == Action::"enrollNode", resource);
The middle column is the policy0 the record carries in deciding_at. Run the
listing against the policy version the record was decided under (on the chain
under policy.edit). For a migrated document, the migrated rules are listed.
| Flag | Default | Purpose |
|---|---|---|
-o, --output <FILE> |
not set | Write the migrated policy here. Written only when the document needed migrating. |
--rule-ids |
off | Also list every rule under the name its decisions are recorded with. |
gdsgate authority policy pull [-o <FILE>] [--version <N>]¶
Fetches the active policy from the cluster and prints it to stdout, or writes
it to --output <file>. Decided by Cedar editPolicy. When the store has no
policy yet, prints nothing and logs
no policy stored yet (Authority is on the deny-all bootstrap) on stderr.
gdsgate --config client.toml authority policy pull -o policy.cedar
gdsgate --config client.toml authority policy pull --version 8 -o policy-v8.cedar
| Flag | Default | Purpose |
|---|---|---|
-o, --output <FILE> |
not set | Write the policy here. Without it, stdout. |
--version <N> |
active | Fetch this stored version, in any state history lists. A version the store does not hold: no policy version N is stored. |
gdsgate authority policy history [--limit <N>] [--json]¶
Lists the stored policy versions, newest first: the newest 50 unless --limit
says otherwise. Decided by Cedar editPolicy.
gdsgate --config client.toml authority policy history
# version state author time change
# v11 pending 1/2 Anna Orlova 2026-09-28 09:12 UTC v10 +0 −1 ~0
# * v10 active Ivan Petrov 2026-09-27 20:25 UTC v9 +1 −0 ~1
# v9 rejected Anna Orlova 2026-09-27 18:02 UTC v8 +3 −0 ~0
# v8 superseded Ivan Petrov 2026-09-20 11:40 UTC v7 +0 −0 ~2
| Column | Meaning |
|---|---|
| (first) | * on the active version. |
state |
active, pending (with approvals collected and required), superseded (was active), rejected (a proposal dropped before it became active). |
author |
The name the user directory holds for the person who pushed it; else their subject; for a file seed or a break-glass restore, the verified pusher. |
time |
When the version was stored, UTC. |
change |
The version it is measured against, then statements added, removed and changed. ? when either side does not read as statements or its statements are not the policies Cedar reads. |
A version is measured against the active version it was proposed on, not against the number before it: a rejected or pending proposal in between never took effect.
When older versions exist past the limit, a last line says so:
older versions not shown (--limit 50); --limit 0 lists every version. With
--json that line goes to stderr.
| Flag | Default | Purpose |
|---|---|---|
--limit <N> |
50 | The newest N versions. 0 lists every version. |
--json |
off | One JSON object per version: version, active, state, author, author_name, proposer, created_at, base_version, added, removed, changed, approvals, approvals_required. |
gdsgate authority policy diff [<A>] [<B>] [--file <PATH>] [--stat] [--json]¶
Shows what changed between two policy versions, one Cedar statement at a time.
Decided by Cedar editPolicy.
| Arguments | Compared |
|---|---|
| none | The version the active one was based on → the active version. |
<A> |
A → the active version. |
<A> <B> |
A → B. |
--file <PATH> |
The active version → the file. |
policy v8 → v9 · Ivan Petrov · 2026-09-27 20:25 UTC · 2 changes
[1] ~ permit connect Group::"admins" → Database::"pg-demo" line 120
permit(principal in Group::"admins", action == Action::"connect",
resource == Database::"pg-demo")
- when { context.mfa_satisfied };
+ when { context.mfa_satisfied && context.src_ip.isInRange(ip("10.0.0.0/8")) };
[2] + permit sshForwardAgent Group::"acme-admins" → SshHost::"win-ssh" line 390
// Agent forwarding on the Windows test host only, to check that a session
// binds one agent socket however many of its channels ask.
+ permit(principal in Group::"acme-admins", action == Action::"sshForwardAgent",
+ resource == SshHost::"win-ssh");
A statement is one permit or forbid with its annotations and the comment
lines directly above it; a blank line detaches a comment. + added, -
removed, ~ changed, with a line diff inside the statement. line is where
the statement starts in the newer side (the older side for -).
Statements are paired in three passes, in document order:
- The same
@idannotation. - The same text. Layout and comments are not compared.
- The same effect, principal, action and resource scope. Two different
@idvalues never pair.
A pair that differs in text or in its comment lines is ~. Moving a statement
is not a change. Changes are numbered in the order of the newer side; a removed
statement stands where it stood. [n] is the same for the same two versions
every time; it is what revert --only takes.
| Flag | Default | Purpose |
|---|---|---|
--file <PATH> |
not set | Compare the active version with this file. Conflicts with <A> and <B>. |
--stat |
off | Print the header lines only. |
--json |
off | from, to, and changes: number, kind, effect, action, principal, resource, id, line, matched_by, lines (op, text). |
Colour is used only on a terminal.
Control characters in policy text and author names are printed escaped, as
\u{1b}: C0 but TAB, DEL, C1 (U+0080–U+009F) and the bidirectional
embeddings, overrides and isolates (U+202A–U+202E, U+2066–U+2069). TAB is
printed as four spaces. The same applies to history, revert, review and
pending-edits. --json escapes by JSON rules. The stored text is not
changed; policy pull prints it as stored.
| Refused | Condition |
|---|---|
no policy version N is stored |
A or B is not in history. |
no policy is active yet |
No argument, one argument or --file, on a store with no policy. |
does not read as policy statements |
One side has text outside a statement; the message names the line. |
the statement view does not match what Cedar parses; review the raw text |
The statements read from one side are not the policies Cedar reads from it. Read the side with policy pull --version N. |
the document does not parse as Cedar |
One side is not valid Cedar. |
A // comment ends at a line feed or a carriage return, as in Cedar. Every
side is also parsed by Cedar; the statements must be the same policies, in the
same order, with the same text and effect.
gdsgate authority policy revert <N> [--only <LIST>] [--dry-run | --approve-plan <DIGEST>]¶
Undoes the changes version N made, on top of the active version, and
publishes the result as a new version. The active version is edited statement
by statement; an older version is never made active again. Decided by Cedar
editPolicy.
gdsgate --config client.toml authority policy revert 9 --only 2 --dry-run
# reverting change [2] of v8 → v9 on top of active v10
# - permit sshForwardAgent Group::"acme-admins" → SshHost::"win-ssh"
# schema check: ok
# would publish v11 · plan digest 5c1e0b7a90d4e213
gdsgate --config client.toml authority policy revert 9 --only 2 --approve-plan 5c1e0b7a90d4e213
The changes are those of policy diff <base> <N>, <base> being the version
N was based on (the header names it). For each undone change, the statement
N left must be in the active version as N left it, comments included.
Without --approve-plan, the command prints the plan, publishes nothing, and
prints the command that publishes it. The digest covers the active version, the
changes chosen and the resulting text; any of them changing gives a different
digest.
The result is pushed as policy push pushes: validated by the server,
anchored to the active version the plan was computed on, and a pending
proposal when [policy.editor].min_approvers is 2 or more. The policy.edit
audit row carries reverted_from and, with --only, only.
| Flag | Default | Purpose |
|---|---|---|
--only <LIST> |
all | Comma-separated change numbers from policy diff <base> <N>, or @id values. |
--dry-run |
off | Print the plan and publish nothing. |
--approve-plan <DIGEST> |
not set | Publish, if the plan's digest is this one. |
| Refused | Condition |
|---|---|
vN is pending / vN is rejected |
The version never took effect. |
refused: the active version no longer holds what the reverted version left |
A statement to undo was changed or removed since N; each is listed with its number. Nothing is published. |
schema check: failed |
The result does not validate against today's schema, usually an old statement using a retired action. The validator's error follows. |
there is no change [n] / no change carries @id(...) |
--only names a change N did not make. |
this plan is <digest>, not <digest> |
--approve-plan names a different plan. Run --dry-run again. |
base version stale |
Another version became active after the plan was computed. |
gdsgate authority policy review <N> | --pending <N> | --file <PATH>¶
A terminal screen for accepting or rejecting changes one at a time. Needs an
interactive TTY. Reading the versions is decided by Cedar editPolicy.
| Arguments | Changes reviewed |
|---|---|
<N> |
What N changed against the version it was based on, as policy diff <base> <N> numbers them. N is active, superseded or rejected. |
--pending <N> |
What the pending proposal N changes against the version it was proposed on. |
--file <PATH> |
The active version → the file. |
┌ policy review · v8 → v9 · proposed by Ivan Petrov ─────────────────────────────────────┐
│ Changes │ [1] ~ permit connect │
│▸ [1] ✓ ~ connect pg-demo │ Group::"admins" → Database::"pg-demo" │
│ [2] ✗ + sshForwardAgent ...│ in effect in v10 · r undoes it │
│ │ │
│ │ permit(principal in Group::"admins", action == │
│ │ Action::"connect", │
│ │ resource == Database::"pg-demo") │
│ │ - when { context.mfa_satisfied }; │
│ │ + when { context.mfa_satisfied && │
│ │ context.src_ip.isInRange(ip("10.0.0.0/8")) }; │
│────────────────────────────────────────────────────────────────────────────────────────│
│ accepted 1 · rejected 1 · open 0 schema: ok result: 1 change against v10 │
│ a accept r reject u undecide ↑↓ move d full diff Enter publish as v11 q quit │
└────────────────────────────────────────────────────────────────────────────────────────┘
The result is computed on the active version, whatever the source:
| Mark | The active version carries the change | It does not |
|---|---|---|
✓ accepted |
Kept. | Applied. |
✗ rejected |
Undone. | Left out. |
| blank, open | Kept. | Left out. |
A change the active version carries starts ✓; any other starts open. So
review N of the version in effect starts with nothing to publish, and
rejecting changes there is revert --only chosen by hand. review --file
starts open; accepting takes the file's changes over one at a time.
An applied change follows the rules of an undo in reverse. An added statement
goes in after the nearest statement above it that the active version holds. A
statement to change or remove must be in the active version as the old side
had it, comments included. Otherwise the bottom line reads
result: refused: [n] changed since and nothing can be published.
The bottom line counts the marks, gives the strict schema check of the result, and what the result changes against the active version.
Enter shows the plan: what the result changes against the active version,
the schema check, and the version it would become. y publishes it the way
policy push does: validated by the server, anchored to the active version
the screen was computed on, and a pending proposal when
[policy.editor].min_approvers is 2 or more. Any other key goes back. A failed
schema check shows the validator's error and no plan.
The policy.edit audit row of a publish carries:
| Key | When |
|---|---|
reverted_from, only |
review N undid changes of N. only lists them when not all were. |
based_on |
review N applied changes of N, or any publish from --pending N. |
A pending proposal¶
With --pending N the key line reads
A approve as proposed R reject Enter publish my selection as a new proposal.
| Key | Action |
|---|---|
A, then y |
One vote on N as proposed, as approve-edit N casts it. The marks play no part. |
R, then y |
Rejects N as a whole, as reject-edit N does. |
Enter, then y |
Publishes the marked result as a new proposal of yours, with based_on = N. |
A selection is never a vote on N, and N stays pending. The new proposal
needs its own approvals, and its author cannot approve it.
| Key | Action |
|---|---|
a / r / u |
Accept / reject / clear the mark on the selected change. |
↑ ↓ or k j |
Select a change. |
d |
Show every change, or the selected one again. |
PgUp / PgDn |
Scroll the right pane. |
Enter |
Show the plan. |
q / Esc |
Quit, publishing nothing. |
| Refused | Condition |
|---|---|
policy review needs a terminal |
Standard input or output is not a terminal. Use policy diff and policy revert --only. |
vN is pending approval |
review N of a pending proposal. Use --pending N. |
vN is <state>, not a pending proposal |
--pending N of a version that is not pending. |
vN became active after this review was computed |
Another version became active before the publish. Nothing was published; run the review again. |
base version stale |
The same race, caught by the server. |
nothing to publish |
The marks give the active version back. |
does not read as policy statements |
A side does not read as statements, is not valid Cedar, or its statements are not the policies Cedar reads. The screen does not open, so A is not offered; read the proposal with policy pull --version N. |
gdsgate authority policy push <FILE>¶
Strict-validates <FILE> locally, then uploads it as the new active policy.
The server validates again, stores a new version, and reloads it on every
Authority instance without restart. Decided by Cedar editPolicy.
Policy lock-out risk
The policy you push must keep an editPolicy permit for your group, or no
one can push again. Recover on the Authority host through the
emergency administrative socket
if configured (policy push --route emergency, no restart), or through
[policy.editor].break_glass_policy_file,
applied at restart past the editPolicy gate. The [policy] file does not
recover a lock-out: it seeds only a store with no policy version. See
Admin guide → Lock-out and recovery.
gdsgate authority policy edit¶
Opens the active policy in a full-screen terminal editor. The policy is pulled
on entry; Ctrl-S validates it locally and pushes it. Both calls are decided by
Cedar editPolicy. Needs an interactive TTY.
Every keystroke runs the policy validate checks. The status bar shows the
statement under the cursor, the number of lines changed since the pull, the
lint state (✓ or ✗ with the first error), a migration notice, the keybindings
and the last deploy result; the failing line is highlighted red. A document in
the vocabulary retired in v0.8.23 is not an error: the status bar lists what
the migration changes, and Ctrl-S migrates it as push does.
Tab completion depends on the cursor position:
| Position | Candidates |
|---|---|
Inside a Type::"..." literal |
Ids of that type from the schema and live catalog (Action::"<action>", SshHost::"<id>", Database::"<id>"). |
After resource. / principal. / context. |
Attributes declared for that base; resource also offers getTag / hasTag. |
| Elsewhere | Entity types and actions from the schema, Cedar keywords, resource ids from gdsgate ls. |
Typing narrows the popup, Backspace widens it, Enter or Tab accepts, any other key cancels.
Ctrl-L runs cedar-policy-formatter; Ctrl-Z right after undoes the
formatting in one step. Copy and paste use an internal buffer; the system
clipboard is not used. On a successful Ctrl-S the editor exits and prints
policy deployed with the version now active. Ctrl-Q on a modified buffer
asks for confirmation; Y discards.
| Key | Action |
|---|---|
| Type / arrows | Edit and move. |
Home / End |
Start / end of the line. |
PgUp / PgDn |
One screen up / down. |
Ctrl-Home / Ctrl-End |
Start / end of the document. |
| Shift + any of the above | Extend selection. |
Tab |
Open context-aware completion; four spaces where nothing completes. |
Ctrl-A |
Select all. |
Ctrl-C / Ctrl-X / Ctrl-V |
Copy / cut / paste (internal). |
Ctrl-Z / Ctrl-R |
Undo / redo. |
Ctrl-F |
Search; Enter for the next match, Esc to exit. |
Ctrl-L |
Format the buffer. |
Ctrl-O / F2 |
Move the keys to the statement tree and back. |
Ctrl-G |
Go to a statement by number. |
Ctrl-S |
Validate and deploy (push, then hot-reload). |
Ctrl-Q |
Quit; confirms when dirty. |
Statement tree¶
The column on the left lists the statements of the buffer. #n is the
statement's position in the current text, counted from 1 and recounted on
every change; an @id is shown beside it. These numbers are not the [n] of
policy diff, which numbers changes between
two versions.
Groups: by action (a statement with action in [...] is listed under each of
its actions; no action constraint → any action), by principal, by resource,
or file order. forbid statements form a group of their own. ✗ marks a
statement holding a validation error, and a closed group containing one.
While the cursor is in the text, the tree marks the statement under it with
›. While the text does not split into statements (a statement half typed,
an unbalanced parenthesis), the tree keeps the last good split and its title
says stale. A whole statement with a Cedar error stays in the tree, marked
✗.
In the text, a control character takes one cell: ␛ for ESC, ␍ for CR,
␀–␟ for the rest of C0, ␡ for DEL, a space for TAB, � for C1 and
bidirectional controls. The tree and the status bar show them as \u{1b}. The
buffer keeps the characters as they are.
Below 90 columns the tree is hidden; Ctrl-O draws it over the text.
| Key (tree focused) | Action |
|---|---|
↑ / ↓ |
Move. The text scrolls to the selected statement and marks it; the cursor stays. |
→ / ← |
Open / close a group; ← on a statement goes to its group. |
Enter |
Cursor to the statement's first line; keys back to the text. On a group: open / close. |
/ |
Filter by text; Enter keeps the filter, Esc clears it. |
v |
Next grouping. |
Esc, Ctrl-O, F2 |
Back to the text; cursor and scroll as before. |
Other Ctrl keys |
Back to the text, then the key acts there. |
Tab never leaves the text. Ctrl-G opens a go to # field: the text
scrolls to the statement as the number is typed, Enter moves the cursor
there, Esc returns to where it was. A number past the last statement is
reported in the field. The mouse is not used.
gdsgate authority policy pending-edits¶
Lists policy proposals waiting for approval. With
[policy.editor].min_approvers of 2 or more, a push becomes a pending proposal.
Decided by Cedar approvePolicyEdit.
gdsgate authority policy approve-edit <version>¶
Casts one approval on a pending proposal (<version> from pending-edits).
At the configured number of distinct approvers the proposal becomes the active
policy on every Authority instance. Decided by Cedar approvePolicyEdit.
gdsgate authority policy reject-edit <version>¶
Rejects a pending proposal as a whole. It moves to rejected in history and
no later vote activates it. Rejecting a proposal already rejected succeeds and
changes nothing. Decided by Cedar approvePolicyEdit, as a vote is; the
proposer may reject their own proposal if the policy lets them vote. Audited
as policy.edit_approve with operation = reject.
| Refused | Condition |
|---|---|
no such policy proposal |
No version <version> is stored. |
proposal is not PendingApproval |
The version is active or superseded. |
gdsgate authority recordings list [filters]¶
Lists recent session-close events, newest first. Decided by Cedar
viewRecordings.
gdsgate --config client.toml authority recordings list --target prod-db --limit 20
# time kind session actor owner target outcome bytes
# 2026-09-20 14:02:11 ssh 3f7c1c2e-9a11-4a54-8b0e-7c0a9d2e4b31 alice - prod-db ok 48.2K
| Flag | Default | Purpose |
|---|---|---|
--actor <name> |
not set | Filter by the certificate subject that ran the session. |
--target <id> |
not set | Filter by resource id. |
--since <when> |
not set | Only events at or after this time: an interval back from now (90s, 30m, 2h, 7d) or a date (2026-09-21, midnight UTC). |
--since-ms <unix-ms> |
0 |
The same floor as a Unix-millisecond instant. Conflicts with --since. |
--limit <n> |
50 |
Cap the number of rows; 0 uses the server default. |
--wide |
off | Add the uri and stored columns. |
--json |
off | One JSON object per row; the default is the table. |
Columns: time (UTC), kind, session, actor, owner, target, outcome,
bytes. session is the full UUID that
recordings show takes. owner is the
delegation owner of a session run on someone's behalf, else -.
--wide adds uri (the cast URI from the close event, recorded before any
upload) and stored:
stored |
Meaning |
|---|---|
central <time> |
The recording store holds the bytes since <time>. |
connector |
The session recorded data the store does not have. |
- |
No recording. |
gdsgate authority recordings show <session_uid>¶
Prints a finished recording to stdout. A terminal recording is printed as cast
bytes; pipe it into asciinema play -. A web or model exchange is rendered as
requests, responses and what was not recorded; see
Configuration → Recording the exchange.
--raw prints the stored bytes unchanged. Decided by viewRecordings.
SESSION_UID="<session_uid>"
gdsgate --config client.toml authority recordings show "$SESSION_UID" | asciinema play -
gdsgate --config client.toml authority recordings show "$SESSION_UID" # a web or model exchange, rendered
gdsgate --config client.toml authority recordings show "$SESSION_UID" --raw # the artifact's own bytes
gdsgate authority recordings live <session_uid>¶
Tails an in-progress recording, printing asciicast chunks as they arrive.
<session_uid> comes from gdsgate session list.
Interrupt to stop. Decided by viewRecordings.
create-token rotate-ca rotate-onward-ca rotate-host-tls-ca rotate-spiffe-ca rotate-jwt-ca onward-ca-pub onward-ca-pubs policy recordings audit list-nodes list-offered revoke-node delete-node forget-user list-workloads revoke-workload import-node list-serving list-resources set-resource list-scopes set-scope remove-scope list-agents set-agent switch-agent remove-agent remove-resource activate bootstrap-admin bootstrap-provisioner init set-profile init-anchor init-approval-key init-revocation-key init-delegation-key init-inventory-key keys verify-audit migrate
list show live
list
validate pull push edit approve-edit reject-edit pending-edits history diff revert review template