diff --git a/.gitignore b/.gitignore index 0b436164..492aafd1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ lcov.info dev/.bash_history dev/cache !dev/cache/.keepme +.bundle diff --git a/CONFIG.md b/CONFIG.md index 3118a49a..d81c1e3e 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -297,6 +297,38 @@ Query to be sent to servers to obtain the hash used for md5 authentication. The established using the database configured in the pool. This parameter is inherited by every pool and can be redefined in pool configuration. +#### Configuration example + +> This example uses the `postgres` database. + +add on `pgcat.toml`: + +```toml +auth_query = "SELECT * FROM pgcat.user_lookup('$1');" +auth_query_user = "connection_pooler" +auth_query_password = "user-look-up-pass" +auth_query_database = "postgres" +``` + +setup in the `postgres` database: + +```sql +CREATE ROLE connection_pooler PASSWORD 'user-look-up-pass' LOGIN; +CREATE SCHEMA IF NOT EXISTS pgcat; + +CREATE OR REPLACE FUNCTION pgcat.user_lookup(i_username text) + RETURNS table ("user" text, hash text) AS $$ +SELECT usename as user, passwd as hash FROM pg_catalog.pg_shadow +WHERE usename = i_username; +$$ LANGUAGE sql SECURITY DEFINER; + +GRANT CONNECT ON DATABASE postgres TO connection_pooler; +GRANT USAGE ON SCHEMA pgcat TO connection_pooler; + +REVOKE ALL ON FUNCTION pgcat.user_lookup(text) FROM public, connection_pooler; +GRANT EXECUTE ON FUNCTION pgcat.user_lookup(text) TO connection_pooler; +``` + ### auth_query_user ``` path: pools..auth_query_user @@ -319,6 +351,16 @@ Password to be used for connecting to servers to obtain the hash used for md5 au specified in `auth_query_user`. The connection will be established using the database configured in the pool. This parameter is inherited by every pool and can be redefined in pool configuration. +### auth_query_database +``` +path: pools..auth_query_database +default: +example: "postgres" +``` + +Database to be used for connecting to servers to obtain the hash used for md5 authentication by sending the query +specified in `auth_query_query`. This parameter is inherited by every pool and can be redefined in pool configuration. + ### automatic_sharding_key ``` path: pools..automatic_sharding_key diff --git a/docker-compose.yml b/docker-compose.yml index 96d1f395..81b094c6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,10 @@ services: environment: POSTGRES_PASSWORD: postgres POSTGRES_HOST_AUTH_METHOD: md5 + ports: + - "15432:5432" + volumes: + - "${PWD}/examples/docker:/docker-entrypoint-initdb.d" pgcat: build: . command: diff --git a/examples/docker/01-setup.sql b/examples/docker/01-setup.sql new file mode 100644 index 00000000..9a930f09 --- /dev/null +++ b/examples/docker/01-setup.sql @@ -0,0 +1,2 @@ +ALTER SYSTEM SET password_encryption to 'md5'; +SELECT pg_reload_conf(); diff --git a/examples/docker/02-add-users-and-db.sql b/examples/docker/02-add-users-and-db.sql new file mode 100644 index 00000000..6d1bdc81 --- /dev/null +++ b/examples/docker/02-add-users-and-db.sql @@ -0,0 +1,3 @@ +CREATE ROLE myappadmin PASSWORD 'myappadmin' LOGIN; +CREATE DATABASE myapp owner myappadmin; +GRANT ALL PRIVILEGES ON DATABASE myapp TO myappadmin; diff --git a/examples/docker/03-query-auth.sql b/examples/docker/03-query-auth.sql new file mode 100644 index 00000000..861c87ff --- /dev/null +++ b/examples/docker/03-query-auth.sql @@ -0,0 +1,17 @@ +CREATE ROLE connection_pooler PASSWORD 'user-look-up-pass' LOGIN; +CREATE SCHEMA IF NOT EXISTS pgcat; + +CREATE OR REPLACE FUNCTION pgcat.user_lookup(i_username text) + RETURNS table ("user" text, hash text) AS $$ +SELECT usename as user, passwd as hash FROM pg_catalog.pg_shadow +WHERE usename = i_username; +$$ LANGUAGE sql SECURITY DEFINER; + +-- usage: +-- SELECT * FROM pgcat.user_lookup('$1'); + +GRANT CONNECT ON DATABASE postgres TO connection_pooler; +GRANT USAGE ON SCHEMA pgcat TO connection_pooler; + +REVOKE ALL ON FUNCTION pgcat.user_lookup(text) FROM public, connection_pooler; +GRANT EXECUTE ON FUNCTION pgcat.user_lookup(text) TO connection_pooler; diff --git a/examples/docker/pgcat.toml b/examples/docker/pgcat.toml index 5fd929de..03a96ec1 100644 --- a/examples/docker/pgcat.toml +++ b/examples/docker/pgcat.toml @@ -1,123 +1,42 @@ -# -# PgCat config example. -# - -# -# General pooler settings +## pgcat.toml [general] -# What IP to run on, 0.0.0.0 means accessible from everywhere. host = "0.0.0.0" - -# Port to run on, same as PgBouncer used in this example. port = 6432 - -# Whether to enable prometheus exporter or not. enable_prometheus_exporter = true - -# Port at which prometheus exporter listens on. prometheus_exporter_port = 9930 -# How long to wait before aborting a server connection (ms). -connect_timeout = 5000 - -# How much time to give `SELECT 1` health check query to return with a result (ms). -healthcheck_timeout = 1000 - -# How long to keep connection available for immediate re-use, without running a healthcheck query on it -healthcheck_delay = 30000 - -# How much time to give clients during shutdown before forcibly killing client connections (ms). -shutdown_timeout = 60000 - -# For how long to ban a server if it fails a health check (seconds). -ban_time = 60 # seconds - -# If we should log client connections -log_client_connections = false +log_client_connections = true +log_client_disconnections = true -# If we should log client disconnections -log_client_disconnections = false +server_tls = false +verify_server_certificate = false +#tls_certificate = "/etc/postgresql/certificate/server.crt" +#tls_private_key = "/etc/postgresql/certificate/server.key" -# TLS -# tls_certificate = "server.cert" -# tls_private_key = "server.key" - -# Credentials to access the virtual administrative database (pgbouncer or pgcat) -# Connecting to that database allows running commands like `SHOW POOLS`, `SHOW DATABASES`, etc.. admin_username = "postgres" admin_password = "postgres" -# pool -# configs are structured as pool. -# the pool_name is what clients use as database name when connecting -# For the example below a client can connect using "postgres://sharding_user:sharding_user@pgcat_host:pgcat_port/sharded" -[pools.postgres] -# Pool mode (see PgBouncer docs for more). -# session: one server connection per connected client -# transaction: one server connection per client transaction -pool_mode = "transaction" +auth_query = "SELECT * FROM pgcat.user_lookup('$1');" +auth_query_user = "connection_pooler" +auth_query_password = "user-look-up-pass" +auth_query_database = "postgres" -# If the client doesn't specify, route traffic to -# this role by default. -# -# any: round-robin between primary and replicas, -# replica: round-robin between replicas only without touching the primary, -# primary: all queries go to the primary unless otherwise specified. +## session mode +[pools.myapp] +pool_mode = "session" default_role = "any" -# Query parser. If enabled, we'll attempt to parse -# every incoming query to determine if it's a read or a write. -# If it's a read query, we'll direct it to a replica. Otherwise, if it's a write, -# we'll direct it to the primary. -query_parser_enabled = true - -# If the query parser is enabled and this setting is enabled, the primary will be part of the pool of databases used for -# load balancing of read queries. Otherwise, the primary will only be used for write -# queries. The primary can always be explicitly selected with our custom protocol. -primary_reads_enabled = true +# query_parser_enabled = true -# So what if you wanted to implement a different hashing function, -# or you've already built one and you want this pooler to use it? -# -# Current options: -# -# pg_bigint_hash: PARTITION BY HASH (Postgres hashing function) -# sha1: A hashing function based on SHA1 -# sharding_function = "pg_bigint_hash" -# Credentials for users that may connect to this cluster -[pools.postgres.users.0] -username = "postgres" -password = "postgres" -# Maximum number of server connections that can be established for this user -# The maximum number of connection from a single Pgcat process to any database in the cluster -# is the sum of pool_size across all users. -pool_size = 9 - -# Maximum query duration. Dangerous, but protects against DBs that died in a non-obvious way. -statement_timeout = 0 - -# Shard 0 -[pools.postgres.shards.0] -# [ host, port, role ] -servers = [ - [ "postgres", 5432, "primary" ], - [ "postgres", 5432, "replica" ] -] -# Database name (e.g. "postgres") -database = "postgres" - -[pools.postgres.shards.1] -servers = [ - [ "postgres", 5432, "primary" ], - [ "postgres", 5432, "replica" ], -] -database = "postgres" +[pools.myapp.users.0] +username = "myappadmin" +pool_size = 23 -[pools.postgres.shards.2] +[pools.myapp.shards.0] servers = [ - [ "postgres", 5432, "primary" ], - [ "postgres", 5432, "replica" ], +# [ "localhost", 15432, "primary" ], # to use without docker + [ "postgres", 5432, "primary" ], # to use with docker ] -database = "postgres" +database = "myapp" diff --git a/pgcat.toml b/pgcat.toml index 3e8801b6..c2560a73 100644 --- a/pgcat.toml +++ b/pgcat.toml @@ -195,6 +195,10 @@ sharding_function = "pg_bigint_hash" # This parameter is inherited by every pool and can be redefined in pool configuration. # auth_query_password = "sharding_user" +# Database to be used for connecting to servers to obtain the hash used for md5 authentication by sending the query +# specified in `auth_query_query`. This parameter is inherited by every pool and can be redefined in pool configuration. +# auth_query_database = "shared_db" + # Automatically parse this from queries and route queries to the right shard! # automatic_sharding_key = "data.id" diff --git a/src/auth_passthrough.rs b/src/auth_passthrough.rs index fc0f6dc6..10f54824 100644 --- a/src/auth_passthrough.rs +++ b/src/auth_passthrough.rs @@ -8,15 +8,17 @@ pub struct AuthPassthrough { password: String, query: String, user: String, + database: Option, } impl AuthPassthrough { /// Initializes an AuthPassthrough. - pub fn new(query: &str, user: &str, password: &str) -> Self { + pub fn new(query: &str, user: &str, password: &str, database: Option) -> Self { AuthPassthrough { password: password.to_string(), query: query.to_string(), user: user.to_string(), + database: database, } } @@ -28,6 +30,7 @@ impl AuthPassthrough { pool_config.auth_query.as_ref().unwrap(), pool_config.auth_query_user.as_ref().unwrap(), pool_config.auth_query_password.as_ref().unwrap(), + pool_config.auth_query_database.clone(), )); } @@ -64,7 +67,7 @@ impl AuthPassthrough { /// ``` /// use pgcat::auth_passthrough::AuthPassthrough; /// use pgcat::config::Address; - /// let auth_passthrough = AuthPassthrough::new("SELECT * FROM public.user_lookup('$1');", "postgres", "postgres"); + /// let auth_passthrough = AuthPassthrough::new("SELECT * FROM public.user_lookup('$1');", "postgres", "postgres", None); /// auth_passthrough.fetch_hash(&Address::default()); /// ``` /// @@ -87,7 +90,11 @@ impl AuthPassthrough { let auth_query = self.query.replace("$1", user); - match Server::exec_simple_query(address, &auth_user, &auth_query).await { + let mut query_address = address.clone(); + if self.database.is_some() { + query_address.database = self.database.clone().unwrap(); + } + match Server::exec_simple_query(&query_address, &auth_user, &auth_query).await { Ok(password_data) => { if password_data.len() == 2 && password_data.first().unwrap() == user { if let Some(stripped_hash) = password_data diff --git a/src/config.rs b/src/config.rs index 9228b9bb..ed53d61d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -322,6 +322,7 @@ pub struct General { pub auth_query: Option, pub auth_query_user: Option, pub auth_query_password: Option, + pub auth_query_database: Option, #[serde(default)] pub prepared_statements: bool, @@ -448,6 +449,7 @@ impl Default for General { auth_query: None, auth_query_user: None, auth_query_password: None, + auth_query_database: None, server_lifetime: Self::default_server_lifetime(), server_round_robin: Self::default_server_round_robin(), validate_config: true, @@ -538,6 +540,7 @@ pub struct Pool { pub auth_query: Option, pub auth_query_user: Option, pub auth_query_password: Option, + pub auth_query_database: Option, #[serde(default = "Pool::default_cleanup_server_connections")] pub cleanup_server_connections: bool, @@ -674,6 +677,7 @@ impl Default for Pool { auth_query: None, auth_query_user: None, auth_query_password: None, + auth_query_database: None, server_lifetime: None, plugins: None, cleanup_server_connections: true, @@ -876,6 +880,10 @@ impl Config { if pool.auth_query_password.is_none() { pool.auth_query_password = self.general.auth_query_password.clone(); } + + if pool.auth_query_database.is_none() { + pool.auth_query_database = self.general.auth_query_database.clone(); + } } } } @@ -1011,6 +1019,28 @@ impl Config { self.general.server_lifetime ); info!("Sever round robin: {}", self.general.server_round_robin); + + if self.general.auth_query.is_some() { + info!( + "Auth query configured: {}", + self.general.auth_query.clone().unwrap() + ); + } + + if self.general.auth_query_user.is_some() { + info!( + "Auth query user: {}", + self.general.auth_query_user.clone().unwrap() + ); + } + + if self.general.auth_query_database.is_some() { + info!( + "Auth query database: {}", + self.general.auth_query_database.clone().unwrap() + ); + } + match self.general.tls_certificate.clone() { Some(tls_certificate) => { info!("TLS certificate: {}", tls_certificate); diff --git a/src/pool.rs b/src/pool.rs index b9293521..b761d9bb 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -142,6 +142,7 @@ pub struct PoolSettings { pub auth_query: Option, pub auth_query_user: Option, pub auth_query_password: Option, + pub auth_query_database: Option, /// Plugins pub plugins: Option, @@ -169,6 +170,7 @@ impl Default for PoolSettings { auth_query: None, auth_query_user: None, auth_query_password: None, + auth_query_database: None, plugins: None, } } @@ -474,6 +476,7 @@ impl ConnectionPool { auth_query: pool_config.auth_query.clone(), auth_query_user: pool_config.auth_query_user.clone(), auth_query_password: pool_config.auth_query_password.clone(), + auth_query_database: pool_config.auth_query_database.clone(), plugins: match pool_config.plugins { Some(ref plugins) => Some(plugins.clone()), None => config.plugins.clone(), diff --git a/src/query_router.rs b/src/query_router.rs index 126b8138..36a94506 100644 --- a/src/query_router.rs +++ b/src/query_router.rs @@ -1176,6 +1176,7 @@ mod test { auth_query: None, auth_query_password: None, auth_query_user: None, + auth_query_database: None, db: "test".to_string(), plugins: None, }; @@ -1251,6 +1252,7 @@ mod test { auth_query: None, auth_query_password: None, auth_query_user: None, + auth_query_database: None, db: "test".to_string(), plugins: None, }; diff --git a/src/server.rs b/src/server.rs index fa68b678..bc877120 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1194,7 +1194,10 @@ impl Server { ) -> Result, Error> { let client_server_map: ClientServerMap = Arc::new(Mutex::new(HashMap::new())); - debug!("Connecting to server to obtain auth hashes."); + debug!( + "Connecting to server to obtain auth hashes from db '{}'.", + &address.database + ); let mut server = Server::startup( address, user, diff --git a/tests/ruby/auth_query_spec.rb b/tests/ruby/auth_query_spec.rb index 1ac62164..8e2746d3 100644 --- a/tests/ruby/auth_query_spec.rb +++ b/tests/ruby/auth_query_spec.rb @@ -3,11 +3,14 @@ require_relative 'spec_helper' require_relative 'helpers/auth_query_helper' -describe "Auth Query" do - let(:configured_instances) {[5432, 10432]} +describe 'Auth Query' do + let(:configured_instances) { [5432, 10_432] } let(:config_user) { { 'username' => 'sharding_user', 'password' => 'sharding_user' } } let(:pg_user) { { 'username' => 'sharding_user', 'password' => 'sharding_user' } } - let(:processes) { Helpers::AuthQuery.single_shard_auth_query(pool_name: "sharded_db", pg_user: pg_user, config_user: config_user, extra_conf: config, wait_until_ready: wait_until_ready ) } + let(:processes) do + Helpers::AuthQuery.single_shard_auth_query(pool_name: 'sharded_db', pg_user: pg_user, config_user: config_user, + extra_conf: config, wait_until_ready: wait_until_ready) + end let(:config) { {} } let(:wait_until_ready) { true } @@ -19,21 +22,25 @@ @failing_process = false end - context "when auth_query is not configured" do + context 'when auth_query is not configured' do context 'and cleartext passwords are set' do - it "uses local passwords" do - conn = PG.connect(processes.pgcat.connection_string("sharded_db", config_user['username'], config_user['password'])) + it 'uses local passwords' do + conn = PG.connect(processes.pgcat.connection_string('sharded_db', config_user['username'], + config_user['password'])) - expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil + expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil end end context 'and cleartext passwords are not set' do let(:config_user) { { 'username' => 'sharding_user' } } - it "does not start because it is not possible to authenticate" do + it 'does not start because it is not possible to authenticate' do @failing_process = true - expect { processes.pgcat }.to raise_error(StandardError, /You have to specify a user password for every pool if auth_query is not specified/) + expect do + processes.pgcat + end.to raise_error(StandardError, + /You have to specify a user password for every pool if auth_query is not specified/) end end end @@ -41,12 +48,11 @@ context 'when auth_query is configured' do context 'with global configuration' do around(:example) do |example| - # Set up auth query Helpers::AuthQuery.set_up_auth_query_for_user( user: 'md5_auth_user', password: 'secret' - ); + ) example.run @@ -54,15 +60,36 @@ Helpers::AuthQuery.tear_down_auth_query_for_user( user: 'md5_auth_user', password: 'secret' - ); + ) + end + + context 'with different lookup database' do + let(:config) do + { 'general' => { + 'auth_query' => "SELECT * FROM public.user_lookup2('$1');", + 'auth_query_user' => 'md5_auth_use2r', + 'auth_query_password' => 'secret', + 'auth_query_database' => 'lookup_db' ## doesn't exist yet + } } + end + + it 'it uses obtained passwords' do ## should fail + connection_string = processes.pgcat.connection_string('sharded_db', pg_user['username']) + conn = PG.connect(connection_string) + + expect(conn.exec('SELECT 1 + 2')).not_to be_nil + end end context 'with correct global parameters' do - let(:config) { { 'general' => { 'auth_query' => "SELECT * FROM public.user_lookup('$1');", 'auth_query_user' => 'md5_auth_user', 'auth_query_password' => 'secret' } } } + let(:config) do + { 'general' => { 'auth_query' => "SELECT * FROM public.user_lookup('$1');", 'auth_query_user' => 'md5_auth_user', + 'auth_query_password' => 'secret' } } + end context 'and with cleartext passwords set' do it 'it uses local passwords' do - conn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password'])) - expect(conn.exec("SELECT 1 + 2")).not_to be_nil + conn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'], pg_user['password'])) + expect(conn.exec('SELECT 1 + 2')).not_to be_nil end end @@ -70,68 +97,75 @@ let(:config_user) { { 'username' => 'sharding_user', 'password' => 'sharding_user' } } it 'it uses obtained passwords' do - connection_string = processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password']) + connection_string = processes.pgcat.connection_string('sharded_db', pg_user['username'], + pg_user['password']) conn = PG.connect(connection_string) - expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil + expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil end it 'allows passwords to be changed without closing existing connections' do - pgconn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'])) - expect(pgconn.exec("SELECT 1 + 2")).not_to be_nil + pgconn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'])) + expect(pgconn.exec('SELECT 1 + 2')).not_to be_nil Helpers::AuthQuery.exec_in_instances(query: "ALTER USER #{pg_user['username']} WITH ENCRYPTED PASSWORD 'secret2';") - expect(pgconn.exec("SELECT 1 + 4")).not_to be_nil + expect(pgconn.exec('SELECT 1 + 4')).not_to be_nil Helpers::AuthQuery.exec_in_instances(query: "ALTER USER #{pg_user['username']} WITH ENCRYPTED PASSWORD '#{pg_user['password']}';") end it 'allows passwords to be changed and that new password is needed when reconnecting' do - pgconn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'])) - expect(pgconn.exec("SELECT 1 + 2")).not_to be_nil + pgconn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'])) + expect(pgconn.exec('SELECT 1 + 2')).not_to be_nil Helpers::AuthQuery.exec_in_instances(query: "ALTER USER #{pg_user['username']} WITH ENCRYPTED PASSWORD 'secret2';") - newconn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'], 'secret2')) - expect(newconn.exec("SELECT 1 + 2")).not_to be_nil + newconn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'], 'secret2')) + expect(newconn.exec('SELECT 1 + 2')).not_to be_nil Helpers::AuthQuery.exec_in_instances(query: "ALTER USER #{pg_user['username']} WITH ENCRYPTED PASSWORD '#{pg_user['password']}';") end end end context 'with wrong parameters' do - let(:config) { { 'general' => { 'auth_query' => 'SELECT 1', 'auth_query_user' => 'wrong_user', 'auth_query_password' => 'wrong' } } } + let(:config) do + { 'general' => { 'auth_query' => 'SELECT 1', 'auth_query_user' => 'wrong_user', + 'auth_query_password' => 'wrong' } } + end context 'and with clear text passwords set' do - it "it uses local passwords" do - conn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password'])) + it 'it uses local passwords' do + conn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'], pg_user['password'])) - expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil + expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil end end context 'and with cleartext passwords not set' do let(:config_user) { { 'username' => 'sharding_user' } } - it "it fails to start as it cannot authenticate against servers" do + it 'it fails to start as it cannot authenticate against servers' do @failing_process = true - expect { PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password'])) }.to raise_error(StandardError, /Error trying to obtain password from auth_query/ ) + expect do + PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'], + pg_user['password'])) + end.to raise_error(StandardError, /Error trying to obtain password from auth_query/) end context 'and we fix the issue and reload' do let(:wait_until_ready) { false } it 'fails in the beginning but starts working after reloading config' do - connection_string = processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password']) - while !(processes.pgcat.logs =~ /Waiting for clients/) do - sleep 0.5 - end + connection_string = processes.pgcat.connection_string('sharded_db', pg_user['username'], + pg_user['password']) + sleep 0.5 until processes.pgcat.logs =~ /Waiting for clients/ - expect { PG.connect(connection_string)}.to raise_error(PG::ConnectionBad) + expect { PG.connect(connection_string) }.to raise_error(PG::ConnectionBad) expect(processes.pgcat.logs).to match(/Error trying to obtain password from auth_query/) current_config = processes.pgcat.current_config - config = { 'general' => { 'auth_query' => "SELECT * FROM public.user_lookup('$1');", 'auth_query_user' => 'md5_auth_user', 'auth_query_password' => 'secret' } } + config = { 'general' => { 'auth_query' => "SELECT * FROM public.user_lookup('$1');", + 'auth_query_user' => 'md5_auth_user', 'auth_query_password' => 'secret' } } processes.pgcat.update_config(current_config.deep_merge(config)) processes.pgcat.reload_config conn = nil - expect { conn = PG.connect(connection_string)}.not_to raise_error - expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil + expect { conn = PG.connect(connection_string) }.not_to raise_error + expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil end end end @@ -140,18 +174,17 @@ context 'with per pool configuration' do around(:example) do |example| - # Set up auth query Helpers::AuthQuery.set_up_auth_query_for_user( user: 'md5_auth_user', password: 'secret' - ); + ) Helpers::AuthQuery.set_up_auth_query_for_user( user: 'md5_auth_user1', password: 'secret', database: 'shard1' - ); + ) example.run @@ -159,40 +192,43 @@ Helpers::AuthQuery.tear_down_auth_query_for_user( user: 'md5_auth_user', password: 'secret' - ); + ) Helpers::AuthQuery.tear_down_auth_query_for_user( user: 'md5_auth_user1', password: 'secret', database: 'shard1' - ); + ) end context 'with correct parameters' do - let(:processes) { Helpers::AuthQuery.two_pools_auth_query(pool_names: ["sharded_db0", "sharded_db1"], pg_user: pg_user, config_user: config_user, extra_conf: config ) } - let(:config) { - { 'pools' => - { - 'sharded_db0' => { - 'auth_query' => "SELECT * FROM public.user_lookup('$1');", - 'auth_query_user' => 'md5_auth_user', - 'auth_query_password' => 'secret' - }, - 'sharded_db1' => { - 'auth_query' => "SELECT * FROM public.user_lookup('$1');", - 'auth_query_user' => 'md5_auth_user1', - 'auth_query_password' => 'secret' - }, + let(:processes) do + Helpers::AuthQuery.two_pools_auth_query(pool_names: %w[sharded_db0 sharded_db1], pg_user: pg_user, + config_user: config_user, extra_conf: config) + end + let(:config) do + { 'pools' => { + 'sharded_db0' => { + 'auth_query' => "SELECT * FROM public.user_lookup('$1');", + 'auth_query_user' => 'md5_auth_user', + 'auth_query_password' => 'secret' + }, + 'sharded_db1' => { + 'auth_query' => "SELECT * FROM public.user_lookup('$1');", + 'auth_query_user' => 'md5_auth_user1', + 'auth_query_password' => 'secret' } - } - } + } } + end context 'and with cleartext passwords set' do it 'it uses local passwords' do - conn = PG.connect(processes.pgcat.connection_string("sharded_db0", pg_user['username'], pg_user['password'])) - expect(conn.exec("SELECT 1 + 2")).not_to be_nil - conn = PG.connect(processes.pgcat.connection_string("sharded_db1", pg_user['username'], pg_user['password'])) - expect(conn.exec("SELECT 1 + 2")).not_to be_nil + conn = PG.connect(processes.pgcat.connection_string('sharded_db0', pg_user['username'], + pg_user['password'])) + expect(conn.exec('SELECT 1 + 2')).not_to be_nil + conn = PG.connect(processes.pgcat.connection_string('sharded_db1', pg_user['username'], + pg_user['password'])) + expect(conn.exec('SELECT 1 + 2')).not_to be_nil end end @@ -200,15 +236,16 @@ let(:config_user) { { 'username' => 'sharding_user' } } it 'it uses obtained passwords' do - connection_string = processes.pgcat.connection_string("sharded_db0", pg_user['username'], pg_user['password']) + connection_string = processes.pgcat.connection_string('sharded_db0', pg_user['username'], + pg_user['password']) conn = PG.connect(connection_string) - expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil - connection_string = processes.pgcat.connection_string("sharded_db1", pg_user['username'], pg_user['password']) + expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil + connection_string = processes.pgcat.connection_string('sharded_db1', pg_user['username'], + pg_user['password']) conn = PG.connect(connection_string) - expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil + expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil end end - end end end diff --git a/tests/ruby/helpers/pgcat_process.rb b/tests/ruby/helpers/pgcat_process.rb index e1dbea8b..b2bffc95 100644 --- a/tests/ruby/helpers/pgcat_process.rb +++ b/tests/ruby/helpers/pgcat_process.rb @@ -18,7 +18,7 @@ def self.finalize(pid, log_filename, config_filename) end def initialize(log_level) - @env = {"RUST_LOG" => log_level} + @env = {"LOG_LEVEL" => log_level} @port = rand(20000..32760) @log_level = log_level @log_filename = "/tmp/pgcat_log_#{SecureRandom.urlsafe_base64}.log"