diff options
Diffstat (limited to 'doc/src/sgml/oauth-validators.sgml')
-rw-r--r-- | doc/src/sgml/oauth-validators.sgml | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/doc/src/sgml/oauth-validators.sgml b/doc/src/sgml/oauth-validators.sgml new file mode 100644 index 00000000000..356f11d3bd8 --- /dev/null +++ b/doc/src/sgml/oauth-validators.sgml @@ -0,0 +1,414 @@ +<!-- doc/src/sgml/oauth-validators.sgml --> + +<chapter id="oauth-validators"> + <title>OAuth Validator Modules</title> + <indexterm zone="oauth-validators"> + <primary>OAuth Validators</primary> + </indexterm> + <para> + <productname>PostgreSQL</productname> provides infrastructure for creating + custom modules to perform server-side validation of OAuth bearer tokens. + Because OAuth implementations vary so wildly, and bearer token validation is + heavily dependent on the issuing party, the server cannot check the token + itself; validator modules provide the integration layer between the server + and the OAuth provider in use. + </para> + <para> + OAuth validator modules must at least consist of an initialization function + (see <xref linkend="oauth-validator-init"/>) and the required callback for + performing validation (see <xref linkend="oauth-validator-callback-validate"/>). + </para> + <warning> + <para> + Since a misbehaving validator might let unauthorized users into the database, + correct implementation is crucial for server safety. See + <xref linkend="oauth-validator-design"/> for design considerations. + </para> + </warning> + + <sect1 id="oauth-validator-design"> + <title>Safely Designing a Validator Module</title> + <warning> + <para> + Read and understand the entirety of this section before implementing a + validator module. A malfunctioning validator is potentially worse than no + authentication at all, both because of the false sense of security it + provides, and because it may contribute to attacks against other pieces of + an OAuth ecosystem. + </para> + </warning> + + <sect2 id="oauth-validator-design-responsibilities"> + <title>Validator Responsibilities</title> + <para> + Although different modules may take very different approaches to token + validation, implementations generally need to perform three separate + actions: + </para> + <variablelist> + <varlistentry> + <term>Validate the Token</term> + <listitem> + <para> + The validator must first ensure that the presented token is in fact a + valid Bearer token for use in client authentication. The correct way to + do this depends on the provider, but it generally involves either + cryptographic operations to prove that the token was created by a trusted + party (offline validation), or the presentation of the token to that + trusted party so that it can perform validation for you (online + validation). + </para> + <para> + Online validation, usually implemented via + <ulink url="https://datatracker.ietf.org/doc/html/rfc7662">OAuth Token + Introspection</ulink>, requires fewer steps of a validator module and + allows central revocation of a token in the event that it is stolen + or misissued. However, it does require the module to make at least one + network call per authentication attempt (all of which must complete + within the configured <xref linkend="guc-authentication-timeout"/>). + Additionally, your provider may not provide introspection endpoints for + use by external resource servers. + </para> + <para> + Offline validation is much more involved, typically requiring a validator + to maintain a list of trusted signing keys for a provider and then + check the token's cryptographic signature along with its contents. + Implementations must follow the provider's instructions to the letter, + including any verification of issuer ("where is this token from?"), + audience ("who is this token for?"), and validity period ("when can this + token be used?"). Since there is no communication between the module and + the provider, tokens cannot be centrally revoked using this method; + offline validator implementations may wish to place restrictions on the + maximum length of a token's validity period. + </para> + <para> + If the token cannot be validated, the module should immediately fail. + Further authentication/authorization is pointless if the bearer token + wasn't issued by a trusted party. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>Authorize the Client</term> + <listitem> + <para> + Next the validator must ensure that the end user has given the client + permission to access the server on their behalf. This generally involves + checking the scopes that have been assigned to the token, to make sure + that they cover database access for the current HBA parameters. + </para> + <para> + The purpose of this step is to prevent an OAuth client from obtaining a + token under false pretenses. If the validator requires all tokens to + carry scopes that cover database access, the provider should then loudly + prompt the user to grant that access during the flow. This gives them the + opportunity to reject the request if the client isn't supposed to be + using their credentials to connect to databases. + </para> + <para> + While it is possible to establish client authorization without explicit + scopes by using out-of-band knowledge of the deployed architecture, doing + so removes the user from the loop, which prevents them from catching + deployment mistakes and allows any such mistakes to be exploited + silently. Access to the database must be tightly restricted to only + trusted clients + <footnote> + <para> + That is, "trusted" in the sense that the OAuth client and the + <productname>PostgreSQL</productname> server are controlled by the same + entity. Notably, the Device Authorization client flow supported by + libpq does not usually meet this bar, since it's designed for use by + public/untrusted clients. + </para> + </footnote> + if users are not prompted for additional scopes. + </para> + <para> + Even if authorization fails, a module may choose to continue to pull + authentication information from the token for use in auditing and + debugging. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>Authenticate the End User</term> + <listitem> + <para> + Finally, the validator should determine a user identifier for the token, + either by asking the provider for this information or by extracting it + from the token itself, and return that identifier to the server (which + will then make a final authorization decision using the HBA + configuration). This identifier will be available within the session via + <link linkend="functions-info-session-table"><function>system_user</function></link> + and recorded in the server logs if <xref linkend="guc-log-connections"/> + is enabled. + </para> + <para> + Different providers may record a variety of different authentication + information for an end user, typically referred to as + <emphasis>claims</emphasis>. Providers usually document which of these + claims are trustworthy enough to use for authorization decisions and + which are not. (For instance, it would probably not be wise to use an + end user's full name as the identifier for authentication, since many + providers allow users to change their display names arbitrarily.) + Ultimately, the choice of which claim (or combination of claims) to use + comes down to the provider implementation and application requirements. + </para> + <para> + Note that anonymous/pseudonymous login is possible as well, by enabling + usermap delegation; see + <xref linkend="oauth-validator-design-usermap-delegation"/>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="oauth-validator-design-guidelines"> + <title>General Coding Guidelines</title> + <para> + Developers should keep the following in mind when implementing token + validation: + </para> + <variablelist> + <varlistentry> + <term>Token Confidentiality</term> + <listitem> + <para> + Modules should not write tokens, or pieces of tokens, into the server + log. This is true even if the module considers the token invalid; an + attacker who confuses a client into communicating with the wrong provider + should not be able to retrieve that (otherwise valid) token from the + disk. + </para> + <para> + Implementations that send tokens over the network (for example, to + perform online token validation with a provider) must authenticate the + peer and ensure that strong transport security is in use. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>Logging</term> + <listitem> + <para> + Modules may use the same <link linkend="error-message-reporting">logging + facilities</link> as standard extensions; however, the rules for emitting + log entries to the client are subtly different during the authentication + phase of the connection. Generally speaking, modules should log + verification problems at the <symbol>COMMERROR</symbol> level and return + normally, instead of using <symbol>ERROR</symbol>/<symbol>FATAL</symbol> + to unwind the stack, to avoid leaking information to unauthenticated + clients. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>Interruptibility</term> + <listitem> + <para> + Modules must remain interruptible by signals so that the server can + correctly handle authentication timeouts and shutdown signals from + <application>pg_ctl</application>. For example, a module receiving + <symbol>EINTR</symbol>/<symbol>EAGAIN</symbol> from a blocking call + should call <function>CHECK_FOR_INTERRUPTS()</function> before retrying. + The same should be done during any long-running loops. Failure to follow + this guidance may result in unresponsive backend sessions. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>Testing</term> + <listitem> + <para> + The breadth of testing an OAuth system is well beyond the scope of this + documentation, but at minimum, negative testing should be considered + mandatory. It's trivial to design a module that lets authorized users in; + the whole point of the system is to keep unauthorized users out. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>Documentation</term> + <listitem> + <para> + Validator implementations should document the contents and format of the + authenticated ID that is reported to the server for each end user, since + DBAs may need to use this information to construct pg_ident maps. (For + instance, is it an email address? an organizational ID number? a UUID?) + They should also document whether or not it is safe to use the module in + <symbol>delegate_ident_mapping=1</symbol> mode, and what additional + configuration is required in order to do so. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="oauth-validator-design-usermap-delegation"> + <title>Authorizing Users (Usermap Delegation)</title> + <para> + The standard deliverable of a validation module is the user identifier, + which the server will then compare to any configured + <link linkend="auth-username-maps"><filename>pg_ident.conf</filename> + mappings</link> and determine whether the end user is authorized to connect. + However, OAuth is itself an authorization framework, and tokens may carry + information about user privileges. For example, a token may be associated + with the organizational groups that a user belongs to, or list the roles + that a user may assume, and duplicating that knowledge into local usermaps + for every server may not be desirable. + </para> + <para> + To bypass username mapping entirely, and have the validator module assume + the additional responsibility of authorizing user connections, the HBA may + be configured with <xref linkend="auth-oauth-delegate-ident-mapping"/>. + The module may then use token scopes or an equivalent method to decide + whether the user is allowed to connect under their desired role. The user + identifier will still be recorded by the server, but it plays no part in + determining whether to continue the connection. + </para> + <para> + Using this scheme, authentication itself is optional. As long as the module + reports that the connection is authorized, login will continue even if there + is no recorded user identifier at all. This makes it possible to implement + anonymous or pseudonymous access to the database, where the third-party + provider performs all necessary authentication but does not provide any + user-identifying information to the server. (Some providers may create an + anonymized ID number that can be recorded instead, for later auditing.) + </para> + <para> + Usermap delegation provides the most architectural flexibility, but it turns + the validator module into a single point of failure for connection + authorization. Use with caution. + </para> + </sect2> + </sect1> + + <sect1 id="oauth-validator-init"> + <title>Initialization Functions</title> + <indexterm zone="oauth-validator-init"> + <primary>_PG_oauth_validator_module_init</primary> + </indexterm> + <para> + OAuth validator modules are dynamically loaded from the shared + libraries listed in <xref linkend="guc-oauth-validator-libraries"/>. + Modules are loaded on demand when requested from a login in progress. + The normal library search path is used to locate the library. To + provide the validator callbacks and to indicate that the library is an OAuth + validator module a function named + <function>_PG_oauth_validator_module_init</function> must be provided. The + return value of the function must be a pointer to a struct of type + <structname>OAuthValidatorCallbacks</structname>, which contains a magic + number and pointers to the module's token validation functions. The returned + pointer must be of server lifetime, which is typically achieved by defining + it as a <literal>static const</literal> variable in global scope. +<programlisting> +typedef struct OAuthValidatorCallbacks +{ + uint32 magic; /* must be set to PG_OAUTH_VALIDATOR_MAGIC */ + + ValidatorStartupCB startup_cb; + ValidatorShutdownCB shutdown_cb; + ValidatorValidateCB validate_cb; +} OAuthValidatorCallbacks; + +typedef const OAuthValidatorCallbacks *(*OAuthValidatorModuleInit) (void); +</programlisting> + + Only the <function>validate_cb</function> callback is required, the others + are optional. + </para> + </sect1> + + <sect1 id="oauth-validator-callbacks"> + <title>OAuth Validator Callbacks</title> + <para> + OAuth validator modules implement their functionality by defining a set of + callbacks. The server will call them as required to process the + authentication request from the user. + </para> + + <sect2 id="oauth-validator-callback-startup"> + <title>Startup Callback</title> + <para> + The <function>startup_cb</function> callback is executed directly after + loading the module. This callback can be used to set up local state and + perform additional initialization if required. If the validator module + has state it can use <structfield>state->private_data</structfield> to + store it. + +<programlisting> +typedef void (*ValidatorStartupCB) (ValidatorModuleState *state); +</programlisting> + </para> + </sect2> + + <sect2 id="oauth-validator-callback-validate"> + <title>Validate Callback</title> + <para> + The <function>validate_cb</function> callback is executed during the OAuth + exchange when a user attempts to authenticate using OAuth. Any state set in + previous calls will be available in <structfield>state->private_data</structfield>. + +<programlisting> +typedef bool (*ValidatorValidateCB) (const ValidatorModuleState *state, + const char *token, const char *role, + ValidatorModuleResult *result); +</programlisting> + + <replaceable>token</replaceable> will contain the bearer token to validate. + <application>PostgreSQL</application> has ensured that the token is well-formed syntactically, but no + other validation has been performed. <replaceable>role</replaceable> will + contain the role the user has requested to log in as. The callback must + set output parameters in the <literal>result</literal> struct, which is + defined as below: + +<programlisting> +typedef struct ValidatorModuleResult +{ + bool authorized; + char *authn_id; +} ValidatorModuleResult; +</programlisting> + + The connection will only proceed if the module sets + <structfield>result->authorized</structfield> to <literal>true</literal>. To + authenticate the user, the authenticated user name (as determined using the + token) shall be palloc'd and returned in the <structfield>result->authn_id</structfield> + field. Alternatively, <structfield>result->authn_id</structfield> may be set to + NULL if the token is valid but the associated user identity cannot be + determined. + </para> + <para> + A validator may return <literal>false</literal> to signal an internal error, + in which case any result parameters are ignored and the connection fails. + Otherwise the validator should return <literal>true</literal> to indicate + that it has processed the token and made an authorization decision. + </para> + <para> + The behavior after <function>validate_cb</function> returns depends on the + specific HBA setup. Normally, the <structfield>result->authn_id</structfield> user + name must exactly match the role that the user is logging in as. (This + behavior may be modified with a usermap.) But when authenticating against + an HBA rule with <literal>delegate_ident_mapping</literal> turned on, + <productname>PostgreSQL</productname> will not perform any checks on the value of + <structfield>result->authn_id</structfield> at all; in this case it is up to the + validator to ensure that the token carries enough privileges for the user to + log in under the indicated <replaceable>role</replaceable>. + </para> + </sect2> + + <sect2 id="oauth-validator-callback-shutdown"> + <title>Shutdown Callback</title> + <para> + The <function>shutdown_cb</function> callback is executed when the backend + process associated with the connection exits. If the validator module has + any allocated state, this callback should free it to avoid resource leaks. +<programlisting> +typedef void (*ValidatorShutdownCB) (ValidatorModuleState *state); +</programlisting> + </para> + </sect2> + + </sect1> +</chapter> |