endpoints

package
v0.5.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 47 Imported by: 0

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

View Source
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.

View Source
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

func ClickhouseHTTPSDatabaseFromRequest(req *http.Request) string

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

func SetBlobStore(b runtime.BlobStore)

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

HandleConn services one inbound native-protocol connection.

Flow:

  1. Read the agent's first packet, parse Hello.
  2. Resolve the bound credential, fetch its secret. Swap any placeholder substring inside Hello.username / Hello.password.
  3. Dial upstream (TLS or plain), send the (possibly modified) Hello.
  4. Emit one ConnEvent describing the session.
  5. 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

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

HandleConn is the postgres ConnEndpointRuntime entry point. One call per inbound TCP connection; returns when either side closes.

Flow:

  1. SSLRequest from agent → reply 'N' (refuse TLS, WG already encrypts).
  2. Read agent's StartupMessage; extract `database` for upstream.
  3. Resolve credential, get (user, password) via PostgresAuthCredential.
  4. Dial upstream, send our own StartupMessage(real_user, database).
  5. Drive upstream auth (SCRAM-SHA-256 or cleartext) using real password. Buffer post-auth frames (ParameterStatus*, BackendKeyData, ReadyForQuery).
  6. Synthesize AuthenticationOk to agent + replay buffered post-auth frames so agent proceeds as if it just authed.
  7. 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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL