Skip to content

Commit e78b618

Browse files
committed
Change auth_query config to be per pool instead of global.
Given that we can connect to different username/databases/servers using connection pools, it makes sense that `auth_query` should be configured in a per pool basis. This change implements that and also leaves the global parameters so pool configuration can inherit global ones. Note that now, `auth_query_database` is dropped in favor of the pool configured database.
1 parent 8552103 commit e78b618

File tree

10 files changed

+111
-51
lines changed

10 files changed

+111
-51
lines changed

.circleci/pgcat.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ admin_password = "admin_pass"
5353
# auth_query = "SELECT * FROM public.user_lookup('$1');"
5454
# auth_query_user = "md5_auth_user"
5555
# auth_query_password = "secret"
56-
# auth_query_database = "postgres"
5756

5857
# pool
5958
# configs are structured as pool.<pool_name>

.circleci/query_auth_test.sh

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ export LOCAL_IP=$(hostname -i)
1212
# auth_query = "SELECT * FROM public.user_lookup('$1');"
1313
# auth_query_user = "md5_auth_user"
1414
# auth_query_password = "secret"
15-
# auth_query_database = "postgres"
1615
# ...
1716

1817
# Before (sets up auth_query in postgres and pgcat)
1918
PGDATABASE=postgres PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_auth_setup.sql
19+
PGDATABASE=shard0 PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_auth_setup_function.sql
20+
PGDATABASE=shard1 PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_auth_setup_function.sql
21+
PGDATABASE=shard2 PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_auth_setup_function.sql
2022
sed -i 's/^# auth_query/auth_query/' .circleci/pgcat.toml
2123

2224
# TEST_WRONG_AUTH_QUERY BEGIN
2325
# When auth_query fails...
24-
PGDATABASE=postgres \
26+
PGDATABASE=shard0 \
2527
PGPASSWORD=postgres \
2628
psql -e -h 127.0.0.1 -p 5432 -U postgres -c "REVOKE ALL ON FUNCTION public.user_lookup(text) FROM public, md5_auth_user;"
2729

@@ -33,7 +35,7 @@ echo "When query_auth_config is wrong, we fall back to passwords set in cleartex
3335
psql -U sharding_user -h 127.0.0.1 -p 6432 -c 'SELECT 1'
3436

3537
# After
36-
PGDATABASE=postgres \
38+
PGDATABASE=shard0 \
3739
PGPASSWORD=postgres \
3840
psql -e -h 127.0.0.1 -p 5432 -U postgres -c "GRANT EXECUTE ON FUNCTION public.user_lookup(text) TO md5_auth_user;"
3941
# TEST_WRONG_AUTH_QUERY END
@@ -80,6 +82,9 @@ PGPASSWORD=another_sharding_password psql -U sharding_user -h "${LOCAL_IP}" -p 6
8082
# TEST_PASSWORD_CHANGE END
8183

8284
# After
85+
PGDATABASE=shard0 PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_auth_teardown_function.sql
86+
PGDATABASE=shard1 PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_auth_teardown_function.sql
87+
PGDATABASE=shard2 PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_auth_teardown_function.sql
8388
PGDATABASE=postgres PGPASSWORD=postgres psql -e -h 127.0.0.1 -p 5432 -U postgres -f tests/sharding/query_auth_teardown.sql
8489
sed -i 's/^auth_query/# auth_query/' .circleci/pgcat.toml
8590
sed -i 's/^# password =/password =/' .circleci/pgcat.toml

src/auth_passthrough.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ use bytes::BytesMut;
77
use fallible_iterator::FallibleIterator;
88
use parking_lot::Mutex;
99

10+
use crate::pool::ClientServerMap;
1011
use log::{debug, trace, warn};
1112
use postgres_protocol::message;
1213
use std::collections::HashMap;
1314
use std::sync::Arc;
1415

15-
use crate::config::get_config;
16-
use crate::pool::ClientServerMap;
17-
1816
pub struct AuthPassthrough {
1917
password: String,
2018
query: String,
@@ -33,23 +31,35 @@ impl AuthPassthrough {
3331
}
3432
}
3533

