Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1hG0fV-0000YB-Ni for pgsql-hackers@arkaria.postgresql.org; Mon, 15 Apr 2019 12:25:02 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1hG0fT-00018K-Hu for pgsql-hackers@arkaria.postgresql.org; Mon, 15 Apr 2019 12:24:59 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1hG0fT-00016B-7m for pgsql-hackers@lists.postgresql.org; Mon, 15 Apr 2019 12:24:59 +0000 Received: from tamriel.snowman.net ([96.255.250.162]) by magus.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1hG0fO-0005BR-JB for pgsql-hackers@postgresql.org; Mon, 15 Apr 2019 12:24:58 +0000 Received: by tamriel.snowman.net (Postfix, from userid 1000) id 1483E5F79F; Mon, 15 Apr 2019 08:24:53 -0400 (EDT) Date: Mon, 15 Apr 2019 08:24:52 -0400 From: Stephen Frost To: Peter Eisentraut Cc: Magnus Hagander , Joe Conway , Alvaro Herrera , David Steele , Michael Paquier , Nico Williams , Robbie Harwood , PostgreSQL-development Subject: Re: [PATCH v20] GSSAPI encryption support Message-ID: <20190415122452.GC6197@tamriel.snowman.net> References: <20190405025925.GN6197@tamriel.snowman.net> <7d0584ab-26a8-6731-aeee-9c8e195533e6@2ndquadrant.com> <20190405124803.GO6197@tamriel.snowman.net> <20190405213745.GR6197@tamriel.snowman.net> <160445e8-2ccf-7ffd-728f-80597c248a57@2ndquadrant.com> <20190409025114.GW6197@tamriel.snowman.net> <5bd413ae-719d-4db4-b25a-346f22054930@2ndquadrant.com> <8416d227-6e9d-092a-4475-b453e1d7d433@2ndquadrant.com> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="HmSQe9C0pvK0dfg+" Content-Disposition: inline In-Reply-To: <8416d227-6e9d-092a-4475-b453e1d7d433@2ndquadrant.com> User-Agent: Mutt/1.5.24 (2015-08-30) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --HmSQe9C0pvK0dfg+ Content-Type: multipart/mixed; boundary="czuImqG1f0klXSxQ" Content-Disposition: inline --czuImqG1f0klXSxQ Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable Greetings, * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > On 2019-04-09 09:32, Peter Eisentraut wrote: > > On 2019-04-09 04:51, Stephen Frost wrote: > >>> Running just 002_enc.pl by itself passes the tests! > >> Great! I think what I'll do is work to incorporate the two tests back > >> into one script, to avoid whatever the race condition or other confusi= on > >> is happening on macOS here. > >=20 > > That seems reasonable. It also avoids the large amount of duplicate > > setup code. >=20 > Another problem is that the two test files cannot be run in parallel > because they use the same hardcoded data directories. That would have > to be replaced by temporary directories. The tests are really fast enough with one KDC that I don't think it makes sense to have two independent tests. > The race condition alluded to above appears to be simply that at the end > of the test the kdc is shut down by kill -INT but nothing waits for that > to finish before a new one is started by the next test. So there is > potential for all kinds of confusion. Ah, good to know that's what was happening. > Putting it all in one test file seems to be the easiest way out. Please find attached a patch which updates the protocol.sgml docs that Michael mentioned before, and merges the tests into one test file (while adding in some additional tests to make sure that the server also agrees with what our expectations are, using the pg_stat_gssapi view). I'll push this soon unless there are concerns. If you get a chance to test the patch out, that would be great. It's working happily for me locally. Thanks! Stephen --czuImqG1f0klXSxQ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="gssapi-doc-test-fixes_v1.patch" Content-Transfer-Encoding: quoted-printable =46rom e0745810ba1582601f2c71db3b67a8cfc2480546 Mon Sep 17 00:00:00 2001 =46rom: Stephen Frost Date: Sun, 14 Apr 2019 21:14:42 -0400 Subject: [PATCH] GSSAPI: Improve documentation and tests The GSSAPI encryption patch neglected to update the protocol documentation to describe how to set up a GSSAPI encrypted connection =66rom a client to the server, so fix that by adding the appropriate documentation to protocol.sgml. The tests added for encryption support were overly long and couldn't be run in parallel due to race conditions; this was largely because each test was setting up its own KDC to perform the tests. Instead, merge the authentication tests and the encryption tests into one test file, where we only create one KDC to run the tests with. Also, have the tests check what the server's opinion is of the connection and if it was GSS authenticated or encrypted using the previously added pg_stat_gssapi view. In passing, fix the libpq label for GSSENC-Mode to be consistent with the "PGGSSENCMODE" environment variable. Missing protocol documentation pointed out by Michael Paquier. Issues with the tests pointed out by Tom Lane and Peter Eisentraut. Refactored tests and added documentation by me. --- doc/src/sgml/protocol.sgml | 103 ++++++++++++++++ src/interfaces/libpq/fe-connect.c | 2 +- src/test/kerberos/t/001_auth.pl | 48 ++++++-- src/test/kerberos/t/002_enc.pl | 197 ------------------------------ 4 files changed, 141 insertions(+), 209 deletions(-) delete mode 100644 src/test/kerberos/t/002_enc.pl diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 09893d96b5..204dca7c33 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1479,6 +1479,72 @@ SELCT 1/0; of authentication checking. + + + <acronym>GSSAPI</acronym> Session Encryption + + + If PostgreSQL was built with + GSSAPI support, frontend/backend communications + can be encrypted using GSSAPI. This provides + communication security in environments where attackers might be + able to capture the session traffic. For more information on + encrypting PostgreSQL sessions with + GSSAPI, see . + + + + To initiate a GSSAPI-encrypted connection, the + frontend initially sends a GSSENCRequest message rather than a + StartupMessage. The server then responds with a single byte + containing G or N, indicating th= at it + is willing or unwilling to perform GSSAPI encryptio= n, + respectively. The frontend might close the connection at this point + if it is dissatisfied with the response. To continue after + G, perform a GSSAPI initializati= on by + calling gss_init_sec_context() in a loop and sending the result to the + server, starting with an empty input and then with each result from the + server, until it returns no output. When sending the results of + gss_init_sec_context() to the server, prepend the length of the messag= e as a + four byte integer in network byte order. If this is successful, then = use + gss_wrap() to encrypt the usual StartupMessage and all subsequent data, + prepending the length of the result from gss_wrap() as a four byte int= eger + in network byte order to the actual encrypted payload. Note that the = server + will only accept encrypted packets from the client which are less than= 16KB; + gss_wrap_size_limit() should be used by the client to determine the si= ze of + the unencrypted message which will fit within this limit and larger me= ssages + should be broken up into multiple gss_wrap() calls. Typical segments = are + 8KB of unencrypted data, resulting in encrypted packets of slightly la= rger + than 8KB but well within the 16KB maximum. The server can be expected= to + not send encrypted packets of larger than 16KB to the client. To cont= inue + after N, send the usual StartupMessage and proceed + without encryption. + + + + The frontend should also be prepared to handle an ErrorMessage + response to GSSENCRequest from the server. This would only occur if + the server predates the addition of GSSAPI encrypti= on + support to PostgreSQL. In this case the + connection must be closed, but the frontend might choose to open a fre= sh + connection and proceed without requesting GSSAPI + encryption. Given the length limits specified above, the ErrorMessage= can + not be confused with a proper response from the server with an appropr= iate + length. + + + + An initial GSSENCRequest can also be used in a connection that is being + opened to send a CancelRequest message. + + + + While the protocol itself does not provide a way for the server to + force GSSAPI encryption, the administrator can + configure the server to reject unencrypted sessions as a byproduct + of authentication checking. + + =20 @@ -5714,6 +5780,43 @@ SSLRequest (F) =20 + + +GSSENCRequest (F) + + + + + + + + Int32(8) + + + + Length of message contents in bytes, including self. + + + + + + Int32(80877104) + + + + The GSSAPI Encryption request code. Th= e value is chosen to contain + 1234 in the most significant 16 bits, a= nd 5680 in the + least significant 16 bits. (To avoid confusion, this code + must not be the same as any protocol version number.) + + + + + + + + + =20 diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-co= nnect.c index 87df79880a..b89df08c29 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -313,7 +313,7 @@ static const internalPQconninfoOption PQconninfoOptions= [] =3D { * and "prefer". */ {"gssencmode", "PGGSSENCMODE", DefaultGSSMode, NULL, - "GSS-Mode", "", 7, /* sizeof("disable") =3D=3D 7 */ + "GSSENC-Mode", "", 7, /* sizeof("disable") =3D=3D 7 */ offsetof(struct pg_conn, gssencmode)}, =20 #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth= =2Epl index 1be89aef4f..b702a79343 100644 --- a/src/test/kerberos/t/001_auth.pl +++ b/src/test/kerberos/t/001_auth.pl @@ -6,7 +6,7 @@ use Test::More; =20 if ($ENV{with_gssapi} eq 'yes') { - plan tests =3D> 4; + plan tests =3D> 12; } else { @@ -155,18 +155,24 @@ note "running tests"; =20 sub test_access { - my ($node, $role, $expected_res, $test_name) =3D @_; + my ($node, $role, $server_check, $expected_res, $gssencmode, $test_name) = =3D @_; =20 # need to connect over TCP/IP for Kerberos - my $res =3D $node->psql( + my ($res, $stdoutres, $stderrres) =3D $node->psql( 'postgres', - 'SELECT 1', + "$server_check", extra_params =3D> [ - '-d', - $node->connstr('postgres') . " host=3D$host hostaddr=3D$hostaddr", + '-XAtd', + $node->connstr('postgres') . " host=3D$host hostaddr=3D$hostaddr $gssen= cmode", '-U', $role ]); - is($res, $expected_res, $test_name); + + # If we get a query result back, it should be true. + if ($res =3D=3D $expected_res and $res eq 0) { + is($stdoutres, "t", $test_name); + } else { + is($res, $expected_res, $test_name); + } return; } =20 @@ -175,16 +181,36 @@ $node->append_conf('pg_hba.conf', qq{host all all $hostaddr/32 gss map=3Dmymap}); $node->restart; =20 -test_access($node, 'test1', 2, 'fails without ticket'); +test_access($node, 'test1', 'SELECT true', 2, '', 'fails without ticket'); =20 run_log [ $kinit, 'test1' ], \$test1_password or BAIL_OUT($?); =20 -test_access($node, 'test1', 2, 'fails without mapping'); +test_access($node, 'test1', 'SELECT true', 2, '', 'fails without mapping'); =20 $node->append_conf('pg_ident.conf', qq{mymap /^(.*)\@$realm\$ \\1}); $node->restart; =20 -test_access($node, 'test1', 0, 'succeeds with mapping'); +test_access($node, 'test1', 'SELECT gss_authenticated AND encrypted from p= g_stat_gssapi where pid =3D pg_backend_pid();', 0, '', 'succeeds with mappi= ng with default gssencmode and host hba'); +test_access($node, "test1", 'SELECT gss_authenticated AND encrypted from p= g_stat_gssapi where pid =3D pg_backend_pid();', 0, "gssencmode=3Dprefer", "= succeeds with GSS-encrypted access preferred with host hba"); +test_access($node, "test1", 'SELECT gss_authenticated AND encrypted from p= g_stat_gssapi where pid =3D pg_backend_pid();', 0, "gssencmode=3Drequire", = "succeeds with GSS-encrypted access required with host hba"); + +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', + qq{hostgssenc all all $hostaddr/32 gss map=3Dmymap}); +$node->restart; + +test_access($node, "test1", 'SELECT gss_authenticated AND encrypted from p= g_stat_gssapi where pid =3D pg_backend_pid();', 0, "gssencmode=3Dprefer", "= succeeds with GSS-encrypted access preferred and hostgssenc hba"); +test_access($node, "test1", 'SELECT gss_authenticated AND encrypted from p= g_stat_gssapi where pid =3D pg_backend_pid();', 0, "gssencmode=3Drequire", = "succeeds with GSS-encrypted access required and hostgssenc hba"); +test_access($node, "test1", 'SELECT true', 2, "gssencmode=3Ddisable", "fai= ls with GSS encryption disabled and hostgssenc hba"); + +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', + qq{hostnogssenc all all $hostaddr/32 gss map=3Dmymap}); +$node->restart; + +test_access($node, "test1", 'SELECT gss_authenticated and not encrypted fr= om pg_stat_gssapi where pid =3D pg_backend_pid();', 0, "gssencmode=3Dprefer= ", "succeeds with GSS-encrypted access preferred and hostnogssenc hba, but = no encryption"); +test_access($node, "test1", 'SELECT true', 2, "gssencmode=3Drequire", "fai= ls with GSS-encrypted access required and hostnogssenc hba"); +test_access($node, "test1", 'SELECT gss_authenticated and not encrypted fr= om pg_stat_gssapi where pid =3D pg_backend_pid();', 0, "gssencmode=3Ddisabl= e", "succeeds with GSS encryption disabled and hostnogssenc hba"); =20 truncate($node->data_dir . '/pg_ident.conf', 0); unlink($node->data_dir . '/pg_hba.conf'); @@ -192,4 +218,4 @@ $node->append_conf('pg_hba.conf', qq{host all all $hostaddr/32 gss include_realm=3D0}); $node->restart; =20 -test_access($node, 'test1', 0, 'succeeds with include_realm=3D0'); +test_access($node, 'test1', 'SELECT gss_authenticated AND encrypted from p= g_stat_gssapi where pid =3D pg_backend_pid();', 0, '', 'succeeds with inclu= de_realm=3D0 and defaults'); diff --git a/src/test/kerberos/t/002_enc.pl b/src/test/kerberos/t/002_enc.pl deleted file mode 100644 index 1a7dc30a81..0000000000 --- a/src/test/kerberos/t/002_enc.pl +++ /dev/null @@ -1,197 +0,0 @@ -use strict; -use warnings; -use TestLib; -use PostgresNode; -use Test::More; -use File::Path 'remove_tree'; - -if ($ENV{with_gssapi} eq 'yes') -{ - plan tests =3D> 5; -} -else -{ - plan skip_all =3D> 'GSSAPI/Kerberos not supported by this build'; -} - -my ($krb5_bin_dir, $krb5_sbin_dir); - -if ($^O eq 'darwin') -{ - $krb5_bin_dir =3D '/usr/local/opt/krb5/bin'; - $krb5_sbin_dir =3D '/usr/local/opt/krb5/sbin'; -} -elsif ($^O eq 'freebsd') -{ - $krb5_bin_dir =3D '/usr/local/bin'; - $krb5_sbin_dir =3D '/usr/local/sbin'; -} -elsif ($^O eq 'linux') -{ - $krb5_sbin_dir =3D '/usr/sbin'; -} - -my $krb5_config =3D 'krb5-config'; -my $kinit =3D 'kinit'; -my $kdb5_util =3D 'kdb5_util'; -my $kadmin_local =3D 'kadmin.local'; -my $krb5kdc =3D 'krb5kdc'; - -if ($krb5_bin_dir && -d $krb5_bin_dir) -{ - $krb5_config =3D $krb5_bin_dir . '/' . $krb5_config; - $kinit =3D $krb5_bin_dir . '/' . $kinit; -} -if ($krb5_sbin_dir && -d $krb5_sbin_dir) -{ - $kdb5_util =3D $krb5_sbin_dir . '/' . $kdb5_util; - $kadmin_local =3D $krb5_sbin_dir . '/' . $kadmin_local; - $krb5kdc =3D $krb5_sbin_dir . '/' . $krb5kdc; -} - -my $host =3D 'auth-test-localhost.postgresql.example.com'; -my $hostaddr =3D '127.0.0.1'; -my $realm =3D 'EXAMPLE.COM'; - -my $krb5_conf =3D "${TestLib::tmp_check}/krb5.conf"; -my $kdc_conf =3D "${TestLib::tmp_check}/kdc.conf"; -my $krb5_log =3D "${TestLib::tmp_check}/krb5libs.log"; -my $kdc_log =3D "${TestLib::tmp_check}/krb5kdc.log"; -my $kdc_port =3D int(rand() * 16384) + 49152; -my $kdc_datadir =3D "${TestLib::tmp_check}/krb5kdc"; -my $kdc_pidfile =3D "${TestLib::tmp_check}/krb5kdc.pid"; -my $keytab =3D "${TestLib::tmp_check}/krb5.keytab"; - -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 =3D~ m/heimdal/; -$stdout =3D~ m/Kerberos 5 release ([0-9]+\.[0-9]+)/ - or BAIL_OUT("could not get Kerberos version"); -$krb5_version =3D $1; - -append_to_file( - $krb5_conf, - qq![logging] -default =3D FILE:$krb5_log -kdc =3D FILE:$kdc_log - -[libdefaults] -default_realm =3D $realm - -[realms] -$realm =3D { - kdc =3D $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 >=3D 1.15) -{ - append_to_file( - $kdc_conf, - qq!kdc_listen =3D $hostaddr:$kdc_port -kdc_tcp_listen =3D $hostaddr:$kdc_port -!); -} -else -{ - append_to_file( - $kdc_conf, - qq!kdc_ports =3D $kdc_port -kdc_tcp_ports =3D $kdc_port -!); -} -append_to_file( - $kdc_conf, - qq! -[realms] -$realm =3D { - database_name =3D $kdc_datadir/principal - admin_keytab =3D FILE:$kdc_datadir/kadm5.keytab - acl_file =3D $kdc_datadir/kadm5.acl - key_stash_file =3D $kdc_datadir/_k5.$realm -}!); - -remove_tree $kdc_datadir; -mkdir $kdc_datadir or die; - -$ENV{'KRB5_CONFIG'} =3D $krb5_conf; -$ENV{'KRB5_KDC_PROFILE'} =3D $kdc_conf; - -my $service_principal =3D "$ENV{with_krb_srvnam}/$host"; - -system_or_bail $kdb5_util, 'create', '-s', '-P', 'secret0'; - -my $test1_password =3D 'secret1'; -system_or_bail $kadmin_local, '-q', "addprinc -pw $test1_password test1"; - -system_or_bail $kadmin_local, '-q', "addprinc -randkey $service_principal"; -system_or_bail $kadmin_local, '-q', "ktadd -k $keytab $service_principal"; - -system_or_bail $krb5kdc, '-P', $kdc_pidfile; - -END -{ - kill 'INT', `cat $kdc_pidfile` if -f $kdc_pidfile; -} - -note "setting up PostgreSQL instance"; - -my $node =3D get_new_node('node'); -$node->init; -$node->append_conf('postgresql.conf', "listen_addresses =3D 'localhost'"); -$node->append_conf('postgresql.conf', "krb_server_keyfile =3D '$keytab'"); -$node->start; - -$node->safe_psql('postgres', 'CREATE USER test1;'); - -note "running tests"; - -sub test_access -{ - my ($node, $gssencmode, $expected_res, $test_name) =3D @_; - - my $res =3D $node->psql( - "postgres", - "SELECT 1", - extra_params =3D> [ - "-d", - $node->connstr("postgres") . " host=3D$host hostaddr=3D$hostaddr gssenc= mode=3D$gssencmode", - "-U", "test1", - ]); - is($res, $expected_res, $test_name); - return; -} - -unlink($node->data_dir . "/pg_ident.conf"); -$node->append_conf("pg_ident.conf", 'mymap /^(.*)@EXAMPLE.COM$ \1'); -run_log [ $kinit, 'test1' ], \$test1_password or BAIL_OUT($?); - -unlink($node->data_dir . '/pg_hba.conf'); -$node->append_conf('pg_hba.conf', - qq{hostgssenc all all $hostaddr/32 gss map=3Dmymap}); -$node->restart; -test_access($node, "require", 0, "GSS-encrypted access"); -test_access($node, "disable", 2, "GSS encryption disabled"); - -unlink($node->data_dir . "/pg_hba.conf"); -$node->append_conf("pg_hba.conf", qq{hostgssenc all all $hostaddr/32 trust= }); -$node->restart; -test_access($node, "require", 0, "GSS encryption without auth"); - -unlink($node->data_dir . "/pg_hba.conf"); -$node->append_conf("pg_hba.conf", - qq{hostnogssenc all all localhost gss map=3Dmymap}); -$node->restart; -test_access($node, "prefer", 0, "GSS unencrypted fallback"); - -# Check that the client can prevent fallback. -test_access($node, "require", 2, "GSS unencrypted fallback prevention"); --=20 2.19.1 --czuImqG1f0klXSxQ-- --HmSQe9C0pvK0dfg+ Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAEBCgAGBQJctHgUAAoJEO1sijiDR2RVmYYP/0nIKDJA3hpSIQb6GN2IKi2d i1udSkwP4ea2VcPQzEQxH8qIYoc9HlFcopxWsAbQ88pfLESnoBnzqyj9SI90DT9c bjKfSW/dv3SI20rUq7p2fsHsiHGZAcyoX5qG+eOYZU81CaqBxChCjUSFxajSHlMR 6GTvhNNBDXPTEOUPhEXWmsYW0KiRN1zNh8aKUzgSo4n6k67cRjx4YG0oNQ9QkLmd RJ9PMokhsZC0LQF8jrTlVijTpIga1+2EszrDYZHC7HxHpJ3Hw34uazUaONz5KRrb LdQ11cv8z4q21hBrvGC2G2Skwux6seCQuDuUchblWcpnyD+IS7pbRemKC/sHiwiv OikkJ4i6ovhYXgHD0em2JXkMxytw1rMJgyaQyTfjhoY1A8cHHPGdufst8WGlINU2 7mxfvtfnGsDxTeGwRK5M6qaQHXPrESXcvENMOwjsl2snAiVCcepS9Z/D5pa8Ob1Y lBkBKOYNorf760k89w75Xr103TT8Ua2U+vkUCvFBhWEh2VqzSku78GayVy+7IQcR BphdmL1v/WXmh6OfJnRzWcZvmWir5kipVvDkxHzeA22owOTM7xW3SIiYBDBhCNc2 0BHRF+uweP5AbXKle/HpsFiRYUlnrXjW8pgzEHrTYa+D/GDD1tbr12elyCwTZHyB PDFfc+basrpWW+S5yzys =545w -----END PGP SIGNATURE----- --HmSQe9C0pvK0dfg+--