Skip to content

Discovery configuration

The flat configuration hub remains the compatibility route. This page is the canonical source-checked field reference for its family, including nested tables and list rows.

Paths in the tables are relative to [discovery] or the nested array-table heading. Each row gives the serde type and source default or unset meaning. Check parsing, unknown keys, and catalog cross-field rules with gdsgate doctor --config <PATH> --json, then follow the resource catalog lifecycle.

[discovery]

Used by: Authority.

The resource catalog Authority seeds into the store on startup. It powers RBAC-filtered listing (gdsgate ls), attribute and label policies on the Cedar side (live resource metadata is read on every authorisation), and the per-resource JIT approval threshold cascade.

The catalog is not required for the data plane to work: connectors declare what they serve at registration and the Proxy routes by that. It is required for any policy that reads resource.environment or resource.getTag("team").

[discovery] is one of two ways into the catalog; the other is Authority's catalog API, which changes it against a running cluster. Seeding is an upsert: every Authority start writes what the file declares over the stored rows and removes nothing, so a cluster whose catalog is managed through the API keeps [discovery] empty. See Admin guide → Resource catalog lifecycle.

[[discovery.resources]]: declared resources

Key Type Default Purpose
id string unset, required Resource id. Matches what the connector registers and what clients request.
kind string unset, required One of ssh, postgres, mysql, kubernetes, tcp, mcp, llm, web, web-egress. Selects the Cedar entity type (SshHost, Database, KubernetesCluster, TcpService, McpServer, LlmService, WebApp, WebEgress). The catalog has no cockroach resource kind.
project string "default" Owning project (Cedar parent: <entity> in Project::"<project>"). Use it to partition by team or tenant.
min_approvers usize unset Resource-local JIT threshold. Written into the resource's catalog row at every start, which is where the approvals cascade reads its narrowest level from.
hostname string the resource id, when it is a legal name Name clients may address the resource by, carried back in the listing. One DNS label: letters, digits and -, case-insensitive, up to 63 bytes, no - at either end. An id carrying _ or . is not a legal name, so a resource with such an id has none until hostname is set.
aliases array of string [] Further names for the same resource, same rules as hostname.
port u16 the port of the protocol: ssh 22, postgres 5432, mysql 3306, kubernetes 6443, web 80 Port clients connect on. tcp, mcp, llm and web-egress have no well-known port, so those kinds must declare one. web's 80 is the port a browser handed only a name dials on its own, since this hop carries no TLS, and a declared port wins over it. Independent of the connector-side [[connector.backends]] addr, which is never published.
native_name string unset The domain the application itself answers on (app.example.com): a whole name, at least two labels, that gdsgate does not own. web only, and refused on any other kind. Declaring it publishes nothing on its own; it is the fact a resident client needs before it can offer to answer to that name locally, and it answers only where the person running that client has also named it in their own [client].intercept_names. It is a different field from hostname, which is a single label this cluster hands out and resolves inside a client's zone; this is a name somebody else owns, the same authority the connector puts in Host when it forwards ([[connector.backends]] url). See Resident client → An application's own name.
pin_tool_descriptors bool false mcp only. Pins the server's tool descriptors: the first descriptor seen for a tool becomes its baseline, and a tools/call on a tool whose descriptor has changed since is refused until the change is re-approved. Declared here, on the resource, rather than on the connector's backend entry; see Pinning tool descriptors. Set on any other kind, it refuses the start.

The connector backend vocabulary is wider than the catalog wire vocabulary in one deliberate case. A [[connector.backends]] entry may use kind = "cockroach" and dial CockroachDB on port 26257; the catalog has no Cockroach resource variant. Publish that backend as a [[discovery.resources]] row with kind = "postgres" and port = 26257. The Authority kind_to_proto and resource_kind_to_str mappings accept the nine catalog kinds above and map no Cockroach value.

Names are unique across the catalog, native_name included: two resources claiming the same name, declared or defaulted from an id, is refused at startup, as is a tcp, mcp, llm or web-egress resource without a port.

[[discovery.import_rules]]: label and environment rules

Applied in order to every declared resource whose id matches the glob. Environment defaults to dev; the last matching rule wins. Labels merge across matches, with later winning per key.

Key Type Default Purpose
match string unset, required Glob over resource id. Only * is special and it matches any run, including empty.
environment optional string unset, resolved environment dev Environment to set on a match.
labels table of string to string {} Labels merged on a match and exposed to Cedar as resource tags.

Reserved labels

A few label keys are read by gdsgate itself as well as by the Cedar policy. They behave like any other label, merged across matching rules and available to Cedar through resource.getTag("environment"), and a specific value also flips a runtime behaviour.

Key Value Effect
pin_src_ip "true" Every access certificate issued for this resource is bound to the client's TCP peer address at issue time. The connector rejects a session whose actual peer does not match. Any other value, or absence, leaves the certificate un-pinned and usable from any peer.

A full discovery example

[[discovery.resources]]
id            = "web-01"
kind          = "ssh"
project       = "frontend"

[[discovery.resources]]
id            = "web-02_prod"
kind          = "ssh"
project       = "frontend"
min_approvers = 2

[[discovery.resources]]
id            = "db-orders_prod"
kind          = "postgres"
project       = "backend"
min_approvers = 3
hostname      = "orders-db"    # the id itself is not a legal name (`_`)
aliases       = ["orders"]

[[discovery.resources]]
id = "redis-cache_prod"
kind = "tcp"
port = 6379                    # required: tcp has no well-known port

[[discovery.resources]]
id = "tools-mcp"
kind = "mcp"
port = 9000

# Glob rules:
[[discovery.import_rules]]
match       = "*_prod"
environment = "prod"
labels      = { criticality = "high", on_call = "primary" }

[[discovery.import_rules]]
match       = "*_staging"
environment = "staging"
labels      = { criticality = "medium" }

[[discovery.import_rules]]
match  = "web-*"
labels = { team = "frontend", tier = "edge" }

[[discovery.import_rules]]
match  = "db-*"
labels = { team = "backend", owner_squad = "orders" }

# Source-IP pinning for the strictest backends: the issued cert is
# bound to the caller's TCP peer; the connector rejects re-use from any
# other peer.
[[discovery.import_rules]]
match  = "*_prod"
labels = { pin_src_ip = "true" }

The catalog is seeded once at Authority startup; changes need an Authority restart, since there is no hot reload in v1.