mirror of https://github.com/postgres/postgres
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
This commit is contained in:
parent
e3ac85014e
commit
419a8dd814
|
@ -144,6 +144,10 @@ static int CheckLDAPAuth(Port *port);
|
||||||
#define LDAP_OPT_DIAGNOSTIC_MESSAGE LDAP_OPT_ERROR_STRING
|
#define LDAP_OPT_DIAGNOSTIC_MESSAGE LDAP_OPT_ERROR_STRING
|
||||||
#endif
|
#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 */
|
#endif /* USE_LDAP */
|
||||||
|
|
||||||
/*----------------------------------------------------------------
|
/*----------------------------------------------------------------
|
||||||
|
@ -2370,6 +2374,12 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
|
||||||
#define LDAPS_PORT 636
|
#define LDAPS_PORT 636
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static char *
|
||||||
|
dummy_ldap_password_mutator(char *input)
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return a newly allocated C string copied from "pattern" with all
|
* Return a newly allocated C string copied from "pattern" with all
|
||||||
* occurrences of the placeholder "$username" replaced with "user_name".
|
* occurrences of the placeholder "$username" replaced with "user_name".
|
||||||
|
@ -2498,7 +2508,7 @@ CheckLDAPAuth(Port *port)
|
||||||
*/
|
*/
|
||||||
r = ldap_simple_bind_s(ldap,
|
r = ldap_simple_bind_s(ldap,
|
||||||
port->hba->ldapbinddn ? port->hba->ldapbinddn : "",
|
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)
|
if (r != LDAP_SUCCESS)
|
||||||
{
|
{
|
||||||
ereport(LOG,
|
ereport(LOG,
|
||||||
|
|
|
@ -28,4 +28,10 @@ extern void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
|
||||||
typedef void (*ClientAuthentication_hook_type) (Port *, int);
|
typedef void (*ClientAuthentication_hook_type) (Port *, int);
|
||||||
extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
|
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 */
|
#endif /* AUTH_H */
|
||||||
|
|
|
@ -42,5 +42,16 @@ else
|
||||||
ALWAYS_SUBDIRS += ssl_passphrase_callback
|
ALWAYS_SUBDIRS += ssl_passphrase_callback
|
||||||
endif
|
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)
|
||||||
$(recurse_always)
|
$(recurse_always)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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'}
|
||||||
|
},
|
||||||
|
}
|
|
@ -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();
|
|
@ -5,6 +5,7 @@ subdir('commit_ts')
|
||||||
subdir('delay_execution')
|
subdir('delay_execution')
|
||||||
subdir('dummy_index_am')
|
subdir('dummy_index_am')
|
||||||
subdir('dummy_seclabel')
|
subdir('dummy_seclabel')
|
||||||
|
subdir('ldap_password_func')
|
||||||
subdir('libpq_pipeline')
|
subdir('libpq_pipeline')
|
||||||
subdir('plsample')
|
subdir('plsample')
|
||||||
subdir('snapshot_too_old')
|
subdir('snapshot_too_old')
|
||||||
|
|
Loading…
Reference in New Issue