Re: Kerberos delegation support in libpq and postgres_fdw

From: Stephen Frost <sfrost(at)snowman(dot)net>
To: David Christensen <david+pg(at)pgguru(dot)net>
Cc: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: Kerberos delegation support in libpq and postgres_fdw
Date: 2023-04-05 20:30:45
Message-ID: ZC3adQeTgB3UX3QF@tamriel.snowman.net
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Greetings,

* David Christensen (david+pg(at)pgguru(dot)net) wrote:
> Did a code review pass here; here is some feedback.

Thanks!

> + /* If password was used to connect, make sure it was one provided */
> + if (PQconnectionUsedPassword(conn) && dblink_connstr_has_pw(connstr))
> + return;
>
> Do we need to consider whether these passwords are the same? Is there a different vector where a different password could be acquired from a different source (PGPASSWORD, say) while both of these criteria are true? Seems like it probably doesn't matter that much considering we only checked Password alone in previous version of this code.

Note that this patch isn't really changing how these checks are being
done but more moving them around and allowing a GSSAPI-based approach
with credential delegation to also be allowed.

That said, as noted in the comments above dblink_connstr_check():

* For non-superusers, insist that the connstr specify a password, except
* if GSSAPI credentials have been proxied (and we check that they are used
* for the connection in dblink_security_check later). This prevents a
* password or GSSAPI credentials from being picked up from .pgpass, a
* service file, the environment, etc. We don't want the postgres user's
* passwords or Kerberos credentials to be accessible to non-superusers.

The point of these checks is, indeed, to ensure that environmental
values such as a .pgpass or variable don't end up getting picked up and
used (or, if they do, we realize it post-connection and then throw away
the connection).

libpq does explicitly prefer to use the password passed in as part of
the connection string and won't attempt to look up passwords in a
.pgpass file or similar if a password has been included in the
connection string.

> Looks like the pg_gssinfo struct hides the `proxy_creds` def behind:
>
> #if defined(ENABLE_GSS) | defined(ENABLE_SSPI)
> typedef struct
> {
> gss_buffer_desc outbuf; /* GSSAPI output token buffer */
> #ifdef ENABLE_GSS
> ...
> bool proxy_creds; /* GSSAPI Delegated/proxy credentials */
> #endif
> } pg_gssinfo;
> #endif

... right, proxy_creds only exists (today anyway) if ENABLE_GSS is set.

> Which means that the later check in `be_gssapi_get_proxy()` we have:
>
> /*
> * Return if GSSAPI delegated/proxy credentials were included on this
> * connection.
> */
> bool
> be_gssapi_get_proxy(Port *port)
> {
> if (!port || !port->gss)
> return NULL;
>
> return port->gss->proxy_creds;
> }

but we don't build be-secure-gssapi.c, where this function is added,
unless --with-gssapi is included, from src/backend/libpq/Makefile:

ifeq ($(with_gssapi),yes)
OBJS += be-gssapi-common.o be-secure-gssapi.o
endif

Further, src/include/libpq/libpq-be.h has a matching #ifdef ENABLE_GSS
for the function declarations:

#ifdef ENABLE_GSS
/*
* Return information about the GSSAPI authenticated connection
*/
extern bool be_gssapi_get_auth(Port *port);
extern bool be_gssapi_get_enc(Port *port);
extern const char *be_gssapi_get_princ(Port *port);
extern bool be_gssapi_get_proxy(Port *port);

> So in theory it'd be possible to have SSPI enabled but GSS disabled and we'd fail to compile in that case. (It may be that this routine is never *actually* called in that case, just noting compile-time considerations.) I'm not seeing guards in the actual PQ* routines, but don't think I've done an exhaustive search.

Fairly confident the analysis here is wrong, further, the cfbot seems to
agree that there isn't a compile failure here:

https://cirrus-ci.com/task/6589717672624128

[20:19:15.985] gss : NO

(we always build with SSPI on Windows, per
src/include/port/win32_port.h).

