auth-api: add PasswordAuthenticator
This is the PbsAuthenticator with the hardcoded shadow.json/lock configurable. Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
c12bbf6241
commit
e5c8d70324
@ -20,6 +20,7 @@ lazy_static = { workspace = true, optional = true }
|
||||
libc = { workspace = true, optional = true }
|
||||
log = { workspace = true, optional = true }
|
||||
http = { workspace = true, optional = true }
|
||||
nix = { workspace = true, optional = true }
|
||||
openssl = { workspace = true, optional = true }
|
||||
pam-sys = { workspace = true, optional = true }
|
||||
percent-encoding = { workspace = true, optional = true }
|
||||
@ -28,9 +29,11 @@ serde = { workspace = true, optional = true, features = [ "derive" ] }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
serde_plain = { workspace = true, optional = true }
|
||||
|
||||
proxmox-product-config = { workspace = true, optional = true }
|
||||
proxmox-rest-server = { workspace = true, optional = true }
|
||||
proxmox-router = { workspace = true, optional = true }
|
||||
proxmox-schema = { workspace = true, optional = true, features = [ "api-macro", "api-types" ] }
|
||||
proxmox-sys = { workspace = true, optional = true }
|
||||
proxmox-tfa = { workspace = true, optional = true, features = [ "api" ] }
|
||||
|
||||
[features]
|
||||
@ -50,3 +53,11 @@ api = [
|
||||
"dep:proxmox-tfa",
|
||||
]
|
||||
pam-authenticator = [ "api", "dep:libc", "dep:log", "dep:pam-sys" ]
|
||||
password-authenticator = [
|
||||
"api",
|
||||
"dep:log",
|
||||
"dep:nix",
|
||||
"dep:proxmox-product-config",
|
||||
"dep:proxmox-sys",
|
||||
"proxmox-sys?/crypt",
|
||||
]
|
||||
|
@ -34,3 +34,8 @@ pub mod types;
|
||||
mod pam_authenticator;
|
||||
#[cfg(feature = "pam-authenticator")]
|
||||
pub use pam_authenticator::Pam;
|
||||
|
||||
#[cfg(feature = "password-authenticator")]
|
||||
mod password_authenticator;
|
||||
#[cfg(feature = "password-authenticator")]
|
||||
pub use password_authenticator::PasswordAuthenticator;
|
||||
|
90
proxmox-auth-api/src/password_authenticator.rs
Normal file
90
proxmox-auth-api/src/password_authenticator.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use std::future::Future;
|
||||
use std::net::IpAddr;
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::{bail, Error};
|
||||
use serde_json::json;
|
||||
|
||||
use proxmox_product_config::open_secret_lockfile;
|
||||
|
||||
use crate::types::UsernameRef;
|
||||
|
||||
/// A simple password authenticator with a configurable path for a shadow json and lock file.
|
||||
pub struct PasswordAuthenticator {
|
||||
pub config_filename: &'static str,
|
||||
pub lock_filename: &'static str,
|
||||
}
|
||||
|
||||
impl crate::api::Authenticator for PasswordAuthenticator {
|
||||
fn authenticate_user<'a>(
|
||||
&'a self,
|
||||
username: &'a UsernameRef,
|
||||
password: &'a str,
|
||||
client_ip: Option<&'a IpAddr>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let data = proxmox_sys::fs::file_get_json(self.config_filename, Some(json!({})))?;
|
||||
match data[username.as_str()].as_str() {
|
||||
None => bail!("no password set"),
|
||||
Some(enc_password) => {
|
||||
proxmox_sys::crypt::verify_crypt_pw(password, enc_password)?;
|
||||
|
||||
// if the password hash is not based on the current hashing function (as
|
||||
// identified by its prefix), rehash the password.
|
||||
if !enc_password.starts_with(proxmox_sys::crypt::HASH_PREFIX) {
|
||||
// only log that we could not upgrade a password, we already know that the
|
||||
// user has a valid password, no reason the deny to log in attempt.
|
||||
if let Err(e) = self.store_password(username, password, client_ip) {
|
||||
log::warn!("could not upgrade a users password! - {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn store_password(
|
||||
&self,
|
||||
username: &UsernameRef,
|
||||
password: &str,
|
||||
_client_ip: Option<&IpAddr>,
|
||||
) -> Result<(), Error> {
|
||||
let enc_password = proxmox_sys::crypt::encrypt_pw(password)?;
|
||||
|
||||
let _guard = open_secret_lockfile(self.lock_filename, None, true);
|
||||
let mut data = proxmox_sys::fs::file_get_json(self.config_filename, Some(json!({})))?;
|
||||
data[username.as_str()] = enc_password.into();
|
||||
|
||||
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
|
||||
let options = proxmox_sys::fs::CreateOptions::new()
|
||||
.perm(mode)
|
||||
.owner(nix::unistd::ROOT)
|
||||
.group(nix::unistd::Gid::from_raw(0));
|
||||
|
||||
let data = serde_json::to_vec_pretty(&data)?;
|
||||
proxmox_sys::fs::replace_file(self.config_filename, &data, options, true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_password(&self, username: &UsernameRef) -> Result<(), Error> {
|
||||
let _guard = open_secret_lockfile(self.lock_filename, None, true);
|
||||
|
||||
let mut data = proxmox_sys::fs::file_get_json(self.config_filename, Some(json!({})))?;
|
||||
if let Some(map) = data.as_object_mut() {
|
||||
map.remove(username.as_str());
|
||||
}
|
||||
|
||||
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
|
||||
let options = proxmox_sys::fs::CreateOptions::new()
|
||||
.perm(mode)
|
||||
.owner(nix::unistd::ROOT)
|
||||
.group(nix::unistd::Gid::from_raw(0));
|
||||
|
||||
let data = serde_json::to_vec_pretty(&data)?;
|
||||
proxmox_sys::fs::replace_file(self.config_filename, &data, options, true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user