Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c3e3b34

Browse files
committedJul 20, 2023
add auth_query_database config
This commit adds the new config auth_query_database to allow use a different database to get the hashes from the database instance. I updated the example folder with functional code to validate the new feature and updated the config docs. Signed-off-by: Sebastian Webber <[email protected]>
1 parent 19cb8a3 commit c3e3b34

12 files changed

+104
-107
lines changed
 

‎CONFIG.md

+10
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,16 @@ Password to be used for connecting to servers to obtain the hash used for md5 au
319319
specified in `auth_query_user`. The connection will be established using the database configured in the pool.
320320
This parameter is inherited by every pool and can be redefined in pool configuration.
321321

322+
### auth_query_database
323+
```
324+
path: pools.<pool_name>.auth_query_database
325+
default: <UNSET>
326+
example: "postgres"
327+
```
328+
329+
Database to be used for connecting to servers to obtain the hash used for md5 authentication by sending the query
330+
specified in `auth_query_query`. This parameter is inherited by every pool and can be redefined in pool configuration.
331+
322332
### automatic_sharding_key
323333
```
324334
path: pools.<pool_name>.automatic_sharding_key

‎docker-compose.yml

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ services:
55
environment:
66
POSTGRES_PASSWORD: postgres
77
POSTGRES_HOST_AUTH_METHOD: md5
8+
ports:
9+
- "54321:5432"
10+
volumes:
11+
- "${PWD}/examples/docker:/docker-entrypoint-initdb.d"
812
pgcat:
913
build: .
1014
command:

‎examples/docker/01-setup.sql

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER SYSTEM SET password_encryption to 'md5';
2+
SELECT pg_reload_conf();
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CREATE ROLE myappadmin PASSWORD 'myappadmin' LOGIN;
2+
CREATE DATABASE myapp owner myappadmin;
3+
GRANT ALL PRIVILEGES ON DATABASE myapp TO myappadmin;

‎examples/docker/03-query-auth.sql

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CREATE ROLE connection_pooler PASSWORD 'user-look-up-pass' LOGIN;
2+
CREATE SCHEMA IF NOT EXISTS pgcat;
3+
4+
CREATE OR REPLACE FUNCTION pgcat.user_lookup(i_username text)
5+
RETURNS table ("user" text, hash text) AS $$
6+
SELECT usename as user, passwd as hash FROM pg_catalog.pg_shadow
7+
WHERE usename = i_username;
8+
$$ LANGUAGE sql SECURITY DEFINER;
9+
10+
-- usage:
11+
-- SELECT * FROM pgcat.user_lookup('$1');
12+
13+
GRANT CONNECT ON DATABASE postgres TO connection_pooler;
14+
GRANT USAGE ON SCHEMA pgcat TO connection_pooler;
15+
16+
REVOKE ALL ON FUNCTION pgcat.user_lookup(text) FROM public, connection_pooler;
17+
GRANT EXECUTE ON FUNCTION pgcat.user_lookup(text) TO connection_pooler;

‎examples/docker/pgcat.toml

+22-103
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,42 @@
1-
#
2-
# PgCat config example.
3-
#
4-
5-
#
6-
# General pooler settings
1+
## pgcat.toml
72
[general]
8-
# What IP to run on, 0.0.0.0 means accessible from everywhere.
93
host = "0.0.0.0"
10-
11-
# Port to run on, same as PgBouncer used in this example.
124
port = 6432
13-
14-
# Whether to enable prometheus exporter or not.
155
enable_prometheus_exporter = true
16-
17-
# Port at which prometheus exporter listens on.
186
prometheus_exporter_port = 9930
197

20-
# How long to wait before aborting a server connection (ms).
21-
connect_timeout = 5000
22-
23-
# How much time to give `SELECT 1` health check query to return with a result (ms).
24-
healthcheck_timeout = 1000
25-
26-
# How long to keep connection available for immediate re-use, without running a healthcheck query on it
27-
healthcheck_delay = 30000
28-
29-
# How much time to give clients during shutdown before forcibly killing client connections (ms).
30-
shutdown_timeout = 60000
31-
32-
# For how long to ban a server if it fails a health check (seconds).
33-
ban_time = 60 # seconds
34-
35-
# If we should log client connections
36-
log_client_connections = false
8+
log_client_connections = true
9+
log_client_disconnections = true
3710

38-
# If we should log client disconnections
39-
log_client_disconnections = false
11+
server_tls = false
12+
verify_server_certificate = false
13+
#tls_certificate = "/etc/postgresql/certificate/server.crt"
14+
#tls_private_key = "/etc/postgresql/certificate/server.key"
4015

41-
# TLS
42-
# tls_certificate = "server.cert"
43-
# tls_private_key = "server.key"
44-
45-
# Credentials to access the virtual administrative database (pgbouncer or pgcat)
46-
# Connecting to that database allows running commands like `SHOW POOLS`, `SHOW DATABASES`, etc..
4716
admin_username = "postgres"
4817
admin_password = "postgres"
4918

50-
# pool
51-
# configs are structured as pool.<pool_name>
52-
# the pool_name is what clients use as database name when connecting
53-
# For the example below a client can connect using "postgres://sharding_user:sharding_user@pgcat_host:pgcat_port/sharded"
54-
[pools.postgres]
55-
# Pool mode (see PgBouncer docs for more).
56-
# session: one server connection per connected client
57-
# transaction: one server connection per client transaction
58-
pool_mode = "transaction"
19+
auth_query = "SELECT * FROM pgcat.user_lookup('$1');"
20+
auth_query_user = "connection_pooler"
21+
auth_query_password = "user-look-up-pass"
22+
auth_query_database = "postgres"
5923

60-
# If the client doesn't specify, route traffic to
61-
# this role by default.
62-
#
63-
# any: round-robin between primary and replicas,
64-
# replica: round-robin between replicas only without touching the primary,
65-
# primary: all queries go to the primary unless otherwise specified.
24+
## session mode
25+
[pools.myapp]
26+
pool_mode = "session"
6627
default_role = "any"
6728

68-
# Query parser. If enabled, we'll attempt to parse
69-
# every incoming query to determine if it's a read or a write.
70-
# If it's a read query, we'll direct it to a replica. Otherwise, if it's a write,
71-
# we'll direct it to the primary.
72-
query_parser_enabled = true
73-
74-
# If the query parser is enabled and this setting is enabled, the primary will be part of the pool of databases used for
75-
# load balancing of read queries. Otherwise, the primary will only be used for write
76-
# queries. The primary can always be explicitly selected with our custom protocol.
77-
primary_reads_enabled = true
29+
# query_parser_enabled = true
7830

79-
# So what if you wanted to implement a different hashing function,
80-
# or you've already built one and you want this pooler to use it?
81-
#
82-
# Current options:
83-
#
84-
# pg_bigint_hash: PARTITION BY HASH (Postgres hashing function)
85-
# sha1: A hashing function based on SHA1
86-
#
8731
sharding_function = "pg_bigint_hash"
8832

89-
# Credentials for users that may connect to this cluster
90-
[pools.postgres.users.0]
91-
username = "postgres"
92-
password = "postgres"
93-
# Maximum number of server connections that can be established for this user
94-
# The maximum number of connection from a single Pgcat process to any database in the cluster
95-
# is the sum of pool_size across all users.
96-
pool_size = 9
97-
98-
# Maximum query duration. Dangerous, but protects against DBs that died in a non-obvious way.
99-
statement_timeout = 0
100-
101-
# Shard 0
102-
[pools.postgres.shards.0]
103-
# [ host, port, role ]
104-
servers = [
105-
[ "postgres", 5432, "primary" ],
106-
[ "postgres", 5432, "replica" ]
107-
]
108-
# Database name (e.g. "postgres")
109-
database = "postgres"
110-
111-
[pools.postgres.shards.1]
112-
servers = [
113-
[ "postgres", 5432, "primary" ],
114-
[ "postgres", 5432, "replica" ],
115-
]
116-
database = "postgres"
33+
[pools.myapp.users.0]
34+
username = "myappadmin"
35+
pool_size = 23
11736

118-
[pools.postgres.shards.2]
37+
[pools.myapp.shards.0]
11938
servers = [
120-
[ "postgres", 5432, "primary" ],
121-
[ "postgres", 5432, "replica" ],
39+
# [ "localhost", 54321, "primary" ], # to use without docker
40+
[ "postgres", 5432, "primary" ], # to use with docker
12241
]
123-
database = "postgres"
42+
database = "myapp"

‎pgcat.toml

+4
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ sharding_function = "pg_bigint_hash"
195195
# This parameter is inherited by every pool and can be redefined in pool configuration.
196196
# auth_query_password = "sharding_user"
197197

198+
#Database to be used for connecting to servers to obtain the hash used for md5 authentication by sending the query
199+
#specified in `auth_query_query`. This parameter is inherited by every pool and can be redefined in pool configuration.
200+
# auth_query_database = "shared_db"
201+
198202
# Automatically parse this from queries and route queries to the right shard!
199203
# automatic_sharding_key = "data.id"
200204

‎src/auth_passthrough.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ pub struct AuthPassthrough {
88
password: String,
99
query: String,
1010
user: String,
11+
database: Option<String>,
1112
}
1213

1314
impl AuthPassthrough {
1415
/// Initializes an AuthPassthrough.
15-
pub fn new(query: &str, user: &str, password: &str) -> Self {
16+
pub fn new(query: &str, user: &str, password: &str, database: Option<String>) -> Self {
1617
AuthPassthrough {
1718
password: password.to_string(),
1819
query: query.to_string(),
1920
user: user.to_string(),
21+
database: database,
2022
}
2123
}
2224

@@ -28,6 +30,7 @@ impl AuthPassthrough {
2830
pool_config.auth_query.as_ref().unwrap(),
2931
pool_config.auth_query_user.as_ref().unwrap(),
3032
pool_config.auth_query_password.as_ref().unwrap(),
33+
pool_config.auth_query_database.clone(),
3134
));
3235
}
3336

@@ -64,7 +67,7 @@ impl AuthPassthrough {
6467
/// ```
6568
/// use pgcat::auth_passthrough::AuthPassthrough;
6669
/// use pgcat::config::Address;
67-
/// let auth_passthrough = AuthPassthrough::new("SELECT * FROM public.user_lookup('$1');", "postgres", "postgres");
70+
/// let auth_passthrough = AuthPassthrough::new("SELECT * FROM public.user_lookup('$1');", "postgres", "postgres", None);
6871
/// auth_passthrough.fetch_hash(&Address::default());
6972
/// ```
7073
///
@@ -87,7 +90,11 @@ impl AuthPassthrough {
8790

8891
let auth_query = self.query.replace("$1", user);
8992

90-
match Server::exec_simple_query(address, &auth_user, &auth_query).await {
93+
let mut query_address = address.clone();
94+
if self.database.is_some() {
95+
query_address.database = self.database.clone().unwrap();
96+
}
97+
match Server::exec_simple_query(&query_address, &auth_user, &auth_query).await {
9198
Ok(password_data) => {
9299
if password_data.len() == 2 && password_data.first().unwrap() == user {
93100
if let Some(stripped_hash) = password_data

‎src/config.rs

+23
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ pub struct General {
322322
pub auth_query: Option<String>,
323323
pub auth_query_user: Option<String>,
324324
pub auth_query_password: Option<String>,
325+
pub auth_query_database: Option<String>,
325326

326327
#[serde(default)]
327328
pub prepared_statements: bool,
@@ -448,6 +449,7 @@ impl Default for General {
448449
auth_query: None,
449450
auth_query_user: None,
450451
auth_query_password: None,
452+
auth_query_database: None,
451453
server_lifetime: Self::default_server_lifetime(),
452454
server_round_robin: Self::default_server_round_robin(),
453455
validate_config: true,
@@ -538,6 +540,7 @@ pub struct Pool {
538540
pub auth_query: Option<String>,
539541
pub auth_query_user: Option<String>,
540542
pub auth_query_password: Option<String>,
543+
pub auth_query_database: Option<String>,
541544

542545
#[serde(default = "Pool::default_cleanup_server_connections")]
543546
pub cleanup_server_connections: bool,
@@ -674,6 +677,7 @@ impl Default for Pool {
674677
auth_query: None,
675678
auth_query_user: None,
676679
auth_query_password: None,
680+
auth_query_database: None,
677681
server_lifetime: None,
678682
plugins: None,
679683
cleanup_server_connections: true,
@@ -876,6 +880,10 @@ impl Config {
876880
if pool.auth_query_password.is_none() {
877881
pool.auth_query_password = self.general.auth_query_password.clone();
878882
}
883+
884+
if pool.auth_query_database.is_none() {
885+
pool.auth_query_database = self.general.auth_query_database.clone();
886+
}
879887
}
880888
}
881889
}
@@ -1011,6 +1019,21 @@ impl Config {
10111019
self.general.server_lifetime
10121020
);
10131021
info!("Sever round robin: {}", self.general.server_round_robin);
1022+
1023+
if self.is_auth_query_configured() {
1024+
info!(
1025+
"Auth query configured: {}",
1026+
self.general.auth_query.clone().unwrap()
1027+
);
1028+
1029+
if self.general.auth_query_database.is_some() {
1030+
info!(
1031+
"Auth query will use the db: {}",
1032+
self.general.auth_query_database.clone().unwrap()
1033+
);
1034+
}
1035+
}
1036+
10141037
match self.general.tls_certificate.clone() {
10151038
Some(tls_certificate) => {
10161039
info!("TLS certificate: {}", tls_certificate);

‎src/pool.rs

+3
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ pub struct PoolSettings {
142142
pub auth_query: Option<String>,
143143
pub auth_query_user: Option<String>,
144144
pub auth_query_password: Option<String>,
145+
pub auth_query_database: Option<String>,
145146

146147
/// Plugins
147148
pub plugins: Option<Plugins>,
@@ -169,6 +170,7 @@ impl Default for PoolSettings {
169170
auth_query: None,
170171
auth_query_user: None,
171172
auth_query_password: None,
173+
auth_query_database: None,
172174
plugins: None,
173175
}
174176
}
@@ -474,6 +476,7 @@ impl ConnectionPool {
474476
auth_query: pool_config.auth_query.clone(),
475477
auth_query_user: pool_config.auth_query_user.clone(),
476478
auth_query_password: pool_config.auth_query_password.clone(),
479+
auth_query_database: pool_config.auth_query_database.clone(),
477480
plugins: match pool_config.plugins {
478481
Some(ref plugins) => Some(plugins.clone()),
479482
None => config.plugins.clone(),

‎src/query_router.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,7 @@ mod test {
11761176
auth_query: None,
11771177
auth_query_password: None,
11781178
auth_query_user: None,
1179+
auth_query_database: None,
11791180
db: "test".to_string(),
11801181
plugins: None,
11811182
};
@@ -1251,6 +1252,7 @@ mod test {
12511252
auth_query: None,
12521253
auth_query_password: None,
12531254
auth_query_user: None,
1255+
auth_query_database: None,
12541256
db: "test".to_string(),
12551257
plugins: None,
12561258
};

‎src/server.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1194,7 +1194,10 @@ impl Server {
11941194
) -> Result<Vec<String>, Error> {
11951195
let client_server_map: ClientServerMap = Arc::new(Mutex::new(HashMap::new()));
11961196

1197-
debug!("Connecting to server to obtain auth hashes.");
1197+
debug!(
1198+
"Connecting to server to obtain auth hashes from db '{}'.",
1199+
&address.database
1200+
);
11981201
let mut server = Server::startup(
11991202
address,
12001203
user,

0 commit comments

Comments
 (0)
Please sign in to comment.