diff options
Diffstat (limited to 'src/test/ldap')
-rw-r--r-- | src/test/ldap/.gitignore | 2 | ||||
-rw-r--r-- | src/test/ldap/Makefile | 25 | ||||
-rw-r--r-- | src/test/ldap/README | 54 | ||||
-rw-r--r-- | src/test/ldap/authdata.ldif | 32 | ||||
-rw-r--r-- | src/test/ldap/t/001_auth.pl | 384 |
5 files changed, 497 insertions, 0 deletions
diff --git a/src/test/ldap/.gitignore b/src/test/ldap/.gitignore new file mode 100644 index 0000000..871e943 --- /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 0000000..e5fa3d8 --- /dev/null +++ b/src/test/ldap/Makefile @@ -0,0 +1,25 @@ +#------------------------------------------------------------------------- +# +# Makefile for src/test/ldap +# +# Portions Copyright (c) 1996-2022, 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 + +export with_ldap + +check: + $(prove_check) + +installcheck: + $(prove_installcheck) + +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 0000000..d869f66 --- /dev/null +++ b/src/test/ldap/README @@ -0,0 +1,54 @@ +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 +================= + +NOTE: You must have given the --enable-tap-tests argument to configure. + +Run + make check +or + make installcheck +You can use "make installcheck" if you previously did "make install". +In that case, the code in the installation tree is tested. With +"make check", a temporary installation tree is built from the current +sources and then tested. + +Either way, this test initializes, starts, and stops a test Postgres +cluster, as well as a test LDAP server. + +See src/test/perl/README for more info about running these tests. + +Requirements +============ + +LDAP server and client tools are required. + +Debian/Ubuntu packages: slapd ldap-utils + +RHEL/CentOS/Fedora packages: openldap-clients openldap-servers +(You will already have needed openldap and openldap-devel to build.) + +FreeBSD: openldap-server +(You will already have needed openldap-client to build. If building +from the ports source tree, you want to build net/openldap24-client +and net/openldap24-server.) + +macOS: We do not recommend trying to use the Apple-provided version of +OpenLDAP; it's very old, plus Apple seem to have changed the launching +conventions for slapd. The paths in the test file are set on the +assumption that you installed OpenLDAP using Homebrew or MacPorts. diff --git a/src/test/ldap/authdata.ldif b/src/test/ldap/authdata.ldif new file mode 100644 index 0000000..c0a15da --- /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 0000000..2a7435e --- /dev/null +++ b/src/test/ldap/t/001_auth.pl @@ -0,0 +1,384 @@ + +# Copyright (c) 2021-2022, PostgreSQL Global Development Group + +use strict; +use warnings; +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 '/opt/homebrew/opt/openldap') +{ + # typical paths for Homebrew on ARM + $slapd = '/opt/homebrew/opt/openldap/libexec/slapd'; + $ldap_schema_dir = '/opt/homebrew/etc/openldap/schema'; +} +elsif ($^O eq 'darwin' && -d '/usr/local/opt/openldap') +{ + # typical paths for Homebrew on Intel + $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 $ldap_rootpw = 'secret'; +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 anonymous 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 $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; + +system_or_bail "openssl", "req", "-new", "-nodes", "-keyout", + "$slapd_certs/ca.key", "-x509", "-out", "$slapd_certs/ca.crt", "-subj", + "/CN=CA"; +system_or_bail "openssl", "req", "-new", "-nodes", "-keyout", + "$slapd_certs/server.key", "-out", "$slapd_certs/server.csr", "-subj", + "/CN=server"; +system_or_bail "openssl", "x509", "-req", "-in", "$slapd_certs/server.csr", + "-CA", "$slapd_certs/ca.crt", "-CAkey", "$slapd_certs/ca.key", + "-CAcreateserial", "-out", "$slapd_certs/server.crt"; + +# -s0 prevents log messages ending up in syslog +system_or_bail $slapd, '-f', $slapd_conf,'-s0', '-h', "$ldap_url $ldaps_url"; + +END +{ + kill 'INT', `cat $slapd_pidfile` if -f $slapd_pidfile; +} + +append_to_file($ldap_pwfile, $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->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 "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->restart; + +$ENV{"PGPASSWORD"} = 'wrong'; +test_access( + $node, 'test0', 2, + 'simple bind authentication fails if user not found in LDAP', + log_unlike => [qr/connection authenticated:/]); +test_access( + $node, 'test1', 2, + 'simple bind authentication fails with wrong password', + log_unlike => [qr/connection authenticated:/]); + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access( + $node, 'test1', 0, + 'simple bind authentication succeeds', + log_like => [ + qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/ + ],); + +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->restart; + +$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', + log_like => [ + qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/ + ],); + +note "multiple servers"; + +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', + qq{local all all ldap ldapserver="$ldap_server $ldap_server" ldapport=$ldap_port ldapbasedn="$ldap_basedn"} +); +$node->restart; + +$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->restart; + +$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->restart; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access( + $node, 'test1', 0, + 'search filter finds by uid', + log_like => [ + qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/ + ],); +$ENV{"PGPASSWORD"} = 'secret2'; +test_access( + $node, + 'test2@example.net', + 0, + 'search filter finds by mail', + log_like => [ + qr/connection authenticated: identity="uid=test2,dc=example,dc=net" method=ldap/ + ],); + +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->restart; + +$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->restart; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'combined LDAP URL and search filter'); + +note "diagnostic message"; + +# note bad ldapprefix with a question mark that triggers a diagnostic message +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=""} +); +$node->restart; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 2, 'any attempt fails due to bad search pattern'); + +note "TLS"; + +# request StartTLS with ldaptls=1 +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)" ldaptls=1} +); +$node->restart; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'StartTLS'); + +# request LDAPS with ldapscheme=ldaps +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', + qq{local all all ldap ldapserver=$ldap_server ldapscheme=ldaps ldapport=$ldaps_port ldapbasedn="$ldap_basedn" ldapsearchfilter="(uid=\$username)"} +); +$node->restart; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'LDAPS'); + +# request LDAPS with ldapurl=ldaps://... +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', + qq{local all all ldap ldapurl="$ldaps_url/$ldap_basedn??sub?(uid=\$username)"} +); +$node->restart; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 0, 'LDAPS with URL'); + +# bad combination of LDAPS and StartTLS +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', + qq{local all all ldap ldapurl="$ldaps_url/$ldap_basedn??sub?(uid=\$username)" ldaptls=1} +); +$node->restart; + +$ENV{"PGPASSWORD"} = 'secret1'; +test_access($node, 'test1', 2, 'bad combination of LDAPS and StartTLS'); + +done_testing(); |