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 }
|
libc = { workspace = true, optional = true }
|
||||||
log = { workspace = true, optional = true }
|
log = { workspace = true, optional = true }
|
||||||
http = { workspace = true, optional = true }
|
http = { workspace = true, optional = true }
|
||||||
|
nix = { workspace = true, optional = true }
|
||||||
openssl = { workspace = true, optional = true }
|
openssl = { workspace = true, optional = true }
|
||||||
pam-sys = { workspace = true, optional = true }
|
pam-sys = { workspace = true, optional = true }
|
||||||
percent-encoding = { 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_json = { workspace = true, optional = true }
|
||||||
serde_plain = { 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-rest-server = { workspace = true, optional = true }
|
||||||
proxmox-router = { workspace = true, optional = true }
|
proxmox-router = { workspace = true, optional = true }
|
||||||
proxmox-schema = { workspace = true, optional = true, features = [ "api-macro", "api-types" ] }
|
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" ] }
|
proxmox-tfa = { workspace = true, optional = true, features = [ "api" ] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@ -50,3 +53,11 @@ api = [
|
|||||||
"dep:proxmox-tfa",
|
"dep:proxmox-tfa",
|
||||||
]
|
]
|
||||||
pam-authenticator = [ "api", "dep:libc", "dep:log", "dep:pam-sys" ]
|
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;
|
mod pam_authenticator;
|
||||||
#[cfg(feature = "pam-authenticator")]
|
#[cfg(feature = "pam-authenticator")]
|
||||||
pub use pam_authenticator::Pam;
|
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…
x
Reference in New Issue
Block a user