> gss_accept_deleg
>
>
> + <para>
> + Forward (delegate) GSS credentials to the server. The default is
> + <literal>disable</literal> which means credentials will not be forwarded
> + to the server. Set this to <literal>enable</literal> to have
> + credentials forwarded when possible.
>
> When is this not possible? Server policy? External factors?

The Kerberos credentials have to be forwardable for them to be allowed
to be forwarded and the server has to be configured to accept them.

> </para>
> <para>
> Only superusers may connect to foreign servers without password
> - authentication, so always specify the <literal>password</literal> option
> - for user mappings belonging to non-superusers.
> + authentication or using gssapi proxied credentials, so specify the
> + <literal>password</literal> option for user mappings belonging to
> + non-superusers who are not able to proxy GSSAPI credentials.
> </para>
> <para>
>
> s/gssapi/GSSAPI/; this is kind of confusing, as this makes it sound like only superuser may use GSSAPI proxied credentials, which I disbelieve to be true. Additionally, it sounds like you're wanting to explicitly maintain a denylist for users to not be allowed proxying; is that correct?

Updated to GSSAPI and reworded in the updated patch (attached).
Certainly open to suggestions on how to improve the documentation here.
There is no 'denylist' for users when it comes to GSSAPI proxied
credentials. If there's a use-case for that then it could be added in
the future.

> ---
>
> libpq/auth.c:
>
> if (proxy != NULL)
> {
> pg_store_proxy_credential(proxy);
> port->gss->proxy_creds = true;
> }
>
> Per GSS docs, seems like we should be comparing to GSS_C_NO_CREDENTIAL and validating that the gflags has the `deleg_flag` bit set before considering whether there are valid credentials; in practice this might be the same effect (haven't looked at what that symbol actually resolves to, but NULL would be sensible).

GSS_C_NO_CREDENTIAL is indeed NULL, but updated to that anyway to be a
bit cleaner and also added an explicit check that GSS_C_DELEG_FLAG was
set in gflags.

> Are there other cases we might need to consider here, like valid credentials, but they are expired? (GSS_S_CREDENTIALS_EXPIRED)

Short answer is no, I don't believe we need to. We shouldn't actually
get any expired credentials but even if we did, worst is that we'd end
up storing them and they wouldn't be able to be used because they're
expired.

> ---
>
> + /*
> + * Set KRB5CCNAME for this backend, so that later calls to gss_acquire_cred
> + * will find the proxied credentials we stored.
> + */
>
> So I'm not seeing this in other use in the code; I assume this is just used by the krb5 libs?

Not sure I'm following. gss_acquire_cred() is called in
src/interfaces/libpq/fe-gssapi-common.c.

> Similar q's for the other places the pg_gss_accept_deleg are used.

pg_gss_accept_deleg is checked in the two paths where we could have
credentials delegated to us- either through the encrypted-GSSAPI
connection path in libpq/be-secure-gssapi.c, or the
not-using-GSSAPI-encryption path in libpq/auth.c.

> ---
>
> +int
> +PQconnectionUsedGSSAPI(const PGconn *conn)
> +{
> + if (!conn)
> + return false;
> + if (conn->gssapi_used)
> + return true;
> + else
> + return false;
> +}
>
> Micro-gripe: this routine seems like could be simpler, though the compiler probably has the same thing to say for either, so maybe code clarity is better as written:
>
> int
> PQconnectionUsedGSSAPI(const PGconn *conn)
> {
> return conn && conn->gssapi_used;
> }

I tend to disagree- explicitly returning true/false seems a bit clearer
to me and is also in-line with what other functions in
libpq/fe-connect.c are doing. Having this function be different from,
eg, PQconnectionUsedPassword, would probably end up having more
questions about why they're different. Either way, I'd say we change
both or neither and that doesn't really need to be part of this patch.

> ---
>
> Anything required for adding meson support? I notice src/test/kerberos has Makefile updated, but no meson.build files are changed.

Short answer is- I don't think so (happy to be told I'm wrong though, if
someone wants to tell me what's wrong). The other src/test modules that
have EXTRA_INSTALL lines don't have anything for those in the
meson.build, so I'm guessing the assumption is that everything is built
when using meson.

> ---
>
> Two tests in src/test/kerberos/t/001_auth.pl at :535 and :545 have the same test description:
>
> + 'succeeds with GSS-encrypted access required and hostgssenc hba and credentials not forwarded',
>
> Since the first test has only `gssencmode` defined (so implicit `gssdeleg` value) and the second has `gssdeleg=disable` I'd suggest that the test on :545 should have its description updated to add the word "explicitly":
>
> 'succeeds with GSS-encrypted access required and hostgssenc hba and credentials explicitly not forwarded',

Sure, updated.

> ---
>
> In the dblink test, this seems like debugging junk:
>
> +print ("$psql_out");
> +print ("$psql_stderr");

Ah, yeah, removed.

> Whacking those lines and reviewing the surrounding code block: so this is testing that dblink won't use `.pgpass`; so is this a behavior change, and dblink could be previously used w/postgres user's .pgpass file? I assume since this patch is forbidding this, we've decided that that is a bad idea--was this updated in the docs to note that this is now forbidden, or is this something that should only apply in some cases (i.e., this is now config-specific)? If config-specific, should we have a test in the non-forwarded version of these tests that exercises that behavior?

Yes, that's what is being tested, but non-superuser dblink already won't
use a .pgpass file if it exists, so it's not a behavior change. I added
explicit tests here though to make sure that even a dblink connection
created without a password being used in the connection string (because
GSSAPI credentials were proxied) won't end up using the .pgpass file.

Additional tests could perhaps be added to dblink itself (don't know
that we really need to hide those tests under src/test/kerberos) to make
sure that it's not going to use the .pgpass file; I'm not sure why that
wasn't done previously (it was done for postgres_fdw though and the
approach in each is basically the same...).

