Thread: Re: SCRAM pass-through authentication for postgres_fdw

Re: SCRAM pass-through authentication for postgres_fdw

From
Jacob Champion
Date:
On Wed, Dec 4, 2024 at 10:45 AM Matheus Alcantara
<matheusssilv97@gmail.com> wrote:
> This is achieved by storing the SCRAM ClientKey and ServerKey obtained
> during client authentication with the backend. These keys are then
> used to complete the SCRAM exchange between the backend and the fdw
> server, eliminating the need to derive them from a stored plain-text
> password.

What are the assumptions that have to be made for pass-through SCRAM
to succeed? Is it just "the two servers have identical verifiers for
the user," or are there others?

It looks like the patch is using the following property [1]:

   If an attacker obtains the authentication information from the
   authentication repository and either eavesdrops on one authentication
   exchange or impersonates a server, the attacker gains the ability to
   impersonate that user to all servers providing SCRAM access using the
   same hash function, password, iteration count, and salt.  For this
   reason, it is important to use randomly generated salt values.

It makes me a little uneasy to give users a reason to copy identical
salts/verifiers around... But for e.g. a loopback connection, it seems
like there'd be no additional risk. Is that the target use case?

I haven't looked at the code very closely yet, but the following hunk
jumped out at me:

> -    pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type);
> +    pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);

Why was that change made?

Thanks,
--Jacob

[1] https://datatracker.ietf.org/doc/html/rfc5802#section-9



Re: SCRAM pass-through authentication for postgres_fdw

From
Jelte Fennema-Nio
Date:
On Wed, 4 Dec 2024 at 23:11, Jacob Champion
<jacob.champion@enterprisedb.com> wrote:
> It makes me a little uneasy to give users a reason to copy identical
> salts/verifiers around... But for e.g. a loopback connection, it seems
> like there'd be no additional risk. Is that the target use case?

I don't think that necessarily has to be the usecase,
clustering/sharding setups could benefit from this too. PgBouncer
supports the same functionality[1]. I only see advantages over the
alternative, which is copying the plaintext password around. In case
of compromise of the server, only the salt+verifier has to be rotated,
not the actual user password.

Regarding the actual patch: This definitely needs a bunch of
documentation explaining how to use this and when not to use this.



Re: SCRAM pass-through authentication for postgres_fdw

From
Jacob Champion
Date:
On Wed, Dec 4, 2024 at 3:05 PM Jelte Fennema-Nio <postgres@jeltef.nl> wrote:
> I only see advantages over the
> alternative, which is copying the plaintext password around. In case
> of compromise of the server, only the salt+verifier has to be rotated,
> not the actual user password.

Sure, I'm not saying it's worse than plaintext. But a third
alternative might be actual pass-through SCRAM [1], where either you
expect the two servers to share a certificate fingerprint, or
explicitly disable channel bindings on the second authentication pass
in order to allow the MITM. (Or, throwing spaghetti, maybe even have
the primary server communicate the backend cert so you can verify it
and use it in the binding?)

All that is a metric ton more work and analysis, though.

--Jacob

[1] https://www.postgresql.org/message-id/9129a012-0415-947e-a68e-59d423071525%40timescale.com



Re: SCRAM pass-through authentication for postgres_fdw

From
Jacob Champion
Date:
On Wed, Dec 4, 2024 at 3:39 PM Jacob Champion
<jacob.champion@enterprisedb.com> wrote:
> actual pass-through SCRAM

