The service authenticates every VGI HTTP request and binds persistent-transport identity once at its cryptographic connection handshake. Session handles are opaque capabilities but are also bound to the authentication domain and principal that created them.
Two authentication modes are available and are mutually exclusive:
- Static bearer tokens are intended for local development and controlled service-to-service deployments. They have no expiry or rotation protocol.
- JWT bearer tokens are signature-checked against a configured HTTPS JWKS URL.
Issuer, audience, expiry, not-before, and a nonblank principal claim are
validated by VGI-RPC. Unknown key IDs cause a guarded JWKS refresh.
[auth.oauth]additionally publishes RFC 9728 protected resource metadata (the issuer and a public client ID) so clients can obtain such tokens with OAuth PKCE; it never changes which tokens are accepted. An optionalclient_secretis published to every client, so configure one only for identity providers that require it for public clients.
When authentication is required, the HTTP listener rejects a request without
an accepted credential with 401 Unauthorized and, with [auth.oauth], a
WWW-Authenticate challenge naming the metadata. The driver refreshes an
expired token from grainlift.auth.oauth_refresh_token and retries the call
once; refresh tokens and client secrets are sent only to HTTPS (or loopback)
token endpoints and are never forwarded to downstream drivers.
The current server executable serves plaintext HTTP. Its default configuration
therefore refuses to bind outside loopback. Production deployments must put a
TLS-terminating reverse proxy, sidecar, or private authenticated service mesh in
front of the loopback listener. allow_insecure_remote = true only disables
that startup check; it does not enable encryption and must not be used on an
untrusted network.
Plain tcp:// carries neither encryption nor authentication. It is refused on
non-loopback interfaces unless explicitly acknowledged and cannot be enabled
when authentication is required. tls+tcp:// uses mandatory client
certificates: the server verifies the certificate chain and a strict
X.509-SVID URI SAN, then binds the SPIFFE identity to every call on that
connection. Client certificate, key, CA, and verified server name are explicit
driver options; there is no certificate-verification bypass.
The native driver may retain one idle TCP/mTLS result socket per ADBC connection. This pool is private to that connection, never shared across targets, principals, credentials or other ADBC connections. Only complete, clean streams with a reusable transport enter the pool; partial reads and failed or timed-out streams close their sockets. Checkout verifies the idle connection through a read-only framework handshake. A failed probe opens a fresh authenticated socket without replaying a query. Existing verified TLS connections retain their negotiated identity; apply new credentials or trust settings by opening a new ADBC connection and closing the old handles.
VGI namespaces a verified SPIFFE workload into a collision-resistant
application principal. For example, spiffe://prod.example.org/worker in the
prod.example.org trust domain becomes
peer/spiffe/spiffe%3A%2F%2Fprod.example.org/spiffe%3A%2F%2Fprod.example.org%2Fworker.
Use that canonical value in auth.target_permissions; the original SPIFFE ID
is also retained as an authenticated claim for policy and telemetry.
Raw iroh:// uses Iroh's cryptographic endpoint identity and long-lived QUIC
streams, not HTTP. A production configuration must keep its Iroh secret key in
a secret store so the endpoint ID remains stable, and must map allowed client
endpoint IDs to principals in iroh.principals. Endpoint-key possession is
not, by itself, organizational membership. The optional endpoint information
file contains only public discovery information.
Clients can use grainlift.iroh.secret_key_file to keep private key material
out of SQL and secret definitions. The driver reads this local file once per
new ADBC connection and retains the parsed identity for all of that connection's
streams and cancellation requests. Changing the file affects future connections,
not existing sessions. The option is exclusive with grainlift.iroh.secret_key.
Reads are limited to 256 bytes; regular files, private Unix permissions, and
valid key encoding are required, and symlinks are rejected. On Windows restrict
the containing directory's ACL to the client account. Errors omit file contents
and paths. Neither option is forwarded to the downstream database driver.
iroh.public_targets = ["sqlite"] explicitly shares named targets with every
cryptographically verified Iroh peer. The list must contain existing target
names; wildcards are rejected. It defaults to empty, which retains the
authenticated listener's endpoint allowlist. An unlisted endpoint uses its
verified 64-character hexadecimal key as principal in the separate iroh-key
authentication domain. This avoids collisions with named iroh principals
and preserves per-key quotas and ownership of sessions, transactions,
statements, results, and partition tokens. No registration or client-supplied
principal is involved. Shared database contents remain visible according to
the downstream database's transaction rules.
Unlisted peers can access only public_targets, even if the general target
permission map is empty or contains a matching principal name. Named peers
retain their normal target permissions plus these shared targets. The grants
are stored in the server's admitted Iroh connection registry, not request
metadata or credential claims; HTTP, TCP, and mTLS do not inherit them. The
target set is shared across connections and connection records remain bounded
by listener admission. Closed connections lose both their grants and sessions.
Keep server.require_authentication = true; the existing HTTP listener still
requires its own bearer or JWT configuration.
Public-target access grants the SQL capabilities of the downstream target, including writes when enabled there. Per-key quotas distinguish clients, but one caller can generate many keys: global connection/session limits still bound the server, while per-key limits are not a per-person abuse limit.
The Iroh listener assigns a server-generated connection identifier after authentication and admission. Sessions opened on that physical QUIC connection are revoked when it disconnects or the listener shuts down, including sessions opened concurrently with disconnect. The identifier is kept in server-owned authentication context, never accepted from request metadata. Cleanup is connection-specific: another connection with the same authenticated endpoint or principal remains valid. Individual logical-stream closure does not trigger connection-wide cleanup. The connection registry is bounded by Iroh admission; closed connection identifiers are removed without retaining tombstones.
Revocation is immediate once the transport detects loss, but native cleanup runs outside the listener and registry locks. Downstream cancellation is best effort, and a stalled native operation can retain resources until it returns or its worker process is terminated. A silent network failure still takes time for QUIC to detect. The configurable idle lease remains a fallback; it is not an additional delay after detected Iroh disconnection. HTTP, TCP, and mTLS session expiry and explicit-close behavior are unchanged.
auth.target_permissions is a principal-to-target allowlist. If the map is
empty, every authenticated principal can use every configured target. Once it
contains an entry, unlisted principals are denied all targets. A literal *
target grants all targets. Session ownership checks continue to prevent one
authorized principal from using another principal's connection handles.
The server enforces independent limits on each decoded HTTP request, the
cumulative native bind stream, request duration, global sessions, sessions per
principal, and statements and results per session. server.max_bind_bytes
defaults to 64 MiB; the client has a separate grainlift.max_bind_bytes
defence. Bind batches are acknowledged one turn at a time and staged in an
anonymous file, so raising the cumulative limit does not require buffering the
whole stream in memory. The HTTP request budget remains a per-batch limit.
Opening calls reserve quota before loading a downstream connection, so
concurrent opens cannot exceed the configured bounds. A background reaper
removes expired idle leases but does not reap a session held by an in-flight
operation. Graceful SIGTERM/Ctrl-C shutdown detaches and cancels all sessions,
then drains listeners for at most server.shutdown_grace_seconds before
detaching remaining work.
A client transport timeout, VGI stream cancellation, and the server's
driver_operation_timeout_seconds deadline are distinct. Keep the driver
deadline below request_timeout_seconds when clients should receive a
structured ADBC TIMEOUT. Downstream work runs on a bounded per-session actor;
expiration does not block the transport worker. Best-effort
statement and connection cancellation bypass the actor through independent
ADBC cancel handles. A driver may ignore cancellation, so hard termination of
a stuck FFI call still requires a process boundary. The production isolation
profile is one proxy worker process per failure domain (driver/tenant), with
the supervisor enforcing its own kill deadline; process death invalidates the
worker's stateful sessions and transactions.
Unauthenticated GET /healthz and GET /readyz probes return 204 after the
process and complete RPC/authentication configuration have initialized. VGI's
equivalent GET /health endpoint remains enabled. Readiness is process-level;
it deliberately does not open every downstream database on each probe.
Database and connection options configured on a target are server-controlled. Client attempts to supply those keys at connection creation or mutate a server-controlled connection key later are rejected without logging the value. Other client options must be named in the corresponding per-target allowlist, or the target must explicitly enable the broad allow flag. Do not place bearer tokens or downstream passwords in a committed TOML file; inject the runtime configuration through a secret-managed deployment mechanism.
Allowing the standard database uri option lets the principal choose a
downstream network destination. Treat that as outbound-network authority: use
it only for trusted principals and combine it with worker-level egress policy
when the proxy must not reach arbitrary hosts.