Add a hook to allow modification of the ldapbindpasswd
This patch, mostly the work of John Naylor, provides a hook whereby a
module can modify the ldapbindpasswd before it is handed to the ldap
server. This is similar in concept to the ssl_passphrase_callback
feature, and allows the user not to have to put the cleartext password
in the pg_hba.conf file. A trivial test is added which provides an
example of such a module.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Attachments:
0001-Add-a-password-handling-hook-for-ldapbindpasswd.patchtext/x-patch; charset=UTF-8; name=0001-Add-a-password-handling-hook-for-ldapbindpasswd.patchDownload
From 65af40c5b3c05dcfcb55675dec066fe779382105 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <andrew@dunslane.net>
Date: Mon, 19 Dec 2022 11:19:24 -0500
Subject: [PATCH] Add a password handling hook for ldapbindpasswd
This hook allows for interception of the ldapbindpasswd in the
pg_hba.conf file before it is passed on to the ldap server. The hook
function receives a copy of the password as specified in the config file
and hands back a pointer to a password string. the default handler
simply hands back its input. A test is supplied which implements a
module that makes a trivial (rot13) modifiction of the input. The
benefit here is that the clear text password no longer needs to be
stored in the config file. This is similar in concept to the
ssl_passphrase_callback feature.
John Naylor, with small modifications by Andrew Dunstan.
---
src/backend/libpq/auth.c | 12 +-
src/include/libpq/auth.h | 6 +
src/test/modules/Makefile | 11 +
src/test/modules/ldap_password_func/Makefile | 25 ++
.../modules/ldap_password_func/authdata.ldif | 34 +++
.../ldap_password_func/ldap_password_func.c | 64 +++++
.../modules/ldap_password_func/meson.build | 35 +++
.../t/001_mutated_bindpasswd.pl | 220 ++++++++++++++++++
src/test/modules/meson.build | 1 +
9 files changed, 407 insertions(+), 1 deletion(-)
create mode 100644 src/test/modules/ldap_password_func/Makefile
create mode 100644 src/test/modules/ldap_password_func/authdata.ldif
create mode 100644 src/test/modules/ldap_password_func/ldap_password_func.c
create mode 100644 src/test/modules/ldap_password_func/meson.build
create mode 100644 src/test/modules/ldap_password_func/t/001_mutated_bindpasswd.pl
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index e2f723d188..1ba2fa92ab 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -144,6 +144,10 @@ static int CheckLDAPAuth(Port *port);
#define LDAP_OPT_DIAGNOSTIC_MESSAGE LDAP_OPT_ERROR_STRING
#endif
+/* Default LDAP password mutator hook, can be overridden by a shared library */
+static char* dummy_ldap_password_mutator(char* input);
+auth_password_hook_typ ldap_password_hook = dummy_ldap_password_mutator;
+
#endif /* USE_LDAP */
/*----------------------------------------------------------------
@@ -2370,6 +2374,12 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
#define LDAPS_PORT 636
#endif
+static char*
+dummy_ldap_password_mutator(char * input)
+{
+ return input;
+}
+
/*
* Return a newly allocated C string copied from "pattern" with all
* occurrences of the placeholder "$username" replaced with "user_name".
@@ -2498,7 +2508,7 @@ CheckLDAPAuth(Port *port)
*/
r = ldap_simple_bind_s(ldap,
port->hba->ldapbinddn ? port->hba->ldapbinddn : "",
- port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
+ port->hba->ldapbindpasswd ? ldap_password_hook(port->hba->ldapbindpasswd) : "");
if (r != LDAP_SUCCESS)
{
ereport(LOG,
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index d3c189efe3..c29bd17516 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -28,4 +28,10 @@ extern void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
typedef void (*ClientAuthentication_hook_type) (Port *, int);
extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
+/* hook type for password manglers */
+typedef char* (* auth_password_hook_typ)(char* input);
+
+/* Default LDAP password mutator hook, can be overridden by a shared library */
+extern PGDLLIMPORT auth_password_hook_typ ldap_password_hook;
+
#endif /* AUTH_H */
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index c629cbe383..79e3033ec2 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -42,5 +42,16 @@ else
ALWAYS_SUBDIRS += ssl_passphrase_callback
endif
+# Test runs an LDAP server, so only run if ldap is in PG_TEST_EXTRA
+ifeq ($(with_ldap),yes)
+ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
+SUBDIRS += ldap_password_func
+else
+ALWAYS_SUBDIRS += ldap_password_func
+endif
+else
+ALWAYS_SUBDIRS += ldap_password_func
+endif
+
$(recurse)
$(recurse_always)
diff --git a/src/test/modules/ldap_password_func/Makefile b/src/test/modules/ldap_password_func/Makefile
new file mode 100644
index 0000000000..3324e04248
--- /dev/null
+++ b/src/test/modules/ldap_password_func/Makefile
@@ -0,0 +1,25 @@
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+# ldap_password_func Makefile
+
+export with_ldap
+
+MODULE_big = ldap_password_func
+OBJS = ldap_password_func.o $(WIN32RES)
+PGFILEDESC = "set hook to mutate ldapbindpasswd"
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/ldap_password_func
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+
+
diff --git a/src/test/modules/ldap_password_func/authdata.ldif b/src/test/modules/ldap_password_func/authdata.ldif
new file mode 100644
index 0000000000..62499e803d
--- /dev/null
+++ b/src/test/modules/ldap_password_func/authdata.ldif
@@ -0,0 +1,34 @@
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+dn: dc=example,dc=net
+objectClass: top
+objectClass: dcObject
+objectClass: organization
+dc: example
+o: ExampleCo
+
+dn: uid=test1,dc=example,dc=net
+objectClass: inetOrgPerson
+objectClass: posixAccount
+uid: test1
+sn: Lastname
+givenName: Firstname
+cn: First Test User
+displayName: First Test User
+uidNumber: 101
+gidNumber: 100
+homeDirectory: /home/test1
+mail: test1@example.net
+
+dn: uid=test2,dc=example,dc=net
+objectClass: inetOrgPerson
+objectClass: posixAccount
+uid: test2
+sn: Lastname
+givenName: Firstname
+cn: Second Test User
+displayName: Second Test User
+uidNumber: 102
+gidNumber: 100
+homeDirectory: /home/test2
+mail: test2@example.net
diff --git a/src/test/modules/ldap_password_func/ldap_password_func.c b/src/test/modules/ldap_password_func/ldap_password_func.c
new file mode 100644
index 0000000000..8a8ae40187
--- /dev/null
+++ b/src/test/modules/ldap_password_func/ldap_password_func.c
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * ldap_password_func.c
+ *
+ * Loadable PostgreSQL module to mutate the ldapbindpasswd. This
+ * implementation just hands back the configured password rot13'd.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <stdio.h>
+
+#include "libpq/libpq.h"
+#include "libpq/libpq-be.h"
+#include "libpq/auth.h"
+#include "utils/guc.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+void _PG_fini(void);
+
+/* hook function */
+static char* rot13_passphrase(char *password);
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+ ldap_password_hook = rot13_passphrase;
+}
+
+void
+_PG_fini(void)
+{
+ /* do nothing yet */
+}
+
+static char*
+rot13_passphrase(char *pw)
+{
+ size_t size = strlen(pw) + 1;
+
+ char* new_pw = (char*) palloc(size);
+ strlcpy(new_pw, pw, size);
+ for (char *p = new_pw; *p; p++)
+ {
+ char c = *p;
+
+ if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
+ *p = c + 13;
+ else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
+ *p = c - 13;
+ }
+
+ return new_pw;
+}
diff --git a/src/test/modules/ldap_password_func/meson.build b/src/test/modules/ldap_password_func/meson.build
new file mode 100644
index 0000000000..8ab6d6e8ac
--- /dev/null
+++ b/src/test/modules/ldap_password_func/meson.build
@@ -0,0 +1,35 @@
+if not ldap.found()
+ subdir_done()
+endif
+
+# FIXME: prevent install during main install, but not during test :/
+
+ldap_password_func_sources = files(
+ 'ldap_password_func.c',
+)
+
+if host_system == 'windows'
+ ldap_password_func_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'ldap_password_func',
+ '--FILEDESC', 'set hook to mutate ldapbindpassw',])
+endif
+
+ldap_password_func = shared_module('ldap_password_func',
+ ldap_password_func_sources,
+ kwargs: pg_mod_args + {
+ 'dependencies': [ldap, pg_mod_args['dependencies']],
+ },
+)
+testprep_targets += ldap_password_func
+
+tests += {
+ 'name': 'ldap_password_func',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'tap': {
+ 'tests': [
+ 't/001_mutated_bindpasswd.pl',
+ ],
+ 'env': {'with_ldap': 'yes'}
+ },
+}
diff --git a/src/test/modules/ldap_password_func/t/001_mutated_bindpasswd.pl b/src/test/modules/ldap_password_func/t/001_mutated_bindpasswd.pl
new file mode 100644
index 0000000000..c9d90194a8
--- /dev/null
+++ b/src/test/modules/ldap_password_func/t/001_mutated_bindpasswd.pl
@@ -0,0 +1,220 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use File::Copy;
+use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::Cluster;
+use Test::More;
+
+
+my ($slapd, $ldap_bin_dir, $ldap_schema_dir);
+
+$ldap_bin_dir = undef; # usually in PATH
+
+if ($ENV{with_ldap} ne 'yes')
+{
+ plan skip_all => 'LDAP not supported by this build';
+}
+elsif ($^O eq 'darwin' && -d '/usr/local/opt/openldap')
+{
+ # typical paths for Homebrew
+ $slapd = '/usr/local/opt/openldap/libexec/slapd';
+ $ldap_schema_dir = '/usr/local/etc/openldap/schema';
+}
+elsif ($^O eq 'darwin' && -d '/opt/local/etc/openldap')
+{
+ # typical paths for MacPorts
+ $slapd = '/opt/local/libexec/slapd';
+ $ldap_schema_dir = '/opt/local/etc/openldap/schema';
+}
+elsif ($^O eq 'linux')
+{
+ $slapd = '/usr/sbin/slapd';
+ $ldap_schema_dir = '/etc/ldap/schema' if -d '/etc/ldap/schema';
+ $ldap_schema_dir = '/etc/openldap/schema' if -d '/etc/openldap/schema';
+}
+elsif ($^O eq 'freebsd')
+{
+ $slapd = '/usr/local/libexec/slapd';
+ $ldap_schema_dir = '/usr/local/etc/openldap/schema';
+}
+elsif ($^O eq 'openbsd')
+{
+ $slapd = '/usr/local/libexec/slapd';
+ $ldap_schema_dir = '/usr/local/share/examples/openldap/schema';
+}
+else
+{
+ plan skip_all => "ldap tests not supported on $^O or dependencies not installed";
+}
+
+# make your own edits here
+#$slapd = '';
+#$ldap_bin_dir = '';
+#$ldap_schema_dir = '';
+
+$ENV{PATH} = "$ldap_bin_dir:$ENV{PATH}" if $ldap_bin_dir;
+
+my $ldap_datadir = "${PostgreSQL::Test::Utils::tmp_check}/openldap-data";
+my $slapd_certs = "${PostgreSQL::Test::Utils::tmp_check}/slapd-certs";
+my $slapd_conf = "${PostgreSQL::Test::Utils::tmp_check}/slapd.conf";
+my $slapd_pidfile = "${PostgreSQL::Test::Utils::tmp_check}/slapd.pid";
+my $slapd_logfile = "${PostgreSQL::Test::Utils::log_path}/slapd.log";
+my $ldap_conf = "${PostgreSQL::Test::Utils::tmp_check}/ldap.conf";
+my $ldap_server = 'localhost';
+my $ldap_port = PostgreSQL::Test::Cluster::get_free_port();
+my $ldaps_port = PostgreSQL::Test::Cluster::get_free_port();
+my $ldap_url = "ldap://$ldap_server:$ldap_port";
+my $ldaps_url = "ldaps://$ldap_server:$ldaps_port";
+my $ldap_basedn = 'dc=example,dc=net';
+my $ldap_rootdn = 'cn=Manager,dc=example,dc=net';
+my $clear_ldap_rootpw = "FooBaR1";
+my $rot13_ldap_rootpw = "SbbOnE1";
+my $ldap_pwfile = "${PostgreSQL::Test::Utils::tmp_check}/ldappassword";
+
+note "setting up slapd";
+
+append_to_file(
+ $slapd_conf,
+ qq{include $ldap_schema_dir/core.schema
+include $ldap_schema_dir/cosine.schema
+include $ldap_schema_dir/nis.schema
+include $ldap_schema_dir/inetorgperson.schema
+
+pidfile $slapd_pidfile
+logfile $slapd_logfile
+
+access to *
+ by * read
+ by users auth
+
+database ldif
+directory $ldap_datadir
+
+TLSCACertificateFile $slapd_certs/ca.crt
+TLSCertificateFile $slapd_certs/server.crt
+TLSCertificateKeyFile $slapd_certs/server.key
+
+suffix "dc=example,dc=net"
+rootdn "$ldap_rootdn"
+rootpw $clear_ldap_rootpw});
+
+# don't bother to check the server's cert (though perhaps we should)
+append_to_file(
+ $ldap_conf,
+ qq{TLS_REQCERT never
+});
+
+mkdir $ldap_datadir or die;
+mkdir $slapd_certs or die;
+
+copy "../../ssl/ssl/server_ca.crt", "$slapd_certs/ca.crt"
+ || die "copying ca.crt: $!";
+copy "../../ssl/ssl/server-cn-only.crt", "$slapd_certs/server.crt"
+ || die "copying server.crt: $!";;
+copy "../../ssl/ssl/server-cn-only.key", "$slapd_certs/server.key"
+ || die "copying server.key: $!";;
+
+system_or_bail $slapd, '-f', $slapd_conf, '-h', "$ldap_url $ldaps_url";
+
+END
+{
+ kill 'INT', `cat $slapd_pidfile` if -f $slapd_pidfile;
+}
+
+append_to_file($ldap_pwfile, $clear_ldap_rootpw);
+chmod 0600, $ldap_pwfile or die;
+
+# wait until slapd accepts requests
+my $retries = 0;
+while (1)
+{
+ last
+ if (
+ system_log(
+ "ldapsearch", "-sbase",
+ "-H", $ldap_url,
+ "-b", $ldap_basedn,
+ "-D", $ldap_rootdn,
+ "-y", $ldap_pwfile,
+ "-n", "'objectclass=*'") == 0);
+ die "cannot connect to slapd" if ++$retries >= 300;
+ note "waiting for slapd to accept requests...";
+ Time::HiRes::usleep(1000000);
+}
+
+$ENV{'LDAPURI'} = $ldap_url;
+$ENV{'LDAPBINDDN'} = $ldap_rootdn;
+$ENV{'LDAPCONF'} = $ldap_conf;
+
+note "loading LDAP data";
+
+system_or_bail 'ldapadd', '-x', '-y', $ldap_pwfile, '-f', 'authdata.ldif';
+system_or_bail 'ldappasswd', '-x', '-y', $ldap_pwfile, '-s', 'secret1',
+ 'uid=test1,dc=example,dc=net';
+system_or_bail 'ldappasswd', '-x', '-y', $ldap_pwfile, '-s', 'secret2',
+ 'uid=test2,dc=example,dc=net';
+
+note "setting up PostgreSQL instance";
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+$node->append_conf('postgresql.conf', "log_connections = on\n");
+$node->append_conf('postgresql.conf', "shared_preload_libraries = 'ldap_password_func'");
+$node->start;
+
+$node->safe_psql('postgres', 'CREATE USER test0;');
+$node->safe_psql('postgres', 'CREATE USER test1;');
+$node->safe_psql('postgres', 'CREATE USER "test2@example.net";');
+
+note "running tests";
+
+sub test_access
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($node, $role, $expected_res, $test_name, %params) = @_;
+ my $connstr = "user=$role";
+
+ if ($expected_res eq 0)
+ {
+ $node->connect_ok($connstr, $test_name, %params);
+ }
+ else
+ {
+ # No checks of the error message, only the status code.
+ $node->connect_fails($connstr, $test_name, %params);
+ }
+}
+
+note "use ldapbindpasswd";
+
+$ENV{"PGPASSWORD"} = 'secret1';
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapbinddn="$ldap_rootdn" ldapbindpasswd=wrong}
+);
+$node->restart;
+
+test_access($node, 'test1', 2, 'search+bind authentication fails with wrong ldapbindpasswd');
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapbinddn="$ldap_rootdn" ldapbindpasswd="$clear_ldap_rootpw"}
+);
+$node->restart;
+
+test_access($node, 'test1', 2, 'search+bind authentication fails with clear password');
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapbinddn="$ldap_rootdn" ldapbindpasswd="$rot13_ldap_rootpw"}
+);
+$node->restart;
+
+test_access($node, 'test1', 0, 'search+bind authentication succeeds with rot13ed password');
+
+done_testing();
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 911a768a29..b797afe4f2 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -3,6 +3,7 @@ subdir('commit_ts')
subdir('delay_execution')
subdir('dummy_index_am')
subdir('dummy_seclabel')
+subdir('ldap_password_func')
subdir('libpq_pipeline')
subdir('plsample')
subdir('snapshot_too_old')
--
2.34.1
On 2022-12-19 Mo 11:29, Andrew Dunstan wrote:
This patch, mostly the work of John Naylor, provides a hook whereby a
module can modify the ldapbindpasswd before it is handed to the ldap
server. This is similar in concept to the ssl_passphrase_callback
feature, and allows the user not to have to put the cleartext password
in the pg_hba.conf file. A trivial test is added which provides an
example of such a module.
Updated to take advantage of refactoring of ldap tests.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Attachments:
0001-Add-a-password-handling-hook-for-ldapbindpasswd-v2.patchtext/x-patch; charset=UTF-8; name=0001-Add-a-password-handling-hook-for-ldapbindpasswd-v2.patchDownload
From f9605b4703e970cf4eac9e26513a7dfca4040b4b Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <andrew@dunslane.net>
Date: Mon, 23 Jan 2023 14:05:32 -0500
Subject: [PATCH] Add a password handling hook for ldapbindpasswd
This hook allows for interception of the ldapbindpasswd in the
pg_hba.conf file before it is passed on to the ldap server. The hook
function receives a copy of the password as specified in the config file
and hands back a pointer to a password string. the default handler
simply hands back its input. A test is supplied which implements a
module that makes a trivial (rot13) modifiction of the input. The
benefit here is that the clear text password no longer needs to be
stored in the config file. This is similar in concept to the
ssl_passphrase_callback feature.
John Naylor, test modified by Andrew Dunstan.
---
src/backend/libpq/auth.c | 12 +-
src/include/libpq/auth.h | 6 +
src/test/modules/Makefile | 11 ++
src/test/modules/ldap_password_func/Makefile | 25 +++++
.../ldap_password_func/ldap_password_func.c | 64 +++++++++++
.../modules/ldap_password_func/meson.build | 35 ++++++
.../t/001_mutated_bindpasswd.pl | 103 ++++++++++++++++++
src/test/modules/meson.build | 1 +
8 files changed, 256 insertions(+), 1 deletion(-)
create mode 100644 src/test/modules/ldap_password_func/Makefile
create mode 100644 src/test/modules/ldap_password_func/ldap_password_func.c
create mode 100644 src/test/modules/ldap_password_func/meson.build
create mode 100644 src/test/modules/ldap_password_func/t/001_mutated_bindpasswd.pl
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 25b3a781cd..9463bf5a62 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -144,6 +144,10 @@ static int CheckLDAPAuth(Port *port);
#define LDAP_OPT_DIAGNOSTIC_MESSAGE LDAP_OPT_ERROR_STRING
#endif
+/* Default LDAP password mutator hook, can be overridden by a shared library */
+static char* dummy_ldap_password_mutator(char* input);
+auth_password_hook_typ ldap_password_hook = dummy_ldap_password_mutator;
+
#endif /* USE_LDAP */
/*----------------------------------------------------------------
@@ -2370,6 +2374,12 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
#define LDAPS_PORT 636
#endif
+static char*
+dummy_ldap_password_mutator(char * input)
+{
+ return input;
+}
+
/*
* Return a newly allocated C string copied from "pattern" with all
* occurrences of the placeholder "$username" replaced with "user_name".
@@ -2498,7 +2508,7 @@ CheckLDAPAuth(Port *port)
*/
r = ldap_simple_bind_s(ldap,
port->hba->ldapbinddn ? port->hba->ldapbinddn : "",
- port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
+ port->hba->ldapbindpasswd ? ldap_password_hook(port->hba->ldapbindpasswd) : "");
if (r != LDAP_SUCCESS)
{
ereport(LOG,
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 137bee7c45..9f2f3ed00d 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -28,4 +28,10 @@ extern void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
typedef void (*ClientAuthentication_hook_type) (Port *, int);
extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
+/* hook type for password manglers */
+typedef char* (* auth_password_hook_typ)(char* input);
+
+/* Default LDAP password mutator hook, can be overridden by a shared library */
+extern PGDLLIMPORT auth_password_hook_typ ldap_password_hook;
+
#endif /* AUTH_H */
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index c629cbe383..79e3033ec2 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -42,5 +42,16 @@ else
ALWAYS_SUBDIRS += ssl_passphrase_callback
endif
+# Test runs an LDAP server, so only run if ldap is in PG_TEST_EXTRA
+ifeq ($(with_ldap),yes)
+ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
+SUBDIRS += ldap_password_func
+else
+ALWAYS_SUBDIRS += ldap_password_func
+endif
+else
+ALWAYS_SUBDIRS += ldap_password_func
+endif
+
$(recurse)
$(recurse_always)
diff --git a/src/test/modules/ldap_password_func/Makefile b/src/test/modules/ldap_password_func/Makefile
new file mode 100644
index 0000000000..3324e04248
--- /dev/null
+++ b/src/test/modules/ldap_password_func/Makefile
@@ -0,0 +1,25 @@
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+# ldap_password_func Makefile
+
+export with_ldap
+
+MODULE_big = ldap_password_func
+OBJS = ldap_password_func.o $(WIN32RES)
+PGFILEDESC = "set hook to mutate ldapbindpasswd"
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/ldap_password_func
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+
+
diff --git a/src/test/modules/ldap_password_func/ldap_password_func.c b/src/test/modules/ldap_password_func/ldap_password_func.c
new file mode 100644
index 0000000000..8a8ae40187
--- /dev/null
+++ b/src/test/modules/ldap_password_func/ldap_password_func.c
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * ldap_password_func.c
+ *
+ * Loadable PostgreSQL module to mutate the ldapbindpasswd. This
+ * implementation just hands back the configured password rot13'd.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+#include <stdio.h>
+
+#include "libpq/libpq.h"
+#include "libpq/libpq-be.h"
+#include "libpq/auth.h"
+#include "utils/guc.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+void _PG_fini(void);
+
+/* hook function */
+static char* rot13_passphrase(char *password);
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+ ldap_password_hook = rot13_passphrase;
+}
+
+void
+_PG_fini(void)
+{
+ /* do nothing yet */
+}
+
+static char*
+rot13_passphrase(char *pw)
+{
+ size_t size = strlen(pw) + 1;
+
+ char* new_pw = (char*) palloc(size);
+ strlcpy(new_pw, pw, size);
+ for (char *p = new_pw; *p; p++)
+ {
+ char c = *p;
+
+ if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
+ *p = c + 13;
+ else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
+ *p = c - 13;
+ }
+
+ return new_pw;
+}
diff --git a/src/test/modules/ldap_password_func/meson.build b/src/test/modules/ldap_password_func/meson.build
new file mode 100644
index 0000000000..8ab6d6e8ac
--- /dev/null
+++ b/src/test/modules/ldap_password_func/meson.build
@@ -0,0 +1,35 @@
+if not ldap.found()
+ subdir_done()
+endif
+
+# FIXME: prevent install during main install, but not during test :/
+
+ldap_password_func_sources = files(
+ 'ldap_password_func.c',
+)
+
+if host_system == 'windows'
+ ldap_password_func_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'ldap_password_func',
+ '--FILEDESC', 'set hook to mutate ldapbindpassw',])
+endif
+
+ldap_password_func = shared_module('ldap_password_func',
+ ldap_password_func_sources,
+ kwargs: pg_mod_args + {
+ 'dependencies': [ldap, pg_mod_args['dependencies']],
+ },
+)
+testprep_targets += ldap_password_func
+
+tests += {
+ 'name': 'ldap_password_func',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'tap': {
+ 'tests': [
+ 't/001_mutated_bindpasswd.pl',
+ ],
+ 'env': {'with_ldap': 'yes'}
+ },
+}
diff --git a/src/test/modules/ldap_password_func/t/001_mutated_bindpasswd.pl b/src/test/modules/ldap_password_func/t/001_mutated_bindpasswd.pl
new file mode 100644
index 0000000000..4174292d2d
--- /dev/null
+++ b/src/test/modules/ldap_password_func/t/001_mutated_bindpasswd.pl
@@ -0,0 +1,103 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use File::Copy;
+use FindBin;
+use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::Cluster;
+use Test::More;
+
+use lib "$FindBin::RealBin/../../../ldap";
+use LdapServer;
+
+my ($slapd, $ldap_bin_dir, $ldap_schema_dir);
+
+$ldap_bin_dir = undef; # usually in PATH
+
+if ($ENV{with_ldap} ne 'yes')
+{
+ plan skip_all => 'LDAP not supported by this build';
+}
+elsif ($ENV{PG_TEST_EXTRA} !~ /\bldap\b/)
+{
+ plan skip_all =>
+ 'Potentially unsafe test LDAP not enabled in PG_TEST_EXTRA';
+}
+elsif (!$LdapServer::setup)
+{
+ plan skip_all =>
+ "ldap tests not supported on $^O or dependencies not installed";
+}
+
+my $clear_ldap_rootpw = "FooBaR1";
+my $rot13_ldap_rootpw = "SbbOnE1";
+
+my $ldap = LdapServer->new($clear_ldap_rootpw, 'users'); # no anonymous auth
+$ldap->ldapadd_file("$FindBin::RealBin/../../../ldap/authdata.ldif");
+$ldap->ldapsetpw('uid=test1,dc=example,dc=net', 'secret1');
+
+my ($ldap_server, $ldap_port, $ldap_basedn, $ldap_rootdn) =
+ $ldap->prop(qw(server port basedn rootdn));
+
+
+note "setting up PostgreSQL instance";
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+$node->append_conf('postgresql.conf', "log_connections = on\n");
+$node->append_conf('postgresql.conf', "shared_preload_libraries = 'ldap_password_func'");
+$node->start;
+
+$node->safe_psql('postgres', 'CREATE USER test1;');
+
+note "running tests";
+
+sub test_access
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($node, $role, $expected_res, $test_name, %params) = @_;
+ my $connstr = "user=$role";
+
+ if ($expected_res eq 0)
+ {
+ $node->connect_ok($connstr, $test_name, %params);
+ }
+ else
+ {
+ # No checks of the error message, only the status code.
+ $node->connect_fails($connstr, $test_name, %params);
+ }
+}
+
+note "use ldapbindpasswd";
+
+$ENV{"PGPASSWORD"} = 'secret1';
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapbinddn="$ldap_rootdn" ldapbindpasswd=wrong}
+);
+$node->restart;
+
+test_access($node, 'test1', 2, 'search+bind authentication fails with wrong ldapbindpasswd');
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapbinddn="$ldap_rootdn" ldapbindpasswd="$clear_ldap_rootpw"}
+);
+$node->restart;
+
+test_access($node, 'test1', 2, 'search+bind authentication fails with clear password');
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapbinddn="$ldap_rootdn" ldapbindpasswd="$rot13_ldap_rootpw"}
+);
+$node->restart;
+
+test_access($node, 'test1', 0, 'search+bind authentication succeeds with rot13ed password');
+
+done_testing();
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 1baa6b558d..dcb82ed68f 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -5,6 +5,7 @@ subdir('commit_ts')
subdir('delay_execution')
subdir('dummy_index_am')
subdir('dummy_seclabel')
+subdir('ldap_password_func')
subdir('libpq_pipeline')
subdir('plsample')
subdir('snapshot_too_old')
--
2.34.1
The CFBot says this patch is failing but I find it hard to believe
this is related to this patch...
2023-03-05 20:56:58.705 UTC [33902][client backend]
[pg_regress/btree_index][18/750:0] STATEMENT: ALTER INDEX
btree_part_idx ALTER COLUMN id SET (n_distinct=100);
2023-03-05 20:56:58.709 UTC [33902][client backend]
[pg_regress/btree_index][:0] LOG: disconnection: session time:
0:00:02.287 user=postgres database=regression host=[local]
2023-03-05 20:56:58.710 UTC [33889][client backend]
[pg_regress/join][:0] LOG: disconnection: session time: 0:00:02.289
user=postgres database=regression host=[local]
2023-03-05 20:56:58.749 UTC [33045][postmaster] LOG: server process
(PID 33898) was terminated by signal 6: Abort trap
2023-03-05 20:56:58.749 UTC [33045][postmaster] DETAIL: Failed
process was running: SELECT * FROM writetest;
2023-03-05 20:56:58.749 UTC [33045][postmaster] LOG: terminating any
other active server processes
--
Gregory Stark
As Commitfest Manager
On 2023-03-06 Mo 15:16, Gregory Stark (as CFM) wrote:
The CFBot says this patch is failing but I find it hard to believe
this is related to this patch...2023-03-05 20:56:58.705 UTC [33902][client backend]
[pg_regress/btree_index][18/750:0] STATEMENT: ALTER INDEX
btree_part_idx ALTER COLUMN id SET (n_distinct=100);
2023-03-05 20:56:58.709 UTC [33902][client backend]
[pg_regress/btree_index][:0] LOG: disconnection: session time:
0:00:02.287 user=postgres database=regression host=[local]
2023-03-05 20:56:58.710 UTC [33889][client backend]
[pg_regress/join][:0] LOG: disconnection: session time: 0:00:02.289
user=postgres database=regression host=[local]
2023-03-05 20:56:58.749 UTC [33045][postmaster] LOG: server process
(PID 33898) was terminated by signal 6: Abort trap
2023-03-05 20:56:58.749 UTC [33045][postmaster] DETAIL: Failed
process was running: SELECT * FROM writetest;
2023-03-05 20:56:58.749 UTC [33045][postmaster] LOG: terminating any
other active server processes
Yeah. It says it's fine now. Neither of the two recent failures look
like they have anything to do with this.
cheers
andrew
--
Andrew Dunstan
EDB:https://www.enterprisedb.com
On 2023-01-23 Mo 14:11, Andrew Dunstan wrote:
On 2022-12-19 Mo 11:29, Andrew Dunstan wrote:
This patch, mostly the work of John Naylor, provides a hook whereby a
module can modify the ldapbindpasswd before it is handed to the ldap
server. This is similar in concept to the ssl_passphrase_callback
feature, and allows the user not to have to put the cleartext password
in the pg_hba.conf file. A trivial test is added which provides an
example of such a module.Updated to take advantage of refactoring of ldap tests.
pushed.
cheers
andrew
--
Andrew Dunstan
EDB:https://www.enterprisedb.com
Andrew Dunstan <andrew@dunslane.net> writes:
pushed.
drongo is not happy with this, but I'm kind of baffled as to why:
"c:\\prog\\bf\\root\\HEAD\\pgsql.build\\pgsql.sln" (default target) (1) ->
"c:\\prog\\bf\\root\\HEAD\\pgsql.build\\ldap_password_func.vcxproj" (default target) (60) ->
(Link target) ->
ldap_password_func.obj : error LNK2001: unresolved external symbol __imp_ldap_password_hook [c:\\prog\\bf\\root\\HEAD\\pgsql.build\\ldap_password_func.vcxproj]
.\\Release\\ldap_password_func\\ldap_password_func.dll : fatal error LNK1120: 1 unresolved externals [c:\\prog\\bf\\root\\HEAD\\pgsql.build\\ldap_password_func.vcxproj]
The only obvious explanation for a link problem would be if the
variable's declaration were missing PGDLLIMPORT; but it's not.
regards, tom lane
On 2023-03-15 We 17:50, Tom Lane wrote:
Andrew Dunstan<andrew@dunslane.net> writes:
pushed.
drongo is not happy with this, but I'm kind of baffled as to why:
"c:\\prog\\bf\\root\\HEAD\\pgsql.build\\pgsql.sln" (default target) (1) ->
"c:\\prog\\bf\\root\\HEAD\\pgsql.build\\ldap_password_func.vcxproj" (default target) (60) ->
(Link target) ->
ldap_password_func.obj : error LNK2001: unresolved external symbol __imp_ldap_password_hook [c:\\prog\\bf\\root\\HEAD\\pgsql.build\\ldap_password_func.vcxproj]
.\\Release\\ldap_password_func\\ldap_password_func.dll : fatal error LNK1120: 1 unresolved externals [c:\\prog\\bf\\root\\HEAD\\pgsql.build\\ldap_password_func.vcxproj]The only obvious explanation for a link problem would be if the
variable's declaration were missing PGDLLIMPORT; but it's not.
Ugh. Not batting 1000 today. Will investigate.
cheers
andrew
--
Andrew Dunstan
EDB:https://www.enterprisedb.com
On Wed, Mar 15, 2023 at 06:18:28PM -0400, Andrew Dunstan wrote:
Ugh. Not batting 1000 today. Will investigate.
I have noticed that you forgot a .gitignore in this new path, as well,
so I have taken the liberty to add one ;)
FWIW, I use git-sh-prompt prompt to detect such things quickly.
--
Michael
On 2023-03-15 We 20:39, Michael Paquier wrote:
On Wed, Mar 15, 2023 at 06:18:28PM -0400, Andrew Dunstan wrote:
Ugh. Not batting 1000 today. Will investigate.
I have noticed that you forgot a .gitignore in this new path, as well,
so I have taken the liberty to add one ;)
Thanks. One benefit of moving to meson is that it would make this sort
of thing obsolete, since it doesn't pollute the source directory.
FWIW, I use git-sh-prompt prompt to detect such things quickly.
I used to use a similar gadget, but I found it occasionally adding a
second or two to return the prompt, so I turned it off. In any case, I
normally use vpath builds, so it probably wouldn't have caught this for
me anyway.
cheers
andrew
--
Andrew Dunstan
EDB:https://www.enterprisedb.com
On 2023-03-15 We 18:18, Andrew Dunstan wrote:
On 2023-03-15 We 17:50, Tom Lane wrote:
Andrew Dunstan<andrew@dunslane.net> writes:
pushed.
drongo is not happy with this, but I'm kind of baffled as to why:
"c:\\prog\\bf\\root\\HEAD\\pgsql.build\\pgsql.sln" (default target) (1) ->
"c:\\prog\\bf\\root\\HEAD\\pgsql.build\\ldap_password_func.vcxproj" (default target) (60) ->
(Link target) ->
ldap_password_func.obj : error LNK2001: unresolved external symbol __imp_ldap_password_hook [c:\\prog\\bf\\root\\HEAD\\pgsql.build\\ldap_password_func.vcxproj]
.\\Release\\ldap_password_func\\ldap_password_func.dll : fatal error LNK1120: 1 unresolved externals [c:\\prog\\bf\\root\\HEAD\\pgsql.build\\ldap_password_func.vcxproj]The only obvious explanation for a link problem would be if the
variable's declaration were missing PGDLLIMPORT; but it's not.Ugh. Not batting 1000 today. Will investigate.
The issue was apparently that I had neglected to suppress building the
test module on MSVC if not configured to build with LDAP, since the hook
is only defined in that case. I have pushed a fix for that and drongo is
happy once more.
cheers
andrew
--
Andrew Dunstan
EDB:https://www.enterprisedb.com