36-
/// Returns an AuthPassthrough given the configuration.
34+
/// Returns an AuthPassthrough given the pool configuration.
3735
/// If any of required values is not set, None is returned.
38-
pub fn from_config() -> Option<Self> {
39-
let config = get_config();
40-
41-
if config.is_auth_query_configured() {
36+
pub fn from_pool_config(pool_config: &crate::config::Pool, database: &String) -> Option<Self> {
37+
if pool_config.is_auth_query_configured() {
4238
return Some(AuthPassthrough::new(
43-
config.general.auth_query.as_ref().unwrap(),
44-
config.general.auth_query_user.as_ref().unwrap(),
45-
config.general.auth_query_password.as_ref().unwrap(),
46-
config.general.auth_query_database.as_ref().unwrap(),
39+
pool_config.auth_query.as_ref().unwrap(),
40+
pool_config.auth_query_user.as_ref().unwrap(),
41+
pool_config.auth_query_password.as_ref().unwrap(),
42+
database,
4743
));
4844
}
4945

5046
None
5147
}
5248

49+
/// Returns an AuthPassthrough given the pool settings.
50+
/// If any of required values is not set, None is returned.
51+
pub fn from_pool_settings(
52+
pool_settings: &crate::pool::PoolSettings,
53+
database: &String,
54+
) -> Option<Self> {
55+
let mut pool_config = crate::config::Pool::default();
56+
pool_config.auth_query = pool_settings.auth_query.clone();
57+
pool_config.auth_query_password = pool_settings.auth_query_password.clone();
58+
pool_config.auth_query_user = pool_settings.auth_query_user.clone();
59+
60+
AuthPassthrough::from_pool_config(&pool_config, database)
61+
}
62+
5363
/// Connects to server and executes auth_query for the specified address.
5464
/// If the response is a row with two columns containing the username set in the address.
5565
/// and its MD5 hash, the MD5 hash returned.

src/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ where
507507
}
508508
}
509509
if password_hash.is_none() {
510-
warn!("Clien auth is not possible, you either have not set a valid auth_query or a password for {{ username: {:?}, pool_name: {:?}, application_name: {:?} }}", username, pool_name, application_name);
510+
warn!("Client auth is not possible, you either have not set a valid auth_query or a password for {{ username: {:?}, pool_name: {:?}, application_name: {:?} }}", username, pool_name, application_name);
511511
wrong_password(&mut write, username).await?;
512512

513513
return Err(Error::ClientError(format!("Invalid client auth {{ username: {:?}, pool_name: {:?}, application_name: {:?} }}", username, pool_name, application_name)));

src/config.rs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// Parse the configuration file.
22
use arc_swap::ArcSwap;
3+
use hmac::digest::typenum::private::IsNotEqualPrivate;
34
use log::{error, info};
45
use once_cell::sync::Lazy;
56
use regex::Regex;
@@ -202,7 +203,6 @@ pub struct General {
202203
pub auth_query: Option<String>,
203204
pub auth_query_user: Option<String>,
204205
pub auth_query_password: Option<String>,
205-
pub auth_query_database: Option<String>,
206206
}
207207

208208
impl General {
@@ -285,7 +285,6 @@ impl Default for General {
285285
auth_query: None,
286286
auth_query_user: None,
287287
auth_query_password: None,
288-
auth_query_database: None,
289288
}
290289
}
291290
}
@@ -360,6 +359,10 @@ pub struct Pool {
360359

361360
pub shards: BTreeMap<String, Shard>,
362361
pub users: BTreeMap<String, User>,
362+
363+
pub auth_query: Option<String>,
364+
pub auth_query_user: Option<String>,
365+
pub auth_query_password: Option<String>,
363366
// Note, don't put simple fields below these configs. There's a compatability issue with TOML that makes it
364367
// incompatible to have simple fields in TOML after complex objects. See
365368
// https://users.rust-lang.org/t/why-toml-to-string-get-error-valueaftertable/85903
@@ -372,6 +375,12 @@ impl Pool {
372375
s.finish()
373376
}
374377

378+
pub fn is_auth_query_configured(&self) -> bool {
379+
self.auth_query_password.is_some()
380+
&& self.auth_query_user.is_some()
381+
&& self.auth_query_password.is_some()
382+
}
383+
375384
pub fn default_pool_mode() -> PoolMode {
376385
PoolMode::Transaction
377386
}
@@ -445,6 +454,9 @@ impl Default for Pool {
445454
sharding_key_regex: None,
446455
shard_id_regex: None,
447456
regex_search_limit: Some(1000),
457+
auth_query: None,
458+
auth_query_user: None,
459+
auth_query_password: None,
448460
}
449461
}
450462
}
@@ -536,6 +548,12 @@ pub struct Config {
536548
}
537549

