From f0e60ee4bc04fd4865dbaf2139d50d6fe71c1bc3 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 15 Sep 2017 11:41:15 -0400 Subject: [PATCH] Add LDAP authentication test suite Like the SSL test suite, this will not be run by default. Reviewed-by: Thomas Munro --- src/test/Makefile | 7 +- src/test/ldap/.gitignore | 2 + src/test/ldap/Makefile | 20 ++++ src/test/ldap/README | 20 ++++ src/test/ldap/authdata.ldif | 32 +++++++ src/test/ldap/t/001_auth.pl | 177 ++++++++++++++++++++++++++++++++++++ 6 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 src/test/ldap/.gitignore create mode 100644 src/test/ldap/Makefile create mode 100644 src/test/ldap/README create mode 100644 src/test/ldap/authdata.ldif create mode 100644 src/test/ldap/t/001_auth.pl diff --git a/src/test/Makefile b/src/test/Makefile index dbfa799a84d..73abf163f1d 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -15,9 +15,10 @@ include $(top_builddir)/src/Makefile.global SUBDIRS = perl regress isolation modules authentication recovery subscription # We don't build or execute examples/, locale/, or thread/ by default, -# but we do want "make clean" etc to recurse into them. Likewise for ssl/, -# because the SSL test suite is not secure to run on a multi-user system. -ALWAYS_SUBDIRS = examples locale thread ssl +# but we do want "make clean" etc to recurse into them. Likewise for +# ldap/ and ssl/, because these test suites are not secure to run on a +# multi-user system. +ALWAYS_SUBDIRS = examples ldap locale thread ssl # We want to recurse to all subdirs for all standard targets, except that # installcheck and install should not recurse into the subdirectory "modules". diff --git a/src/test/ldap/.gitignore b/src/test/ldap/.gitignore new file mode 100644 index 00000000000..871e943d50e --- /dev/null +++ b/src/test/ldap/.gitignore @@ -0,0 +1,2 @@ +# Generated by test suite +/tmp_check/ diff --git a/src/test/ldap/Makefile b/src/test/ldap/Makefile new file mode 100644 index 00000000000..9dd1bbeadea --- /dev/null +++ b/src/test/ldap/Makefile @@ -0,0 +1,20 @@ +#------------------------------------------------------------------------- +# +# Makefile for src/test/ldap +# +# Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/test/ldap/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/test/ldap +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +check: + $(prove_check) + +clean distclean maintainer-clean: + rm -rf tmp_check diff --git a/src/test/ldap/README b/src/test/ldap/README new file mode 100644 index 00000000000..61579f87c6a --- /dev/null +++ b/src/test/ldap/README @@ -0,0 +1,20 @@ +src/test/ldap/README + +Tests for LDAP functionality +============================ + +This directory contains a test suite for LDAP functionality. This +requires a full OpenLDAP installation, including server and client +tools, and is therefore kept separate and not run by default. You +might need to adjust some paths in the test file to have it find +OpenLDAP in a place that hadn't been thought of yet. + +Also, this test suite creates an LDAP server that listens for TCP/IP +connections on localhost without any real access control, so it is not +safe to run this on a system where there might be untrusted local +users. + +Running the tests +================= + + make check diff --git a/src/test/ldap/authdata.ldif b/src/test/ldap/authdata.ldif new file mode 100644 index 00000000000..c0a15daffbc --- /dev/null +++ b/src/test/ldap/authdata.ldif @@ -0,0 +1,32 @@ +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/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl new file mode 100644 index 00000000000..01b90e95a1c --- /dev/null +++ b/src/test/ldap/t/001_auth.pl @@ -0,0 +1,177 @@ +use strict; +use warnings; +use TestLib; +use PostgresNode; +use Test::More tests => 14; + +my ($slapd, $ldap_bin_dir, $ldap_schema_dir); + +$ldap_bin_dir = undef; # usually in PATH + +if ($^O eq 'darwin') +{ + $slapd = '/usr/local/opt/openldap/libexec/slapd'; + $ldap_schema_dir = '/usr/local/etc/openldap/schema'; +} +elsif ($^O eq 'linux') +{ + $slapd = '/usr/sbin/slapd'; + $ldap_schema_dir = '/etc/ldap/schema' if -f '/etc/ldap/schema'; + $ldap_schema_dir = '/etc/openldap/schema' if -f '/etc/openldap/schema'; +} +elsif ($^O eq 'freebsd') +{ + $slapd = '/usr/local/libexec/slapd'; + $ldap_schema_dir = '/usr/local/etc/openldap/schema'; +} + +# 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 = "${TestLib::tmp_check}/openldap-data"; +my $slapd_conf = "${TestLib::tmp_check}/slapd.conf"; +my $slapd_pidfile = "${TestLib::tmp_check}/slapd.pid"; +my $slapd_logfile = "${TestLib::tmp_check}/slapd.log"; +my $ldap_conf = "${TestLib::tmp_check}/ldap.conf"; +my $ldap_server = 'localhost'; +my $ldap_port = int(rand() * 16384) + 49152; +my $ldap_url = "ldap://$ldap_server:$ldap_port"; +my $ldap_basedn = 'dc=example,dc=net'; +my $ldap_rootdn = 'cn=Manager,dc=example,dc=net'; +my $ldap_rootpw = 'secret'; +my $ldap_pwfile = "${TestLib::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 anonymous auth + +database ldif +directory $ldap_datadir + +suffix "dc=example,dc=net" +rootdn "$ldap_rootdn" +rootpw $ldap_rootpw}); + +mkdir $ldap_datadir or die; + +system_or_bail $slapd, '-f', $slapd_conf, '-h', $ldap_url; + +END +{ + kill 'INT', `cat $slapd_pidfile` if -f $slapd_pidfile; +} + +append_to_file($ldap_pwfile, $ldap_rootpw); +chmod 0600, $ldap_pwfile or die; + +$ENV{'LDAPURI'} = $ldap_url; +$ENV{'LDAPBINDDN'} = $ldap_rootdn; + +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 = get_new_node('node'); +$node->init; +$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 +{ + my ($node, $role, $expected_res, $test_name) = @_; + + my $res = $node->psql('postgres', 'SELECT 1', extra_params => [ '-U', $role ]); + is($res, $expected_res, $test_name); +} + +note "simple bind"; + +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="uid=" ldapsuffix=",dc=example,dc=net"}); +$node->reload; + +$ENV{"PGPASSWORD"} = 'wrong'; +test_access($node, 'test0', 2, 'simple bind authentication fails if user not found in LDAP'); +test_access($node, 'test1', 2, 'simple bind authentication fails with wrong password'); +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'simple bind authentication succeeds'); + +note "search+bind"; + +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"}); +$node->reload; + +$ENV{"PGPASSWORD"} = 'wrong'; +test_access($node, 'test0', 2, 'search+bind authentication fails if user not found in LDAP'); +test_access($node, 'test1', 2, 'search+bind authentication fails with wrong password'); +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'search+bind authentication succeeds'); + +note "LDAP URLs"; + +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', qq{local all all ldap ldapurl="$ldap_url/$ldap_basedn?uid?sub"}); +$node->reload; + +$ENV{"PGPASSWORD"} = 'wrong'; +test_access($node, 'test0', 2, 'search+bind with LDAP URL authentication fails if user not found in LDAP'); +test_access($node, 'test1', 2, 'search+bind with LDAP URL authentication fails with wrong password'); +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'search+bind with LDAP URL authentication succeeds'); + +note "search filters"; + +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" ldapsearchfilter="(|(uid=\$username)(mail=\$username))"}); +$node->reload; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'search filter finds by uid'); +$ENV{"PGPASSWORD"} = 'secret2'; +test_access($node, 'test2@example.net', 0, 'search filter finds by mail'); + +note "search filters in LDAP URLs"; + +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', qq{local all all ldap ldapurl="$ldap_url/$ldap_basedn??sub?(|(uid=\$username)(mail=\$username))"}); +$node->reload; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'search filter finds by uid'); +$ENV{"PGPASSWORD"} = 'secret2'; +test_access($node, 'test2@example.net', 0, 'search filter finds by mail'); + +# This is not documented: You can combine ldapurl and other ldap* +# settings. ldapurl is always parsed first, then the other settings +# override. It might be useful in a case like this. +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', qq{local all all ldap ldapurl="$ldap_url/$ldap_basedn??sub" ldapsearchfilter="(|(uid=\$username)(mail=\$username))"}); +$node->reload; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'combined LDAP URL and search filter');