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)]
|
#[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
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user