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:
Wolfgang Bumiller 2024-06-12 15:12:29 +02:00
parent c12bbf6241
commit e5c8d70324
3 changed files with 106 additions and 0 deletions

View File

@ -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",
]

View File

@ -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;

View 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(())
}
}