Documentation
¶
Overview ¶
Package endpoints registers every built-in endpoint plugin.
An endpoint is a typed network target: hosts (or RDS host / kubernetes server) plus protocol-family connection parameters. The credential-binding lives on the credential block now — each credential declares which endpoint(s) it authenticates against via the framework-level `endpoint = X` (singular) or `endpoints = [X, Y, ...]` (multi) attrs, with an optional `placeholder` for dispatch among multiple credentials at one endpoint.
Per-endpoint plugins live in their own file (https.go, postgres.go, kubernetes.go, clickhouse_https.go, clickhouse_native.go); this file is the cross-cutting helpers they share.
Index ¶
- Constants
- func ClickhouseHTTPSDatabaseFromRequest(req *http.Request) string
- func SetBlobStore(b runtime.BlobStore)
- type ChHello
- type ClickhouseHTTPSEndpoint
- type ClickhouseHTTPSEndpointRuntime
- type ClickhouseNativeEndpoint
- type ClickhouseNativeEndpointRuntime
- type HTTPSEndpoint
- type HTTPSEndpointRuntime
- type KubernetesEndpoint
- type OpenAICodexHTTPSEndpoint
- type OpenAICodexHTTPSEndpointRuntime
- type PostgresEndpoint
- type PostgresEndpointRuntime
- type SSHEndpoint
- type SSHEndpointRuntime
Constants ¶
const CodexJWTKeysKind = "codex_jwt_keys"
CodexJWTKeysKind is the BlobStore namespace for the gateway's minted RSA + ed25519 Agent Identity JWT signing material. Exported so the gateway's legacy-state importer can address the row when migrating on-disk codex_jwt_keys.json into sqlite.
const SSHHostKeyKind = "ssh_host_key"
SSHHostKeyKind is the BlobStore namespace for SSH endpoint host keys. Exported so the gateway's legacy-state importer can address the same rows when migrating on-disk <ca_dir>/ssh/<name>.key files into sqlite on first boot.
Variables ¶
This section is empty.
Functions ¶
func ClickhouseHTTPSDatabaseFromRequest ¶
ClickhouseHTTPSDatabaseFromRequest extracts the agent-declared database from a ClickHouse HTTPS request. ClickHouse accepts the target database two ways: the `database` URL query parameter or the `X-ClickHouse-Database` header; the query parameter takes precedence when both are set, mirroring clickhouse-server's own resolution order. Returns "" when neither is set.
func SetBlobStore ¶
SetBlobStore is part of the clawpatrol plugin API.
Types ¶
type ChHello ¶
type ChHello struct {
ClientName string
VersionMajor int
VersionMinor int
ProtocolRevision int
Database string
Username string
Password string
}
ChHello mirrors the subset of the ClientHello fields the gateway inspects or rewrites: client identification (forwarded as-is), negotiated protocol revision (drives field-set decisions downstream), and (database, username, password) — username and password are swapped for the credential's real values before forwarding.
type ClickhouseHTTPSEndpoint ¶
type ClickhouseHTTPSEndpoint struct {
// Hosts is the set of ClickHouse HTTPS hostnames or host:port pairs
// this endpoint intercepts.
Hosts []string `hcl:"hosts"`
}
ClickhouseHTTPSEndpoint is part of the clawpatrol plugin API.
func (*ClickhouseHTTPSEndpoint) EndpointHosts ¶
func (e *ClickhouseHTTPSEndpoint) EndpointHosts() []string
EndpointHosts is part of the clawpatrol plugin API.
type ClickhouseHTTPSEndpointRuntime ¶
type ClickhouseHTTPSEndpointRuntime struct{}
ClickhouseHTTPSEndpointRuntime is the per-request handler. The HTTPS MITM loop runs the request through this runtime's PlaceholderDetector when an endpoint has a multi-credential dispatch binding.
func (ClickhouseHTTPSEndpointRuntime) DetectPlaceholder ¶
func (ClickhouseHTTPSEndpointRuntime) DetectPlaceholder(req *runtime.Request, candidates []string) string
DetectPlaceholder scans the agent's request for a placeholder substring. ClickHouse HTTPS clients put credentials in the Authorization header (Basic), in the `?user=` / `?password=` query params, or in `X-ClickHouse-User` / `X-ClickHouse-Key` headers. We scan all of them and return the first candidate found.
type ClickhouseNativeEndpoint ¶
type ClickhouseNativeEndpoint struct {
// Hosts is the set of ClickHouse native-protocol hostnames or
// host:port pairs this endpoint intercepts.
Hosts []string `hcl:"hosts"`
// Port is the default upstream port for hosts that omit one.
// Defaults to 9000 without TLS and 9440 with TLS.
Port int `hcl:"port,optional"`
// TLS enables ClickHouse native-over-TLS on the upstream hop.
TLS bool `hcl:"tls,optional"`
// AcceptInvalidCertificate skips upstream certificate validation
// when TLS is enabled.
AcceptInvalidCertificate bool `hcl:"accept_invalid_certificate,optional"`
}
ClickhouseNativeEndpoint addresses one ClickHouse server reachable via the binary native protocol. Operators bind a single clickhouse_credential; the runtime parses the agent's Hello and substitutes the credential's (user, password) where the agent embedded a placeholder.
TLS toggles TLS on both hops: the gateway terminates the agent's TLS using a leaf minted off the gateway CA, parses the Hello in plaintext, then re-wraps to upstream. The wrapped client therefore keeps speaking native-over-TLS exactly as it would against the real cloud ClickHouse — `clawpatrol run` is transparent to its TLS posture. Default false: WG-only deployments where the operator wants plaintext on the inner hop (typical self-hosted ClickHouse on 9000 behind a private network) leave it off.
AcceptInvalidCertificate mirrors clickhouse-client's flag of the same name: when true and tls is on, the gateway skips upstream cert validation. Use for self-hosted ClickHouse fronted by a private CA. Default false keeps full validation against system roots.
func (*ClickhouseNativeEndpoint) ConnRouteHosts ¶
func (e *ClickhouseNativeEndpoint) ConnRouteHosts() []string
ConnRouteHosts mirrors EndpointHosts so every host lands in the gateway's conn-index. Hostname entries reach HandleConn through the VIP path (RequiresVIP=true allocates a per-host VIP); IP-literal entries are skipped by dnsvip — there's no DNS query to intercept — and reach HandleConn through the WG forwarder's direct-IP dispatch, which keys off this index.
func (*ClickhouseNativeEndpoint) EndpointHosts ¶
func (e *ClickhouseNativeEndpoint) EndpointHosts() []string
EndpointHosts returns the endpoint's host:port list, normalized so every entry carries an explicit port. The dnsvip allocator and runtime helpers both consume this; emitting host:port everywhere lets a single endpoint mix bare hostnames and host:port literals in HCL without the plugin or dnsvip having to special-case the "default port" rule.
func (*ClickhouseNativeEndpoint) RequiresVIP ¶
func (e *ClickhouseNativeEndpoint) RequiresVIP() bool
RequiresVIP opts the endpoint into DNS-VIP interception. The wire protocol carries no SNI / Host header, so the gateway can't dispatch on dst IP alone — dnsvip allocates a stable VIP per hostname at policy build, intercepts the agent's DNS query for that hostname, and the WG forwarder routes the resulting traffic to handleVIPConn → this plugin's HandleConn.
type ClickhouseNativeEndpointRuntime ¶
type ClickhouseNativeEndpointRuntime struct{}
ClickhouseNativeEndpointRuntime is the per-connection handler. Stateless — all per-session state lives on ConnHandle. HandleConn is implemented in clickhouse_native_runtime.go.
func (ClickhouseNativeEndpointRuntime) DetectPlaceholder ¶
func (ClickhouseNativeEndpointRuntime) DetectPlaceholder(req *runtime.Request, candidates []string) string
DetectPlaceholder scans the agent's Hello (username + password) for any candidate placeholder substring and returns the first match. The clickhouse_native runtime constructs a partial match.Request whose Meta.Statement carries `username + "\x00" + password` for detector consumption — same shape postgres uses.
func (ClickhouseNativeEndpointRuntime) HandleConn ¶
func (ClickhouseNativeEndpointRuntime) HandleConn(ctx context.Context, ch *runtime.ConnHandle) error
HandleConn services one inbound native-protocol connection.
Flow:
- Read the agent's first packet, parse Hello.
- Resolve the bound credential, fetch its secret. Swap any placeholder substring inside Hello.username / Hello.password.
- Dial upstream (TLS or plain), send the (possibly modified) Hello.
- Emit one ConnEvent describing the session.
- Read the server Hello (forwarded back to the agent), capture the negotiated revision, then run an agent → server pump that decodes every client packet via ch-go / lib/proto: Query packets feed the SQL matcher with the agent's compression preference preserved verbatim, uncompressed Data blocks decode through lib/proto.Block, compressed Data blocks walk the frame chain opaquely with a CityHash-discriminator probe (no LZ4/ZSTD on the path). Cancel/Ping forward as-is. Server → agent stays a pure copy past the Hello.
func (ClickhouseNativeEndpointRuntime) ParseStatement ¶
func (ClickhouseNativeEndpointRuntime) ParseStatement(sql string) (any, bool)
ParseStatement satisfies runtime.SQLParser so the action-fixture loader can populate match.Request.Meta from a raw statement using the same AST extractor live dispatch uses.
type HTTPSEndpoint ¶
type HTTPSEndpoint struct {
// Hosts is the set of HTTPS hostnames or host:port pairs this
// endpoint intercepts.
Hosts []string `hcl:"hosts"`
}
HTTPSEndpoint is part of the clawpatrol plugin API.
func (*HTTPSEndpoint) EndpointHosts ¶
func (e *HTTPSEndpoint) EndpointHosts() []string
EndpointHosts is part of the clawpatrol plugin API.
type HTTPSEndpointRuntime ¶
type HTTPSEndpointRuntime struct{}
HTTPSEndpointRuntime detects placeholders in an HTTP request's Authorization header. Plain-substring scan rather than strict equality because agents send placeholders embedded in `Bearer <PH>` or `Basic <base64(user:<PH>)>` shapes; we only need to recognize that the agent picked one of our placeholders, not parse the auth scheme beyond safe Basic decoding.
func (HTTPSEndpointRuntime) DetectPlaceholder ¶
func (HTTPSEndpointRuntime) DetectPlaceholder(req *runtime.Request, candidates []string) string
DetectPlaceholder is part of the clawpatrol plugin API.
type KubernetesEndpoint ¶
type KubernetesEndpoint struct {
// Hosts is an optional list of Kubernetes API hostnames or
// host:port pairs to intercept.
Hosts []string `hcl:"hosts,optional"`
// Server is the Kubernetes API server URL or host:port used when
// hosts is not set.
Server string `hcl:"server,optional"`
// CACert is the PEM-encoded cluster CA, often loaded with
// `<<file:cluster-ca.pem>>`.
CACert string `hcl:"ca_cert,optional"`
// ClusterName is the EKS cluster name used by aws_credential.
ClusterName string `hcl:"cluster_name,optional"`
// Region is the AWS region used by aws_credential for EKS auth.
Region string `hcl:"region,optional"`
}
KubernetesEndpoint is part of the clawpatrol plugin API.
ClusterName + Region are EKS auth parameters: when the bound credential is `aws_credential`, the gateway presigns an STS GetCallerIdentity URL scoped to (region, cluster_name) and stamps the result as a `k8s-aws-v1.<…>` bearer. Leave both unset for self-hosted clusters with a non-EKS credential (bearer_token, mtls_credential).
func (*KubernetesEndpoint) AWSEKSAuthParams ¶
func (e *KubernetesEndpoint) AWSEKSAuthParams() (cluster, region string)
AWSEKSAuthParams is the contract the aws_credential plugin reads at request time to mint an EKS bearer token. Kept narrow so a future alternative k8s-on-AWS endpoint (e.g. one that wraps a different auth flow) can satisfy the same shape without leaking KubernetesEndpoint internals.
func (*KubernetesEndpoint) ConfigureUpstreamTLS ¶
func (e *KubernetesEndpoint) ConfigureUpstreamTLS(cfg *tls.Config) error
ConfigureUpstreamTLS pins cfg.RootCAs to the cluster CA when the endpoint declares one. EKS apiservers present a per-cluster CA that the system trust store can't validate; the operator inlines it via `ca_cert = <<file:cluster-ca.pem>>` (or the base64 from `aws eks describe-cluster`). Self-hosted clusters whose mtls_credential already supplies a `ca` slot leave this empty — the credential's ConfigureUpstreamTLS runs next and wins.
func (*KubernetesEndpoint) EndpointHosts ¶
func (e *KubernetesEndpoint) EndpointHosts() []string
EndpointHosts is part of the clawpatrol plugin API.
func (*KubernetesEndpoint) FileIncludeFields ¶
func (e *KubernetesEndpoint) FileIncludeFields() []config.FileIncludeField
FileIncludeFields tells the loader to inline `<<file:NAME>>` markers in ca_cert. Self-hosted clusters reference the cluster CA via filename so cert material stays out of the policy file.
type OpenAICodexHTTPSEndpoint ¶
type OpenAICodexHTTPSEndpoint struct {
// Hosts is the chatgpt.com host list intercepted for Codex
// subscription-auth traffic.
Hosts []string `hcl:"hosts"`
}
OpenAICodexHTTPSEndpoint is part of the clawpatrol plugin API.
func (*OpenAICodexHTTPSEndpoint) EndpointHosts ¶
func (e *OpenAICodexHTTPSEndpoint) EndpointHosts() []string
EndpointHosts is part of the clawpatrol plugin API. It always includes codexAuthAPIHost so the codex >= 0.142 registration host is MITM'd without an HCL edit on upgrade.
func (*OpenAICodexHTTPSEndpoint) EnvVars ¶
func (e *OpenAICodexHTTPSEndpoint) EnvVars() []config.EnvVar
EnvVars pushes down a synthetic CODEX_ACCESS_TOKEN so codex enters AgentIdentity mode (which routes it to chatgpt.com). Also pushes CODEX_AGENT_IDENTITY for codex <= 0.128, which read the same JWT from the older env-var name.
CODEX_AGENT_IDENTITY_AUTHAPI_BASE_URL is load-bearing only for codex <= 0.141: it redirected per-task registration onto a host clawpatrol MITMs (chatgpt.com/backend-api/wham). codex 0.142 REMOVED this override and hardcoded registration to auth.openai.com, so for 0.142+ the gateway instead MITMs auth.openai.com directly (see codexAuthAPIHost / RespondHTTP). The var is kept here because it's harmless on 0.142 (ignored) and still required on <= 0.141. We do NOT push the 0.142 replacement CODEX_AUTHAPI_BASE_URL: codex host-restricts it to OpenAI hosts, so it can't be pointed at a clawpatrol host anyway.
type OpenAICodexHTTPSEndpointRuntime ¶
type OpenAICodexHTTPSEndpointRuntime struct{}
OpenAICodexHTTPSEndpointRuntime is part of the clawpatrol plugin API.
func (OpenAICodexHTTPSEndpointRuntime) DetectPlaceholder ¶
func (OpenAICodexHTTPSEndpointRuntime) DetectPlaceholder(req *runtime.Request, candidates []string) string
DetectPlaceholder mirrors the default https endpoint — agent placeholders show up in the Authorization header, possibly wrapped as `Bearer <PH>` or `AgentAssertion <PH>`.
func (OpenAICodexHTTPSEndpointRuntime) RespondHTTP ¶
func (OpenAICodexHTTPSEndpointRuntime) RespondHTTP(_ context.Context, req *http.Request) (*http.Response, bool, error)
RespondHTTP intercepts the two paths codex hits during Agent Identity load: the JWKS that validates the JWT we minted, and the agent-task registration POST that returns a task_id. Both are served from clawpatrol-controlled state — neither reaches real OpenAI. The JWKS is on chatgpt.com; the registration host moved to auth.openai.com in codex 0.142 (was chatgpt.com/backend-api/wham on <= 0.141), so the register matcher is deliberately host-agnostic and keys on the stable path shape. This is safe because this endpoint only owns chatgpt.com + auth.openai.com (see EndpointHosts); every other auth.openai.com path (codex login, token refresh) falls through to be forwarded untouched.
type PostgresEndpoint ¶
type PostgresEndpoint struct {
// Host is the upstream Postgres host:port pair.
Host string `hcl:"host"`
// SSLMode controls upstream TLS negotiation. Valid values mirror
// libpq: "disable", "prefer", "require", and "verify-full".
SSLMode string `hcl:"sslmode,optional"`
}
PostgresEndpoint addresses a single RDS-or-equivalent server. Tunnel topologies (kubectl-portforward-ssh and friends) aren't supported in this iteration — operators run the gateway with network reachability already arranged.
SSLMode mirrors libpq's sslmode names — "disable" / "prefer" / "require" / "verify-full". Default "prefer": try TLS, fall back to plain when the upstream replies 'N'. "require" hard-fails on 'N'. "verify-full" additionally validates the upstream cert against Host. "disable" skips the SSLRequest probe entirely — fine for self-hosted pg on a private network where WG already encrypts the path.
func (*PostgresEndpoint) ConnRouteHosts ¶
func (e *PostgresEndpoint) ConnRouteHosts() []string
ConnRouteHosts implements runtime.ConnRouter — postgres traffic arrives at the WG forwarder as raw conns (no SNI), so the gateway indexes the upstream host:port → endpoint at policy-load time. The compile pass skips this entry for tunneled endpoints: those route through the VIP path, not real-IP dispatch.
func (*PostgresEndpoint) EndpointHosts ¶
func (e *PostgresEndpoint) EndpointHosts() []string
EndpointHosts is part of the clawpatrol plugin API.
func (*PostgresEndpoint) RequiresVIP ¶
func (e *PostgresEndpoint) RequiresVIP() bool
RequiresVIP opts the endpoint into DNS-VIP interception. Without a VIP, clients in Tailscale exit-node mode can't reach postgres: the real upstream IP is often RFC1918, and Tailscale exit-nodes do not forward RFC1918 traffic (only public IPs and the tailnet's own fd78::/64 VIP range reach the gateway's iptables REDIRECT). The VIP guarantees a routable address (IPv6 fd78::N) regardless of the real upstream IP family or prefix.
type PostgresEndpointRuntime ¶
type PostgresEndpointRuntime struct{}
PostgresEndpointRuntime detects placeholders in a postgres StartupMessage. The wire-protocol front-end populates Request with a SQL meta whose Statement field carries the agent's submitted password verbatim before injection.
func (PostgresEndpointRuntime) DetectPlaceholder ¶
func (PostgresEndpointRuntime) DetectPlaceholder(req *runtime.Request, candidates []string) string
DetectPlaceholder is part of the clawpatrol plugin API.
func (PostgresEndpointRuntime) HandleConn ¶
func (PostgresEndpointRuntime) HandleConn(ctx context.Context, ch *runtime.ConnHandle) error
HandleConn is the postgres ConnEndpointRuntime entry point. One call per inbound TCP connection; returns when either side closes.
Flow:
- SSLRequest from agent → reply 'N' (refuse TLS, WG already encrypts).
- Read agent's StartupMessage; extract `database` for upstream.
- Resolve credential, get (user, password) via PostgresAuthCredential.
- Dial upstream, send our own StartupMessage(real_user, database).
- Drive upstream auth (SCRAM-SHA-256 or cleartext) using real password. Buffer post-auth frames (ParameterStatus*, BackendKeyData, ReadyForQuery).
- Synthesize AuthenticationOk to agent + replay buffered post-auth frames so agent proceeds as if it just authed.
- Bidirectional pump with per-query inspection.
func (PostgresEndpointRuntime) ParseStatement ¶
func (PostgresEndpointRuntime) ParseStatement(sql string) (any, bool)
ParseStatement satisfies runtime.SQLParser so the action-fixture loader can populate match.Request.Meta from a raw statement using the same AST extractor pgEvaluate uses on live wire traffic. Returns *sqlfacet.Meta as `any` to keep the runtime interface free of the facets-package import. The bool mirrors the Unparseable contract — true when pgplex refused the bytes; the fixture loader then sets match.Request.Unparseable so the unevaluable fail-close evaluates the test request the same way live dispatch would.
type SSHEndpoint ¶
type SSHEndpoint struct {
// Hosts is the set of SSH host:port pairs this endpoint intercepts.
Hosts []string `hcl:"hosts"`
}
SSHEndpoint binds one or more host:port tuples. The credentials that authenticate against it live on credential blocks via the framework-level `endpoint = X` / `endpoints = [...]` binding. When a profile wields more than one SSH credential at the endpoint, each ambiguous credential carries a `user = "..."` disambiguator — either on its profile-inline entry (`{ credential = X, user = "..." }`) or on the credential block itself — and the agent's wire-protocol username picks the matching entry. The agent's username is also passed through verbatim as the upstream SSH user; credentials carry only auth material (key / password / host_pubkey), never a username override.
func (*SSHEndpoint) ConnRouteHosts ¶
func (e *SSHEndpoint) ConnRouteHosts() []string
ConnRouteHosts implements runtime.ConnRouter — gives the gateway's IP-keyed dispatch index a chance to route direct-IP dialers (an agent that bypasses DNS) back to the same endpoint. The VIP path in dnsvip is the primary route; this is the safety net.
func (*SSHEndpoint) EndpointHosts ¶
func (e *SSHEndpoint) EndpointHosts() []string
EndpointHosts is part of the clawpatrol plugin API.
func (*SSHEndpoint) RequiresVIP ¶
func (e *SSHEndpoint) RequiresVIP() bool
RequiresVIP marks the endpoint as needing a DNS-MitM virtual IP. SSH always returns true: the wire protocol can't be disambiguated at TCP accept time, so even a single SSH endpoint benefits from a dedicated VIP (avoids ambiguity if the operator later adds a second one behind the same upstream IP).
type SSHEndpointRuntime ¶
type SSHEndpointRuntime struct {
// contains filtered or unexported fields
}
SSHEndpointRuntime is stateful only in the host-key cache: each endpoint's persisted ed25519 key is parsed once and reused for the lifetime of the process. The runtime struct itself is shared across all SSH endpoints — config dispatch picks the right endpoint via ch.Endpoint.
func (*SSHEndpointRuntime) HandleConn ¶
func (rt *SSHEndpointRuntime) HandleConn(ctx context.Context, ch *runtime.ConnHandle) error
HandleConn is part of the clawpatrol plugin API.