1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-20 05:13:53 +03:00

Restructure Ldap TAP test

The code for detecting the Ldap installation and setting up a test
server is broken out into a reusable module that can be used  for
additional tests to be added in later patches.

Discussion: https://postgr.es/m/06005bfb-0fd7-9d08-e0e5-440f277b73b4@dunslane.net
This commit is contained in:
Andrew Dunstan 2023-01-23 08:31:37 -05:00
parent b90f0b5747
commit ee4613d2b7
2 changed files with 339 additions and 143 deletions

320
src/test/ldap/LdapServer.pm Normal file
View File

@ -0,0 +1,320 @@
############################################################################
#
# LdapServer.pm
#
# Module to set up an LDAP server for testing pg_hba.conf ldap authentication
#
# Copyright (c) 2023, PostgreSQL Global Development Group
#
############################################################################
=pod
=head1 NAME
LdapServer - class for an LDAP server for testing pg_hba.conf authentication
=head1 SYNOPSIS
use LdapServer;
# have we found openldap binaies suitable for setting up a server?
my $ldap_binaries_found = $LdapServer::setup;
# create a server with the given root password and auth type
# (users or anonymous)
my $server = LdapServer->new($root_password, $auth_type);
# Add the contents of an LDIF file to the server
$server->ldapadd_file ($path_to_ldif_data);
# set the Ldap password for a user
$server->ldapsetpw($user, $password);
# get details of some settings for the server
my @properties = $server->prop($propname1, $propname2, ...);
=head1 DESCRIPTION
LdapServer tests in its INIT phase for the presence of suitable openldap
binaries. Its constructor method sets up and runs an LDAP server, and any
servers that are set up are terminated during its END phase.
=cut
package LdapServer;
use strict;
use warnings;
use PostgreSQL::Test::Utils;
use Test::More;
use File::Copy;
use File::Basename;
# private variables
my ($slapd, $ldap_schema_dir, @servers);
# visible variable
our ($setup);
INIT
{
$setup = 1;
if ($^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
{
$setup = 0;
}
}
END
{
foreach my $server (@servers)
{
next unless -f $server->{pidfile};
my $pid = slurp_file($server->{pidfile});
chomp $pid;
kill 'INT', $pid;
}
}
=pod
=head1 METHODS
=over
=item LdapServer->new($rootpw, $auth_type)
Create a new LDAP server.
The rootpw can be used when authenticating with the ldapbindpasswd option.
The auth_type is either 'users' or 'anonymous'.
=back
=cut
sub new
{
die "no suitable binaries found" unless $setup;
my $class = shift;
my $rootpw = shift;
my $authtype = shift; # 'users' or 'anonymous'
my $testname = basename((caller)[1], '.pl');
my $self = {};
my $test_temp = PostgreSQL::Test::Utils::tempdir("ldap-$testname");
my $ldap_datadir = "$test_temp/openldap-data";
my $slapd_certs = "$test_temp/slapd-certs";
my $slapd_pidfile = "$test_temp/slapd.pid";
my $slapd_conf = "$test_temp/slapd.conf";
my $slapd_logfile =
"${PostgreSQL::Test::Utils::log_path}/slapd-$testname.log";
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 = $rootpw;
my $ldap_pwfile = "$test_temp/ldappassword";
(my $conf = <<"EOC") =~ s/^\t\t//gm;
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 $authtype 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"
EOC
append_to_file($slapd_conf, $conf);
mkdir $ldap_datadir or die "making $ldap_datadir: $!";
mkdir $slapd_certs or die "making $slapd_certs: $!";
my $certdir = dirname(__FILE__) . "/../ssl/ssl";
copy "$certdir/server_ca.crt", "$slapd_certs/ca.crt"
|| die "copying ca.crt: $!";
# check we actually have the file, as copy() sometimes gives a false success
-f "$slapd_certs/ca.crt" || die "copying ca.crt (error unknown)";
copy "$certdir/server-cn-only.crt", "$slapd_certs/server.crt"
|| die "copying server.crt: $!";
copy "$certdir/server-cn-only.key", "$slapd_certs/server.key"
|| die "copying server.key: $!";
append_to_file($ldap_pwfile, $ldap_rootpw);
chmod 0600, $ldap_pwfile or die "chmod on $ldap_pwfile";
system_or_bail $slapd, '-f', $slapd_conf, '-h', "$ldap_url $ldaps_url";
# 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);
}
$self->{pidfile} = $slapd_pidfile;
$self->{pwfile} = $ldap_pwfile;
$self->{url} = $ldap_url;
$self->{s_url} = $ldaps_url;
$self->{server} = $ldap_server;
$self->{port} = $ldap_port;
$self->{s_port} = $ldaps_port;
$self->{basedn} = $ldap_basedn;
$self->{rootdn} = $ldap_rootdn;
bless $self, $class;
push @servers, $self;
return $self;
}
# private routine to set up the environment for methods below
sub _ldapenv
{
my $self = shift;
my %env = %ENV;
$env{'LDAPURI'} = $self->{url};
$env{'LDAPBINDDN'} = $self->{rootdn};
return %env;
}
=pod
=over
=item ldap_add(filename)
filename is the path to a file containing LDIF data which is added to the LDAP
server.
=back
=cut
sub ldapadd_file
{
my $self = shift;
my $file = shift;
local %ENV = $self->_ldapenv;
system_or_bail 'ldapadd', '-x', '-y', $self->{pwfile}, '-f', $file;
}
=pod
=over
=item ldapsetpw(user, password)
Set the user's password in the LDAP server
=back
=cut
sub ldapsetpw
{
my $self = shift;
my $user = shift;
my $password = shift;
local %ENV = $self->_ldapenv;
system_or_bail 'ldappasswd', '-x', '-y', $self->{pwfile}, '-s', $password,
$user;
}
=pod
=over
=item prop(name1, ...)
Returns the list of values for the specified properties of the instance, such
as 'url', 'port', 'basedn'.
=back
=cut
sub prop
{
my $self = shift;
my @settings;
push @settings, $self->{$_} foreach (@_);
return @settings;
}
1;

View File

@ -3,170 +3,46 @@
use strict;
use warnings;
use FindBin;
use lib "$FindBin::RealBin/..";
use File::Copy;
use File::Basename;
use LdapServer;
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 ($ENV{PG_TEST_EXTRA} !~ /\bldap\b/)
{
plan skip_all => 'Potentially unsafe test LDAP not enabled in PG_TEST_EXTRA';
plan skip_all =>
'Potentially unsafe test LDAP not enabled in PG_TEST_EXTRA';
}
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
elsif (!$LdapServer::setup)
{
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 = '';
note "setting up LDAP server";
$ENV{PATH} = "$ldap_bin_dir:$ENV{PATH}" if $ldap_bin_dir;
my $ldap_rootpw = 'secret';
my $ldap = LdapServer->new($ldap_rootpw, 'anonymous'); # use anonymous auth
$ldap->ldapadd_file('authdata.ldif');
$ldap->ldapsetpw('uid=test1,dc=example,dc=net', 'secret1');
$ldap->ldapsetpw('uid=test2,dc=example,dc=net', 'secret2');
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});
my ($ldap_server, $ldap_port, $ldaps_port, $ldap_url,
$ldaps_url, $ldap_basedn, $ldap_rootdn
) = $ldap->prop(qw(server port s_port url s_url basedn rootdn));
# 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;
# use existing certs from nearby SSL test suite
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, $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';
$ENV{'LDAPTLS_REQCERT'} = "never";
note "setting up PostgreSQL instance";