aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Dunstan <andrew@dunslane.net>2023-03-15 16:37:28 -0400
committerAndrew Dunstan <andrew@dunslane.net>2023-03-15 16:37:28 -0400
commit419a8dd8142afef790dafd91ba39afac2ca48aaf (patch)
treeaa4e854f657f52a0a02ac1092c63533f33db659c
parente3ac85014eb280ee2e82b36dc3be1b62c838b3e4 (diff)
downloadpostgresql-419a8dd8142afef790dafd91ba39afac2ca48aaf.tar.gz
postgresql-419a8dd8142afef790dafd91ba39afac2ca48aaf.zip
Add a hook for modifying the ldapbind password
The hook can be installed by a shared_preload library. A similar mechanism could be used for radius paswords, for example, and the type name auth_password_hook_typ has been shosen with that in mind. John Naylor and Andrew Dunstan Discussion: https://postgr.es/m/469b06ed-69de-ba59-c13a-91d2372e52a9@dunslane.net
-rw-r--r--src/backend/libpq/auth.c12
-rw-r--r--src/include/libpq/auth.h6
-rw-r--r--src/test/modules/Makefile11
-rw-r--r--src/test/modules/ldap_password_func/Makefile25
-rw-r--r--src/test/modules/ldap_password_func/ldap_password_func.c65
-rw-r--r--src/test/modules/ldap_password_func/meson.build33
-rw-r--r--src/test/modules/ldap_password_func/t/001_mutated_bindpasswd.pl103
-rw-r--r--src/test/modules/meson.build1
8 files changed, 255 insertions, 1 deletions
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 25b3a781cdc..bc0cf26b122 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 137bee7c455..9916c99df17 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 c629cbe3830..79e3033ec28 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 00000000000..3324e04248d
--- /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 00000000000..4d980d28b1e
--- /dev/null
+++ b/src/test/modules/ldap_password_func/ldap_password_func.c
@@ -0,0 +1,65 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 00000000000..653a5e94792
--- /dev/null
+++ b/src/test/modules/ldap_password_func/meson.build
@@ -0,0 +1,33 @@
+if not ldap.found()
+ subdir_done()
+endif
+
+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']],
+ },
+)
+test_install_libs += 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 00000000000..4174292d2df
--- /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 1baa6b558d1..dcb82ed68f4 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')