Conversation
Plugins can now take part in client authentication. A plugin implements Plugin::authenticate, receives the user, database, credential, client address and TLS details as borrowed strings, and returns Skip, Allow or Deny. Allow can carry a derived user, a server_role to assume on the backend, backend credentials, a read-only flag and a provision flag. No ownership crosses the FFI boundary: the owned decision stays on the plugin's stack and each string field is streamed to the host through a sink callback as a borrowed PdStr while the call is in progress, so there is nothing to free on either side. A panic inside the plugin's authenticate is caught on the plugin side and turned into a Deny, so it never unwinds into the host as a foreign exception. The hook is a new slot appended to PluginVtable, which is an ABI change, so pgdog-plugin moves from 0.4.0 to 0.5.0. The loader compares major.minor, so plugins built against 0.4 are skipped with a warning rather than loaded against a vtable they do not match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Store loaded plugins in an IndexMap so they are consulted in the order they appear in pgdog.toml. HashMap iteration order was arbitrary, which made "first plugin to answer wins" nondeterministic for router plugins and would make ordering authentication plugins impossible. Keep one Option<Library> slot per configured plugin instead of dropping failed dlopen results from the vector. Previously a plugin that failed to load shifted every later library down one index, so the next plugin name was paired with the wrong library. Also drop the unwrap on LIBS right after setting it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This adds a client authentication hook to the pgdog-plugin ABI. It is the first PR of the auth plugin work discussed in the RFC (https://gist.github.com/ziomarco/ad18e5c5f65472f13671958514b30c0d) and it only contains the ABI and loader groundwork. The
auth_type = "plugin"host wiring, the per-userserver_roleand the Google plugin come in follow-up PRs.A plugin implements
Plugin::authenticate(AuthContext) -> AuthDecision. The context carries the startup packet user and database, the credential the client presented, the client address, the TLS identity and whether TLS is in use, all as borrowedPdStrvalues valid for the call. The decision isSkip(not my credential, ask the next plugin),Allow(AuthGrant)orDeny(reason). The grant can name a derived user, aserver_roleto assume on the backend, backend credentials, a read-only flag and a provision flag. The default implementation returnsSkip, so every existing plugin keeps compiling and behaving as before.Nothing owned crosses the FFI boundary. The plugin keeps its decision on its own stack and the generated bridge streams each string to the host through a sink callback, so there is no allocator handshake and nothing to free on either side. A panic inside
authenticateis caught on the plugin side and turned into aDenyrather than unwinding into PgDog.Because this appends a slot to
PluginVtable, pgdog-plugin goes from 0.4.0 to 0.5.0 and plugins built against 0.4 are skipped at load time with a warning, as the existing major.minor gate already does. The in-tree plugins and the integration test plugins build unchanged.The hook documents its own contract: PgDog calls it from the blocking pool, so it may run concurrently on several threads, blocking I/O is expected and wants its own timeout, and a panic becomes a
Deny. The host callback the bridge passes in must not panic, which is documented where it is implemented.AuthOutcome::read_only_flag()decodes the POD read-only code (with named constants instead of0/1/2literals) so the host does not re-implement the match, and unknown codes decode to "unset".On the PgDog side the loader now keeps plugins in
[[plugins]]order (anIndexMap), so "first plugin to answer wins" is deterministic. It also keeps one slot per configured plugin instead of compacting the library vector, which used to pair the wrong library with a plugin name whenever an earlier plugin failed todlopen.🤖 Generated with Claude Code