diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c index 82aa14a65d..ba2edf0cba 100644 --- a/contrib/postgres_fdw/connection.c +++ b/contrib/postgres_fdw/connection.c @@ -333,6 +333,38 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user) entry->conn, server->servername, user->umid, user->userid); } + +/* + * Look for "krb_client_keyfile" option in user mapping. + * If found, set the KRB5_CLIENT_KTNAME environment just like we do + * in backend. Also remove this option from option list. + * + * If there are multiple principals in the keytab file, only the + * default(first) one will be selected. + */ +static void setup_kerberos_env(UserMapping *user) +{ + ListCell *lc; + DefElem *d; + StringInfo si = NULL; + foreach(lc, user->options) + { + d = (DefElem *) lfirst(lc); + if (strcmp(d->defname, "krb_client_keyfile") == 0) + { + si = makeStringInfo(); + appendStringInfo(si, "KRB5_CLIENT_KTNAME=%s", defGetString(d)); + user->options = list_delete_cell(user->options, lc); + putenv(si->data); + /* We don't want credential to be cached as file, keep it within current process. + * The credential can be reused within the same session. + */ + putenv("KRB5CCNAME=MEMORY:"); + return; + } + } +} + /* * Connect to remote server using specified server and user mapping properties. */ @@ -350,6 +382,8 @@ connect_pg_server(ForeignServer *server, UserMapping *user) const char **values; int n; + setup_kerberos_env(user); + /* * Construct connection params from generic options of ForeignServer * and UserMapping. (Some of them might not be libpq options, in diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index b8561d6a3c..afb2dc9b14 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -188,7 +188,7 @@ ALTER USER MAPPING FOR public SERVER testserver1 ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (ADD sslmode 'require'); ERROR: invalid option "sslmode" -HINT: Valid options in this context are: user, password, sslpassword, password_required, sslcert, sslkey +HINT: Valid options in this context are: user, password, sslpassword, password_required, sslcert, sslkey, krb_client_keyfile, gssencmode -- But we can add valid ones fine ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (ADD sslpassword 'dummy'); @@ -196,6 +196,8 @@ ALTER USER MAPPING FOR public SERVER testserver1 -- permitted to check validation. ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (ADD sslkey 'value', ADD sslcert 'value'); +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD krb_client_keyfile 'value', ADD gssencmode 'value'); ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); @@ -9298,6 +9300,13 @@ HINT: User mappings with the sslcert or sslkey options set may only be created ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslkey 'foo.key'); ERROR: sslcert and sslkey are superuser-only HINT: User mappings with the sslcert or sslkey options set may only be created or modified by the superuser +-- unpriv user also cannot set krb_client_keyfile / gssencmode on the user mapping +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD krb_client_keyfile 'foo.ktab'); +ERROR: krb_client_keyfile and gssencmode are superuser-only +HINT: User mappings with the krb_client_keyfile or gssencmode options set may only be created or modified by the superuser +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD gssencmode 'require'); +ERROR: krb_client_keyfile and gssencmode are superuser-only +HINT: User mappings with the krb_client_keyfile or gssencmode options set may only be created or modified by the superuser -- We're done with the role named after a specific user and need to check the -- changes to the public mapping. DROP USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; diff --git a/contrib/postgres_fdw/kerberos/.gitignore b/contrib/postgres_fdw/kerberos/.gitignore new file mode 100644 index 0000000000..871e943d50 --- /dev/null +++ b/contrib/postgres_fdw/kerberos/.gitignore @@ -0,0 +1,2 @@ +# Generated by test suite +/tmp_check/ diff --git a/contrib/postgres_fdw/kerberos/Makefile b/contrib/postgres_fdw/kerberos/Makefile new file mode 100644 index 0000000000..3efec922f0 --- /dev/null +++ b/contrib/postgres_fdw/kerberos/Makefile @@ -0,0 +1,25 @@ +#------------------------------------------------------------------------- +# +# Makefile for src/test/kerberos +# +# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/test/kerberos/Makefile +# +#------------------------------------------------------------------------- + +subdir = contrib/postgres_fdw/kerberos +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +export with_gssapi with_krb_srvnam + +check: + $(prove_check) + +installcheck: + $(prove_installcheck) + +clean distclean maintainer-clean: + rm -rf tmp_check diff --git a/contrib/postgres_fdw/kerberos/README b/contrib/postgres_fdw/kerberos/README new file mode 100644 index 0000000000..40b8758543 --- /dev/null +++ b/contrib/postgres_fdw/kerberos/README @@ -0,0 +1,45 @@ +contrib/postgres_fdw/kerberos/README + +Tests for Kerberos/GSSAPI functionality +======================================= + +This directory contains a test suite for Kerberos/GSSAPI +functionality. This requires a full MIT Kerberos installation, +including server and client tools, and is therefore kept separate and +not run by default. + +CAUTION: The test server run by this test is configured to listen for TCP +connections on localhost. Any user on the same host is able to log in to the +test server while the tests are running. Do not run this suite on a multi-user +system where you don't trust all local users! Also, this test suite creates a +KDC server that listens for TCP/IP connections on localhost without any real +access control. + +Running the tests +================= + +NOTE: You must have given the --enable-tap-tests argument to configure. + +Run + make check +or + make installcheck +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. + +Either way, this test initializes, starts, and stops a test Postgres +cluster, as well as a test KDC server. + +Requirements +============ + +MIT Kerberos server and client tools are required. Heimdal is not +supported. + +Debian/Ubuntu packages: krb5-admin-server krb5-kdc krb5-user + +RHEL/CentOS/Fedora packages: krb5-server krb5-workstation + +FreeBSD port: krb5 (base system has Heimdal) diff --git a/contrib/postgres_fdw/kerberos/t/001_auth.pl b/contrib/postgres_fdw/kerberos/t/001_auth.pl new file mode 100644 index 0000000000..156fe96f07 --- /dev/null +++ b/contrib/postgres_fdw/kerberos/t/001_auth.pl @@ -0,0 +1,471 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# Sets up a KDC and then runs a variety of tests to make sure that the +# GSSAPI/Kerberos authentication and encryption are working properly, +# that the options in pg_hba.conf and pg_ident.conf are handled correctly, +# and that the server-side pg_stat_gssapi view reports what we expect to +# see for each test. +# +# Since this requires setting up a full KDC, it doesn't make much sense +# to have multiple test scripts (since they'd have to also create their +# own KDC and that could cause race conditions or other problems)- so +# just add whatever other tests are needed to here. +# +# See the README for additional information. + +use strict; +use warnings; +use TestLib; +use PostgresNode; +use Test::More; +use Time::HiRes qw(usleep); +use Cwd 'abs_path'; + +if ($ENV{with_gssapi} eq 'yes') +{ + plan tests => 53; +} +else +{ + plan skip_all => 'GSSAPI/Kerberos not supported by this build'; +} + +my ($krb5_bin_dir, $krb5_sbin_dir); + +if ($^O eq 'darwin') +{ + $krb5_bin_dir = '/usr/local/opt/krb5/bin'; + $krb5_sbin_dir = '/usr/local/opt/krb5/sbin'; +} +elsif ($^O eq 'freebsd') +{ + $krb5_bin_dir = '/usr/local/bin'; + $krb5_sbin_dir = '/usr/local/sbin'; +} +elsif ($^O eq 'linux') +{ + $krb5_sbin_dir = '/usr/sbin'; +} + +my $krb5_config = 'krb5-config'; +my $kinit = 'kinit'; +my $kdb5_util = 'kdb5_util'; +my $kadmin_local = 'kadmin.local'; +my $krb5kdc = 'krb5kdc'; + +if ($krb5_bin_dir && -d $krb5_bin_dir) +{ + $krb5_config = $krb5_bin_dir . '/' . $krb5_config; + $kinit = $krb5_bin_dir . '/' . $kinit; +} +if ($krb5_sbin_dir && -d $krb5_sbin_dir) +{ + $kdb5_util = $krb5_sbin_dir . '/' . $kdb5_util; + $kadmin_local = $krb5_sbin_dir . '/' . $kadmin_local; + $krb5kdc = $krb5_sbin_dir . '/' . $krb5kdc; +} + +my $host = 'auth-test-localhost.postgresql.example.com'; +my $hostaddr = '127.0.0.1'; +my $realm = 'EXAMPLE.COM'; + + +my $tmp_check = abs_path(${TestLib::tmp_check}); +my $krb5_conf = "$tmp_check/krb5.conf"; +my $kdc_conf = "$tmp_check/kdc.conf"; +my $krb5_cache = "$tmp_check/krb5cc"; +my $krb5_log = "${TestLib::log_path}/krb5libs.log"; +my $kdc_log = "${TestLib::log_path}/krb5kdc.log"; +my $kdc_port = get_free_port(); +my $kdc_datadir = "$tmp_check/krb5kdc"; +my $kdc_pidfile = "$tmp_check/krb5kdc.pid"; +my $keytab = "$tmp_check/krb5.keytab"; + +my $dbname = 'testdb'; +my $testuser = 'testuser'; +my $test1 = 'test1'; +my $application = '001_auth.pl'; + +note "setting up Kerberos"; + +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/; +$stdout =~ m/Kerberos 5 release ([0-9]+\.[0-9]+)/ + or BAIL_OUT("could not get Kerberos version"); +$krb5_version = $1; + +append_to_file( + $krb5_conf, + qq![logging] +default = FILE:$krb5_log +kdc = FILE:$kdc_log + +[libdefaults] +default_realm = $realm + +[realms] +$realm = { + kdc = $hostaddr:$kdc_port +}!); + +append_to_file( + $kdc_conf, + qq![kdcdefaults] +!); + +# For new-enough versions of krb5, use the _listen settings rather +# than the _ports settings so that we can bind to localhost only. +if ($krb5_version >= 1.15) +{ + append_to_file( + $kdc_conf, + qq!kdc_listen = $hostaddr:$kdc_port +kdc_tcp_listen = $hostaddr:$kdc_port +!); +} +else +{ + append_to_file( + $kdc_conf, + qq!kdc_ports = $kdc_port +kdc_tcp_ports = $kdc_port +!); +} + +append_to_file( + $kdc_conf, + qq! +[realms] +$realm = { + database_name = $kdc_datadir/principal + admin_keytab = FILE:$kdc_datadir/kadm5.keytab + acl_file = $kdc_datadir/kadm5.acl + key_stash_file = $kdc_datadir/_k5.$realm +}!); + +mkdir $kdc_datadir or die; + +# Ensure that we use test's config and cache files, not global ones. +$ENV{'KRB5_CONFIG'} = $krb5_conf; +$ENV{'KRB5_KDC_PROFILE'} = $kdc_conf; +$ENV{'KRB5CCNAME'} = $krb5_cache; + +my $service_principal = "$ENV{with_krb_srvnam}/$host"; + +system_or_bail $kdb5_util, 'create', '-s', '-P', 'secret0'; + +my $test1_password = 'secret1'; +system_or_bail $kadmin_local, '-q', "addprinc -randkey $testuser"; +system_or_bail $kadmin_local, '-q', "addprinc -randkey $service_principal"; +system_or_bail $kadmin_local, '-q', "ktadd -k $keytab $testuser"; +system_or_bail $kadmin_local, '-q', "ktadd -k $keytab $service_principal"; + +system_or_bail $krb5kdc, '-P', $kdc_pidfile; + +#system_or_bail $krb5kdc; + +END +{ + kill 'INT', `cat $kdc_pidfile` if -f $kdc_pidfile; +} + +note "setting up PostgreSQL instance"; + +# server node for fdw to connect +my $server_node = get_new_node('node'); +$server_node->init; +$server_node->append_conf( + 'postgresql.conf', qq{ +listen_addresses = '$hostaddr' +krb_server_keyfile = '$keytab' +log_connections = on +lc_messages = 'C' +}); +$server_node->start; + +note "setting up user on server"; +$server_node->safe_psql('postgres', "CREATE USER $testuser;"); +# there's no direct way to get the backend pid from postgres_fdw, create a VIEW at server side to provide this. +$server_node->safe_psql('postgres', 'CREATE VIEW my_pg_stat_gssapi AS + SELECT S.pid, + S.gss_auth AS gss_authenticated, + S.gss_princ AS principal, + S.gss_enc AS encrypted + FROM pg_stat_get_activity(NULL) AS S + WHERE S.client_port IS NOT NULL AND S.pid = pg_backend_pid();'); +$server_node->safe_psql('postgres', "GRANT ALL ON my_pg_stat_gssapi TO $testuser;"); + +my $server_node_port = $server_node->port; +note "setting up PostgreSQL fdw instance"; +# client node that runs postgres fdw +my $fdw_node = get_new_node('node_fdw'); +$fdw_node->init; +$fdw_node->append_conf( + 'postgresql.conf', qq{ +listen_addresses = '$hostaddr' +log_connections = on +lc_messages = 'C' +}); +$fdw_node->start; + +$fdw_node->safe_psql('postgres', "CREATE USER $testuser;"); +$fdw_node->safe_psql('postgres', "CREATE DATABASE $dbname;"); +$fdw_node->safe_psql($dbname, 'CREATE EXTENSION postgres_fdw'); +$fdw_node->safe_psql($dbname, "CREATE SERVER postgres_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '$host', hostaddr '$hostaddr', port '$server_node_port', dbname 'postgres');"); +$fdw_node->safe_psql($dbname, "CREATE FOREIGN TABLE my_pg_stat_gssapi\(pid int, gss_authenticated boolean, principal text, encrypted boolean\) SERVER postgres_server OPTIONS(table_name 'my_pg_stat_gssapi'); "); +$fdw_node->safe_psql($dbname, "CREATE USER MAPPING for $testuser SERVER postgres_server OPTIONS (user '$testuser');"); +$fdw_node->safe_psql($dbname, "GRANT SELECT ON my_pg_stat_gssapi TO $testuser;"); + +note "running tests"; + +sub set_user_mapping_option +{ + my ($option, $value, $action) = @_; + + if (!defined $action) + { + $action = "SET"; + } + + my $option_str = "$option"; + if (defined $value) + { + $option_str = "$option '$value'"; + } + + $fdw_node->safe_psql($dbname, "ALTER USER MAPPING for $testuser SERVER postgres_server OPTIONS ($action $option_str);"); +} + +sub set_gssencmode +{ + my ($mode, $action) = @_; + set_user_mapping_option('gssencmode', $mode, $action); +} + +# Test connection success or failure, and if success, that query returns true. +sub test_access +{ + my ($query, $expected_res, $test_name, + $expect_output, @expect_log_msgs) + = @_; + + my %params = (sql => $query,); + + # Check log in server node. Obtain log file size before we run any query. + # This way we can obtain logs for this psql connection only. + my $log_location = -s $server_node->logfile; + + # Run psql in fdw node. + my ($ret, $stdout, $stderr) = $fdw_node->psql( + $dbname, + $query, + extra_params => ['-w'], + connstr => "user=$testuser dbname=$dbname host=$host hostaddr=$hostaddr"); + + is($ret, $expected_res, $test_name); + + if (defined $expect_output) + { + if ($expected_res eq 0) + { + like($stdout, $expect_output, "$test_name: result matches"); + } + else + { + like($stderr, $expect_output, "$test_name: result matches"); + } + } + + if (@expect_log_msgs) + { + # Match every message literally. + my @regexes = map { qr/\Q$_\E/ } @expect_log_msgs; + my $log_contents = TestLib::slurp_file($server_node->logfile, $log_location); + + while (my $regex = shift @regexes) + { + like($log_contents, $regex, "$test_name: matches"); + } + } +} + +test_access( + 'SELECT * FROM pg_user_mapping', + 3, + 'no access to user mapping', + '/permission denied for table pg_user_mapping/' +); + +test_access( + 'SELECT pid FROM my_pg_stat_gssapi', + 3, + 'fails without password', + '/Non-superusers must provide a password in the user mapping/' +); + +set_user_mapping_option('password_required', 'false', 'ADD'); + +unlink($server_node->data_dir . '/pg_hba.conf'); +$server_node->append_conf('pg_hba.conf', + qq{host all all $hostaddr/32 gss map=mymap}); +$server_node->restart; + +test_access( + 'SELECT pid FROM my_pg_stat_gssapi', + 3, + 'fails without ticket', + '/No Kerberos credentials available/' +); + +my $keytabfile = abs_path($keytab); +$fdw_node->safe_psql($dbname, "ALTER USER MAPPING for $testuser SERVER postgres_server OPTIONS (ADD krb_client_keyfile '$keytabfile');"); +test_access( + 'SELECT true from my_pg_stat_gssapi', + 3, + 'fails without mapping', + "/GSSAPI authentication failed for user \"$testuser\"/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss", + "no match in usermap \"mymap\" for user \"$testuser\"" +); + +$server_node->append_conf('pg_ident.conf', qq{mymap /^(.*)\@$realm\$ \\1}); +$server_node->restart; + +test_access( + 'SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi;', + 0, + 'succeeds with mapping with default gssencmode and host hba', + "/^t\$/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss", + "connection authorized: user=$testuser database=postgres application_name=postgres_fdw GSS (authenticated=yes, encrypted=yes, principal=$testuser\@$realm)" +); + +# set gssencmode in user mapping +set_gssencmode('prefer', 'ADD'); +test_access( + 'SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi;', + 0, + 'succeeds with GSS-encrypted access preferred with host hba', + "/^t\$/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss", + "connection authorized: user=$testuser database=postgres application_name=postgres_fdw GSS (authenticated=yes, encrypted=yes, principal=$testuser\@$realm)" +); + +set_gssencmode('require'); +test_access( + 'SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi;', + 0, + 'succeeds with GSS-encrypted access required with host hba', + "/^t\$/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss", + "connection authorized: user=$testuser database=postgres application_name=postgres_fdw GSS (authenticated=yes, encrypted=yes, principal=$testuser\@$realm)" +); + + +# Test that we can transport a reasonable amount of data. +test_access( + 'SELECT generate_series(1, 100000) FROM my_pg_stat_gssapi;', + 0, + 'receiving 100K lines works', + qr/^1\n.*\n1024\n.*\n9999\n.*\n100000$/s +); + +unlink($server_node->data_dir . '/pg_hba.conf'); +$server_node->append_conf('pg_hba.conf', + qq{hostgssenc all all $hostaddr/32 gss map=mymap}); +$server_node->restart; + +set_gssencmode('prefer'); +test_access( + 'SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi', + 0, + 'succeeds with GSS-encrypted access preferred and hostgssenc hba', + "/^t\$/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss", + "connection authorized: user=$testuser database=postgres application_name=postgres_fdw GSS (authenticated=yes, encrypted=yes, principal=$testuser\@$realm)" +); + +set_gssencmode('require'); +test_access( + 'SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi', + 0, + 'succeeds with GSS-encrypted access required and hostgssenc hba', + "/^t\$/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss", + "connection authorized: user=$testuser database=postgres application_name=postgres_fdw GSS (authenticated=yes, encrypted=yes, principal=$testuser\@$realm)" +); + +set_gssencmode('disable'); +test_access( + 'SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi', + 3, + 'fails with GSS encryption disabled and hostgssenc hba', + qr/\Qno pg_hba.conf entry for host "$hostaddr", user "$testuser", database "postgres", no encryption\E/, + "no pg_hba.conf entry for host \"$hostaddr\", user \"$testuser\", database \"postgres\", no encryption"); + +unlink($server_node->data_dir . '/pg_hba.conf'); +$server_node->append_conf('pg_hba.conf', + qq{hostnogssenc all all $hostaddr/32 gss map=mymap}); +$server_node->restart; + +set_gssencmode('prefer'); +test_access( + 'SELECT gss_authenticated and not encrypted from my_pg_stat_gssapi', + 0, + 'succeeds with GSS-encrypted access preferred and hostnogssenc hba, but no encryption', + "/^t\$/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss", + "connection authorized: user=$testuser database=postgres application_name=postgres_fdw GSS (authenticated=yes, encrypted=no, principal=$testuser\@$realm)" +); + +set_gssencmode('require'); +test_access( + 'SELECT gss_authenticated and not encrypted from my_pg_stat_gssapi', + 3, + 'fails with GSS-encrypted access required and hostnogssenc hba', + qr/\Qno pg_hba.conf entry for host "$hostaddr", user "$testuser", database "postgres", GSS encryption\E/, + "no pg_hba.conf entry for host \"$hostaddr\", user \"$testuser\", database \"postgres\", GSS encryption"); + +set_gssencmode('disable'); +test_access( + 'SELECT gss_authenticated and not encrypted from my_pg_stat_gssapi;', + 0, + 'succeeds with GSS encryption disabled and hostnogssenc hba', + "/^t\$/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss", + "connection authorized: user=$testuser database=postgres application_name=postgres_fdw GSS (authenticated=yes, encrypted=no, principal=$testuser\@$realm)" +); + + +truncate($server_node->data_dir . '/pg_ident.conf', 0); +unlink($server_node->data_dir . '/pg_hba.conf'); +$server_node->append_conf('pg_hba.conf', + qq{host all all $hostaddr/32 gss include_realm=0}); +$server_node->restart; + +set_gssencmode(undef, 'DROP'); +test_access( + 'SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi;', + 0, + 'succeeds with include_realm=0 and defaults', + "/^t\$/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss", + "connection authorized: user=$testuser database=postgres application_name=postgres_fdw GSS (authenticated=yes, encrypted=yes, principal=$testuser\@$realm)" +); + +# Reset pg_hba.conf, and cause a usermap failure with an authentication +# that has passed. +unlink($server_node->data_dir . '/pg_hba.conf'); +$server_node->append_conf('pg_hba.conf', + qq{host all all $hostaddr/32 gss include_realm=0 krb_realm=EXAMPLE.ORG}); +$server_node->restart; + +test_access( + 'SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi;', + 3, + 'fails with wrong krb_realm, but still authenticates', + "/GSSAPI authentication failed for user \"$testuser\"/", + "connection authenticated: identity=\"$testuser\@$realm\" method=gss"); \ No newline at end of file diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index 4593cbc540..4326f95846 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -193,6 +193,16 @@ postgres_fdw_validator(PG_FUNCTION_ARGS) errmsg("sslcert and sslkey are superuser-only"), errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser"))); } + else if (strcmp(def->defname, "krb_client_keyfile") == 0 || + strcmp(def->defname, "gssencmode") == 0) + { + /* similarly for krb_client_keyfile / gssencmode on user mapping */ + if (catalog == UserMappingRelationId && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("krb_client_keyfile and gssencmode are superuser-only"), + errhint("User mappings with the krb_client_keyfile or gssencmode options set may only be created or modified by the superuser"))); + } } PG_RETURN_VOID(); @@ -247,6 +257,10 @@ InitPgFdwOptions(void) {"sslcert", UserMappingRelationId, true}, {"sslkey", UserMappingRelationId, true}, + /* kerberos settings */ + {"krb_client_keyfile", UserMappingRelationId, false}, + {"gssencmode", UserMappingRelationId, false}, + {NULL, InvalidOid, false} }; diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index c283e74715..9209c32681 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -211,6 +211,8 @@ ALTER USER MAPPING FOR public SERVER testserver1 -- permitted to check validation. ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (ADD sslkey 'value', ADD sslcert 'value'); +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD krb_client_keyfile 'value', ADD gssencmode 'value'); ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); @@ -2793,6 +2795,9 @@ SELECT 1 FROM ft1_nopw LIMIT 1; ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (SET password_required 'true'); ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslcert 'foo.crt'); ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslkey 'foo.key'); +-- unpriv user also cannot set krb_client_keyfile / gssencmode on the user mapping +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD krb_client_keyfile 'foo.ktab'); +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD gssencmode 'require'); -- We're done with the role named after a specific user and need to check the -- changes to the public mapping. diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml index c9fce77599..c46e63ae82 100644 --- a/doc/src/sgml/postgres-fdw.sgml +++ b/doc/src/sgml/postgres-fdw.sgml @@ -144,6 +144,15 @@ connection setting. + + + krb_client_keyfile location of keytab + file on fdw server host. Only the first principal in this keytab can + be selected. It will only be cached in backend memory to prevent other + users from using the ticket. + gssencmode same to "gssencmode" libpq option. + +