538550
impl Config {
551+
pub fn is_auth_query_configured(&self) -> bool {
552+
self.pools
553+
.iter()
554+
.any(|(_name, pool)| pool.is_auth_query_configured())
555+
}
556+
539557
pub fn default_path() -> String {
540558
String::from("pgcat.toml")
541559
}
@@ -549,6 +567,22 @@ impl Config {
549567
self.general.auth_query_password = Some(val);
550568
}
551569
}
570+
571+
pub fn fill_up_auth_query_config(&mut self) {
572+
for (_name, pool) in self.pools.iter_mut() {
573+
if pool.auth_query.is_none() {
574+
pool.auth_query = self.general.auth_query.clone();
575+
}
576+
577+
if pool.auth_query_user.is_none() {
578+
pool.auth_query_user = self.general.auth_query_user.clone();
579+
}
580+
581+
if pool.auth_query_password.is_none() {
582+
pool.auth_query_password = self.general.auth_query_password.clone();
583+
}
584+
}
585+
}
552586
}
553587

554588
impl Default for Config {
@@ -754,21 +788,13 @@ impl Config {
754788
}
755789
}
756790

757-
pub fn is_auth_query_configured(&self) -> bool {
758-
self.general.auth_query_password.is_some()
759-
&& self.general.auth_query_user.is_some()
760-
&& self.general.auth_query_password.is_some()
761-
&& self.general.auth_query_database.is_some()
762-
}
763-
764791
pub fn validate(&mut self) -> Result<(), Error> {
765792
// Validation for auth_query feature
766793
if self.general.auth_query.is_some()
767794
&& (self.general.auth_query_user.is_none()
768-
|| self.general.auth_query_password.is_none()
769-
|| self.general.auth_query_database.is_none())
795+
|| self.general.auth_query_password.is_none())
770796
{
771-
error!("If auth_query is specified, you need to provide a value for `auth_query_user`, `auth_query_password` and `auth_query_database`");
797+
error!("If auth_query is specified, you need to provide a value for `auth_query_user`, `auth_query_password`");
772798
return Err(Error::BadConfig);
773799
}
774800

@@ -857,6 +883,7 @@ pub async fn parse(path: &str) -> Result<(), Error> {
857883
};
858884

859885
config.overwrite_passwords();
886+
config.fill_up_auth_query_config();
860887
config.validate()?;
861888

862889
config.path = path.to_string();
@@ -972,7 +999,6 @@ mod test {
972999
);
9731000
assert_eq!(get_config().pools["simple_db"].users["0"].pool_size, 5);
9741001
assert_eq!(get_config().general.auth_query, None);
975-
assert_eq!(get_config().general.auth_query_database, None);
9761002
assert_eq!(get_config().general.auth_query_user, None);
9771003
assert_eq!(get_config().general.auth_query_password, None);
9781004
}

src/pool.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ pub struct PoolSettings {
113113

114114
// Limit how much of each query is searched for a potential shard regex match
115115
pub regex_search_limit: usize,
116+
117+
// Auth query parameters
118+
pub auth_query: Option<String>,
119+
pub auth_query_user: Option<String>,
120+
pub auth_query_password: Option<String>,
116121
}
117122