> +$psql_rc = $node->psql(
> + 'postgres',
> + "SELECT * FROM dblink('user=test2 dbname=$dbname port=$port passfile=$pgpass','select 1') as t1(c1 int);",
> + connstr => "user=test1 host=$host hostaddr=$hostaddr gssencmode=require gssdeleg=disable",
> + stdout => \$psql_out,
>
> +is($psql_rc,'3','dblink does not work without proxied credentials and with passfile');
> +like($psql_stderr, qr/password or gssapi proxied credentials required/,'dblink does not work without proxied credentials and with passfile');
>
> Same Q's apply to the postgres_fdw version of these tests.

Regarding postgres_fdw, there are already tests in contrib/postgres_fdw
to make sure that when password_required=true that .pgpass and such
don't end up getting used, but when password_required=false (which can
only be set by a superuser) then it's allowed to use environmental
authentication options such as a .pgpass file.

> ---
>
> :659 and :667, the test description says non-encrypted and the gssencmode=prefer implies encrypted; seems like those descriptions might need to be updated, since it seems like what it's really testing is dblink/postgres_fdw against gssdeleg=enabled.

The server is configured at this point to not accept encrypted
connections (the pg_hba.conf has only:

local all test2 scram-sha-256
hostnogssenc all all $hostaddr/32 gss map=mymap

in it).

Updated the test descriptions.

> Also looks like later tests are explicitly testing w/gssencmode=require so making me think this is mislabeled further.

Those are after the pg_hba.conf has been adjusted again to allow
encrypted connections.

> This is what I noticed on an initial pass-through.
> The new status of this patch is: Waiting on Author

Changed back to Needs Review.

Thanks again!

Stephen

Attachment Content-Type Size
gss_delegation_v7.patch text/x-diff 73.6 KB

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Daniel Gustafsson 2023-04-05 20:38:17 Re: Should vacuum process config file reload more often
Previous Message Peter Geoghegan 2023-04-05 20:19:39 Re: Should vacuum process config file reload more often