1# This module sets up a test server, for the SSL regression tests.
2#
3# The server is configured as follows:
4#
5# - SSL enabled, with the server certificate specified by argument to
6#   switch_server_cert function.
7# - ssl/root+client_ca.crt as the CA root for validating client certs.
8# - reject non-SSL connections
9# - a database called trustdb that lets anyone in
10# - another database called certdb that uses certificate authentication, ie.
11#   the client must present a valid certificate signed by the client CA
12# - two users, called ssltestuser and anotheruser.
13#
14# The server is configured to only accept connections from localhost. If you
15# want to run the client from another host, you'll have to configure that
16# manually.
17package SSLServer;
18
19use strict;
20use warnings;
21use PostgresNode;
22use TestLib;
23use File::Basename;
24use File::Copy;
25use Test::More;
26
27use Exporter 'import';
28our @EXPORT = qw(
29  configure_test_server_for_ssl
30  switch_server_cert
31  test_connect_fails
32  test_connect_ok
33);
34
35# Define a couple of helper functions to test connecting to the server.
36
37# The first argument is a base connection string to use for connection.
38# The second argument is a complementary connection string.
39sub test_connect_ok
40{
41	my ($common_connstr, $connstr, $test_name) = @_;
42
43	my $cmd = [
44		'psql', '-X', '-A', '-t', '-c',
45		"SELECT \$\$connected with $connstr\$\$",
46		'-d', "$common_connstr $connstr"
47	];
48
49	command_ok($cmd, $test_name);
50	return;
51}
52
53sub test_connect_fails
54{
55	my ($common_connstr, $connstr, $expected_stderr, $test_name) = @_;
56
57	my $cmd = [
58		'psql', '-X', '-A', '-t', '-c',
59		"SELECT \$\$connected with $connstr\$\$",
60		'-d', "$common_connstr $connstr"
61	];
62
63	command_fails_like($cmd, $expected_stderr, $test_name);
64	return;
65}
66
67# Copy a set of files, taking into account wildcards
68sub copy_files
69{
70	my $orig = shift;
71	my $dest = shift;
72
73	my @orig_files = glob $orig;
74	foreach my $orig_file (@orig_files)
75	{
76		my $base_file = basename($orig_file);
77		copy($orig_file, "$dest/$base_file")
78		  or die "Could not copy $orig_file to $dest";
79	}
80	return;
81}
82
83sub configure_test_server_for_ssl
84{
85	my ($node, $serverhost, $authmethod, $password, $password_enc) = @_;
86
87	my $pgdata = $node->data_dir;
88
89	# Create test users and databases
90	$node->psql('postgres', "CREATE USER ssltestuser");
91	$node->psql('postgres', "CREATE USER anotheruser");
92	$node->psql('postgres', "CREATE DATABASE trustdb");
93	$node->psql('postgres', "CREATE DATABASE certdb");
94
95	# Update password of each user as needed.
96	if (defined($password))
97	{
98		$node->psql('postgres',
99			"SET password_encryption='$password_enc'; ALTER USER ssltestuser PASSWORD '$password';"
100		);
101		$node->psql('postgres',
102			"SET password_encryption='$password_enc'; ALTER USER anotheruser PASSWORD '$password';"
103		);
104	}
105
106	# enable logging etc.
107	open my $conf, '>>', "$pgdata/postgresql.conf";
108	print $conf "fsync=off\n";
109	print $conf "log_connections=on\n";
110	print $conf "log_hostname=on\n";
111	print $conf "listen_addresses='$serverhost'\n";
112	print $conf "log_statement=all\n";
113
114	# enable SSL and set up server key
115	print $conf "include 'sslconfig.conf'";
116
117	close $conf;
118
119	# ssl configuration will be placed here
120	open my $sslconf, '>', "$pgdata/sslconfig.conf";
121	close $sslconf;
122
123	# Copy all server certificates and keys, and client root cert, to the data dir
124	copy_files("ssl/server-*.crt", $pgdata);
125	copy_files("ssl/server-*.key", $pgdata);
126	chmod(0600, glob "$pgdata/server-*.key") or die $!;
127	copy_files("ssl/root+client_ca.crt", $pgdata);
128	copy_files("ssl/root_ca.crt",        $pgdata);
129	copy_files("ssl/root+client.crl",    $pgdata);
130
131	# Stop and restart server to load new listen_addresses.
132	$node->restart;
133
134	# Change pg_hba after restart because hostssl requires ssl=on
135	configure_hba_for_ssl($node, $serverhost, $authmethod);
136
137	return;
138}
139
140# Change the configuration to use given server cert file, and reload
141# the server so that the configuration takes effect.
142sub switch_server_cert
143{
144	my $node     = $_[0];
145	my $certfile = $_[1];
146	my $cafile   = $_[2] || "root+client_ca";
147	my $pgdata   = $node->data_dir;
148
149	open my $sslconf, '>', "$pgdata/sslconfig.conf";
150	print $sslconf "ssl=on\n";
151	print $sslconf "ssl_ca_file='$cafile.crt'\n";
152	print $sslconf "ssl_cert_file='$certfile.crt'\n";
153	print $sslconf "ssl_key_file='$certfile.key'\n";
154	print $sslconf "ssl_crl_file='root+client.crl'\n";
155	close $sslconf;
156
157	$node->restart;
158	return;
159}
160
161sub configure_hba_for_ssl
162{
163	my ($node, $serverhost, $authmethod) = @_;
164	my $pgdata = $node->data_dir;
165
166	# Only accept SSL connections from localhost. Our tests don't depend on this
167	# but seems best to keep it as narrow as possible for security reasons.
168	#
169	# When connecting to certdb, also check the client certificate.
170	open my $hba, '>', "$pgdata/pg_hba.conf";
171	print $hba
172	  "# TYPE  DATABASE        USER            ADDRESS                 METHOD\n";
173	print $hba
174	  "hostssl trustdb         all             $serverhost/32            $authmethod\n";
175	print $hba
176	  "hostssl trustdb         all             ::1/128                 $authmethod\n";
177	print $hba
178	  "hostssl certdb          all             $serverhost/32            cert\n";
179	print $hba
180	  "hostssl certdb          all             ::1/128                 cert\n";
181	close $hba;
182	return;
183}
184
1851;
186