Laurenz, Thank you for the review.
Included with this email are 2 patches. `0004-Add-ldapservice-connection-parameter.patch` contains all of the changes you recommended: - Removed the redundant/unneeded check for the string ldap - The function has now been moved from parseServiceInfo() to conninfo_add_defaults(). - I will say though the reason I included this in parseServiceInfo() was I consider this new LDAP functionality and the existing pg_service.conf() functionality all different ways of accessing a postgres "service". I thought it made sense to handle all service parsing in a single function. Happy to keep it moved it if I was incorrect about this - A failed parseServiceInfo() now returns false in conninfo_add_defaults() - Made documentation a bit more explicit. Also I added support for specifying PGLDAPPSERVICE in the form of an env var as well. Also added additional tests. > For the same reason, I am not entirely happy with the name "ldapservice", but > I can't think of anything better. I have a few suggestions here. Maybe `remoteserviceuri` would be a better name. This tells the reader that it's a uri and not the name of a service. LDAP is not mentioned because maybe in the future this functionality could be expanded to read connection parameters from an HTTP server, postgres side channel, etc. Alternatively we could bypass the naming altogether and add this functionality into the existing pgservice parameter. I have attached a second patch `0001-Allow-LDAP-lookup-from-pgservice-connection-paramete.patch` that has an implementation of this. The idea being that if you pass a string that starts with ldap:// it will look up the service at the given LDAP uri instead of looking for the corresponding service in the pg_service.conf file. This would be a breaking change for anyone who populated their service names as LDAP uris, I would imagine this would be a very small number of users, if any. It also ends up being a smaller patch overall. > I don't have an LDAP server handy, so I couldn't test the patch On the off chance that it helps, we now have unit tests over the LDAP service functionality. It should automatically start a slapd server, etc. If you add a false assertion in the perl code it should fail the test and also dump a bunch of files into the src/test/ldap directory. You can copy paste the slapd invocation that is left there. I did have to fight with App Armor a bit to get it to be okay with a slapd process reading files in unexpected locations. Thanks again for the review, Andrew Jackson
From d883f5a8c0340667a13c41f1a33852deff427a1b Mon Sep 17 00:00:00 2001 From: Andrew Jackson <[email protected]> Date: Sat, 28 Mar 2026 23:29:48 -0500 Subject: [PATCH] Add ldapservice connection parameter Currently there exists, only in pg_service.conf, the ability to look up connection parameters from a centralized LDAP server. This patch expands the usability of this be allowing it to be specified directly in a connection string instead of only in a pg_service.conf file. This adds the PGLDAPSERVICE env var that provides an envvar interface to this functionality. Also the functionality has been moved to conninfo_add_defaults after review. Also 2 tests have been added. One to validate the env var functionality and one to validate that this parameter is ignored when used in pg_service.conf files --- doc/src/sgml/libpq.sgml | 22 ++++++++++++++ src/interfaces/libpq/fe-connect.c | 14 +++++++++ src/interfaces/libpq/libpq-int.h | 1 + .../t/003_ldap_connection_param_lookup.pl | 29 +++++++++++++++++++ 4 files changed, 66 insertions(+) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 6db823808fc..bbd31cf6f95 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2350,6 +2350,18 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-ldapservice" xreflabel="ldapservice"> + <term><literal>ldapservice</literal></term> + <listitem> + <para> + This option specifies an LDAP query that can be used to reference connection parameters + stored on an LDAP server. Any connection parameter that is looked up in this way is + overridden by explicitly named connection parameters. This parameter is ignored when used + in a pg_service.conf file. This functionality is described in more detail in <xref linkend="libpq-ldap"/>. + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-target-session-attrs" xreflabel="target_session_attrs"> <term><literal>target_session_attrs</literal></term> <listitem> @@ -9170,6 +9182,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) </para> </listitem> + <listitem> + <para> + <indexterm> + <primary><envar>PGLDAPSERVICE</envar></primary> + </indexterm> + <envar>PGLDAPSERVICE</envar> behaves the same as the + <xref linkend="libpq-connect-ldapservice"/> connection parameter. + </para> + </listitem> + <listitem> <para> <indexterm> diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index db9b4c8edbf..b4b9bb1bb24 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -207,6 +207,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Database-Service-File", "", 64, offsetof(struct pg_conn, pgservicefile)}, + {"ldapservice", "PGLDAPSERVICE", NULL, NULL, + "Database-LDAP-Service", "", 20, + offsetof(struct pg_conn, pgldapservice)}, + {"user", "PGUSER", NULL, NULL, "Database-User", "", 20, offsetof(struct pg_conn, pguser)}, @@ -6726,6 +6730,16 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage) PQconninfoOption *sslmode_default = NULL, *sslrootcert = NULL; char *tmp; +#ifdef USE_LDAP + const char *ldapservice = conninfo_getval(options, "ldapservice"); + + if (ldapservice == NULL) + ldapservice = getenv("PGLDAPSERVICE"); + + if (ldapservice != NULL) + if (ldapServiceLookup(ldapservice, options, errorMessage) != 0) + return false; +#endif /* * If there's a service spec, use it to obtain any not-explicitly-given diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index bd7eb59f5f8..d8510cc0928 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -392,6 +392,7 @@ struct pg_conn char *pgservice; /* Postgres service, if any */ char *pgservicefile; /* path to a service file containing * service(s) */ + char *pgldapservice; /* Postgres LDAP service, if any */ char *pguser; /* Postgres username and password, if any */ char *pgpass; char *pgpassfile; /* path to a file containing password(s) */ diff --git a/src/test/ldap/t/003_ldap_connection_param_lookup.pl b/src/test/ldap/t/003_ldap_connection_param_lookup.pl index 359fc7a998a..03d276120b4 100644 --- a/src/test/ldap/t/003_ldap_connection_param_lookup.pl +++ b/src/test/ldap/t/003_ldap_connection_param_lookup.pl @@ -80,6 +80,9 @@ append_to_file( $srvfile_valid, qq{ [my_srv] ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) + +[my_srv_2] +ldapservice=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) }); # File defined with no contents, used as default value for @@ -196,6 +199,32 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty"; expected_stdout => qr/definition of service "undefined-service" not found/); + delete $ENV{PGSERVICE}; + + $dummy_node->connect_ok( + "ldapservice=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)", + 'connection with correct "ldapservice" string', + sql => "SELECT 'connect2_4'", + expected_stdout => qr/connect2_4/); + + $dummy_node->connect_ok( + "postgres://?ldapservice=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29", + 'connection with correct "ldapservice"', + sql => "SELECT 'connect2_5'", + expected_stdout => qr/connect2_5/); + + local $ENV{PGLDAPSERVICE} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)"; + $dummy_node->connect_ok( + "", + 'connection with correct "ldapservice" provided by env var', + sql => "SELECT 'connect2_6'", + expected_stdout => qr/connect2_6/); + delete $ENV{PGLDAPSERVICE}; + + $dummy_node->connect_fails( + '', + 'connection fails with ldapservice specified in pg_service.conf file'); + # Remove default pg_service.conf. unlink($srvfile_default); } -- 2.51.2
From 5f9a720a526471286864d92559e6361460ca04cb Mon Sep 17 00:00:00 2001 From: Andrew Jackson <[email protected]> Date: Sat, 28 Mar 2026 23:29:48 -0500 Subject: [PATCH] Allow LDAP lookup from pgservice connection parameter Currently there exists, only in pg_service.conf, the ability to look up connection parameters from a centralized LDAP server. This patch expands the usability of this be allowing it to be specified directly in a connection string instead of only in a pg_service.conf file. This patch adds a check in parseServiceInfo that checks if pgservice is an LDAP scheme address and if so attempts to connect to that LDAP server and grab connection parameters from there. This is a breaking change in that it is possible that people previously named their pgservice after an LDAP address. --- doc/src/sgml/libpq.sgml | 4 +++ src/interfaces/libpq/fe-connect.c | 5 ++++ .../t/003_ldap_connection_param_lookup.pl | 25 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 6db823808fc..4d230a2a65f 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2333,6 +2333,10 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname name in <filename>pg_service.conf</filename> that holds additional connection parameters. This allows applications to specify only a service name so connection parameters can be centrally maintained. See <xref linkend="libpq-pgservice"/>. + + You can also specify and LDAP address here which will look up parameters from an LDAP + server. See <xref linkend="libpq-ldap"/>. Please note that if an LDAP address is specified + no attempt will be made to look up parameters in <filename>pg_service.conf</filename>. </para> </listitem> </varlistentry> diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index db9b4c8edbf..50ada2939b6 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -6006,6 +6006,11 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage) if (service == NULL) return 0; +#ifdef USE_LDAP + if (pg_strncasecmp(service, LDAP_URL, strlen(LDAP_URL)) == 0) + return ldapServiceLookup(service, options, errorMessage); +#endif + /* * First, try the "servicefile" option in connection string. Then, try * the PGSERVICEFILE environment variable. Finally, check diff --git a/src/test/ldap/t/003_ldap_connection_param_lookup.pl b/src/test/ldap/t/003_ldap_connection_param_lookup.pl index 359fc7a998a..67da90578cf 100644 --- a/src/test/ldap/t/003_ldap_connection_param_lookup.pl +++ b/src/test/ldap/t/003_ldap_connection_param_lookup.pl @@ -80,6 +80,9 @@ append_to_file( $srvfile_valid, qq{ [my_srv] ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) + +[my_srv_2] +ldapservice=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase) }); # File defined with no contents, used as default value for @@ -196,6 +199,28 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty"; expected_stdout => qr/definition of service "undefined-service" not found/); + delete $ENV{PGSERVICE}; + + $dummy_node->connect_ok( + "service=ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)", + 'connection with correct "service" string populated with LDAP address', + sql => "SELECT 'connect2_4'", + expected_stdout => qr/connect2_4/); + + $dummy_node->connect_ok( + "postgres://?service=ldap%3A%2F%2Flocalhost%3A$ldap_port%2Fdc%3Dexample%2Cdc%3Dnet%3Fdescription%3Fone%3F%28cn%3Dmydatabase%29", + 'connection with correct "ldapservice" string populated with LDAP address', + sql => "SELECT 'connect2_5'", + expected_stdout => qr/connect2_5/); + + local $ENV{PGSERVICE} = "ldap://localhost:$ldap_port/dc=example,dc=net?description?one?(cn=mydatabase)"; + $dummy_node->connect_ok( + "", + 'connection with correct "service" provided by env var populated with LDAP address', + sql => "SELECT 'connect2_6'", + expected_stdout => qr/connect2_6/); + delete $ENV{PGLDAPSERVICE}; + # Remove default pg_service.conf. unlink($srvfile_default); } -- 2.51.2
