From d7f57dfe4a316c8ac1270a5f35f837447e335153 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Sat, 31 Dec 2022 14:41:57 +1300 Subject: [PATCH v2 1/3] Add simple test for RADIUS authentication. This is similar to the existing tests for ldap and kerberos. It requires FreeRADIUS to be installed, and opens ports that may be considered insecure, so users have to opt in with PG_EXTRA_TESTS=radius. Discussion: https://postgr.es/m/CA%2BhUKGKxNoVjkMCksnj6z3BwiS3y2v6LN6z7_CisLK%2Brv%2B0V4g%40mail.gmail.com --- src/test/Makefile | 2 +- src/test/meson.build | 1 + src/test/radius/Makefile | 23 +++++ src/test/radius/meson.build | 12 +++ src/test/radius/t/001_auth.pl | 187 ++++++++++++++++++++++++++++++++++ 5 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 src/test/radius/Makefile create mode 100644 src/test/radius/meson.build create mode 100644 src/test/radius/t/001_auth.pl diff --git a/src/test/Makefile b/src/test/Makefile index dbd3192874..687164412c 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -12,7 +12,7 @@ subdir = src/test top_builddir = ../.. include $(top_builddir)/src/Makefile.global -SUBDIRS = perl regress isolation modules authentication recovery subscription +SUBDIRS = perl regress isolation modules authentication recovery radius subscription ifeq ($(with_icu),yes) SUBDIRS += icu diff --git a/src/test/meson.build b/src/test/meson.build index 5f3c9c2ba2..b5da17b531 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -5,6 +5,7 @@ subdir('isolation') subdir('authentication') subdir('recovery') +subdir('radius') subdir('subscription') subdir('modules') diff --git a/src/test/radius/Makefile b/src/test/radius/Makefile new file mode 100644 index 0000000000..56768a3ca9 --- /dev/null +++ b/src/test/radius/Makefile @@ -0,0 +1,23 @@ +#------------------------------------------------------------------------- +# +# Makefile for src/test/radius +# +# Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/test/radius/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/test/radius +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +check: + $(prove_check) + +installcheck: + $(prove_installcheck) + +clean distclean maintainer-clean: + rm -rf tmp_check diff --git a/src/test/radius/meson.build b/src/test/radius/meson.build new file mode 100644 index 0000000000..ea7afc4555 --- /dev/null +++ b/src/test/radius/meson.build @@ -0,0 +1,12 @@ +# Copyright (c) 2022, PostgreSQL Global Development Group + +tests += { + 'name': 'radius', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_auth.pl', + ], + }, +} diff --git a/src/test/radius/t/001_auth.pl b/src/test/radius/t/001_auth.pl new file mode 100644 index 0000000000..44db62a3d7 --- /dev/null +++ b/src/test/radius/t/001_auth.pl @@ -0,0 +1,187 @@ + +# Copyright (c) 2021-2022, PostgreSQL Global Development Group + +# Debian: apt-get install freeradius +# Homebrew: brew install freeradius-server +# FreeBSD: pkg install freeradius3 +# MacPorts: port install freeradius + +use strict; +use warnings; +use File::Copy; +use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Cluster; +use Test::More; + +my $radiusd_dir = "${PostgreSQL::Test::Utils::tmp_check}/radiusd_data"; +my $radiusd_conf = "radiusd.conf"; +my $radiusd_users = "users.txt"; +my $radiusd_prefix; +my $radiusd; + +if ($ENV{PG_TEST_EXTRA} !~ /\bradius\b/) +{ + plan skip_all => 'Potentially unsafe test RADIUS not enabled in PG_TEST_EXTRA'; +} +elsif ($^O eq 'freebsd') +{ + $radiusd = '/usr/local/sbin/radiusd'; +} +elsif ($^O eq 'linux' && -f '/usr/sbin/freeradius') +{ + $radiusd = '/usr/sbin/freeradius'; +} +elsif ($^O eq 'linux') +{ + $radiusd = '/usr/sbin/radiusd'; +} +elsif ($^O eq 'darwin' && -d '/opt/local') +{ + # typical path for MacPorts + $radiusd = '/opt/local/sbin/radiusd'; + $radiusd_prefix = '/opt/local'; +} +elsif ($^O eq 'darwin' && -d '/opt/homebrew') +{ + # typical path for Homebrew on ARM + $radiusd = '/opt/homebrew/bin/radiusd'; + $radiusd_prefix = '/opt/homebrew'; +} +elsif ($^O eq 'darwin' && -d '/usr/local') +{ + # typical path for Homebrew on Intel + $radiusd = '/usr/local/bin/radiusd'; + $radiusd_prefix = '/usr/local'; +} +else +{ + plan skip_all => + "radius tests not supported on $^O or dependencies not installed"; +} + +my $radius_port = PostgreSQL::Test::Cluster::get_free_port(); + +note "setting up radiusd"; + +mkdir $radiusd_dir or die "cannot create $radiusd_dir"; + +append_to_file( + "$radiusd_dir/$radiusd_conf", + qq{client default { + ipaddr = "127.0.0.1" + secret = "secret" +} + +modules { + files { + filename = "$radiusd_dir/users.txt" + } + pap { + } +} + +server default { + listen { + type = "auth" + ipv4addr = "127.0.0.1" + port = "$radius_port" + } + authenticate { + Auth-Type PAP { + pap + } + } + authorize { + files + pap + } +} + +log { + destination = "files" + localstatedir = "$radiusd_dir" + logdir = "$radiusd_dir" + file = "$radiusd_dir/radius.log" +} + +pidfile = "$radiusd_dir/radiusd.pid" +}); + +# help to find libraries that radiusd dlopens +if ($radiusd_prefix) +{ + append_to_file( + "$radiusd_dir/$radiusd_conf", + qq{prefix="$radiusd_prefix"\n}) +} + +append_to_file( + "$radiusd_dir/$radiusd_users", + qq{test2 Cleartext-Password := "secret2"}); + +system_or_bail $radiusd, '-xx', '-d', $radiusd_dir; + +END +{ + kill 'INT', `cat $radiusd_dir/radiusd.pid` if -f "$radiusd_dir/radiusd.pid"; +} + +note "setting up PostgreSQL instance"; + +my $node = PostgreSQL::Test::Cluster->new('node'); +$node->init; +$node->append_conf('postgresql.conf', "log_connections = on\n"); +$node->start; + +$node->safe_psql('postgres', 'CREATE USER test1;'); +$node->safe_psql('postgres', 'CREATE USER test2;'); + +note "running tests"; + +sub test_access +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($node, $role, $expected_res, $test_name, %params) = @_; + my $connstr = "user=$role"; + + if ($expected_res eq 0) + { + $node->connect_ok($connstr, $test_name, %params); + } + else + { + # No checks of the error message, only the status code. + $node->connect_fails($connstr, $test_name, %params); + } +} + +note "enable RADIUS auth"; + +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', + qq{local all all radius radiusservers="127.0.0.1" radiussecrets="secret" radiusports="$radius_port"} +); +$node->restart; + +note "simple negative and positive tests"; + +$ENV{"PGPASSWORD"} = 'wrong'; +test_access( + $node, 'test1', 2, + 'authentication fails if user not found in RADIUS', + log_unlike => [qr/connection authenticated:/]); +test_access( + $node, 'test2', 2, + 'authentication fails with wrong password', + log_unlike => [qr/connection authenticated:/]); + +$ENV{"PGPASSWORD"} = 'secret2'; +test_access( + $node, 'test2', 0, + 'authentication succeeds with right password', + log_like => [ + qr/connection authenticated: identity="test2" method=radius/ + ],); + +done_testing(); -- 2.39.2