mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-21 05:04:32 +08:00
Add optional user mappings support
A user mapping is a standard PostgreSQL object that can be used to provide a remote username and password when connecting to a data node. The downside of a user mapping, however, is that one mapping needs to exist per server and user while, in comparison, only one password file needs to exist per instance of PostgreSQL. Still, since user mappings are database objects they provide an alternative to password files for environments that cannot easily modify files outside the database. Providing a user mapping is completely optional and doesn't change other methods of providing credentials for authentication.
This commit is contained in:
parent
97254783d4
commit
45db3b9b64
18
appveyor.yml
18
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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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@
|
||||
|
@ -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}'" \
|
||||
|
@ -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}
|
||||
|
@ -12,9 +12,12 @@
|
||||
*/
|
||||
#include <postgres.h>
|
||||
#include <access/xact.h>
|
||||
#include <access/reloptions.h>
|
||||
#include <catalog/pg_foreign_server.h>
|
||||
#include <catalog/pg_user_mapping.h>
|
||||
#include <commands/defrem.h>
|
||||
#include <common/md5.h>
|
||||
#include <foreign/foreign.h>
|
||||
#include <libpq-events.h>
|
||||
#include <libpq/libpq.h>
|
||||
#include <mb/pg_wchar.h>
|
||||
@ -26,6 +29,7 @@
|
||||
#include <utils/fmgrprotos.h>
|
||||
#include <utils/inval.h>
|
||||
#include <utils/guc.h>
|
||||
#include <utils/syscache.h>
|
||||
|
||||
#include <dist_util.h>
|
||||
#include <errors.h>
|
||||
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user