118123
impl Default for PoolSettings {
@@ -133,6 +138,9 @@ impl Default for PoolSettings {
133138
sharding_key_regex: None,
134139
shard_id_regex: None,
135140
regex_search_limit: 1000,
141+
auth_query: None,
142+
auth_query_user: None,
143+
auth_query_password: None,
136144
}
137145
}
138146
}
@@ -182,15 +190,19 @@ pub struct ConnectionPool {
182190

183191
impl ConnectionPool {
184192
async fn auth_hash_has_changed(&mut self) -> bool {
185-
if let Some(apt) = AuthPassthrough::from_config() {
186-
if let Some(shard) = self.addresses.first() {
187-
if let Some(address) = shard.first() {
193+
if let Some(shard) = self.addresses.first() {
194+
if let Some(address) = shard.first() {
195+
if let Some(apt) =
196+
AuthPassthrough::from_pool_settings(&self.settings, &address.database)
197+
{
188198
match apt.fetch_hash(address).await {
189199
Ok(md5) => {
190200
if let Some(auth_hash) = self.auth_hash.as_ref() {
191201
if auth_hash != &md5 {
192202
info!("Password hash changed for user: {:?}. Pool will be reloaded.", address.username);
193203
return true
204+
} else {
205+
debug!("Password hash has not changed for user: {:?}.", address.username);
194206
}
195207
}
196208
},
@@ -205,7 +217,6 @@ impl ConnectionPool {
205217
/// Construct the connection pool from the configuration.
206218
pub async fn from_config(client_server_map: ClientServerMap) -> Result<(), Error> {
207219
let config = get_config();
208-
let auth_passthrough = AuthPassthrough::from_config();
209220

210221
let mut new_pools = HashMap::new();
211222
let mut address_id = 0;
@@ -283,14 +294,16 @@ impl ConnectionPool {
283294
}
284295

285296
// We assume every server in the pool share user/passwords
286-
// TODO: Make it server dependant.
287297
let mut auth_hash = None;
298+
let auth_passthrough =
299+
AuthPassthrough::from_pool_config(pool_config, &shard.database);
288300
if let Some(apt) = auth_passthrough.as_ref() {
289301
match apt.fetch_hash(&address).await {
290302
Ok(ok) => {
291303
if let Some(pool_auth_hash_value) = pool_auth_hash {
292304
if ok != pool_auth_hash_value {
293-
warn!("Hash is not the same across servers of the same pool, client auth will be done using last obtained hash.");
305+
warn!("Hash is not the same across shards of the same pool, client auth will \
306+
be done using last obtained hash. Server: {}:{}, Database: {}", server.host, server.port, shard.database);
294307
}
295308
}
296309
pool_auth_hash = Some(ok.clone());
@@ -338,6 +351,12 @@ impl ConnectionPool {
338351
}
339352

340353
assert_eq!(shards.len(), addresses.len());
354+
if pool_auth_hash.is_some() {
355+
info!(
356+
"Auth hash obtained from query_auth for pool {{ name: {}, user: {} }}",
357+
pool_name, user.username
358+
);
359+
}
341360

342361
let pool = ConnectionPool {
343362
databases: shards,
@@ -375,6 +394,9 @@ impl ConnectionPool {
375394
.clone()
376395
.map(|regex| Regex::new(regex.as_str()).unwrap()),
377396
regex_search_limit: pool_config.regex_search_limit.unwrap_or(1000),
397+
auth_query: pool_config.auth_query.clone(),
398+
auth_query_user: pool_config.auth_query_user.clone(),
399+
auth_query_password: pool_config.auth_query_password.clone(),
378400
},
379401
validated: Arc::new(AtomicBool::new(false)),
380402
paused: Arc::new(AtomicBool::new(false)),

tests/sharding/query_auth_setup.sql

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,3 @@
44

55
ALTER ROLE sharding_user ENCRYPTED PASSWORD 'md5fa9d23e5a874c61a91bf37e1e4a9c86e'; --- sharding_user
66
CREATE ROLE md5_auth_user ENCRYPTED PASSWORD 'md54ab2c5d00339c4b2a4e921d2dc4edec7' LOGIN; --- secret
7-
8-
CREATE OR REPLACE FUNCTION public.user_lookup(in i_username text, out uname text, out phash text)
9-
RETURNS record AS $$
10-
BEGIN
11-
SELECT usename, passwd FROM pg_catalog.pg_shadow
12-
WHERE usename = i_username INTO uname, phash;
13-
RETURN;
14-
END;
15-
$$ LANGUAGE plpgsql SECURITY DEFINER;
16-
17-
REVOKE ALL ON FUNCTION public.user_lookup(text) FROM public, md5_auth_user;
18-
GRANT EXECUTE ON FUNCTION public.user_lookup(text) TO md5_auth_user;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CREATE OR REPLACE FUNCTION public.user_lookup(in i_username text, out uname text, out phash text)
2+
RETURNS record AS $$
3+
BEGIN
4+
SELECT usename, passwd FROM pg_catalog.pg_shadow
5+
WHERE usename = i_username INTO uname, phash;
6+
RETURN;
7+
END;
8+
$$ LANGUAGE plpgsql SECURITY DEFINER;
9+
10+
REVOKE ALL ON FUNCTION public.user_lookup(text) FROM public, md5_auth_user;
11+
GRANT EXECUTE ON FUNCTION public.user_lookup(text) TO md5_auth_user;

tests/sharding/query_auth_teardown.sql

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,4 @@
33
--- - Drops auth query function and user.
44

55
ALTER ROLE sharding_user ENCRYPTED PASSWORD 'sharding_user' LOGIN;
6-
7-
REVOKE ALL ON FUNCTION public.user_lookup(text) FROM public, md5_auth_user;
8-
DROP FUNCTION public.user_lookup(in i_username text, out uname text, out phash text);
96
DROP ROLE md5_auth_user;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
REVOKE ALL ON FUNCTION public.user_lookup(text) FROM public, md5_auth_user;
2+
DROP FUNCTION public.user_lookup(in i_username text, out uname text, out phash text);

0 commit comments

Comments
 (0)