feat: ignore passwd verification if ruid == root
This commit is contained in:
+25
-7
@@ -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
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user