mirror of
https://github.com/postgres/postgres.git
synced 2025-05-20 05:13:53 +03:00
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
321 lines
7.2 KiB
Perl
321 lines
7.2 KiB
Perl
|
|
############################################################################
|
|
#
|
|
# 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;
|