diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6661866a..57d1015a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,7 @@ This will run the vast majority of the tests, but some tests will be skipped (e. ### Creating a New PostgreSQL Cluster Exclusively for Testing The following environment variables need to be set both for initial setup and whenever the tests are run. (direnv is -highly recommended): +highly recommended). Depending on your platform, you may need to change the host for `PGX_TEST_UNIX_SOCKET_CONN_STRING`. ``` export PGPORT=5015 @@ -49,12 +49,13 @@ export PGUSER=postgres export PGDATABASE=pgx_test export POSTGRESQL_DATA_DIR=postgresql -export PGX_TEST_DATABASE="host=/private/tmp database=pgx_test" +export PGX_TEST_DATABASE="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret" export PGX_TEST_UNIX_SOCKET_CONN_STRING="host=/private/tmp database=pgx_test" export PGX_TEST_TCP_CONN_STRING="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret" export PGX_TEST_MD5_PASSWORD_CONN_STRING="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret" -export PGX_TEST_PLAIN_PASSWORD_CONN_STRING=postgres://pgx_pw:secret@127.0.0.1/pgx_test -export PGX_TEST_TLS_CONN_STRING=postgres://pgx_ssl:secret@127.0.0.1/pgx_test?sslmode=require +export PGX_TEST_PLAIN_PASSWORD_CONN_STRING="host=127.0.0.1 user=pgx_pw password=secret" +export PGX_TEST_TLS_CONN_STRING="host=localhost user=pgx_ssl password=secret sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem" +export PGX_TEST_TLS_CLIENT_CONN_STRING="host=127.0.0.1 user=pgx_sslcert sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem database=pgx_test" export PGX_TEST_SCRAM_PASSWORD_CONN_STRING="host=127.0.0.1 user=pgx_scram password=secret database=pgx_test" ``` @@ -62,22 +63,25 @@ Create a new database cluster. ``` initdb --locale=en_US -E UTF-8 --username=postgres .testdb/$POSTGRESQL_DATA_DIR + +echo "listen_addresses = '127.0.0.1'" >> .testdb/$POSTGRESQL_DATA_DIR/postgresql.conf echo "port = $PGPORT" >> .testdb/$POSTGRESQL_DATA_DIR/postgresql.conf cat testsetup/postgresql_ssl.conf >> .testdb/$POSTGRESQL_DATA_DIR/postgresql.conf cp testsetup/pg_hba.conf .testdb/$POSTGRESQL_DATA_DIR/pg_hba.conf - +cp testsetup/ca.cnf .testdb cp testsetup/localhost.cnf .testdb +cp testsetup/pgx_sslcert.cnf .testdb cd .testdb # Generate a CA public / private key pair. openssl genrsa -out ca.key 4096 -openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -subj '/O=pgx-test-root' -out ca.pem +openssl req -x509 -config ca.cnf -new -nodes -key ca.key -sha256 -days 365 -subj '/O=pgx-test-root' -out ca.pem # Generate the certificate for localhost (the server). openssl genrsa -out localhost.key 2048 openssl req -new -config localhost.cnf -key localhost.key -out localhost.csr -openssl x509 -req -in localhost.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out localhost.crt -days 3650 -sha256 -extfile localhost.cnf -extensions v3_req +openssl x509 -req -in localhost.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out localhost.crt -days 364 -sha256 -extfile localhost.cnf -extensions v3_req # Copy certificates to server directory and set permissions. cp ca.pem $POSTGRESQL_DATA_DIR/root.crt @@ -85,6 +89,11 @@ cp localhost.key $POSTGRESQL_DATA_DIR/server.key chmod 600 $POSTGRESQL_DATA_DIR/server.key cp localhost.crt $POSTGRESQL_DATA_DIR/server.crt +# Generate the certificate for client authentication. +openssl genrsa -des -out pgx_sslcert.key -passout pass:certpw 2048 +openssl req -new -config pgx_sslcert.cnf -key pgx_sslcert.key -passin pass:certpw -out pgx_sslcert.csr +openssl x509 -req -in pgx_sslcert.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out pgx_sslcert.crt -days 363 -sha256 -extfile pgx_sslcert.cnf -extensions v3_req + cd .. ``` diff --git a/pgconn/pgconn_test.go b/pgconn/pgconn_test.go index abca5540..dd238b65 100644 --- a/pgconn/pgconn_test.go +++ b/pgconn/pgconn_test.go @@ -93,15 +93,60 @@ func TestConnectTLS(t *testing.T) { t.Skipf("Skipping due to missing environment variable %v", "PGX_TEST_TLS_CONN_STRING") } - var conn *pgconn.PgConn - var err error + conn, err := pgconn.Connect(context.Background(), connString) + require.NoError(t, err) + + result := conn.ExecParams(context.Background(), `select ssl from pg_stat_ssl where pg_backend_pid() = pid;`, nil, nil, nil, nil).Read() + require.NoError(t, result.Err) + require.Len(t, result.Rows, 1) + require.Len(t, result.Rows[0], 1) + require.Equalf(t, "t", string(result.Rows[0][0]), "not a TLS connection") + + closeConn(t, conn) +} + +func TestConnectTLSPasswordProtectedClientCertWithSSLPassword(t *testing.T) { + t.Parallel() + + connString := os.Getenv("PGX_TEST_TLS_CLIENT_CONN_STRING") + if connString == "" { + t.Skipf("Skipping due to missing environment variable %v", "PGX_TEST_TLS_CLIENT_CONN_STRING") + } + if os.Getenv("PGX_SSL_PASSWORD") == "" { + t.Skipf("Skipping due to missing environment variable %v", "PGX_SSL_PASSWORD") + } + + connString += " sslpassword=" + os.Getenv("PGX_SSL_PASSWORD") + + conn, err := pgconn.Connect(context.Background(), connString) + require.NoError(t, err) + + result := conn.ExecParams(context.Background(), `select ssl from pg_stat_ssl where pg_backend_pid() = pid;`, nil, nil, nil, nil).Read() + require.NoError(t, result.Err) + require.Len(t, result.Rows, 1) + require.Len(t, result.Rows[0], 1) + require.Equalf(t, "t", string(result.Rows[0][0]), "not a TLS connection") + + closeConn(t, conn) +} + +func TestConnectTLSPasswordProtectedClientCertWithGetSSLPasswordConfigOption(t *testing.T) { + t.Parallel() + + connString := os.Getenv("PGX_TEST_TLS_CLIENT_CONN_STRING") + if connString == "" { + t.Skipf("Skipping due to missing environment variable %v", "PGX_TEST_TLS_CLIENT_CONN_STRING") + } + if os.Getenv("PGX_SSL_PASSWORD") == "" { + t.Skipf("Skipping due to missing environment variable %v", "PGX_SSL_PASSWORD") + } var sslOptions pgconn.ParseConfigOptions sslOptions.GetSSLPassword = GetSSLPassword config, err := pgconn.ParseConfigWithOptions(connString, sslOptions) require.Nil(t, err) - conn, err = pgconn.ConnectConfig(context.Background(), config) + conn, err := pgconn.ConnectConfig(context.Background(), config) require.NoError(t, err) result := conn.ExecParams(context.Background(), `select ssl from pg_stat_ssl where pg_backend_pid() = pid;`, nil, nil, nil, nil).Read() diff --git a/testsetup/ca.cnf b/testsetup/ca.cnf new file mode 100644 index 00000000..bd018037 --- /dev/null +++ b/testsetup/ca.cnf @@ -0,0 +1,6 @@ +[ req ] +distinguished_name = dn +[ dn ] +commonName = ca +[ ext ] +basicConstraints =CA:TRUE,pathlen:0 diff --git a/testsetup/pg_hba.conf b/testsetup/pg_hba.conf index e7bf461d..6609372e 100644 --- a/testsetup/pg_hba.conf +++ b/testsetup/pg_hba.conf @@ -4,3 +4,4 @@ host all pgx_md5 127.0.0.1/32 md5 host all pgx_scram 127.0.0.1/32 scram-sha-256 host all pgx_pw 127.0.0.1/32 password hostssl all pgx_ssl 127.0.0.1/32 scram-sha-256 +hostssl all pgx_sslcert 127.0.0.1/32 cert diff --git a/testsetup/pgx_sslcert.cnf b/testsetup/pgx_sslcert.cnf new file mode 100644 index 00000000..2d5d0ff7 --- /dev/null +++ b/testsetup/pgx_sslcert.cnf @@ -0,0 +1,9 @@ +[ req ] +default_bits = 2048 +distinguished_name = dn +req_extensions = v3_req +prompt = no +[ dn ] +commonName = pgx_sslcert +[ v3_req ] +keyUsage = digitalSignature diff --git a/testsetup/postgresql_setup.sql b/testsetup/postgresql_setup.sql index cda17490..51414863 100644 --- a/testsetup/postgresql_setup.sql +++ b/testsetup/postgresql_setup.sql @@ -3,12 +3,13 @@ create extension hstore; create domain uint64 as numeric(20,0); -- Create users for different types of connections and authentication. -create user pgx_ssl PASSWORD 'secret' with superuser; +create user pgx_ssl with superuser PASSWORD 'secret'; +create user pgx_sslcert with superuser PASSWORD 'secret'; set password_encryption = md5; -create user pgx_md5 PASSWORD 'secret' with superuser; +create user pgx_md5 with superuser PASSWORD 'secret'; set password_encryption = 'scram-sha-256'; -create user pgx_pw PASSWORD 'secret' with superuser; -create user pgx_scram PASSWORD 'secret' with superuser; +create user pgx_pw with superuser PASSWORD 'secret'; +create user pgx_scram with superuser PASSWORD 'secret'; \set whoami `whoami` create user :whoami with superuser; -- unix domain socket user