(This was probably not a helpful way to describe what I'm talking
about; the method here in the thread could be considered "actual
pass-through SCRAM" as well. Proxied SCRAM, maybe?)

--Jacob



Re: SCRAM pass-through authentication for postgres_fdw

From
Matheus Alcantara
Date:
Em qua., 4 de dez. de 2024 às 20:39, Jacob Champion
<jacob.champion@enterprisedb.com> escreveu:
>
> On Wed, Dec 4, 2024 at 3:05 PM Jelte Fennema-Nio <postgres@jeltef.nl> wrote:
> > I only see advantages over the
> > alternative, which is copying the plaintext password around. In case
> > of compromise of the server, only the salt+verifier has to be rotated,
> > not the actual user password.
>
> Sure, I'm not saying it's worse than plaintext. But a third
> alternative might be actual pass-through SCRAM [1], where either you
> expect the two servers to share a certificate fingerprint, or
> explicitly disable channel bindings on the second authentication pass
> in order to allow the MITM. (Or, throwing spaghetti, maybe even have
> the primary server communicate the backend cert so you can verify it
> and use it in the binding?)

I'm not understanding how these options would work for this scenario.
I understand your concern about making the users copying the SCRAM
verifiers around but I don't understand how this third alternative fix
this issue. Would it be something similar to what you've implemented on
[1]?

The approach on this patch is that when the backend open the
connection with the foreign server, it will use the ClientKey stored
from the first client connection with the backend to build the final
client message that will be sent to the foreign server, which was
created/validated based on verifiers of the first backend. I can't see
how the foreign server can validate the client proof without having
the identical verifiers with the first backend.

I tested a scenario where the client open a connection with the
backend using channel binding and the backend open a connection with
the foreign server also using channel binding and everything seems to
works fine. I don't know if I missing something here, but here is how
I've tested this:

- Configure build system to use openssl
    meson setup build -Dssl=openssl ...
- Start two Postgresql servers
- Configure to use ssl on both servers
    ssl = on
    ssl_cert_file = '/path/to/server.crt'
    ssl_key_file = '/path/to/server.key'
- Changed pg_hba to use ssl on both servers
    hostssl   all   all   127.0.0.1/32   scram-sha-256
    hostssl   all   all   ::1/128        scram-sha-256
- Performed all foreign server setup (CREATE SERVER ...)
- Connect into the backend using channel_binding=require
    psql "host=127.0.0.1 dbname=local sslmode=require channel_binding=require"
- Execute a query on fdw server

I've also put a debug message before strcmp(channel_binding, b64_message)
[2] to ensure that channel binding is being used on both servers and I
got the log message on both servers logs.

Sorry if I misunderstood your message, I probably missed something here.

[1] https://www.postgresql.org/message-id/9129a012-0415-947e-a68e-59d423071525%40timescale.com
[2] src/backend/libpq/auth-scram.c#read_client_final_message

--
Matheus Alcantara
EDB: https://www.enterprisedb.com



Re: SCRAM pass-through authentication for postgres_fdw

From
Jacob Champion
Date:
On Wed, Dec 11, 2024 at 11:04 AM Matheus Alcantara
<matheusssilv97@gmail.com> wrote:
> Em qua., 4 de dez. de 2024 às 20:39, Jacob Champion
> <jacob.champion@enterprisedb.com> escreveu:
> > Sure, I'm not saying it's worse than plaintext. But a third
> > alternative might be actual pass-through SCRAM [1], where either you
> > expect the two servers to share a certificate fingerprint, or
> > explicitly disable channel bindings on the second authentication pass
> > in order to allow the MITM. (Or, throwing spaghetti, maybe even have
> > the primary server communicate the backend cert so you can verify it
> > and use it in the binding?)
>
> I'm not understanding how these options would work for this scenario.
> I understand your concern about making the users copying the SCRAM
> verifiers around but I don't understand how this third alternative fix
> this issue. Would it be something similar to what you've implemented on
> [1]?

Yeah, I was speaking in reference to my LDAP/SCRAM patchset from a
while back. (I'm just going to call that approach "proxied SCRAM" for
now.)

> The approach on this patch is that when the backend open the
> connection with the foreign server, it will use the ClientKey stored
> from the first client connection with the backend to build the final
> client message that will be sent to the foreign server, which was
> created/validated based on verifiers of the first backend. I can't see
> how the foreign server can validate the client proof without having
> the identical verifiers with the first backend.

Correct. The only way this strategy will work is if the verifiers are
the same. (Proxied SCRAM allows for different verifiers -- with
different salts and/or iterations -- with the same password.)

I do like that the action "copy the verifier" is a pretty clear signal
that you want the servers to be able to MITM each other. It's less
attack surface than having the two servers share a certificate, for
sure, and less work than communicating a new binding. Only users that
have opted into that are "vulnerable".

> I tested a scenario where the client open a connection with the
> backend using channel binding and the backend open a connection with
> the foreign server also using channel binding and everything seems to
> works fine. I don't know if I missing something here, but here is how
> I've tested this:

All that looks good. Sorry, I hadn't intended to derail your
particular proposal with that -- the channel binding problem only
shows up with my proxied SCRAM, because the client has to decide which
tls-server-end-point to trust and put into the binding.

(It's important that your patchset works with channel bindings too, of
course, but I don't expect that to be a problem. It shouldn't matter
to this approach.)

--Jacob