feat: ignore passwd verification if ruid == root

This commit is contained in:
2026-05-11 03:01:28 +02:00
parent 9463c26036
commit 875fb215bf
3 changed files with 78 additions and 20 deletions
+25 -7
View File
@@ -20,7 +20,7 @@ pub struct KeycloakAPI<'a> {
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum GetCredentialsError { pub enum GetUserIdError {
#[error("on request: {0}")] #[error("on request: {0}")]
Fetch(#[from] curl::Error), Fetch(#[from] curl::Error),
#[error("unexpected API response: {0}")] #[error("unexpected API response: {0}")]
@@ -31,6 +31,16 @@ pub enum GetCredentialsError {
ErrorResponse, ErrorResponse,
} }
#[derive(Debug, thiserror::Error)]
pub enum GetCredentialsError {
#[error("on request: {0}")]
Fetch(#[from] curl::Error),
#[error("unexpected API response: {0}")]
InvalidAPIResponse(#[from] serde_json::Error),
#[error("server error response")]
ErrorResponse,
}
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum SetCredentialsError { pub enum SetCredentialsError {
#[error("on request: {0}")] #[error("on request: {0}")]
@@ -143,11 +153,11 @@ impl<'a> KeycloakAPI<'a> {
} }
} }
pub fn get_user_credentials( pub fn get_user_id(
&self, &self,
username: &str, username: &str,
admin_token: &str, admin_token: &str,
) -> Result<(String, Vec<api::Password>), GetCredentialsError> { ) -> Result<String, GetUserIdError> {
let Response { status, data: body } = self.curl.request( let Response { status, data: body } = self.curl.request(
Method::GET, Method::GET,
&format!( &format!(
@@ -158,20 +168,28 @@ impl<'a> KeycloakAPI<'a> {
)?; )?;
if !status.is_ok() { if !status.is_ok() {
err_http_body!(if self.debug => &body); err_http_body!(if self.debug => &body);
return Err(GetCredentialsError::ErrorResponse); return Err(GetUserIdError::ErrorResponse);
} }
let found_users: Vec<api::User> = serde_json::from_slice(&body)?; let found_users: Vec<api::User> = serde_json::from_slice(&body)?;
debug_info!(if self.debug => "matched users" = found_users); debug_info!(if self.debug => "matched users" = found_users);
let Ok([first_user]) = TryInto::<[api::User; _]>::try_into(found_users) else { let Ok([first_user]) = TryInto::<[api::User; _]>::try_into(found_users) else {
return Err(GetCredentialsError::NonUnitResults); return Err(GetUserIdError::NonUnitResults);
}; };
Ok(first_user.id)
}
pub fn get_user_credentials(
&self,
user_id: &str,
admin_token: &str,
) -> Result<Vec<api::Password>, GetCredentialsError> {
let Response { status, data: body } = self.curl.request( let Response { status, data: body } = self.curl.request(
Method::GET, Method::GET,
&format!( &format!(
"{}/admin/realms/{}/users/{}/credentials", "{}/admin/realms/{}/users/{}/credentials",
self.config.base_url, self.config.default_realm, first_user.id self.config.base_url, self.config.default_realm, user_id
), ),
&[ &[
&headers::authorization_bearer!(admin_token), &headers::authorization_bearer!(admin_token),
@@ -183,7 +201,7 @@ impl<'a> KeycloakAPI<'a> {
return Err(GetCredentialsError::ErrorResponse); return Err(GetCredentialsError::ErrorResponse);
} }
Ok((first_user.id, serde_json::from_slice(&body)?)) Ok(serde_json::from_slice(&body)?)
} }
pub fn set_user_credentials( pub fn set_user_credentials(
+43 -9
View File
@@ -58,8 +58,8 @@ impl<M: ModuleClient> PamModule<M> for KeycloakPAM {
/// ///
/// # Errors /// # Errors
/// ///
/// As a baseline, the errors of [`Self::init`], [`Self::get_token_for`] and /// As a baseline, the errors of [`Self::init`], [`Self::get_token_for`], [`Self::get_id_for`]
/// [`Self::get_creds_for`]. /// and [`Self::get_creds_for`].
/// ///
/// Extra: /// Extra:
/// - [`E::BufferError`]: If some data could not be converted to UTF-8 or anything went wrong /// - [`E::BufferError`]: If some data could not be converted to UTF-8 or anything went wrong
@@ -103,19 +103,33 @@ impl<M: ModuleClient> PamModule<M> for KeycloakPAM {
}, },
)?; )?;
debug_print!(if args.debug => "verifying credentials"); let user_id = Self::get_id_for(
let (user_id, user_creds) = Self::get_creds_for(
&api_client, &api_client,
username_str, username_str,
&admin_token.access_token, &admin_token.access_token,
can_print, can_print,
)?; )?;
let skip_verification = libc::is_root_ruid();
if skip_verification {
debug_print!(if args.debug => "authtok coming from root ruid, \
skipping verification...");
} else {
debug_print!(if args.debug => "verifying credentials");
let user_creds = Self::get_creds_for(
&api_client,
&user_id,
&admin_token.access_token,
can_print,
)?;
if !user_creds.is_empty() { if !user_creds.is_empty() {
let old_authtok = handle.old_authtok(None)?; let old_authtok = handle.old_authtok(None)?;
Self::get_token_for(&api_client, &username, &old_authtok, can_print)?; Self::get_token_for(&api_client, &username, &old_authtok, can_print)?;
debug_print!(if args.debug => "identity verified"); debug_print!(if args.debug => "identity verified");
} }
}
let authtok = handle.authtok(None)?; let authtok = handle.authtok(None)?;
let authtok_str = str::from_utf8(authtok.as_bytes()).map_err(|_| E::BufferError)?; let authtok_str = str::from_utf8(authtok.as_bytes()).map_err(|_| E::BufferError)?;
@@ -209,26 +223,46 @@ impl KeycloakPAM {
}) })
} }
/// # Errors
///
/// - [`E::AuthInfoUnavailable`]: Anything wrong with keycloak, we assume it's just down now.
/// - [`E::CredentialsError`]: If username matched other than one user.
/// - [`E::ServiceError`]: If got an unexpected error response.
fn get_id_for(
api_client: &keycloak::KeycloakAPI,
username: &str,
admin_token: &str,
can_print: bool,
) -> nonstick::Result<String> {
error_print!(api_client.get_user_id(username, admin_token), can_print).map_err(|error| {
match error {
keycloak::GetUserIdError::Fetch(_)
| keycloak::GetUserIdError::InvalidAPIResponse(_) => E::AuthInfoUnavailable,
keycloak::GetUserIdError::NonUnitResults => E::CredentialsError,
keycloak::GetUserIdError::ErrorResponse => E::ServiceError,
}
})
}
/// Can be used to see if user has configured passwords. /// Can be used to see if user has configured passwords.
/// ///
/// # Errors /// # Errors
/// ///
/// - [`E::AuthInfoUnavailable`]: Anything wrong with keycloak, we assume it's just down now. /// - [`E::AuthInfoUnavailable`]: Anything wrong with keycloak, we assume it's just down now.
/// - [`E::AuthenticationError`]: If everything went right but checking credentials. /// - [`E::ServiceError`]: If got an unexpected error response.
fn get_creds_for( fn get_creds_for(
api_client: &keycloak::KeycloakAPI, api_client: &keycloak::KeycloakAPI,
username: &str, user_id: &str,
admin_token: &str, admin_token: &str,
can_print: bool, can_print: bool,
) -> nonstick::Result<(String, Vec<keycloak::api::Password>)> { ) -> nonstick::Result<Vec<keycloak::api::Password>> {
error_print!( error_print!(
api_client.get_user_credentials(username, admin_token), api_client.get_user_credentials(user_id, admin_token),
can_print can_print
) )
.map_err(|error| match error { .map_err(|error| match error {
keycloak::GetCredentialsError::Fetch(_) keycloak::GetCredentialsError::Fetch(_)
| keycloak::GetCredentialsError::InvalidAPIResponse(_) => E::AuthInfoUnavailable, | keycloak::GetCredentialsError::InvalidAPIResponse(_) => E::AuthInfoUnavailable,
keycloak::GetCredentialsError::NonUnitResults => E::CredentialsError,
keycloak::GetCredentialsError::ErrorResponse => E::ServiceError, keycloak::GetCredentialsError::ErrorResponse => E::ServiceError,
}) })
} }
+6
View File
@@ -13,6 +13,12 @@ pub enum GetgrnamError {
Unexpected, Unexpected,
} }
#[must_use]
pub fn is_root_ruid() -> bool {
// SAFETY: `getuid` has no side effects or thread safety concerns, just gets the number
0 == unsafe { libc::getuid() }
}
/// # Safety /// # Safety
/// ///
/// The function is completely safe to call but the use of `cb` requires unsafe: /// The function is completely safe to call but the use of `cb` requires unsafe: