Introduce auth_ident_file setting and enhance cert and peer auth methods with user name maps#996
Conversation
| bouncer.write_ini(f"auth_hba_file = pgbouncer_hba.conf") | ||
| bouncer.write_ini(f"auth_pgident_file = pgident.conf") |
There was a problem hiding this comment.
These two files are not committed, which is why this test is failing.
b3d6308 to
1f306ce
Compare
1b4e846 to
fea81da
Compare
| goto fail; | ||
|
|
||
| if (strcmp(client->login_user->name, rule->identmap->postgres_user_name)) { | ||
| if (strcmp(rule->identmap->postgres_user_name, "all")) |
There was a problem hiding this comment.
There's two ways all can be formatted in the ident file, which means two different things:
all(literal string), this is the special keyword that matches all users"all"(quoted), this means it only matches the user calledall(assuming this user exists)
Postgres tracks this difference by setting a flag in case the special keyword is used.
There was a problem hiding this comment.
For HBA parsing we use the NAME_ALL flag in pgbouncer too actually.
| return false; | ||
| } | ||
|
|
||
| if (!expect(tp, TOK_IDENT, &map_name)) { |
There was a problem hiding this comment.
Right now this does not support TOK_STRING, so any quoted names will fail. Let's fix that. To not duplicate this logic for each of the columns, I think it probably makes sense to create a new function that is very similar to the already existing parse_names function but handles ident names instead of hba names (e.g. called parse_ident_names).
There was a problem hiding this comment.
This one still requires TOK_IDENT for the map_name afaict.
b963e06 to
5782254
Compare
|
SIGSEGV happens when trying to connect to a database that's present in [databases]. The setup doesn't configure auth_ident_file but has some entries for cert auth in auth_hba_file. #0 0x000055a024613e61 in finish_set_pool (client=client@entry=0x55a025a09a80, takeover=takeover@entry=false) at src/client.c:310
#1 0x000055a024615918 in handle_client_startup (client=client@entry=0x55a025a09a80, pkt=pkt@entry=0x7ffc669afb10) at src/client.c:928
#2 0x000055a024616549 in client_proto (sbuf=0x55a025a09c50, evtype=<optimized out>, data=0x7ffc669afb90) at src/client.c:1365
#3 0x000055a024628f68 in sbuf_call_proto (sbuf=sbuf@entry=0x55a025a09c50, event=event@entry=0) at src/sbuf.c:498
#4 0x000055a0246294fa in sbuf_process_pending (sbuf=sbuf@entry=0x55a025a09c50) at src/sbuf.c:802
#5 0x000055a024629e24 in sbuf_main_loop (skip_recv=<optimized out>, sbuf=0x55a025a09c50) at src/sbuf.c:1031
#6 sbuf_main_loop (sbuf=0x55a025a09c50, skip_recv=<optimized out>) at src/sbuf.c:966
#7 0x000055a024616230 in handle_auth_query_response (client=0x55a025a09a80, pkt=pkt@entry=0x7ffc669b05f0) at src/client.c:545
#8 0x000055a02462cf56 in handle_server_work (pkt=0x7ffc669b05f0, server=0x55a025a23a80) at src/server.c:458
#9 server_proto (sbuf=0x55a025a23c50, evtype=<optimized out>, data=<optimized out>) at src/server.c:702
#10 0x000055a024628f68 in sbuf_call_proto (sbuf=sbuf@entry=0x55a025a23c50, event=event@entry=0) at src/sbuf.c:498
#11 0x000055a0246294fa in sbuf_process_pending (sbuf=sbuf@entry=0x55a025a23c50) at src/sbuf.c:802
#12 0x000055a024629e24 in sbuf_main_loop (skip_recv=<optimized out>, sbuf=0x55a025a23c50) at src/sbuf.c:1031
#13 sbuf_main_loop (sbuf=0x55a025a23c50, skip_recv=<optimized out>) at src/sbuf.c:966
#14 0x00007ff08f61e13f in ?? () from /lib/x86_64-linux-gnu/libevent-2.1.so.7
#15 0x00007ff08f61e87f in event_base_loop () from /lib/x86_64-linux-gnu/libevent-2.1.so.7
#16 0x000055a02461ccba in main_loop_once () at src/main.c:794
#17 0x000055a02460ec45 in main (argc=<optimized out>, argv=<optimized out>) at src/main.c:1082Assuming that log_warning("hba_eval returned Ident map %s %s %s", rule->identmap->map_name, rule->identmap->system_user_name, rule->identmap->postgres_user_name); |
b3666f2 to
1dcc21d
Compare
| slog_error(client, "TLS certificate name mismatch"); | ||
|
|
||
| if (rule && rule->identmap) { | ||
| if (!tls_peer_cert_contains_name(client->sbuf.tls, rule->identmap->system_user_name)) |
There was a problem hiding this comment.
We should log a unique error here (and the two error cases below), failures in these kind of things are hard enough to debug already.
| if (!tls_peer_cert_contains_name(client->sbuf.tls, client->login_user->name)) { | ||
| slog_error(client, "TLS certificate name mismatch"); | ||
|
|
||
| if (rule && rule->identmap) { |
There was a problem hiding this comment.
Let's add similar logic to use the map when available to login_as_unix_peer, because postgres allow username mapping for that one too: https://www.postgresql.org/docs/current/auth-peer.html
Afaict to support that too, we only need these if lines (and some tests). If it's much more than that, let's make it a separate PR.
There was a problem hiding this comment.
I have done this change but could not add tests for it. I was able to manually test.
I tried adding a test via:
result = subprocess.run([f"sudo -u asystemuser psql -p {bouncer.port} -h {bouncer.admin_host} p0 -c "SELECT 1;""], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
print(result.returncode, result.stdout, result.stderr)
returns error
..... Permission denied\npsql: error: connection to server on socket "/tmp/pytest-of-emel/pytest-34/test_client_hba_cert0/bouncer/.s.PGSQL.10202" failed: Permission denied\n\tIs the server running locally and accepting connections on that socket?\n'
There was a problem hiding this comment.
Maybe we should make this a seperate PR with proper tests added.
There was a problem hiding this comment.
I think probably the easiest way to create a test is to manually create an ident file, that contains the current system user. So something like this (untested code):
cur_user = os.current_user()
with open("myident.conf", 'w') as f:
f.write(f"mymap {cur_user} postgres")
bouncer.write_ini(f"auth_ident_file = pgident.conf")There was a problem hiding this comment.
Maybe we should make this a seperate PR with proper tests added.
One big reason why I'd like it in too, is because it's generally much easier to do some manual testing with it. So if we cannot easily do automated tests (in the way I suggested in the previous comment) I think it's fine to merge without tests, because I'll do some manual testing with it.
There was a problem hiding this comment.
Thanks @JelteF. I pushed a simple test now.
| # ========================= | ||
|
|
||
| # MAPNAME SYSTEM-USERNAME PG-USERNAME | ||
| test pgbouncer.acme.org someuser |
There was a problem hiding this comment.
I think it would be good to test a case where there are multiple mappings for the same name, then the cert should be able to authenticate as both:
| test pgbouncer.acme.org someuser | |
| test pgbouncer.acme.org someuser | |
| test pgbouncer.acme.org someuser2 |
There was a problem hiding this comment.
The current design does not support this. The first encountered map is effective. But I see that postgres supports multiple mappings with the same name:
https://www.postgresql.org/docs/current/auth-username-maps.html#:~:text=%23%20MAPNAME%20%20%20%20%20%20%20SYSTEM%2DUSERNAME,omicron%20%20%20%20%20%20%20%20%20bryanh%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20guest1
Need to make data structure changes to support this.
| sslrootcert=root, | ||
| ) | ||
|
|
||
| bouncer.pg.sql("create user anotheruser with login;") |
There was a problem hiding this comment.
sidenote: We should change this for bouncer.pg.create_user once #876 is merged
| struct List rules; | ||
| }; | ||
|
|
||
| struct IDENTMap { |
There was a problem hiding this comment.
Let's use CamelCase for the struct name. I realize took the naming scheme from HBA, but that is an acronym (and even then I don't agree with this type of casing personally).
| struct IDENTMap { | |
| struct IdentMap { |
| if (!expect(tp, TOK_IDENT, &system_user_name)) { | ||
| goto failed; | ||
| } |
There was a problem hiding this comment.
This one should allow TOK_STRING too.
|
|
||
| # MAPNAME SYSTEM-USERNAME PG-USERNAME | ||
| test pgbouncer.acme.org someuser | ||
| test2 bouncer all |
There was a problem hiding this comment.
We should have some tests for quoted names too in all of the columns.
| return false; | ||
| } | ||
|
|
||
| if (!expect(tp, TOK_IDENT, &map_name)) { |
There was a problem hiding this comment.
This one still requires TOK_IDENT for the map_name afaict.
| ) | ||
|
|
||
|
|
||
| @pytest.mark.skipif("WINDOWS", reason="Windows does not have SIGHUP") |
There was a problem hiding this comment.
| @pytest.mark.skipif("WINDOWS", reason="Windows does not have SIGHUP") | |
| @pytest.mark.skipif("WINDOWS", reason="Windows does not have peer authentication") |
|
|
||
| with open("ident.conf", "w") as f: | ||
| f.write(f"mymap {cur_user} postgres\n") | ||
| f.write(f"mymap {cur_user} someuser\n") |
There was a problem hiding this comment.
Should also have a test with the all system user. That's causing a segfault currently with this manual test:
pgbouncer.ini
[databases]
postgres = host=localhost dbname=postgres port=9700
postgres1 = host=localhost dbname=postgres port=9700
postgres2 = host=localhost dbname=postgres port=9700
postgres3 = host=localhost dbname=postgres port=9700
[pgbouncer]
pool_mode=transaction
listen_addr=127.0.0.1
auth_type=hba
auth_hba_file=hba.conf
auth_ident_file=ident.conf
admin_users=jelte
auth_file=auth_file.conf
so_reuseport=1
unix_socket_dir=/tmphba.conf
# TYPE DATABASE USER ADDRESS METHOD
local postgres1 jelte peer map=allowmap
ident.conf
allowmap jelte all
auth_file.conf
"jelte" "password" ...
| def test_peer_auth_ident_map(bouncer): | ||
| cur_user = getpass.getuser() | ||
|
|
||
| with open("ident.conf", "w") as f: |
There was a problem hiding this comment.
These files should be created in the config directory of pgbouncer. Otherwise they polute the current working directory when running the tests locally.
0878a53 to
9871ce6
Compare
9871ce6 to
dab510e
Compare
This adds support for user name maps via auth_ident_file. cf: pgbouncer/pgbouncer#996 Signed-off-by: Christopher Broglie <cbroglie@cloudflare.com>
This adds support for user name maps via auth_ident_file. cf: pgbouncer/pgbouncer#996 Signed-off-by: Christopher Broglie <cbroglie@cloudflare.com>
This adds support for user name maps via auth_ident_file. cf: pgbouncer/pgbouncer#996 Signed-off-by: Christopher Broglie <cbroglie@cloudflare.com>
Postgres supports user name maps[1] for
certandpeerauthentication methods. This change provides the same functionality with some limitations for pgbouncer. The limitations are listed in "Ident map file format" section of the docs.Fixes #767
Fixes #148