mirror of https://github.com/postgres/postgres
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.netpull/117/head
parent
b90f0b5747
commit
ee4613d2b7
@ -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; |
||||
Loading…
Reference in new issue