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)]
pub enum GetCredentialsError {
pub enum GetUserIdError {
#[error("on request: {0}")]
Fetch(#[from] curl::Error),
#[error("unexpected API response: {0}")]
@@ -31,6 +31,16 @@ pub enum GetCredentialsError {
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)]
pub enum SetCredentialsError {
#[error("on request: {0}")]
@@ -143,11 +153,11 @@ impl<'a> KeycloakAPI<'a> {
}
}
pub fn get_user_credentials(
pub fn get_user_id(
&self,
username: &str,
admin_token: &str,
) -> Result<(String, Vec<api::Password>), GetCredentialsError> {
) -> Result<String, GetUserIdError> {
let Response { status, data: body } = self.curl.request(
Method::GET,
&format!(
@@ -158,20 +168,28 @@ impl<'a> KeycloakAPI<'a> {
)?;
if !status.is_ok() {
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)?;
debug_info!(if self.debug => "matched users" = found_users);
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(
Method::GET,
&format!(
"{}/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),
@@ -183,7 +201,7 @@ impl<'a> KeycloakAPI<'a> {
return Err(GetCredentialsError::ErrorResponse);
}
Ok((first_user.id, serde_json::from_slice(&body)?))
Ok(serde_json::from_slice(&body)?)
}
pub fn set_user_credentials(
+47 -13
View File
@@ -58,8 +58,8 @@ impl<M: ModuleClient> PamModule<M> for KeycloakPAM {
///
/// # Errors
///
/// As a baseline, the errors of [`Self::init`], [`Self::get_token_for`] and
/// [`Self::get_creds_for`].
/// As a baseline, the errors of [`Self::init`], [`Self::get_token_for`], [`Self::get_id_for`]
/// and [`Self::get_creds_for`].
///
/// Extra:
/// - [`E::BufferError`]: If some data could not be converted to UTF-8 or anything went wrong
@@ -103,18 +103,32 @@ impl<M: ModuleClient> PamModule<M> for KeycloakPAM {
},
)?;
debug_print!(if args.debug => "verifying credentials");
let (user_id, user_creds) = Self::get_creds_for(
let user_id = Self::get_id_for(
&api_client,
username_str,
&admin_token.access_token,
can_print,
)?;
if !user_creds.is_empty() {
let old_authtok = handle.old_authtok(None)?;
Self::get_token_for(&api_client, &username, &old_authtok, can_print)?;
debug_print!(if args.debug => "identity verified");
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() {
let old_authtok = handle.old_authtok(None)?;
Self::get_token_for(&api_client, &username, &old_authtok, can_print)?;
debug_print!(if args.debug => "identity verified");
}
}
let authtok = handle.authtok(None)?;
@@ -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.
///
/// # Errors
///
/// - [`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(
api_client: &keycloak::KeycloakAPI,
username: &str,
user_id: &str,
admin_token: &str,
can_print: bool,
) -> nonstick::Result<(String, Vec<keycloak::api::Password>)> {
) -> nonstick::Result<Vec<keycloak::api::Password>> {
error_print!(
api_client.get_user_credentials(username, admin_token),
api_client.get_user_credentials(user_id, admin_token),
can_print
)
.map_err(|error| match error {
keycloak::GetCredentialsError::Fetch(_)
| keycloak::GetCredentialsError::InvalidAPIResponse(_) => E::AuthInfoUnavailable,
keycloak::GetCredentialsError::NonUnitResults => E::CredentialsError,
keycloak::GetCredentialsError::ErrorResponse => E::ServiceError,
})
}
+6
View File
@@ -13,6 +13,12 @@ pub enum GetgrnamError {
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
///
/// The function is completely safe to call but the use of `cb` requires unsafe: