v7-0002-Add-enc-tests.patch

text/x-patch

Filename: v7-0002-Add-enc-tests.patch
Type: text/x-patch
Part: 1
Message: Re: Experiments with Postgres and SSL

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v7-0002
Subject: Add enc tests
File+
src/test/libpq_encryption/Makefile 25 0
src/test/libpq_encryption/meson.build 19 0
src/test/libpq_encryption/README 23 0
src/test/libpq_encryption/t/001_negotiate_encryption.pl 294 0
src/test/perl/PostgreSQL/Test/Kerberos.pm 3 3
From 9df81d1607ad9a94ddfa48132bfd3f7b7c49f120 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 10 Jan 2024 09:47:46 +0200
Subject: [PATCH v7 02/12] Add enc tests

---
 src/test/libpq_encryption/Makefile            |  25 ++
 src/test/libpq_encryption/README              |  23 ++
 src/test/libpq_encryption/meson.build         |  19 ++
 .../t/001_negotiate_encryption.pl             | 294 ++++++++++++++++++
 src/test/perl/PostgreSQL/Test/Kerberos.pm     |   6 +-
 5 files changed, 364 insertions(+), 3 deletions(-)
 create mode 100644 src/test/libpq_encryption/Makefile
 create mode 100644 src/test/libpq_encryption/README
 create mode 100644 src/test/libpq_encryption/meson.build
 create mode 100644 src/test/libpq_encryption/t/001_negotiate_encryption.pl

diff --git a/src/test/libpq_encryption/Makefile b/src/test/libpq_encryption/Makefile
new file mode 100644
index 00000000000..710929c4cce
--- /dev/null
+++ b/src/test/libpq_encryption/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/libpq_encryption
+#
+# Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ldap/libpq_encryption
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/libpq_encryption
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export  OPENSSL with_ssl with_gssapi with_krb_srvnam 
+
+check:
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
+
+clean distclean:
+	rm -rf tmp_check
diff --git a/src/test/libpq_encryption/README b/src/test/libpq_encryption/README
new file mode 100644
index 00000000000..66430b54316
--- /dev/null
+++ b/src/test/libpq_encryption/README
@@ -0,0 +1,23 @@
+src/test/libpq_encryption/README
+
+Tests for negotiating network encryption method
+===============================================
+
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+    make check PG_TEST_EXTRA=libpq_encryption
+
+XXX 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.
+
+XXX 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.
diff --git a/src/test/libpq_encryption/meson.build b/src/test/libpq_encryption/meson.build
new file mode 100644
index 00000000000..04f479e9fe7
--- /dev/null
+++ b/src/test/libpq_encryption/meson.build
@@ -0,0 +1,19 @@
+# Copyright (c) 2022-2024, PostgreSQL Global Development Group
+
+tests += {
+  'name': 'ldap',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'tap': {
+    'tests': [
+      't/001_auth.pl',
+      't/002_bindpasswd.pl',
+    ],
+    'env': {
+      'with_ssl': ssl_library,
+      'OPENSSL': openssl.found() ? openssl.path() : '',
+      'with_gssapi': gssapi.found() ? 'yes' : 'no',
+      'with_krb_srvnam': 'postgres',
+    },
+  },
+}
diff --git a/src/test/libpq_encryption/t/001_negotiate_encryption.pl b/src/test/libpq_encryption/t/001_negotiate_encryption.pl
new file mode 100644
index 00000000000..7a80fe90f56
--- /dev/null
+++ b/src/test/libpq_encryption/t/001_negotiate_encryption.pl
@@ -0,0 +1,294 @@
+
+# Copyright (c) 2021-2024, PostgreSQL Global Development Group
+
+# Test negotiation of SSL and GSSAPI encryption
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Kerberos;
+use File::Basename;
+use File::Copy;
+use Test::More;
+
+if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\blibpq_encryption\b/)
+{
+	plan skip_all =>
+	  'Potentially unsafe test libpq_encryption not enabled in PG_TEST_EXTRA';
+}
+
+my $host = 'enc-test-localhost.postgresql.example.com';
+my $hostaddr = '127.0.0.1';
+my $servercidr = '127.0.0.1/32';
+
+note "setting up PostgreSQL instance";
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+$node->append_conf(
+	'postgresql.conf', qq{
+listen_addresses = '$hostaddr'
+log_connections = on
+lc_messages = 'C'
+});
+my $pgdata = $node->data_dir;
+
+my $dbname = 'postgres';
+my $username = 'enctest';
+my $application = '001_negotiate_encryption.pl';
+
+my $gssuser_password = 'secret1';
+
+my $krb;
+
+my $ssl_supported = $ENV{with_ssl} eq 'openssl';
+my $gss_supported = $ENV{with_gssapi} eq 'yes';
+
+if ($ENV{with_gssapi} eq 'yes')
+{
+	note "setting up Kerberos";
+
+	my $realm = 'EXAMPLE.COM';
+	$krb = PostgreSQL::Test::Kerberos->new($host, $hostaddr, $realm);
+	$node->append_conf('postgresql.conf', "krb_server_keyfile = '$krb->{keytab}'\n");
+}
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+	my $certdir = dirname(__FILE__) . "/../../ssl/ssl";
+
+	copy "$certdir/server-cn-only.crt", "$pgdata/server.crt"
+	  || die "copying server.crt: $!";
+	copy "$certdir/server-cn-only.key", "$pgdata/server.key"
+	  || die "copying server.key: $!";
+	chmod(0600, "$pgdata/server.key");
+
+	# Start with SSL disabled.
+	$node->append_conf('postgresql.conf', "ssl = off\n");
+}
+
+$node->start;
+
+$node->safe_psql('postgres', 'CREATE USER localuser;');
+$node->safe_psql('postgres', 'CREATE USER testuser;');
+$node->safe_psql('postgres', 'CREATE USER ssluser;');
+$node->safe_psql('postgres', 'CREATE USER nossluser;');
+$node->safe_psql('postgres', 'CREATE USER gssuser;');
+$node->safe_psql('postgres', 'CREATE USER nogssuser;');
+
+my $unixdir = $node->safe_psql('postgres', 'SHOW unix_socket_directories;');
+chomp($unixdir);
+
+$node->safe_psql('postgres', q{
+CREATE FUNCTION current_enc() RETURNS text LANGUAGE plpgsql AS $$
+DECLARE
+  ssl_in_use bool;
+  gss_in_use bool;
+BEGIN
+  ssl_in_use = (SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid());
+  gss_in_use = (SELECT encrypted FROM pg_stat_gssapi WHERE pid = pg_backend_pid());
+
+  raise log 'ssl %  gss %', ssl_in_use, gss_in_use;
+
+  IF ssl_in_use AND gss_in_use THEN
+    RETURN 'ssl+gss';   -- shouldn't happen
+  ELSIF ssl_in_use THEN
+    RETURN 'ssl';
+  ELSIF gss_in_use THEN
+    RETURN 'gss';
+  ELSE
+    RETURN 'plain';
+  END IF;
+END;
+$$;
+});
+
+# Only accept SSL connections from $servercidr. Our tests don't depend on this
+# but seems best to keep it as narrow as possible for security reasons.
+#
+# When connecting to certdb, also check the client certificate.
+open my $hba, '>', "$pgdata/pg_hba.conf";
+print $hba qq{
+# TYPE        DATABASE        USER            ADDRESS                 METHOD             OPTIONS
+local         postgres        localuser                               trust
+host          postgres        testuser        $servercidr             trust
+hostnossl     postgres        nossluser       $servercidr             trust
+hostnogssenc  postgres        nogssuser       $servercidr             trust
+};
+
+print $hba qq{
+hostssl       postgres        ssluser         $servercidr             trust
+} if ($ENV{with_ssl} eq 'openssl');
+
+print $hba qq{
+hostgssenc    postgres        gssuser         $servercidr             trust
+} if ($ENV{with_gssapi} eq 'yes');
+close $hba;
+$node->reload;
+
+note "running tests";
+
+sub connect_test
+{
+	local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+	my ($node, $connstr, $expected_enc, @expect_log_msgs)
+	  = @_;
+
+	my %params = ();
+
+	if (@expect_log_msgs)
+	{
+		# Match every message literally.
+		my @regexes = map { qr/\Q$_\E/ } @expect_log_msgs;
+
+		$params{log_like} = \@regexes;
+	}
+
+	my $test_name = "'$connstr' -> $expected_enc";
+
+	my $connstr_full = "";
+	$connstr_full .= "dbname=postgres " unless $connstr =~ m/dbname=/;
+	$connstr_full .= "host=$host hostaddr=$hostaddr " unless $connstr =~ m/host=/;
+	$connstr_full .= $connstr;
+
+	if ($expected_enc eq "fail")
+	{
+		$node->connect_fails($connstr_full, $test_name, %params);
+	}
+	else
+	{
+		$params{sql} = "SELECT current_enc()";
+		$params{expected_stdout} = qr/^$expected_enc$/;;
+
+		$node->connect_ok($connstr_full, $test_name, %params);
+	}
+}
+
+
+# First test with SSL disabled in the server
+connect_test($node, 'user=testuser sslmode=disable', 'plain');
+connect_test($node, 'user=testuser sslmode=allow', 'plain');
+connect_test($node, 'user=testuser sslmode=prefer', 'plain');
+connect_test($node, 'user=testuser sslmode=require', 'fail');
+
+# Test GSSAPI
+SKIP:
+{
+	skip "GSSAPI/Kerberos not supported by this build" unless $ENV{with_gssapi} eq 'yes';
+
+	# No ticket
+	connect_test($node, 'user=testuser sslmode=disable gssencmode=require', 'fail');
+
+	$krb->create_principle('gssuser', $gssuser_password);
+	$krb->create_ticket('gssuser', $gssuser_password);
+
+	connect_test($node, 'user=testuser sslmode=disable gssencmode=disable', 'plain');
+	connect_test($node, 'user=testuser sslmode=disable gssencmode=prefer', 'gss');
+	connect_test($node, 'user=testuser sslmode=disable gssencmode=require', 'gss');
+
+	connect_test($node, 'user=testuser sslmode=prefer gssencmode=disable', 'plain');
+	connect_test($node, 'user=testuser sslmode=prefer gssencmode=prefer', 'gss');
+	connect_test($node, 'user=testuser sslmode=prefer gssencmode=require', 'gss');
+
+	connect_test($node, 'user=testuser sslmode=require gssencmode=disable', 'fail');
+	connect_test($node, 'user=testuser sslmode=require gssencmode=prefer', 'gss');
+
+	# If you set both sslmode and gssencmode to 'require', 'gssencmode=require' takes
+	# precedence.
+	connect_test($node, 'user=testuser sslmode=require gssencmode=require', 'gss');
+
+	# Test case that server supports GSSAPI, but it's not allowed for
+	# this user.
+	connect_test($node, 'user=nogssuser sslmode=prefer gssencmode=require', 'fail',
+	  'no pg_hba.conf entry for host "127.0.0.1", user "nogssuser", database "postgres", GSS encryption');
+
+	# With 'gssencmode=prefer', libpq will first negotiate GSSAPI
+	# encryption, but the connection will fail because pg_hba.conf
+	# forbids GSSAPI encryption for this user. It will then reconnect
+	# with SSL, but the server doesn't support it, so it will continue
+	# with no encryption.
+	connect_test($node, 'user=nogssuser sslmode=prefer gssencmode=prefer', 'plain',
+				 'no pg_hba.conf entry for host "127.0.0.1", user "nogssuser", database "postgres", GSS encryption');
+}
+
+# Enable SSL in the server
+SKIP:
+{
+	skip "SSL not supported by this build" unless $ssl_supported;
+
+	my $certdir = dirname(__FILE__) . "/../../ssl/ssl";
+
+	copy "$certdir/server-cn-only.crt", "$pgdata/server.crt"
+	  || die "copying server.crt: $!";
+	copy "$certdir/server-cn-only.key", "$pgdata/server.key"
+	  || die "copying server.key: $!";
+	chmod(0600, "$pgdata/server.key");
+
+	$node->adjust_conf('postgresql.conf', 'ssl', 'on');
+	$node->reload;
+
+	connect_test($node, 'user=testuser sslmode=disable gssencmode=disable', 'plain');
+	connect_test($node, 'user=testuser sslmode=allow gssencmode=disable', 'plain');
+	connect_test($node, 'user=testuser sslmode=prefer gssencmode=disable', 'ssl');
+	connect_test($node, 'user=testuser sslmode=require gssencmode=disable', 'ssl');
+
+	connect_test($node, 'user=ssluser sslmode=disable gssencmode=disable', 'fail');
+	connect_test($node, 'user=ssluser sslmode=allow gssencmode=disable', 'ssl');
+	connect_test($node, 'user=ssluser sslmode=prefer gssencmode=disable', 'ssl');
+	connect_test($node, 'user=ssluser sslmode=require gssencmode=disable', 'ssl');
+
+	connect_test($node, 'user=nossluser sslmode=disable gssencmode=disable', 'plain');
+	connect_test($node, 'user=nossluser sslmode=allow gssencmode=disable', 'plain');
+	connect_test($node, 'user=nossluser sslmode=prefer gssencmode=disable', 'plain');
+	connect_test($node, 'user=nossluser sslmode=require gssencmode=disable', 'fail');
+}
+
+# Server supports SSL and GSSAPI
+SKIP:
+{
+	skip "GSSAPI/Kerberos or SSL not supported by this build" unless ($ssl_supported && $gss_supported);
+
+	connect_test($node, 'user=testuser sslmode=disable gssencmode=disable', 'plain');
+	connect_test($node, 'user=testuser sslmode=disable gssencmode=prefer', 'gss');
+	connect_test($node, 'user=testuser sslmode=disable gssencmode=require', 'gss');
+
+	connect_test($node, 'user=testuser sslmode=prefer gssencmode=disable', 'ssl');
+	connect_test($node, 'user=testuser sslmode=prefer gssencmode=prefer', 'gss');
+	connect_test($node, 'user=testuser sslmode=prefer gssencmode=require', 'gss');
+
+	connect_test($node, 'user=testuser sslmode=require gssencmode=disable', 'ssl');
+	connect_test($node, 'user=testuser sslmode=require gssencmode=prefer', 'gss');
+
+	# If you set both sslmode and gssencmode to 'require', 'gssencmode=require' takes
+	# precedence.
+	connect_test($node, 'user=testuser sslmode=require gssencmode=require', 'gss');
+
+	# Test case that server supports GSSAPI, but it's not allowed for
+	# this user.
+	connect_test($node, 'user=nogssuser sslmode=prefer gssencmode=require', 'fail',
+	  'no pg_hba.conf entry for host "127.0.0.1", user "nogssuser", database "postgres", GSS encryption');
+
+	# with 'gssencmode=prefer', libpq will first negotiate GSSAPI
+	# encryption, but the connection will fail because pg_hba.conf
+	# forbids GSSAPI encryption for this user. It will then reconnect
+	# with SSL.
+	connect_test($node, 'user=nogssuser sslmode=prefer gssencmode=prefer', 'ssl',
+	  'no pg_hba.conf entry for host "127.0.0.1", user "nogssuser", database "postgres", GSS encryption');
+
+	# Setting both sslmode=require and gssencmode=require fails if GSSAPI is not
+	# available.
+	connect_test($node, 'user=nogssuser sslmode=require gssencmode=require', 'fail');
+}
+
+# Test negotiation over unix domain sockets.
+SKIP:
+{
+	skip "Unix domain sockets not supported" unless ($unixdir ne "");
+
+	connect_test($node, "user=localuser sslmode=require gssencmode=prefer host=$unixdir", 'plain');
+	connect_test($node, "user=localuser sslmode=prefer gssencmode=require host=$unixdir", 'fail');
+}
+
+done_testing();
diff --git a/src/test/perl/PostgreSQL/Test/Kerberos.pm b/src/test/perl/PostgreSQL/Test/Kerberos.pm
index 382a38ec9d2..b9c5c388085 100644
--- a/src/test/perl/PostgreSQL/Test/Kerberos.pm
+++ b/src/test/perl/PostgreSQL/Test/Kerberos.pm
@@ -81,10 +81,10 @@ sub new
 
 	my ($stdout, $krb5_version);
 	run_log [ $krb5_config, '--version' ], '>', \$stdout
-	  or BAIL_OUT("could not execute krb5-config");
-	BAIL_OUT("Heimdal is not supported") if $stdout =~ m/heimdal/;
+	  or die("could not execute krb5-config");
+	die("Heimdal is not supported") if $stdout =~ m/heimdal/;
 	$stdout =~ m/Kerberos 5 release ([0-9]+\.[0-9]+)/
-	  or BAIL_OUT("could not get Kerberos version");
+	  or die("could not get Kerberos version");
 	$krb5_version = $1;
 
 	# Build the krb5.conf to use.
-- 
2.39.2