mirror of
https://github.com/postgresml/pgcat.git
synced 2026-03-22 17:06:29 +00:00
* Add a new exec_simple_query method This adds a new `exec_simple_query` method so we can make 'out of band' queries to servers that don't interfere with pools at all. In order to reuse startup code for making these simple queries, we need to set the stats (`Reporter`) optional, so using these simple queries wont interfere with stats. * Add auth passthough (auth_query) Adds a feature that allows setting auth passthrough for md5 auth. It adds 3 new (general and pool) config parameters: - `auth_query`: An string containing a query that will be executed on boot to obtain the hash of a given user. This query have to use a placeholder `$1`, so pgcat can replace it with the user its trying to fetch the hash from. - `auth_query_user`: The user to use for connecting to the server and executing the auth_query. - `auth_query_password`: The password to use for connecting to the server and executing the auth_query. The configuration can be done either on the general config (so pools share them) or in a per-pool basis. The behavior is, at boot time, when validating server connections, a hash is fetched per server and stored in the pool. When new server connections are created, and no cleartext password is specified, the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries it. When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no hash (we could not obtain one when validating the connection), a new fetch is tried. Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure we refetch the hash and retry auth (so password changes can be done). The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be obtained during connection validation, or the password has change, we can still connect later. * Add documentation for Auth passthrough
108 lines
3.9 KiB
Rust
108 lines
3.9 KiB
Rust
use crate::errors::Error;
|
|
use crate::server::Server;
|
|
use log::debug;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct AuthPassthrough {
|
|
password: String,
|
|
query: String,
|
|
user: String,
|
|
}
|
|
|
|
impl AuthPassthrough {
|
|
/// Initializes an AuthPassthrough.
|
|
pub fn new(query: &str, user: &str, password: &str) -> Self {
|
|
AuthPassthrough {
|
|
password: password.to_string(),
|
|
query: query.to_string(),
|
|
user: user.to_string(),
|
|
}
|
|
}
|
|
|
|
/// Returns an AuthPassthrough given the pool configuration.
|
|
/// If any of required values is not set, None is returned.
|
|
pub fn from_pool_config(pool_config: &crate::config::Pool) -> Option<Self> {
|
|
if pool_config.is_auth_query_configured() {
|
|
return Some(AuthPassthrough::new(
|
|
pool_config.auth_query.as_ref().unwrap(),
|
|
pool_config.auth_query_user.as_ref().unwrap(),
|
|
pool_config.auth_query_password.as_ref().unwrap(),
|
|
));
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Returns an AuthPassthrough given the pool settings.
|
|
/// If any of required values is not set, None is returned.
|
|
pub fn from_pool_settings(pool_settings: &crate::pool::PoolSettings) -> Option<Self> {
|
|
let pool_config = crate::config::Pool {
|
|
auth_query: pool_settings.auth_query.clone(),
|
|
auth_query_password: pool_settings.auth_query_password.clone(),
|
|
auth_query_user: pool_settings.auth_query_user.clone(),
|
|
..Default::default()
|
|
};
|
|
|
|
AuthPassthrough::from_pool_config(&pool_config)
|
|
}
|
|
|
|
/// Connects to server and executes auth_query for the specified address.
|
|
/// If the response is a row with two columns containing the username set in the address.
|
|
/// and its MD5 hash, the MD5 hash returned.
|
|
///
|
|
/// Note that the query is executed, changing $1 with the name of the user
|
|
/// this is so we only hold in memory (and transfer) the least amount of 'sensitive' data.
|
|
/// Also, it is compatible with pgbouncer.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `address` - An Address of the server we want to connect to. The username for the hash will be obtained from this value.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use pgcat::auth_passthrough::AuthPassthrough;
|
|
/// use pgcat::config::Address;
|
|
/// let auth_passthrough = AuthPassthrough::new("SELECT * FROM public.user_lookup('$1');", "postgres", "postgres");
|
|
/// auth_passthrough.fetch_hash(&Address::default());
|
|
/// ```
|
|
///
|
|
pub async fn fetch_hash(&self, address: &crate::config::Address) -> Result<String, Error> {
|
|
let auth_user = crate::config::User {
|
|
username: self.user.clone(),
|
|
password: Some(self.password.clone()),
|
|
pool_size: 1,
|
|
statement_timeout: 0,
|
|
};
|
|
|
|
let user = &address.username;
|
|
|
|
debug!("Connecting to server to obtain auth hashes.");
|
|
let auth_query = self.query.replace("$1", user);
|
|
match Server::exec_simple_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.last().unwrap().to_string().strip_prefix("md5") {
|
|
Ok(stripped_hash.to_string())
|
|
}
|
|
else {
|
|
Err(Error::AuthPassthroughError(
|
|
"Obtained hash from auth_query does not seem to be in md5 format.".to_string(),
|
|
))
|
|
}
|
|
} else {
|
|
Err(Error::AuthPassthroughError(
|
|
"Data obtained from query does not follow the scheme 'user','hash'."
|
|
.to_string(),
|
|
))
|
|
}
|
|
}
|
|
Err(err) => {
|
|
Err(Error::AuthPassthroughError(
|
|
format!("Error trying to obtain password from auth_query, ignoring hash for user '{}'. Error: {:?}",
|
|
user, err)))
|
|
}
|
|
}
|
|
}
|
|
}
|