diff --git a/appveyor.yml b/appveyor.yml index 9d708923c..3a7430279 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -120,21 +120,27 @@ build_script: Add-Content "C:\Program Files\postgresql\12\data\postgresql.conf" "ssl_key_file='C:/projects/timescaledb/build/tsl/test/ts_data_node.key'" Add-Content "C:\Program Files\postgresql\12\data\postgresql.conf" "timescaledb.ssl_dir='C:/projects/timescaledb/build/tsl/test/timescaledb/certs'" + Add-Content "C:\Program Files\postgresql\12\data\postgresql.conf" "timescaledb.passfile='C:/projects/timescaledb/build/tsl/test/pgpass.conf'" Add-Content "C:\Program Files\postgresql\12\data\postgresql.conf" "timescaledb.license = 'apache'" # Add-Content "C:\Program Files\postgresql\12\data\postgresql.conf" "log_min_messages='debug5'" - - Set-Content "C:\Program Files\postgresql\12\data\pg_hba.conf" "host all all ::1/128 trust" - - Add-Content "C:\Program Files\postgresql\12\data\pg_hba.conf" "host all all 127.0.0.1/32 trust" - # build timescale .\bootstrap -DUSE_OPENSSL=0 -DPG_PATH="C:\Program Files\PostgreSQL\12" -DREGRESS_CHECKS=OFF -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CONFIGURATION_TYPES=Debug -DCMAKE_C_FLAGS=/MP + # Filter ssl and local configuration from pg_hba.conf file since + # we have turned off SSL and local (unix domain socket) + # connections are not supported on Windows. + + Get-Content "./build/test/pg_hba.conf" | Where-Object {$_ -notmatch "^hostssl|^local"} | Set-Content "C:\Program Files\postgresql\12\data\pg_hba.conf" + + Write-Output "Showing pg_hba.conf" + + Get-Content "C:\Program Files\postgresql\12\data\pg_hba.conf" + cmake --build ./build --config Debug cmake --build ./build --config Debug --target install @@ -181,7 +187,7 @@ test_script: Get-Service -Name "postgresql-x64-12" - docker exec -it pgregress /bin/bash -c "psql -a -e -E -p 55432 -U postgres --host='docker.for.win.localhost' -v VERBOSITY=verbose -c'\dx;'" + docker exec -it pgregress /bin/bash -c "psql -a -e -E -p 55432 -U postgres --host='docker.for.win.localhost' -v VERBOSITY=verbose -c'\dx;'" #right now we only run timescale regression tests, others will be set up later diff --git a/test/pg_hba.conf.in b/test/pg_hba.conf.in index e20b83050..9f0d77845 100644 --- a/test/pg_hba.conf.in +++ b/test/pg_hba.conf.in @@ -5,12 +5,12 @@ local all all # IPv4 local connections: hostssl all @TEST_ROLE_CLUSTER_SUPERUSER@ 127.0.0.1/32 cert clientcert=1 hostssl all @TEST_ROLE_1@ 127.0.0.1/32 cert clientcert=1 -hostssl all @TEST_ROLE_2@ 127.0.0.1/32 cert clientcert=1 +host all @TEST_ROLE_2@ 127.0.0.1/32 password host all @TEST_ROLE_3@ 127.0.0.1/32 password host all all 127.0.0.1/32 trust # IPv6 local connections: hostssl all @TEST_ROLE_CLUSTER_SUPERUSER@ ::1/128 cert clientcert=1 hostssl all @TEST_ROLE_1@ ::1/128 cert clientcert=1 -hostssl all @TEST_ROLE_2@ ::1/128 cert clientcert=1 +host all @TEST_ROLE_2@ ::1/128 password host all @TEST_ROLE_3@ ::1/128 password host all all ::1/128 trust diff --git a/test/pgpass.conf.in b/test/pgpass.conf.in index 516fd9896..db8d26c11 100644 --- a/test/pgpass.conf.in +++ b/test/pgpass.conf.in @@ -1 +1,3 @@ -*:*:*:@TEST_ROLE_3@:@TEST_ROLE_3_PASS@ +# Only TEST_ROLE_2 should have password in passfile +# TEST_ROLE_3 needs to rely on, e.g., user mappings in the DB +*:*:*:@TEST_ROLE_2@:@TEST_ROLE_2_PASS@ diff --git a/test/runner.sh b/test/runner.sh index ffab9f2ae..cfb9f285b 100755 --- a/test/runner.sh +++ b/test/runner.sh @@ -38,6 +38,7 @@ TEST_ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2:-default_perm_user TEST_ROLE_CLUSTER_SUPERUSER=${TEST_ROLE_CLUSTER_SUPERUSER:-cluster_superuser} TEST_ROLE_1=${TEST_ROLE_1:-test_role_1} TEST_ROLE_2=${TEST_ROLE_2:-test_role_2} +TEST_ROLE_2_PASS=${TEST_ROLE_2_PASS:-pass} TEST_ROLE_3=${TEST_ROLE_3:-test_role_3} TEST_ROLE_3_PASS=${TEST_ROLE_3_PASS:-pass} @@ -63,7 +64,7 @@ if mkdir ${TEST_OUTPUT_DIR}/.pg_init 2>/dev/null; then ALTER USER ${TEST_ROLE_SUPERUSER} WITH SUPERUSER; ALTER USER ${TEST_ROLE_CLUSTER_SUPERUSER} WITH SUPERUSER; ALTER USER ${TEST_ROLE_1} WITH CREATEDB CREATEROLE; - ALTER USER ${TEST_ROLE_2} WITH CREATEDB; + ALTER USER ${TEST_ROLE_2} WITH CREATEDB PASSWORD '${TEST_ROLE_2_PASS}'; ALTER USER ${TEST_ROLE_3} WITH CREATEDB PASSWORD '${TEST_ROLE_3_PASS}'; EOF ${PSQL} $@ -U ${USER} -d postgres -v ECHO=none -c "ALTER USER ${TEST_ROLE_SUPERUSER} WITH SUPERUSER;" >/dev/null @@ -105,6 +106,7 @@ ${PSQL} -U ${TEST_PGUSER} \ -v ROLE_1=${TEST_ROLE_1} \ -v ROLE_2=${TEST_ROLE_2} \ -v ROLE_3=${TEST_ROLE_3} \ + -v ROLE_2_PASS=${TEST_ROLE_2_PASS} \ -v ROLE_3_PASS=${TEST_ROLE_3_PASS} \ -v MODULE_PATHNAME="'timescaledb-${EXT_VERSION}'" \ -v TSL_MODULE_PATHNAME="'timescaledb-tsl-${EXT_VERSION}'" \ diff --git a/test/test-defs.cmake b/test/test-defs.cmake index a775c5409..44fc90a15 100644 --- a/test/test-defs.cmake +++ b/test/test-defs.cmake @@ -6,6 +6,10 @@ set(TEST_ROLE_DEFAULT_PERM_USER_2 default_perm_user_2) set(TEST_ROLE_1 test_role_1) set(TEST_ROLE_2 test_role_2) set(TEST_ROLE_3 test_role_3) + +# TEST_ROLE_2 has password in passfile +set(TEST_ROLE_2_PASS pass) +# TEST_ROLE_3 does not have password in passfile set(TEST_ROLE_3_PASS pass) set(TEST_INPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) @@ -93,6 +97,7 @@ if(PG_REGRESS) TEST_ROLE_1=${TEST_ROLE_1} TEST_ROLE_2=${TEST_ROLE_2} TEST_ROLE_3=${TEST_ROLE_3} + TEST_ROLE_2_PASS=${TEST_ROLE_2_PASS} TEST_ROLE_3_PASS=${TEST_ROLE_3_PASS} TEST_DBNAME=${TEST_DBNAME} TEST_INPUT_DIR=${TEST_INPUT_DIR} @@ -113,6 +118,7 @@ if(PG_ISOLATION_REGRESS) TEST_ROLE_1=${TEST_ROLE_1} TEST_ROLE_2=${TEST_ROLE_2} TEST_ROLE_3=${TEST_ROLE_3} + TEST_ROLE_2_PASS=${TEST_2_PASS} TEST_ROLE_3_PASS=${TEST_3_PASS} TEST_DBNAME=${TEST_DBNAME} TEST_INPUT_DIR=${TEST_INPUT_DIR} diff --git a/tsl/src/remote/connection.c b/tsl/src/remote/connection.c index 1d3d76cc5..75c8e85e2 100644 --- a/tsl/src/remote/connection.c +++ b/tsl/src/remote/connection.c @@ -12,9 +12,12 @@ */ #include #include +#include #include +#include #include #include +#include #include #include #include @@ -26,6 +29,7 @@ #include #include #include +#include #include #include @@ -1259,11 +1263,71 @@ remote_connection_open_with_options(const char *node_name, List *connection_opti return conn; } +/* + * Based on PG's GetUserMapping, but this version does not fail when a user + * mapping is not found. + */ +static UserMapping * +get_user_mapping(Oid userid, Oid serverid) +{ + Datum datum; + HeapTuple tp; + bool isnull; + UserMapping *um; + + tp = SearchSysCache2(USERMAPPINGUSERSERVER, + ObjectIdGetDatum(userid), + ObjectIdGetDatum(serverid)); + + if (!HeapTupleIsValid(tp)) + { + /* Not found for the specific user -- try PUBLIC */ + tp = SearchSysCache2(USERMAPPINGUSERSERVER, + ObjectIdGetDatum(InvalidOid), + ObjectIdGetDatum(serverid)); + } + + if (!HeapTupleIsValid(tp)) + return NULL; + + um = (UserMapping *) palloc(sizeof(UserMapping)); +#if PG12_GE + um->umid = ((Form_pg_user_mapping) GETSTRUCT(tp))->oid; +#else + um->umid = HeapTupleGetOid(tp); +#endif + um->userid = userid; + um->serverid = serverid; + + /* Extract the umoptions */ + datum = SysCacheGetAttr(USERMAPPINGUSERSERVER, tp, Anum_pg_user_mapping_umoptions, &isnull); + if (isnull) + um->options = NIL; + else + um->options = untransformRelOptions(datum); + + ReleaseSysCache(tp); + + return um; +} + +/* + * Add user info (username and optionally password) to the connection + * options). + */ static List * -add_username_to_server_options(ForeignServer *server, Oid user_id) +add_userinfo_to_server_options(ForeignServer *server, Oid user_id) { const char *user_name = GetUserNameFromId(user_id, false); List *server_options = list_copy(server->options); + const UserMapping *um = get_user_mapping(user_id, server->serverid); + + /* If a user mapping exists, then use the "user" and "password" options + * from the user mapping (we assume that these options exist, or the + * connection will later fail). Otherwise, just add the "user" and rely on + * other authentication mechanisms. */ + if (NULL != um) + return list_concat(server_options, um->options); return lappend(server_options, makeDefElem("user", (Node *) makeString(pstrdup(user_name)), -1)); @@ -1273,7 +1337,7 @@ TSConnection * remote_connection_open_by_id(TSConnectionId id) { ForeignServer *server = GetForeignServer(id.server_id); - List *connection_options = add_username_to_server_options(server, id.user_id); + List *connection_options = add_userinfo_to_server_options(server, id.user_id); return remote_connection_open_with_options(server->servername, connection_options, true); } @@ -1306,7 +1370,7 @@ remote_connection_open_nothrow(Oid server_id, Oid user_id, char **errmsg) return NULL; } - connection_options = add_username_to_server_options(server, user_id); + connection_options = add_userinfo_to_server_options(server, user_id); conn = remote_connection_open_with_options_nothrow(server->servername, connection_options); if (NULL == conn) diff --git a/tsl/test/expected/dist_grant.out b/tsl/test/expected/dist_grant.out index 289f00d1c..d3c4b46e9 100644 --- a/tsl/test/expected/dist_grant.out +++ b/tsl/test/expected/dist_grant.out @@ -700,3 +700,74 @@ SET ROLE :ROLE_DEFAULT_PERM_USER_2; INSERT INTO conditions SELECT time, 1 + (random()*30)::int, random()*80 FROM generate_series('2019-01-01 00:00:00'::timestamptz, '2019-02-01 00:00:00', '1 min') AS time; +RESET ROLE; +SELECT current_user; + current_user +-------------------- + cluster_super_user +(1 row) + +-- Test GRANT on foreign server and data node authentication using a +-- user mapping +SET ROLE :ROLE_3; +SELECT current_user; + current_user +-------------- + test_role_3 +(1 row) + +CREATE TABLE disttable_role_3(time timestamptz, device int, temp float); +\set ON_ERROR_STOP 0 +-- Can't create distributed hypertable without GRANTs on foreign servers (data nodes) +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); +ERROR: permission denied for foreign server data1 +\set ON_ERROR_STOP 1 +-- Grant USAGE on data1 (but it is not enough) +RESET ROLE; +GRANT USAGE ON FOREIGN SERVER data1 TO :ROLE_3; +SET ROLE :ROLE_3; +\set ON_ERROR_STOP 0 +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); +ERROR: permission denied for foreign server data2 +\set ON_ERROR_STOP 1 +-- Creating the hypertable should work with GRANTs on both servers. +RESET ROLE; +GRANT USAGE ON FOREIGN SERVER data2 TO :ROLE_3; +SET ROLE :ROLE_3; +\set ON_ERROR_STOP 0 +-- Still cannot connect since there is no password in the passfile and +-- no user mapping. +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); +NOTICE: adding not-null constraint to column "time" +ERROR: could not connect to "data1" +\set ON_ERROR_STOP 1 +RESET ROLE; +CREATE USER MAPPING FOR :ROLE_3 SERVER data1 OPTIONS (user :'ROLE_3', password :'ROLE_3_PASS'); +SET ROLE :ROLE_3; +\set ON_ERROR_STOP 0 +-- Still cannot connect since there is only a user mapping for data +-- node "data1". +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); +NOTICE: adding not-null constraint to column "time" +ERROR: could not connect to "data2" +\set ON_ERROR_STOP 1 +RESET ROLE; +CREATE USER MAPPING FOR :ROLE_3 SERVER data2 OPTIONS (user :'ROLE_3', password :'ROLE_3_PASS'); +SET ROLE :ROLE_3; +-- User should be able to connect and create the distributed +-- hypertable at this point. +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); +NOTICE: adding not-null constraint to column "time" + hypertable_id | schema_name | table_name | created +---------------+-------------+------------------+--------- + 7 | public | disttable_role_3 | t +(1 row) + +-- Test insert and query +INSERT INTO disttable_role_3 VALUES ('2019-01-01 00:00:00', 1, 23.4); +SELECT * FROM disttable_role_3; + time | device | temp +------------------------------+--------+------ + Tue Jan 01 00:00:00 2019 PST | 1 | 23.4 +(1 row) + diff --git a/tsl/test/sql/dist_grant.sql b/tsl/test/sql/dist_grant.sql index 66bd50e2a..e9ed32f50 100644 --- a/tsl/test/sql/dist_grant.sql +++ b/tsl/test/sql/dist_grant.sql @@ -216,3 +216,60 @@ SET ROLE :ROLE_DEFAULT_PERM_USER_2; INSERT INTO conditions SELECT time, 1 + (random()*30)::int, random()*80 FROM generate_series('2019-01-01 00:00:00'::timestamptz, '2019-02-01 00:00:00', '1 min') AS time; + +RESET ROLE; +SELECT current_user; + +-- Test GRANT on foreign server and data node authentication using a +-- user mapping +SET ROLE :ROLE_3; +SELECT current_user; +CREATE TABLE disttable_role_3(time timestamptz, device int, temp float); + +\set ON_ERROR_STOP 0 +-- Can't create distributed hypertable without GRANTs on foreign servers (data nodes) +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); +\set ON_ERROR_STOP 1 + +-- Grant USAGE on data1 (but it is not enough) +RESET ROLE; +GRANT USAGE ON FOREIGN SERVER data1 TO :ROLE_3; +SET ROLE :ROLE_3; + +\set ON_ERROR_STOP 0 +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); +\set ON_ERROR_STOP 1 + +-- Creating the hypertable should work with GRANTs on both servers. +RESET ROLE; +GRANT USAGE ON FOREIGN SERVER data2 TO :ROLE_3; +SET ROLE :ROLE_3; + +\set ON_ERROR_STOP 0 +-- Still cannot connect since there is no password in the passfile and +-- no user mapping. +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); +\set ON_ERROR_STOP 1 + +RESET ROLE; +CREATE USER MAPPING FOR :ROLE_3 SERVER data1 OPTIONS (user :'ROLE_3', password :'ROLE_3_PASS'); +SET ROLE :ROLE_3; + +\set ON_ERROR_STOP 0 +-- Still cannot connect since there is only a user mapping for data +-- node "data1". +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); +\set ON_ERROR_STOP 1 + +RESET ROLE; +CREATE USER MAPPING FOR :ROLE_3 SERVER data2 OPTIONS (user :'ROLE_3', password :'ROLE_3_PASS'); +SET ROLE :ROLE_3; + +-- User should be able to connect and create the distributed +-- hypertable at this point. +SELECT * FROM create_distributed_hypertable('disttable_role_3', 'time', data_nodes => '{"data1", "data2"}'); + +-- Test insert and query +INSERT INTO disttable_role_3 VALUES ('2019-01-01 00:00:00', 1, 23.4); +SELECT * FROM disttable_role_3; +