On Tue, Nov 12, 2024 at 1:47 PM Jacob Champion <jacob.champ...@enterprisedb.com> wrote: > On Fri, Nov 8, 2024 at 1:21 AM Peter Eisentraut <pe...@eisentraut.org> wrote: > > Also, shouldn't [oauth_validator_library] be an hba option instead? What > > if you want to > > use different validators for different connections? > > Yes. This is again the multiple-issuers problem; I will split that off > into its own email since this one's getting long. It has security > implications.
Okay, so, how to use multiple issuers/providers. Here's my current plan, with justification below: 1. libpq connection strings must specify exactly one issuer 2. the discovery document coming from the server must belong to that libpq issuer 3. the HBA should allow a choice of discovery document and validator = Current Bug = First, I should point out a critical mistake I've made on the client side: I treat oauth_issuer and oauth_client_id as if they can be arbitrarily mixed and matched. Some of the providers I've been testing do allow you to use one registered client across multiple issuers, but that's the exception rather than the norm. Even if you have multiple issuers available, you still expect your registered client to be talking to only the provider you registered it with. And you don't want the Postgres server to switch providers for you. Imagine that you've registered a client application for use with a big provider, and that provider has given you a client secret. You expect to share that secret only with them, but with the current setup, if a DBA wants to steal that secret from you, all they have to do is stand up a provider of their own, and libpq will send the secret straight to it instead. Great. There's actually a worse scenario that's pointed out in the spec for the Device Authorization flow [1]: Note that if an authorization server used with this flow is malicious, then it could perform a man-in-the-middle attack on the backchannel flow to another authorization server. [...] For this to be possible, the device manufacturer must either be the attacker and shipping a device intended to perform the man-in-the-middle attack, or be using an authorization server that is controlled by an attacker, possibly because the attacker compromised the authorization server used by the device. Back when I implemented this, that paragraph seemed pointlessly obvious: of course you must trust your authorization server. What I missed was, the Postgres server MUST NOT be able to control the entry point into the device flow, because that means a malicious DBA can trivially start a device prompt with a different provider, forward you all the details through the endpoint they control, and hope you're too fatigued to notice the difference before clicking through. (This is easier if that provider is one of the big ones that you're already used to trusting.) Then they have a token with which to attack you on a completely different platform. So in my opinion, my patchset must be changed to require a trusted issuer in the libpq connection string. The server can tell you which discovery document to get from that issuer, and it can tell you which scopes are required (as long as the user hasn't hardcoded those too), but it shouldn't be able to force the client to talk to an arbitrary provider or swap out issuers. = Multiple Issuers = Okay, with that out of the way, let's talk about multiple issuer support. First, server-side. If a server wants different groups of users/databases/etc. to go through different issuers, then it stands to reason that a validator should be selectable in the HBA settings, since a validator for Provider A may not have any clue how to validate Provider B. I don't like the idea of pg_hba being used to load arbitrary libraries, though; I think the superuser should have to designate a pool of "blessed" validator libraries to load through a GUC. As a UX improvement for the common case, maybe we don't require the HBA to have an explicit validator parameter if the conf contains exactly one blessed library. In case someone does want to develop a multi-issuer validator (say, to deal with the providers that have multiple issuers underneath their umbrella), we need to make sure that the configured issuer in use is available to the validator, so that they aren't susceptible to a mix-up attack of their own. As for the client side, I think v1 should allow only one expected issuer per connection. There are OAuth features [2] that help clients handle more safely, but as far as I can tell they are not widely deployed yet, and I don't know if any of them apply to the device flow. (With the device flow, if the client allows multiple providers, those providers can attack each other as described above.) If a more complicated client application associates a single end user with multiple Postgres connections, and each connection needs its own issuer, then that application needs to be encouraged to use a flow which has been hardened for that use case. (Setting aside the security problems with mix-ups, the device flow won't be particularly pleasant for that anyway. "Here's a bunch of URLs and codes, go to all of them before they time out, good luck!") = Discovery Documents = There are two flavors of discovery document, OAuth and OpenID. And OIDC Discovery and RFC 8414 disagree on the rules, so for the issuer "https://example.com/abcd", you have two discovery document locations using postfix or infix styles for the path: - OpenID: https://example.com/abcd/.well-known/openid-configuration - OAuth: https://example.com/.well-known/oauth-authorization-server/abcd Some providers publish different information at each [3], so the difference may be important for some deployments. RFC 8414 claims the OpenID flavor should transition to the infix style at some point (a transition that is not happening as far as I can see), so now there are three standards. And Okta uses the construction "https://example.com/abcd/.well-known/oauth-authorization-server", which you may notice matches _neither_ of the two options above, so now there are four standards. To deal with all of this, I plan to better separate the difference between the issuer and the discovery URL in the code, as well as allow DBAs and clients to specify the discovery URL explicitly to override the default OpenID flavor. For now I plan to support only "openid-configuration" and "oauth-authorization-server" in both postfix and infix notation (four options total, as seen in the wild). How's all that sound? --Jacob [1] https://datatracker.ietf.org/doc/html/rfc8628#section-5.3 [2] https://datatracker.ietf.org/doc/html/rfc9207 [3] https://devforum.okta.com/t/is-userinfo-endpoint-available-in-oauth-authorization-server/24284