introduce Username, Realm and Userid api types
and begin splitting up types.rs as it has grown quite large already Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
27d864210a
commit
e7cb4dc50d
|
@ -4,6 +4,7 @@ use anyhow::{Error};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
|
use proxmox_backup::api2::types::Userid;
|
||||||
use proxmox_backup::client::{HttpClient, HttpClientOptions, BackupReader};
|
use proxmox_backup::client::{HttpClient, HttpClientOptions, BackupReader};
|
||||||
|
|
||||||
pub struct DummyWriter {
|
pub struct DummyWriter {
|
||||||
|
@ -27,7 +28,7 @@ async fn run() -> Result<(), Error> {
|
||||||
|
|
||||||
let host = "localhost";
|
let host = "localhost";
|
||||||
|
|
||||||
let username = "root@pam";
|
let username = Userid::root_userid();
|
||||||
|
|
||||||
let options = HttpClientOptions::new()
|
let options = HttpClientOptions::new()
|
||||||
.interactive(true)
|
.interactive(true)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::{Error};
|
use anyhow::{Error};
|
||||||
|
|
||||||
|
use proxmox_backup::api2::types::Userid;
|
||||||
use proxmox_backup::client::*;
|
use proxmox_backup::client::*;
|
||||||
|
|
||||||
async fn upload_speed() -> Result<f64, Error> {
|
async fn upload_speed() -> Result<f64, Error> {
|
||||||
|
@ -7,7 +8,7 @@ async fn upload_speed() -> Result<f64, Error> {
|
||||||
let host = "localhost";
|
let host = "localhost";
|
||||||
let datastore = "store2";
|
let datastore = "store2";
|
||||||
|
|
||||||
let username = "root@pam";
|
let username = Userid::root_userid();
|
||||||
|
|
||||||
let options = HttpClientOptions::new()
|
let options = HttpClientOptions::new()
|
||||||
.interactive(true)
|
.interactive(true)
|
||||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::{bail, format_err, Error};
|
||||||
|
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use proxmox::api::{api, RpcEnvironment, Permission, UserInformation};
|
use proxmox::api::{api, RpcEnvironment, Permission};
|
||||||
use proxmox::api::router::{Router, SubdirMap};
|
use proxmox::api::router::{Router, SubdirMap};
|
||||||
use proxmox::{sortable, identity};
|
use proxmox::{sortable, identity};
|
||||||
use proxmox::{http_err, list_subdirs_api_method};
|
use proxmox::{http_err, list_subdirs_api_method};
|
||||||
|
@ -23,7 +23,7 @@ pub mod role;
|
||||||
/// returns Ok(true) if a ticket has to be created
|
/// returns Ok(true) if a ticket has to be created
|
||||||
/// and Ok(false) if not
|
/// and Ok(false) if not
|
||||||
fn authenticate_user(
|
fn authenticate_user(
|
||||||
username: &str,
|
userid: &Userid,
|
||||||
password: &str,
|
password: &str,
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
privs: Option<String>,
|
privs: Option<String>,
|
||||||
|
@ -31,7 +31,7 @@ fn authenticate_user(
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
|
|
||||||
if !user_info.is_active_user(&username) {
|
if !user_info.is_active_user(&userid) {
|
||||||
bail!("user account disabled or expired.");
|
bail!("user account disabled or expired.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,10 +39,10 @@ fn authenticate_user(
|
||||||
|
|
||||||
if password.starts_with("PBS:") {
|
if password.starts_with("PBS:") {
|
||||||
if let Ok((_age, Some(ticket_username))) = tools::ticket::verify_rsa_ticket(public_auth_key(), "PBS", password, None, -300, ticket_lifetime) {
|
if let Ok((_age, Some(ticket_username))) = tools::ticket::verify_rsa_ticket(public_auth_key(), "PBS", password, None, -300, ticket_lifetime) {
|
||||||
if ticket_username == username {
|
if *userid == ticket_username {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
} else {
|
} else {
|
||||||
bail!("ticket login failed - wrong username");
|
bail!("ticket login failed - wrong userid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if password.starts_with("PBSTERM:") {
|
} else if password.starts_with("PBSTERM:") {
|
||||||
|
@ -55,7 +55,7 @@ fn authenticate_user(
|
||||||
let port = port.unwrap();
|
let port = port.unwrap();
|
||||||
|
|
||||||
if let Ok((_age, _data)) =
|
if let Ok((_age, _data)) =
|
||||||
tools::ticket::verify_term_ticket(public_auth_key(), &username, &path, port, password)
|
tools::ticket::verify_term_ticket(public_auth_key(), &userid, &path, port, password)
|
||||||
{
|
{
|
||||||
for (name, privilege) in PRIVILEGES {
|
for (name, privilege) in PRIVILEGES {
|
||||||
if *name == privilege_name {
|
if *name == privilege_name {
|
||||||
|
@ -66,7 +66,7 @@ fn authenticate_user(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user_info.check_privs(username, &path_vec, *privilege, false)?;
|
user_info.check_privs(userid, &path_vec, *privilege, false)?;
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ fn authenticate_user(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = crate::auth::authenticate_user(username, password)?;
|
let _ = crate::auth::authenticate_user(userid, password)?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ fn authenticate_user(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
username: {
|
username: {
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
schema: PASSWORD_SCHEMA,
|
schema: PASSWORD_SCHEMA,
|
||||||
|
@ -130,7 +130,7 @@ fn authenticate_user(
|
||||||
///
|
///
|
||||||
/// Returns: An authentication ticket with additional infos.
|
/// Returns: An authentication ticket with additional infos.
|
||||||
fn create_ticket(
|
fn create_ticket(
|
||||||
username: String,
|
username: Userid,
|
||||||
password: String,
|
password: String,
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
privs: Option<String>,
|
privs: Option<String>,
|
||||||
|
@ -165,7 +165,7 @@ fn create_ticket(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
userid: {
|
userid: {
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
schema: PASSWORD_SCHEMA,
|
schema: PASSWORD_SCHEMA,
|
||||||
|
@ -183,13 +183,15 @@ fn create_ticket(
|
||||||
/// Each user is allowed to change his own password. Superuser
|
/// Each user is allowed to change his own password. Superuser
|
||||||
/// can change all passwords.
|
/// can change all passwords.
|
||||||
fn change_password(
|
fn change_password(
|
||||||
userid: String,
|
userid: Userid,
|
||||||
password: String,
|
password: String,
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
|
|
||||||
let current_user = rpcenv.get_user()
|
let current_user: Userid = rpcenv
|
||||||
.ok_or_else(|| format_err!("unknown user"))?;
|
.get_user()
|
||||||
|
.ok_or_else(|| format_err!("unknown user"))?
|
||||||
|
.parse()?;
|
||||||
|
|
||||||
let mut allowed = userid == current_user;
|
let mut allowed = userid == current_user;
|
||||||
|
|
||||||
|
@ -205,9 +207,8 @@ fn change_password(
|
||||||
bail!("you are not authorized to change the password.");
|
bail!("you are not authorized to change the password.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let (username, realm) = crate::auth::parse_userid(&userid)?;
|
let authenticator = crate::auth::lookup_authenticator(userid.realm())?;
|
||||||
let authenticator = crate::auth::lookup_authenticator(&realm)?;
|
authenticator.store_password(userid.name(), &password)?;
|
||||||
authenticator.store_password(&username, &password)?;
|
|
||||||
|
|
||||||
Ok(Value::Null)
|
Ok(Value::Null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ pub fn read_acl(
|
||||||
},
|
},
|
||||||
userid: {
|
userid: {
|
||||||
optional: true,
|
optional: true,
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
group: {
|
group: {
|
||||||
optional: true,
|
optional: true,
|
||||||
|
@ -168,7 +168,7 @@ pub fn update_acl(
|
||||||
path: String,
|
path: String,
|
||||||
role: String,
|
role: String,
|
||||||
propagate: Option<bool>,
|
propagate: Option<bool>,
|
||||||
userid: Option<String>,
|
userid: Option<Userid>,
|
||||||
group: Option<String>,
|
group: Option<String>,
|
||||||
delete: Option<bool>,
|
delete: Option<bool>,
|
||||||
digest: Option<String>,
|
digest: Option<String>,
|
||||||
|
@ -193,7 +193,7 @@ pub fn update_acl(
|
||||||
} else if let Some(ref userid) = userid {
|
} else if let Some(ref userid) = userid {
|
||||||
if !delete { // Note: we allow to delete non-existent users
|
if !delete { // Note: we allow to delete non-existent users
|
||||||
let user_cfg = crate::config::user::cached_config()?;
|
let user_cfg = crate::config::user::cached_config()?;
|
||||||
if user_cfg.sections.get(userid).is_none() {
|
if user_cfg.sections.get(&userid.to_string()).is_none() {
|
||||||
bail!("no such user.");
|
bail!("no such user.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub fn list_users(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
userid: {
|
userid: {
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
comment: {
|
comment: {
|
||||||
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
||||||
|
@ -94,19 +94,18 @@ pub fn create_user(password: Option<String>, param: Value) -> Result<(), Error>
|
||||||
|
|
||||||
let (mut config, _digest) = user::config()?;
|
let (mut config, _digest) = user::config()?;
|
||||||
|
|
||||||
if let Some(_) = config.sections.get(&user.userid) {
|
if let Some(_) = config.sections.get(user.userid.as_str()) {
|
||||||
bail!("user '{}' already exists.", user.userid);
|
bail!("user '{}' already exists.", user.userid);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (username, realm) = crate::auth::parse_userid(&user.userid)?;
|
let authenticator = crate::auth::lookup_authenticator(&user.userid.realm())?;
|
||||||
let authenticator = crate::auth::lookup_authenticator(&realm)?;
|
|
||||||
|
|
||||||
config.set_data(&user.userid, "user", &user)?;
|
config.set_data(user.userid.as_str(), "user", &user)?;
|
||||||
|
|
||||||
user::save_config(&config)?;
|
user::save_config(&config)?;
|
||||||
|
|
||||||
if let Some(password) = password {
|
if let Some(password) = password {
|
||||||
authenticator.store_password(&username, &password)?;
|
authenticator.store_password(user.userid.name(), &password)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -116,7 +115,7 @@ pub fn create_user(password: Option<String>, param: Value) -> Result<(), Error>
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
userid: {
|
userid: {
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -129,9 +128,9 @@ pub fn create_user(password: Option<String>, param: Value) -> Result<(), Error>
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
/// Read user configuration data.
|
/// Read user configuration data.
|
||||||
pub fn read_user(userid: String, mut rpcenv: &mut dyn RpcEnvironment) -> Result<user::User, Error> {
|
pub fn read_user(userid: Userid, mut rpcenv: &mut dyn RpcEnvironment) -> Result<user::User, Error> {
|
||||||
let (config, digest) = user::config()?;
|
let (config, digest) = user::config()?;
|
||||||
let user = config.lookup("user", &userid)?;
|
let user = config.lookup("user", userid.as_str())?;
|
||||||
rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
|
rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
@ -141,7 +140,7 @@ pub fn read_user(userid: String, mut rpcenv: &mut dyn RpcEnvironment) -> Result<
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
userid: {
|
userid: {
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
comment: {
|
comment: {
|
||||||
optional: true,
|
optional: true,
|
||||||
|
@ -183,7 +182,7 @@ pub fn read_user(userid: String, mut rpcenv: &mut dyn RpcEnvironment) -> Result<
|
||||||
)]
|
)]
|
||||||
/// Update user configuration.
|
/// Update user configuration.
|
||||||
pub fn update_user(
|
pub fn update_user(
|
||||||
userid: String,
|
userid: Userid,
|
||||||
comment: Option<String>,
|
comment: Option<String>,
|
||||||
enable: Option<bool>,
|
enable: Option<bool>,
|
||||||
expire: Option<i64>,
|
expire: Option<i64>,
|
||||||
|
@ -203,7 +202,7 @@ pub fn update_user(
|
||||||
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
|
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data: user::User = config.lookup("user", &userid)?;
|
let mut data: user::User = config.lookup("user", userid.as_str())?;
|
||||||
|
|
||||||
if let Some(comment) = comment {
|
if let Some(comment) = comment {
|
||||||
let comment = comment.trim().to_string();
|
let comment = comment.trim().to_string();
|
||||||
|
@ -223,9 +222,8 @@ pub fn update_user(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(password) = password {
|
if let Some(password) = password {
|
||||||
let (username, realm) = crate::auth::parse_userid(&userid)?;
|
let authenticator = crate::auth::lookup_authenticator(userid.realm())?;
|
||||||
let authenticator = crate::auth::lookup_authenticator(&realm)?;
|
authenticator.store_password(userid.name(), &password)?;
|
||||||
authenticator.store_password(&username, &password)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(firstname) = firstname {
|
if let Some(firstname) = firstname {
|
||||||
|
@ -239,7 +237,7 @@ pub fn update_user(
|
||||||
data.email = if email.is_empty() { None } else { Some(email) };
|
data.email = if email.is_empty() { None } else { Some(email) };
|
||||||
}
|
}
|
||||||
|
|
||||||
config.set_data(&userid, "user", &data)?;
|
config.set_data(userid.as_str(), "user", &data)?;
|
||||||
|
|
||||||
user::save_config(&config)?;
|
user::save_config(&config)?;
|
||||||
|
|
||||||
|
@ -251,7 +249,7 @@ pub fn update_user(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
userid: {
|
userid: {
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
digest: {
|
digest: {
|
||||||
optional: true,
|
optional: true,
|
||||||
|
@ -264,7 +262,7 @@ pub fn update_user(
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
/// Remove a user from the configuration file.
|
/// Remove a user from the configuration file.
|
||||||
pub fn delete_user(userid: String, digest: Option<String>) -> Result<(), Error> {
|
pub fn delete_user(userid: Userid, digest: Option<String>) -> Result<(), Error> {
|
||||||
|
|
||||||
let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0))?;
|
let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0))?;
|
||||||
|
|
||||||
|
@ -275,8 +273,8 @@ pub fn delete_user(userid: String, digest: Option<String>) -> Result<(), Error>
|
||||||
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
|
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
match config.sections.get(&userid) {
|
match config.sections.get(userid.as_str()) {
|
||||||
Some(_) => { config.sections.remove(&userid); },
|
Some(_) => { config.sections.remove(userid.as_str()); },
|
||||||
None => bail!("user '{}' does not exist.", userid),
|
None => bail!("user '{}' does not exist.", userid),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ use serde_json::{json, Value};
|
||||||
|
|
||||||
use proxmox::api::{
|
use proxmox::api::{
|
||||||
api, ApiResponseFuture, ApiHandler, ApiMethod, Router,
|
api, ApiResponseFuture, ApiHandler, ApiMethod, Router,
|
||||||
RpcEnvironment, RpcEnvironmentType, Permission, UserInformation};
|
RpcEnvironment, RpcEnvironmentType, Permission
|
||||||
|
};
|
||||||
use proxmox::api::router::SubdirMap;
|
use proxmox::api::router::SubdirMap;
|
||||||
use proxmox::api::schema::*;
|
use proxmox::api::schema::*;
|
||||||
use proxmox::tools::fs::{replace_file, CreateOptions};
|
use proxmox::tools::fs::{replace_file, CreateOptions};
|
||||||
|
@ -36,7 +37,11 @@ use crate::config::acl::{
|
||||||
PRIV_DATASTORE_BACKUP,
|
PRIV_DATASTORE_BACKUP,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn check_backup_owner(store: &DataStore, group: &BackupGroup, userid: &str) -> Result<(), Error> {
|
fn check_backup_owner(
|
||||||
|
store: &DataStore,
|
||||||
|
group: &BackupGroup,
|
||||||
|
userid: &Userid,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let owner = store.get_owner(group)?;
|
let owner = store.get_owner(group)?;
|
||||||
if &owner != userid {
|
if &owner != userid {
|
||||||
bail!("backup owner check failed ({} != {})", userid, owner);
|
bail!("backup owner check failed ({} != {})", userid, owner);
|
||||||
|
@ -44,7 +49,10 @@ fn check_backup_owner(store: &DataStore, group: &BackupGroup, userid: &str) -> R
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_backup_index(store: &DataStore, backup_dir: &BackupDir) -> Result<(BackupManifest, Vec<BackupContent>), Error> {
|
fn read_backup_index(
|
||||||
|
store: &DataStore,
|
||||||
|
backup_dir: &BackupDir,
|
||||||
|
) -> Result<(BackupManifest, Vec<BackupContent>), Error> {
|
||||||
|
|
||||||
let (manifest, index_size) = store.load_manifest(backup_dir)?;
|
let (manifest, index_size) = store.load_manifest(backup_dir)?;
|
||||||
|
|
||||||
|
@ -131,9 +139,9 @@ fn list_groups(
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Vec<GroupListItem>, Error> {
|
) -> Result<Vec<GroupListItem>, Error> {
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
|
@ -154,7 +162,7 @@ fn list_groups(
|
||||||
let list_all = (user_privs & PRIV_DATASTORE_AUDIT) != 0;
|
let list_all = (user_privs & PRIV_DATASTORE_AUDIT) != 0;
|
||||||
let owner = datastore.get_owner(group)?;
|
let owner = datastore.get_owner(group)?;
|
||||||
if !list_all {
|
if !list_all {
|
||||||
if owner != username { continue; }
|
if owner != userid { continue; }
|
||||||
}
|
}
|
||||||
|
|
||||||
let result_item = GroupListItem {
|
let result_item = GroupListItem {
|
||||||
|
@ -212,16 +220,16 @@ pub fn list_snapshot_files(
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Vec<BackupContent>, Error> {
|
) -> Result<Vec<BackupContent>, Error> {
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
let snapshot = BackupDir::new(backup_type, backup_id, backup_time);
|
let snapshot = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ)) != 0;
|
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ)) != 0;
|
||||||
if !allowed { check_backup_owner(&datastore, snapshot.group(), &username)?; }
|
if !allowed { check_backup_owner(&datastore, snapshot.group(), &userid)?; }
|
||||||
|
|
||||||
let info = BackupInfo::new(&datastore.base_path(), snapshot)?;
|
let info = BackupInfo::new(&datastore.base_path(), snapshot)?;
|
||||||
|
|
||||||
|
@ -264,16 +272,16 @@ fn delete_snapshot(
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let snapshot = BackupDir::new(backup_type, backup_id, backup_time);
|
let snapshot = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
let allowed = (user_privs & PRIV_DATASTORE_MODIFY) != 0;
|
let allowed = (user_privs & PRIV_DATASTORE_MODIFY) != 0;
|
||||||
if !allowed { check_backup_owner(&datastore, snapshot.group(), &username)?; }
|
if !allowed { check_backup_owner(&datastore, snapshot.group(), &userid)?; }
|
||||||
|
|
||||||
datastore.remove_backup_dir(&snapshot, false)?;
|
datastore.remove_backup_dir(&snapshot, false)?;
|
||||||
|
|
||||||
|
@ -320,9 +328,9 @@ pub fn list_snapshots (
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Vec<SnapshotListItem>, Error> {
|
) -> Result<Vec<SnapshotListItem>, Error> {
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
|
@ -345,7 +353,7 @@ pub fn list_snapshots (
|
||||||
let owner = datastore.get_owner(group)?;
|
let owner = datastore.get_owner(group)?;
|
||||||
|
|
||||||
if !list_all {
|
if !list_all {
|
||||||
if owner != username { continue; }
|
if owner != userid { continue; }
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut size = None;
|
let mut size = None;
|
||||||
|
@ -481,12 +489,15 @@ pub fn verify(
|
||||||
_ => bail!("parameters do not spefify a backup group or snapshot"),
|
_ => bail!("parameters do not spefify a backup group or snapshot"),
|
||||||
}
|
}
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
||||||
|
|
||||||
let upid_str = WorkerTask::new_thread(
|
let upid_str = WorkerTask::new_thread(
|
||||||
"verify", Some(worker_id.clone()), &username, to_stdout, move |worker|
|
"verify",
|
||||||
{
|
Some(worker_id.clone()),
|
||||||
|
userid,
|
||||||
|
to_stdout,
|
||||||
|
move |worker| {
|
||||||
let failed_dirs = if let Some(backup_dir) = backup_dir {
|
let failed_dirs = if let Some(backup_dir) = backup_dir {
|
||||||
let mut verified_chunks = HashSet::with_capacity(1024*16);
|
let mut verified_chunks = HashSet::with_capacity(1024*16);
|
||||||
let mut corrupt_chunks = HashSet::with_capacity(64);
|
let mut corrupt_chunks = HashSet::with_capacity(64);
|
||||||
|
@ -508,7 +519,8 @@ pub fn verify(
|
||||||
bail!("verfication failed - please check the log for details");
|
bail!("verfication failed - please check the log for details");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(json!(upid_str))
|
Ok(json!(upid_str))
|
||||||
}
|
}
|
||||||
|
@ -593,9 +605,9 @@ fn prune(
|
||||||
let backup_type = tools::required_string_param(¶m, "backup-type")?;
|
let backup_type = tools::required_string_param(¶m, "backup-type")?;
|
||||||
let backup_id = tools::required_string_param(¶m, "backup-id")?;
|
let backup_id = tools::required_string_param(¶m, "backup-id")?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let dry_run = param["dry-run"].as_bool().unwrap_or(false);
|
let dry_run = param["dry-run"].as_bool().unwrap_or(false);
|
||||||
|
|
||||||
|
@ -604,7 +616,7 @@ fn prune(
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
let allowed = (user_privs & PRIV_DATASTORE_MODIFY) != 0;
|
let allowed = (user_privs & PRIV_DATASTORE_MODIFY) != 0;
|
||||||
if !allowed { check_backup_owner(&datastore, &group, &username)?; }
|
if !allowed { check_backup_owner(&datastore, &group, &userid)?; }
|
||||||
|
|
||||||
let prune_options = PruneOptions {
|
let prune_options = PruneOptions {
|
||||||
keep_last: param["keep-last"].as_u64(),
|
keep_last: param["keep-last"].as_u64(),
|
||||||
|
@ -646,7 +658,7 @@ fn prune(
|
||||||
|
|
||||||
|
|
||||||
// We use a WorkerTask just to have a task log, but run synchrounously
|
// We use a WorkerTask just to have a task log, but run synchrounously
|
||||||
let worker = WorkerTask::new("prune", Some(worker_id), "root@pam", true)?;
|
let worker = WorkerTask::new("prune", Some(worker_id), Userid::root_userid().clone(), true)?;
|
||||||
|
|
||||||
let result = try_block! {
|
let result = try_block! {
|
||||||
if keep_all {
|
if keep_all {
|
||||||
|
@ -728,11 +740,15 @@ fn start_garbage_collection(
|
||||||
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
||||||
|
|
||||||
let upid_str = WorkerTask::new_thread(
|
let upid_str = WorkerTask::new_thread(
|
||||||
"garbage_collection", Some(store.clone()), "root@pam", to_stdout, move |worker|
|
"garbage_collection",
|
||||||
{
|
Some(store.clone()),
|
||||||
|
Userid::root_userid().clone(),
|
||||||
|
to_stdout,
|
||||||
|
move |worker| {
|
||||||
worker.log(format!("starting garbage collection on store {}", store));
|
worker.log(format!("starting garbage collection on store {}", store));
|
||||||
datastore.garbage_collection(&worker)
|
datastore.garbage_collection(&worker)
|
||||||
})?;
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(json!(upid_str))
|
Ok(json!(upid_str))
|
||||||
}
|
}
|
||||||
|
@ -796,13 +812,13 @@ fn get_datastore_list(
|
||||||
|
|
||||||
let (config, _digest) = datastore::config()?;
|
let (config, _digest) = datastore::config()?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
|
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
|
|
||||||
for (store, (_, data)) in &config.sections {
|
for (store, (_, data)) in &config.sections {
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
|
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
|
||||||
if allowed {
|
if allowed {
|
||||||
let mut entry = json!({ "store": store });
|
let mut entry = json!({ "store": store });
|
||||||
|
@ -847,9 +863,9 @@ fn download_file(
|
||||||
let store = tools::required_string_param(¶m, "store")?;
|
let store = tools::required_string_param(¶m, "store")?;
|
||||||
let datastore = DataStore::lookup_datastore(store)?;
|
let datastore = DataStore::lookup_datastore(store)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let file_name = tools::required_string_param(¶m, "file-name")?.to_owned();
|
let file_name = tools::required_string_param(¶m, "file-name")?.to_owned();
|
||||||
|
|
||||||
|
@ -860,7 +876,7 @@ fn download_file(
|
||||||
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
||||||
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &username)?; }
|
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
|
||||||
|
|
||||||
println!("Download {} from {} ({}/{})", file_name, store, backup_dir, file_name);
|
println!("Download {} from {} ({}/{})", file_name, store, backup_dir, file_name);
|
||||||
|
|
||||||
|
@ -920,9 +936,9 @@ fn download_file_decoded(
|
||||||
let store = tools::required_string_param(¶m, "store")?;
|
let store = tools::required_string_param(¶m, "store")?;
|
||||||
let datastore = DataStore::lookup_datastore(store)?;
|
let datastore = DataStore::lookup_datastore(store)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let file_name = tools::required_string_param(¶m, "file-name")?.to_owned();
|
let file_name = tools::required_string_param(¶m, "file-name")?.to_owned();
|
||||||
|
|
||||||
|
@ -933,7 +949,7 @@ fn download_file_decoded(
|
||||||
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
||||||
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &username)?; }
|
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
|
||||||
|
|
||||||
let (_manifest, files) = read_backup_index(&datastore, &backup_dir)?;
|
let (_manifest, files) = read_backup_index(&datastore, &backup_dir)?;
|
||||||
for file in files {
|
for file in files {
|
||||||
|
@ -1038,8 +1054,8 @@ fn upload_backup_log(
|
||||||
|
|
||||||
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
check_backup_owner(&datastore, backup_dir.group(), &username)?;
|
check_backup_owner(&datastore, backup_dir.group(), &userid)?;
|
||||||
|
|
||||||
let mut path = datastore.base_path();
|
let mut path = datastore.base_path();
|
||||||
path.push(backup_dir.relative_path());
|
path.push(backup_dir.relative_path());
|
||||||
|
@ -1108,14 +1124,14 @@ fn catalog(
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
||||||
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &username)?; }
|
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
|
||||||
|
|
||||||
let mut path = datastore.base_path();
|
let mut path = datastore.base_path();
|
||||||
path.push(backup_dir.relative_path());
|
path.push(backup_dir.relative_path());
|
||||||
|
@ -1207,9 +1223,9 @@ fn pxar_file_download(
|
||||||
let store = tools::required_string_param(¶m, "store")?;
|
let store = tools::required_string_param(¶m, "store")?;
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let filepath = tools::required_string_param(¶m, "filepath")?.to_owned();
|
let filepath = tools::required_string_param(¶m, "filepath")?.to_owned();
|
||||||
|
|
||||||
|
@ -1220,7 +1236,7 @@ fn pxar_file_download(
|
||||||
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
||||||
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &username)?; }
|
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
|
||||||
|
|
||||||
let mut path = datastore.base_path();
|
let mut path = datastore.base_path();
|
||||||
path.push(backup_dir.relative_path());
|
path.push(backup_dir.relative_path());
|
||||||
|
@ -1346,14 +1362,14 @@ fn get_notes(
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
||||||
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &username)?; }
|
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
|
||||||
|
|
||||||
let manifest = datastore.load_manifest_json(&backup_dir)?;
|
let manifest = datastore.load_manifest_json(&backup_dir)?;
|
||||||
|
|
||||||
|
@ -1399,14 +1415,14 @@ fn set_notes(
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
|
|
||||||
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
||||||
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &username)?; }
|
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
|
||||||
|
|
||||||
let mut manifest = datastore.load_manifest_json(&backup_dir)?;
|
let mut manifest = datastore.load_manifest_json(&backup_dir)?;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::{Error};
|
use anyhow::{Error};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use proxmox::api::{api, ApiMethod, Router, RpcEnvironment};
|
use proxmox::api::{api, ApiMethod, Router, RpcEnvironment};
|
||||||
use proxmox::api::router::SubdirMap;
|
use proxmox::api::router::SubdirMap;
|
||||||
|
@ -92,16 +93,23 @@ async fn run_sync_job(
|
||||||
let (config, _digest) = sync::config()?;
|
let (config, _digest) = sync::config()?;
|
||||||
let sync_job: SyncJobConfig = config.lookup("sync", &id)?;
|
let sync_job: SyncJobConfig = config.lookup("sync", &id)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
|
|
||||||
let delete = sync_job.remove_vanished.unwrap_or(true);
|
let delete = sync_job.remove_vanished.unwrap_or(true);
|
||||||
let (client, src_repo, tgt_store) = get_pull_parameters(&sync_job.store, &sync_job.remote, &sync_job.remote_store).await?;
|
let (client, src_repo, tgt_store) = get_pull_parameters(&sync_job.store, &sync_job.remote, &sync_job.remote_store).await?;
|
||||||
|
|
||||||
let upid_str = WorkerTask::spawn("syncjob", Some(id.clone()), &username.clone(), false, move |worker| async move {
|
let upid_str = WorkerTask::spawn("syncjob", Some(id.clone()), userid, false, move |worker| async move {
|
||||||
|
|
||||||
worker.log(format!("sync job '{}' start", &id));
|
worker.log(format!("sync job '{}' start", &id));
|
||||||
|
|
||||||
crate::client::pull::pull_store(&worker, &client, &src_repo, tgt_store.clone(), delete, String::from("backup@pam")).await?;
|
crate::client::pull::pull_store(
|
||||||
|
&worker,
|
||||||
|
&client,
|
||||||
|
&src_repo,
|
||||||
|
tgt_store.clone(),
|
||||||
|
delete,
|
||||||
|
Userid::backup_userid().clone(),
|
||||||
|
).await?;
|
||||||
|
|
||||||
worker.log(format!("sync job '{}' end", &id));
|
worker.log(format!("sync job '{}' end", &id));
|
||||||
|
|
||||||
|
|
|
@ -56,12 +56,12 @@ fn upgrade_to_backup_protocol(
|
||||||
async move {
|
async move {
|
||||||
let debug = param["debug"].as_bool().unwrap_or(false);
|
let debug = param["debug"].as_bool().unwrap_or(false);
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
|
|
||||||
let store = tools::required_string_param(¶m, "store")?.to_owned();
|
let store = tools::required_string_param(¶m, "store")?.to_owned();
|
||||||
|
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
user_info.check_privs(&username, &["datastore", &store], PRIV_DATASTORE_BACKUP, false)?;
|
user_info.check_privs(&userid, &["datastore", &store], PRIV_DATASTORE_BACKUP, false)?;
|
||||||
|
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
|
@ -90,11 +90,11 @@ async move {
|
||||||
let backup_group = BackupGroup::new(backup_type, backup_id);
|
let backup_group = BackupGroup::new(backup_type, backup_id);
|
||||||
|
|
||||||
// lock backup group to only allow one backup per group at a time
|
// lock backup group to only allow one backup per group at a time
|
||||||
let (owner, _group_guard) = datastore.create_locked_backup_group(&backup_group, &username)?;
|
let (owner, _group_guard) = datastore.create_locked_backup_group(&backup_group, &userid)?;
|
||||||
|
|
||||||
// permission check
|
// permission check
|
||||||
if owner != username { // only the owner is allowed to create additional snapshots
|
if owner != userid { // only the owner is allowed to create additional snapshots
|
||||||
bail!("backup owner check failed ({} != {})", username, owner);
|
bail!("backup owner check failed ({} != {})", userid, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
let last_backup = BackupInfo::last_backup(&datastore.base_path(), &backup_group, true).unwrap_or(None);
|
let last_backup = BackupInfo::last_backup(&datastore.base_path(), &backup_group, true).unwrap_or(None);
|
||||||
|
@ -109,9 +109,9 @@ async move {
|
||||||
let (path, is_new) = datastore.create_backup_dir(&backup_dir)?;
|
let (path, is_new) = datastore.create_backup_dir(&backup_dir)?;
|
||||||
if !is_new { bail!("backup directory already exists."); }
|
if !is_new { bail!("backup directory already exists."); }
|
||||||
|
|
||||||
WorkerTask::spawn("backup", Some(worker_id), &username.clone(), true, move |worker| {
|
WorkerTask::spawn("backup", Some(worker_id), userid.clone(), true, move |worker| {
|
||||||
let mut env = BackupEnvironment::new(
|
let mut env = BackupEnvironment::new(
|
||||||
env_type, username.clone(), worker.clone(), datastore, backup_dir);
|
env_type, userid, worker.clone(), datastore, backup_dir);
|
||||||
|
|
||||||
env.debug = debug;
|
env.debug = debug;
|
||||||
env.last_backup = last_backup;
|
env.last_backup = last_backup;
|
||||||
|
|
|
@ -9,8 +9,9 @@ use proxmox::tools::digest_to_hex;
|
||||||
use proxmox::tools::fs::{replace_file, CreateOptions};
|
use proxmox::tools::fs::{replace_file, CreateOptions};
|
||||||
use proxmox::api::{RpcEnvironment, RpcEnvironmentType};
|
use proxmox::api::{RpcEnvironment, RpcEnvironmentType};
|
||||||
|
|
||||||
use crate::server::WorkerTask;
|
use crate::api2::types::Userid;
|
||||||
use crate::backup::*;
|
use crate::backup::*;
|
||||||
|
use crate::server::WorkerTask;
|
||||||
use crate::server::formatter::*;
|
use crate::server::formatter::*;
|
||||||
use hyper::{Body, Response};
|
use hyper::{Body, Response};
|
||||||
|
|
||||||
|
@ -100,7 +101,7 @@ impl SharedBackupState {
|
||||||
pub struct BackupEnvironment {
|
pub struct BackupEnvironment {
|
||||||
env_type: RpcEnvironmentType,
|
env_type: RpcEnvironmentType,
|
||||||
result_attributes: Value,
|
result_attributes: Value,
|
||||||
user: String,
|
user: Userid,
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub formatter: &'static OutputFormatter,
|
pub formatter: &'static OutputFormatter,
|
||||||
pub worker: Arc<WorkerTask>,
|
pub worker: Arc<WorkerTask>,
|
||||||
|
@ -113,7 +114,7 @@ pub struct BackupEnvironment {
|
||||||
impl BackupEnvironment {
|
impl BackupEnvironment {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
env_type: RpcEnvironmentType,
|
env_type: RpcEnvironmentType,
|
||||||
user: String,
|
user: Userid,
|
||||||
worker: Arc<WorkerTask>,
|
worker: Arc<WorkerTask>,
|
||||||
datastore: Arc<DataStore>,
|
datastore: Arc<DataStore>,
|
||||||
backup_dir: BackupDir,
|
backup_dir: BackupDir,
|
||||||
|
@ -558,7 +559,7 @@ impl RpcEnvironment for BackupEnvironment {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_user(&self) -> Option<String> {
|
fn get_user(&self) -> Option<String> {
|
||||||
Some(self.user.clone())
|
Some(self.user.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub fn list_remotes(
|
||||||
schema: DNS_NAME_OR_IP_SCHEMA,
|
schema: DNS_NAME_OR_IP_SCHEMA,
|
||||||
},
|
},
|
||||||
userid: {
|
userid: {
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
schema: remote::REMOTE_PASSWORD_SCHEMA,
|
schema: remote::REMOTE_PASSWORD_SCHEMA,
|
||||||
|
@ -155,7 +155,7 @@ pub enum DeletableProperty {
|
||||||
},
|
},
|
||||||
userid: {
|
userid: {
|
||||||
optional: true,
|
optional: true,
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
optional: true,
|
optional: true,
|
||||||
|
@ -188,7 +188,7 @@ pub fn update_remote(
|
||||||
name: String,
|
name: String,
|
||||||
comment: Option<String>,
|
comment: Option<String>,
|
||||||
host: Option<String>,
|
host: Option<String>,
|
||||||
userid: Option<String>,
|
userid: Option<Userid>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
fingerprint: Option<String>,
|
fingerprint: Option<String>,
|
||||||
delete: Option<Vec<DeletableProperty>>,
|
delete: Option<Vec<DeletableProperty>>,
|
||||||
|
|
|
@ -90,12 +90,12 @@ async fn termproxy(
|
||||||
cmd: Option<String>,
|
cmd: Option<String>,
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
let userid = rpcenv
|
let userid: Userid = rpcenv
|
||||||
.get_user()
|
.get_user()
|
||||||
.ok_or_else(|| format_err!("unknown user"))?;
|
.ok_or_else(|| format_err!("unknown user"))?
|
||||||
let (username, realm) = crate::auth::parse_userid(&userid)?;
|
.parse()?;
|
||||||
|
|
||||||
if realm != "pam" {
|
if userid.realm() != "pam" {
|
||||||
bail!("only pam users can use the console");
|
bail!("only pam users can use the console");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,10 +133,11 @@ async fn termproxy(
|
||||||
_ => bail!("invalid command"),
|
_ => bail!("invalid command"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let username = userid.name().to_owned();
|
||||||
let upid = WorkerTask::spawn(
|
let upid = WorkerTask::spawn(
|
||||||
"termproxy",
|
"termproxy",
|
||||||
None,
|
None,
|
||||||
&userid,
|
userid,
|
||||||
false,
|
false,
|
||||||
move |worker| async move {
|
move |worker| async move {
|
||||||
// move inside the worker so that it survives and does not close the port
|
// move inside the worker so that it survives and does not close the port
|
||||||
|
@ -233,6 +234,7 @@ async fn termproxy(
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// FIXME: We're returning the user NAME only?
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"user": username,
|
"user": username,
|
||||||
"ticket": ticket,
|
"ticket": ticket,
|
||||||
|
@ -270,14 +272,14 @@ fn upgrade_to_websocket(
|
||||||
rpcenv: Box<dyn RpcEnvironment>,
|
rpcenv: Box<dyn RpcEnvironment>,
|
||||||
) -> ApiResponseFuture {
|
) -> ApiResponseFuture {
|
||||||
async move {
|
async move {
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let ticket = tools::required_string_param(¶m, "vncticket")?.to_owned();
|
let ticket = tools::required_string_param(¶m, "vncticket")?.to_owned();
|
||||||
let port: u16 = tools::required_integer_param(¶m, "port")? as u16;
|
let port: u16 = tools::required_integer_param(¶m, "port")? as u16;
|
||||||
|
|
||||||
// will be checked again by termproxy
|
// will be checked again by termproxy
|
||||||
tools::ticket::verify_term_ticket(
|
tools::ticket::verify_term_ticket(
|
||||||
crate::auth_helpers::public_auth_key(),
|
crate::auth_helpers::public_auth_key(),
|
||||||
&username,
|
&userid,
|
||||||
&"/system",
|
&"/system",
|
||||||
port,
|
port,
|
||||||
&ticket,
|
&ticket,
|
||||||
|
|
|
@ -9,7 +9,7 @@ use proxmox::api::router::{Router, SubdirMap};
|
||||||
use crate::server::WorkerTask;
|
use crate::server::WorkerTask;
|
||||||
|
|
||||||
use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
|
use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
|
||||||
use crate::api2::types::{APTUpdateInfo, NODE_SCHEMA, UPID_SCHEMA};
|
use crate::api2::types::{APTUpdateInfo, NODE_SCHEMA, Userid, UPID_SCHEMA};
|
||||||
|
|
||||||
const_regex! {
|
const_regex! {
|
||||||
VERSION_EPOCH_REGEX = r"^\d+:";
|
VERSION_EPOCH_REGEX = r"^\d+:";
|
||||||
|
@ -233,11 +233,11 @@ pub fn apt_update_database(
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
||||||
let quiet = quiet.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_QUIET);
|
let quiet = quiet.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_QUIET);
|
||||||
|
|
||||||
let upid_str = WorkerTask::new_thread("aptupdate", None, &username.clone(), to_stdout, move |worker| {
|
let upid_str = WorkerTask::new_thread("aptupdate", None, userid, to_stdout, move |worker| {
|
||||||
if !quiet { worker.log("starting apt-get update") }
|
if !quiet { worker.log("starting apt-get update") }
|
||||||
|
|
||||||
// TODO: set proxy /etc/apt/apt.conf.d/76pbsproxy like PVE
|
// TODO: set proxy /etc/apt/apt.conf.d/76pbsproxy like PVE
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::tools::disks::{
|
||||||
};
|
};
|
||||||
use crate::server::WorkerTask;
|
use crate::server::WorkerTask;
|
||||||
|
|
||||||
use crate::api2::types::{UPID_SCHEMA, NODE_SCHEMA, BLOCKDEVICE_NAME_SCHEMA};
|
use crate::api2::types::{Userid, UPID_SCHEMA, NODE_SCHEMA, BLOCKDEVICE_NAME_SCHEMA};
|
||||||
|
|
||||||
pub mod directory;
|
pub mod directory;
|
||||||
pub mod zfs;
|
pub mod zfs;
|
||||||
|
@ -140,7 +140,7 @@ pub fn initialize_disk(
|
||||||
|
|
||||||
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
|
|
||||||
let info = get_disk_usage_info(&disk, true)?;
|
let info = get_disk_usage_info(&disk, true)?;
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ pub fn initialize_disk(
|
||||||
}
|
}
|
||||||
|
|
||||||
let upid_str = WorkerTask::new_thread(
|
let upid_str = WorkerTask::new_thread(
|
||||||
"diskinit", Some(disk.clone()), &username.clone(), to_stdout, move |worker|
|
"diskinit", Some(disk.clone()), userid, to_stdout, move |worker|
|
||||||
{
|
{
|
||||||
worker.log(format!("initialize disk {}", disk));
|
worker.log(format!("initialize disk {}", disk));
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ pub fn create_datastore_disk(
|
||||||
|
|
||||||
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
|
|
||||||
let info = get_disk_usage_info(&disk, true)?;
|
let info = get_disk_usage_info(&disk, true)?;
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ pub fn create_datastore_disk(
|
||||||
}
|
}
|
||||||
|
|
||||||
let upid_str = WorkerTask::new_thread(
|
let upid_str = WorkerTask::new_thread(
|
||||||
"dircreate", Some(name.clone()), &username.clone(), to_stdout, move |worker|
|
"dircreate", Some(name.clone()), userid, to_stdout, move |worker|
|
||||||
{
|
{
|
||||||
worker.log(format!("create datastore '{}' on disk {}", name, disk));
|
worker.log(format!("create datastore '{}' on disk {}", name, disk));
|
||||||
|
|
||||||
|
|
|
@ -254,7 +254,7 @@ pub fn create_zpool(
|
||||||
|
|
||||||
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
|
|
||||||
let add_datastore = add_datastore.unwrap_or(false);
|
let add_datastore = add_datastore.unwrap_or(false);
|
||||||
|
|
||||||
|
@ -314,7 +314,7 @@ pub fn create_zpool(
|
||||||
}
|
}
|
||||||
|
|
||||||
let upid_str = WorkerTask::new_thread(
|
let upid_str = WorkerTask::new_thread(
|
||||||
"zfscreate", Some(name.clone()), &username.clone(), to_stdout, move |worker|
|
"zfscreate", Some(name.clone()), userid, to_stdout, move |worker|
|
||||||
{
|
{
|
||||||
worker.log(format!("create {:?} zpool '{}' on devices '{}'", raidlevel, name, devices_text));
|
worker.log(format!("create {:?} zpool '{}' on devices '{}'", raidlevel, name, devices_text));
|
||||||
|
|
||||||
|
|
|
@ -625,9 +625,9 @@ pub async fn reload_network_config(
|
||||||
|
|
||||||
network::assert_ifupdown2_installed()?;
|
network::assert_ifupdown2_installed()?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
|
|
||||||
let upid_str = WorkerTask::spawn("srvreload", Some(String::from("networking")), &username.clone(), true, |_worker| async {
|
let upid_str = WorkerTask::spawn("srvreload", Some(String::from("networking")), userid, true, |_worker| async {
|
||||||
|
|
||||||
let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME);
|
let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::io::{BufRead, BufReader};
|
||||||
use anyhow::{Error};
|
use anyhow::{Error};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use proxmox::api::{api, Router, RpcEnvironment, Permission, UserInformation};
|
use proxmox::api::{api, Router, RpcEnvironment, Permission};
|
||||||
use proxmox::api::router::SubdirMap;
|
use proxmox::api::router::SubdirMap;
|
||||||
use proxmox::{identity, list_subdirs_api_method, sortable};
|
use proxmox::{identity, list_subdirs_api_method, sortable};
|
||||||
|
|
||||||
|
@ -84,11 +84,11 @@ async fn get_task_status(
|
||||||
|
|
||||||
let upid = extract_upid(¶m)?;
|
let upid = extract_upid(¶m)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
|
|
||||||
if username != upid.username {
|
if userid != upid.userid {
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
user_info.check_privs(&username, &["system", "tasks"], PRIV_SYS_AUDIT, false)?;
|
user_info.check_privs(&userid, &["system", "tasks"], PRIV_SYS_AUDIT, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = json!({
|
let mut result = json!({
|
||||||
|
@ -99,7 +99,7 @@ async fn get_task_status(
|
||||||
"starttime": upid.starttime,
|
"starttime": upid.starttime,
|
||||||
"type": upid.worker_type,
|
"type": upid.worker_type,
|
||||||
"id": upid.worker_id,
|
"id": upid.worker_id,
|
||||||
"user": upid.username,
|
"user": upid.userid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if crate::server::worker_is_active(&upid).await? {
|
if crate::server::worker_is_active(&upid).await? {
|
||||||
|
@ -161,11 +161,11 @@ async fn read_task_log(
|
||||||
|
|
||||||
let upid = extract_upid(¶m)?;
|
let upid = extract_upid(¶m)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
|
|
||||||
if username != upid.username {
|
if userid != upid.userid {
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
user_info.check_privs(&username, &["system", "tasks"], PRIV_SYS_AUDIT, false)?;
|
user_info.check_privs(&userid, &["system", "tasks"], PRIV_SYS_AUDIT, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let test_status = param["test-status"].as_bool().unwrap_or(false);
|
let test_status = param["test-status"].as_bool().unwrap_or(false);
|
||||||
|
@ -234,11 +234,11 @@ fn stop_task(
|
||||||
|
|
||||||
let upid = extract_upid(¶m)?;
|
let upid = extract_upid(¶m)?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
|
|
||||||
if username != upid.username {
|
if userid != upid.userid {
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
user_info.check_privs(&username, &["system", "tasks"], PRIV_SYS_MODIFY, false)?;
|
user_info.check_privs(&userid, &["system", "tasks"], PRIV_SYS_MODIFY, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
server::abort_worker_async(upid);
|
server::abort_worker_async(upid);
|
||||||
|
@ -307,9 +307,9 @@ pub fn list_tasks(
|
||||||
mut rpcenv: &mut dyn RpcEnvironment,
|
mut rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Vec<TaskListItem>, Error> {
|
) -> Result<Vec<TaskListItem>, Error> {
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["system", "tasks"]);
|
let user_privs = user_info.lookup_privs(&userid, &["system", "tasks"]);
|
||||||
|
|
||||||
let list_all = (user_privs & PRIV_SYS_AUDIT) != 0;
|
let list_all = (user_privs & PRIV_SYS_AUDIT) != 0;
|
||||||
|
|
||||||
|
@ -324,11 +324,11 @@ pub fn list_tasks(
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
for info in list {
|
for info in list {
|
||||||
if !list_all && info.upid.username != username { continue; }
|
if !list_all && info.upid.userid != userid { continue; }
|
||||||
|
|
||||||
|
|
||||||
if let Some(username) = userfilter {
|
if let Some(userid) = userfilter {
|
||||||
if !info.upid.username.contains(username) { continue; }
|
if !info.upid.userid.as_str().contains(userid) { continue; }
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(store) = store {
|
if let Some(store) = store {
|
||||||
|
|
|
@ -18,7 +18,7 @@ use crate::config::{
|
||||||
|
|
||||||
|
|
||||||
pub fn check_pull_privs(
|
pub fn check_pull_privs(
|
||||||
username: &str,
|
userid: &Userid,
|
||||||
store: &str,
|
store: &str,
|
||||||
remote: &str,
|
remote: &str,
|
||||||
remote_store: &str,
|
remote_store: &str,
|
||||||
|
@ -27,11 +27,11 @@ pub fn check_pull_privs(
|
||||||
|
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
|
|
||||||
user_info.check_privs(username, &["datastore", store], PRIV_DATASTORE_BACKUP, false)?;
|
user_info.check_privs(userid, &["datastore", store], PRIV_DATASTORE_BACKUP, false)?;
|
||||||
user_info.check_privs(username, &["remote", remote, remote_store], PRIV_REMOTE_READ, false)?;
|
user_info.check_privs(userid, &["remote", remote, remote_store], PRIV_REMOTE_READ, false)?;
|
||||||
|
|
||||||
if delete {
|
if delete {
|
||||||
user_info.check_privs(username, &["datastore", store], PRIV_DATASTORE_PRUNE, false)?;
|
user_info.check_privs(userid, &["datastore", store], PRIV_DATASTORE_PRUNE, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -99,19 +99,19 @@ async fn pull (
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let delete = remove_vanished.unwrap_or(true);
|
let delete = remove_vanished.unwrap_or(true);
|
||||||
|
|
||||||
check_pull_privs(&username, &store, &remote, &remote_store, delete)?;
|
check_pull_privs(&userid, &store, &remote, &remote_store, delete)?;
|
||||||
|
|
||||||
let (client, src_repo, tgt_store) = get_pull_parameters(&store, &remote, &remote_store).await?;
|
let (client, src_repo, tgt_store) = get_pull_parameters(&store, &remote, &remote_store).await?;
|
||||||
|
|
||||||
// fixme: set to_stdout to false?
|
// fixme: set to_stdout to false?
|
||||||
let upid_str = WorkerTask::spawn("sync", Some(store.clone()), &username.clone(), true, move |worker| async move {
|
let upid_str = WorkerTask::spawn("sync", Some(store.clone()), userid.clone(), true, move |worker| async move {
|
||||||
|
|
||||||
worker.log(format!("sync datastore '{}' start", store));
|
worker.log(format!("sync datastore '{}' start", store));
|
||||||
|
|
||||||
pull_store(&worker, &client, &src_repo, tgt_store.clone(), delete, username).await?;
|
pull_store(&worker, &client, &src_repo, tgt_store.clone(), delete, userid).await?;
|
||||||
|
|
||||||
worker.log(format!("sync datastore '{}' end", store));
|
worker.log(format!("sync datastore '{}' end", store));
|
||||||
|
|
||||||
|
|
|
@ -55,11 +55,11 @@ fn upgrade_to_backup_reader_protocol(
|
||||||
async move {
|
async move {
|
||||||
let debug = param["debug"].as_bool().unwrap_or(false);
|
let debug = param["debug"].as_bool().unwrap_or(false);
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let store = tools::required_string_param(¶m, "store")?.to_owned();
|
let store = tools::required_string_param(¶m, "store")?.to_owned();
|
||||||
|
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
user_info.check_privs(&username, &["datastore", &store], PRIV_DATASTORE_READ, false)?;
|
user_info.check_privs(&userid, &["datastore", &store], PRIV_DATASTORE_READ, false)?;
|
||||||
|
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
|
@ -90,9 +90,14 @@ fn upgrade_to_backup_reader_protocol(
|
||||||
|
|
||||||
let worker_id = format!("{}_{}_{}_{:08X}", store, backup_type, backup_id, backup_dir.backup_time().timestamp());
|
let worker_id = format!("{}_{}_{}_{:08X}", store, backup_type, backup_id, backup_dir.backup_time().timestamp());
|
||||||
|
|
||||||
WorkerTask::spawn("reader", Some(worker_id), &username.clone(), true, move |worker| {
|
WorkerTask::spawn("reader", Some(worker_id), userid.clone(), true, move |worker| {
|
||||||
let mut env = ReaderEnvironment::new(
|
let mut env = ReaderEnvironment::new(
|
||||||
env_type, username.clone(), worker.clone(), datastore, backup_dir);
|
env_type,
|
||||||
|
userid,
|
||||||
|
worker.clone(),
|
||||||
|
datastore,
|
||||||
|
backup_dir,
|
||||||
|
);
|
||||||
|
|
||||||
env.debug = debug;
|
env.debug = debug;
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,10 @@ use serde_json::{json, Value};
|
||||||
|
|
||||||
use proxmox::api::{RpcEnvironment, RpcEnvironmentType};
|
use proxmox::api::{RpcEnvironment, RpcEnvironmentType};
|
||||||
|
|
||||||
use crate::server::WorkerTask;
|
use crate::api2::types::Userid;
|
||||||
use crate::backup::*;
|
use crate::backup::*;
|
||||||
use crate::server::formatter::*;
|
use crate::server::formatter::*;
|
||||||
|
use crate::server::WorkerTask;
|
||||||
|
|
||||||
//use proxmox::tools;
|
//use proxmox::tools;
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ use crate::server::formatter::*;
|
||||||
pub struct ReaderEnvironment {
|
pub struct ReaderEnvironment {
|
||||||
env_type: RpcEnvironmentType,
|
env_type: RpcEnvironmentType,
|
||||||
result_attributes: Value,
|
result_attributes: Value,
|
||||||
user: String,
|
user: Userid,
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub formatter: &'static OutputFormatter,
|
pub formatter: &'static OutputFormatter,
|
||||||
pub worker: Arc<WorkerTask>,
|
pub worker: Arc<WorkerTask>,
|
||||||
|
@ -28,7 +29,7 @@ pub struct ReaderEnvironment {
|
||||||
impl ReaderEnvironment {
|
impl ReaderEnvironment {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
env_type: RpcEnvironmentType,
|
env_type: RpcEnvironmentType,
|
||||||
user: String,
|
user: Userid,
|
||||||
worker: Arc<WorkerTask>,
|
worker: Arc<WorkerTask>,
|
||||||
datastore: Arc<DataStore>,
|
datastore: Arc<DataStore>,
|
||||||
backup_dir: BackupDir,
|
backup_dir: BackupDir,
|
||||||
|
@ -77,7 +78,7 @@ impl RpcEnvironment for ReaderEnvironment {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_user(&self) -> Option<String> {
|
fn get_user(&self) -> Option<String> {
|
||||||
Some(self.user.clone())
|
Some(self.user.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,14 @@ use proxmox::api::{
|
||||||
Router,
|
Router,
|
||||||
RpcEnvironment,
|
RpcEnvironment,
|
||||||
SubdirMap,
|
SubdirMap,
|
||||||
UserInformation,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::api2::types::{
|
use crate::api2::types::{
|
||||||
DATASTORE_SCHEMA,
|
DATASTORE_SCHEMA,
|
||||||
RRDMode,
|
RRDMode,
|
||||||
RRDTimeFrameResolution,
|
RRDTimeFrameResolution,
|
||||||
TaskListItem
|
TaskListItem,
|
||||||
|
Userid,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::server;
|
use crate::server;
|
||||||
|
@ -84,13 +84,13 @@ fn datastore_status(
|
||||||
|
|
||||||
let (config, _digest) = datastore::config()?;
|
let (config, _digest) = datastore::config()?;
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
|
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
|
|
||||||
for (store, (_, _)) in &config.sections {
|
for (store, (_, _)) in &config.sections {
|
||||||
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
|
||||||
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
|
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
|
||||||
if !allowed {
|
if !allowed {
|
||||||
continue;
|
continue;
|
||||||
|
@ -202,9 +202,9 @@ pub fn list_tasks(
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Vec<TaskListItem>, Error> {
|
) -> Result<Vec<TaskListItem>, Error> {
|
||||||
|
|
||||||
let username = rpcenv.get_user().unwrap();
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
let user_privs = user_info.lookup_privs(&username, &["system", "tasks"]);
|
let user_privs = user_info.lookup_privs(&userid, &["system", "tasks"]);
|
||||||
|
|
||||||
let list_all = (user_privs & PRIV_SYS_AUDIT) != 0;
|
let list_all = (user_privs & PRIV_SYS_AUDIT) != 0;
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ pub fn list_tasks(
|
||||||
let list: Vec<TaskListItem> = server::read_task_list()?
|
let list: Vec<TaskListItem> = server::read_task_list()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(TaskListItem::from)
|
.map(TaskListItem::from)
|
||||||
.filter(|entry| list_all || entry.user == username)
|
.filter(|entry| list_all || entry.user == userid)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(list.into())
|
Ok(list.into())
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
//! Macros exported from api2::types.
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") }
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::{bail};
|
use anyhow::bail;
|
||||||
use ::serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use proxmox::api::{api, schema::*};
|
use proxmox::api::{api, schema::*};
|
||||||
use proxmox::const_regex;
|
use proxmox::const_regex;
|
||||||
|
@ -7,6 +7,16 @@ use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
|
||||||
|
|
||||||
use crate::backup::CryptMode;
|
use crate::backup::CryptMode;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod userid;
|
||||||
|
pub use userid::{Realm, RealmRef};
|
||||||
|
pub use userid::{Username, UsernameRef};
|
||||||
|
pub use userid::Userid;
|
||||||
|
pub use userid::PROXMOX_GROUP_ID_SCHEMA;
|
||||||
|
|
||||||
// File names: may not contain slashes, may not start with "."
|
// File names: may not contain slashes, may not start with "."
|
||||||
pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
|
pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
|
||||||
if name.starts_with('.') {
|
if name.starts_with('.') {
|
||||||
|
@ -21,19 +31,6 @@ pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
|
||||||
macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
|
macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
|
||||||
macro_rules! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!())) }
|
macro_rules! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!())) }
|
||||||
|
|
||||||
// we only allow a limited set of characters
|
|
||||||
// colon is not allowed, because we store usernames in
|
|
||||||
// colon separated lists)!
|
|
||||||
// slash is not allowed because it is used as pve API delimiter
|
|
||||||
// also see "man useradd"
|
|
||||||
macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
|
|
||||||
macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
|
|
||||||
|
|
||||||
macro_rules! USER_ID_REGEX_STR { () => (concat!(USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!())) }
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") }
|
|
||||||
|
|
||||||
macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
|
macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
|
||||||
macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
|
macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
|
||||||
|
|
||||||
|
@ -67,12 +64,8 @@ const_regex!{
|
||||||
|
|
||||||
pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|", IPRE!(), r"$");
|
pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|", IPRE!(), r"$");
|
||||||
|
|
||||||
pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_ID_REGEX_STR!(), r"$");
|
|
||||||
|
|
||||||
pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|", IPRE!() ,"):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$");
|
pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|", IPRE!() ,"):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$");
|
||||||
|
|
||||||
pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$");
|
|
||||||
|
|
||||||
pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
|
pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
|
||||||
|
|
||||||
pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
|
pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
|
||||||
|
@ -115,12 +108,6 @@ pub const DNS_NAME_FORMAT: ApiStringFormat =
|
||||||
pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
|
pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
|
||||||
ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
|
ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
|
||||||
|
|
||||||
pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
|
|
||||||
ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
|
|
||||||
|
|
||||||
pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
|
|
||||||
ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
|
|
||||||
|
|
||||||
pub const PASSWORD_FORMAT: ApiStringFormat =
|
pub const PASSWORD_FORMAT: ApiStringFormat =
|
||||||
ApiStringFormat::Pattern(&PASSWORD_REGEX);
|
ApiStringFormat::Pattern(&PASSWORD_REGEX);
|
||||||
|
|
||||||
|
@ -343,24 +330,6 @@ pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP addr
|
||||||
.format(&DNS_NAME_OR_IP_FORMAT)
|
.format(&DNS_NAME_OR_IP_FORMAT)
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = StringSchema::new("Authentication domain ID")
|
|
||||||
.format(&PROXMOX_SAFE_ID_FORMAT)
|
|
||||||
.min_length(3)
|
|
||||||
.max_length(32)
|
|
||||||
.schema();
|
|
||||||
|
|
||||||
pub const PROXMOX_USER_ID_SCHEMA: Schema = StringSchema::new("User ID")
|
|
||||||
.format(&PROXMOX_USER_ID_FORMAT)
|
|
||||||
.min_length(3)
|
|
||||||
.max_length(64)
|
|
||||||
.schema();
|
|
||||||
|
|
||||||
pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID")
|
|
||||||
.format(&PROXMOX_GROUP_ID_FORMAT)
|
|
||||||
.min_length(3)
|
|
||||||
.max_length(64)
|
|
||||||
.schema();
|
|
||||||
|
|
||||||
pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
|
pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
|
||||||
.format(&BLOCKDEVICE_NAME_FORMAT)
|
.format(&BLOCKDEVICE_NAME_FORMAT)
|
||||||
.min_length(3)
|
.min_length(3)
|
||||||
|
@ -388,6 +357,10 @@ pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name
|
||||||
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
owner: {
|
||||||
|
type: Userid,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -403,7 +376,7 @@ pub struct GroupListItem {
|
||||||
pub files: Vec<String>,
|
pub files: Vec<String>,
|
||||||
/// The owner of group
|
/// The owner of group
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub owner: Option<String>,
|
pub owner: Option<Userid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
|
@ -422,6 +395,10 @@ pub struct GroupListItem {
|
||||||
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
owner: {
|
||||||
|
type: Userid,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -441,7 +418,7 @@ pub struct SnapshotListItem {
|
||||||
pub size: Option<u64>,
|
pub size: Option<u64>,
|
||||||
/// The owner of the snapshots group
|
/// The owner of the snapshots group
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub owner: Option<String>,
|
pub owner: Option<Userid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
|
@ -584,7 +561,8 @@ pub struct StorageStatus {
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
properties: {
|
properties: {
|
||||||
"upid": { schema: UPID_SCHEMA },
|
upid: { schema: UPID_SCHEMA },
|
||||||
|
user: { type: Userid },
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -604,7 +582,7 @@ pub struct TaskListItem {
|
||||||
/// Worker ID (arbitrary ASCII string)
|
/// Worker ID (arbitrary ASCII string)
|
||||||
pub worker_id: Option<String>,
|
pub worker_id: Option<String>,
|
||||||
/// The user who started the task
|
/// The user who started the task
|
||||||
pub user: String,
|
pub user: Userid,
|
||||||
/// The task end time (Epoch)
|
/// The task end time (Epoch)
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub endtime: Option<i64>,
|
pub endtime: Option<i64>,
|
||||||
|
@ -627,7 +605,7 @@ impl From<crate::server::TaskListInfo> for TaskListItem {
|
||||||
starttime: info.upid.starttime,
|
starttime: info.upid.starttime,
|
||||||
worker_type: info.upid.worker_type,
|
worker_type: info.upid.worker_type,
|
||||||
worker_id: info.upid.worker_id,
|
worker_id: info.upid.worker_id,
|
||||||
user: info.upid.username,
|
user: info.upid.userid,
|
||||||
endtime,
|
endtime,
|
||||||
status,
|
status,
|
||||||
}
|
}
|
||||||
|
@ -893,9 +871,6 @@ fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
|
fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
|
||||||
|
|
||||||
let schema = PROXMOX_USER_ID_SCHEMA;
|
|
||||||
|
|
||||||
let invalid_user_ids = [
|
let invalid_user_ids = [
|
||||||
"x", // too short
|
"x", // too short
|
||||||
"xx", // too short
|
"xx", // too short
|
||||||
|
@ -909,7 +884,7 @@ fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
|
||||||
];
|
];
|
||||||
|
|
||||||
for name in invalid_user_ids.iter() {
|
for name in invalid_user_ids.iter() {
|
||||||
if let Ok(_) = parse_simple_value(name, &schema) {
|
if let Ok(_) = parse_simple_value(name, &Userid::API_SCHEMA) {
|
||||||
bail!("test userid '{}' failed - got Ok() while exception an error.", name);
|
bail!("test userid '{}' failed - got Ok() while exception an error.", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -923,7 +898,7 @@ fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
|
||||||
];
|
];
|
||||||
|
|
||||||
for name in valid_user_ids.iter() {
|
for name in valid_user_ids.iter() {
|
||||||
let v = match parse_simple_value(name, &schema) {
|
let v = match parse_simple_value(name, &Userid::API_SCHEMA) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
bail!("unable to parse userid '{}' - {}", name, err);
|
bail!("unable to parse userid '{}' - {}", name, err);
|
||||||
|
|
|
@ -0,0 +1,376 @@
|
||||||
|
//! Types for user handling.
|
||||||
|
//!
|
||||||
|
//! We have [`Username`]s and [`Realm`]s. To uniquely identify a user, they must be combined into a [`Userid`].
|
||||||
|
//!
|
||||||
|
//! Since they're all string types, they're organized as follows:
|
||||||
|
//!
|
||||||
|
//! * [`Username`]: an owned user name. Internally a `String`.
|
||||||
|
//! * [`UsernameRef`]: a borrowed user name. Pairs with a `Username` the same way a `str` pairs
|
||||||
|
//! with `String`, meaning you can only make references to it.
|
||||||
|
//! * [`Realm`]: an owned realm (`String` equivalent).
|
||||||
|
//! * [`RealmRef`]: a borrowed realm (`str` equivalent).
|
||||||
|
//! * [`Userid`]: an owned user id (`"user@realm"`). Note that this does not have a separte
|
||||||
|
//! borrowed type.
|
||||||
|
//!
|
||||||
|
//! Note that `Username`s are not unique, therefore they do not implement `Eq` and cannot be
|
||||||
|
//! compared directly. If a direct comparison is really required, they can be compared as strings
|
||||||
|
//! via the `as_str()` method. [`Realm`]s and [`Userid`]s on the other hand can be compared with
|
||||||
|
//! each other, as in those two cases the comparison has meaning.
|
||||||
|
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use anyhow::{bail, format_err, Error};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox::api::api;
|
||||||
|
use proxmox::api::schema::{ApiStringFormat, Schema, StringSchema};
|
||||||
|
use proxmox::const_regex;
|
||||||
|
|
||||||
|
// we only allow a limited set of characters
|
||||||
|
// colon is not allowed, because we store usernames in
|
||||||
|
// colon separated lists)!
|
||||||
|
// slash is not allowed because it is used as pve API delimiter
|
||||||
|
// also see "man useradd"
|
||||||
|
macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
|
||||||
|
macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
|
||||||
|
macro_rules! USER_ID_REGEX_STR { () => (concat!(USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!())) }
|
||||||
|
|
||||||
|
const_regex! {
|
||||||
|
pub PROXMOX_USER_NAME_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"$");
|
||||||
|
pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_ID_REGEX_STR!(), r"$");
|
||||||
|
pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const PROXMOX_USER_NAME_FORMAT: ApiStringFormat =
|
||||||
|
ApiStringFormat::Pattern(&PROXMOX_USER_NAME_REGEX);
|
||||||
|
|
||||||
|
pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
|
||||||
|
ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
|
||||||
|
|
||||||
|
pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
|
||||||
|
ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
|
||||||
|
|
||||||
|
pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID")
|
||||||
|
.format(&PROXMOX_GROUP_ID_FORMAT)
|
||||||
|
.min_length(3)
|
||||||
|
.max_length(64)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const PROXMOX_AUTH_REALM_STRING_SCHEMA: StringSchema =
|
||||||
|
StringSchema::new("Authentication domain ID")
|
||||||
|
.format(&super::PROXMOX_SAFE_ID_FORMAT)
|
||||||
|
.min_length(3)
|
||||||
|
.max_length(32);
|
||||||
|
pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = PROXMOX_AUTH_REALM_STRING_SCHEMA.schema();
|
||||||
|
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
type: String,
|
||||||
|
format: &PROXMOX_USER_NAME_FORMAT,
|
||||||
|
)]
|
||||||
|
/// The user name part of a user id.
|
||||||
|
///
|
||||||
|
/// This alone does NOT uniquely identify the user and therefore does not implement `Eq`. In order
|
||||||
|
/// to compare user names directly, they need to be explicitly compared as strings by calling
|
||||||
|
/// `.as_str()`.
|
||||||
|
#[derive(Clone, Debug, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct Username(String);
|
||||||
|
|
||||||
|
/// A reference to a user name part of a user id. This alone does NOT uniquely identify the user.
|
||||||
|
///
|
||||||
|
/// This is like a `str` to the `String` of a [`Username`].
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct UsernameRef(str);
|
||||||
|
|
||||||
|
impl UsernameRef {
|
||||||
|
fn new(s: &str) -> &Self {
|
||||||
|
unsafe { &*(s as *const str as *const UsernameRef) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Username {
|
||||||
|
type Target = UsernameRef;
|
||||||
|
|
||||||
|
fn deref(&self) -> &UsernameRef {
|
||||||
|
self.borrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<UsernameRef> for Username {
|
||||||
|
fn borrow(&self) -> &UsernameRef {
|
||||||
|
UsernameRef::new(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<UsernameRef> for Username {
|
||||||
|
fn as_ref(&self) -> &UsernameRef {
|
||||||
|
UsernameRef::new(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToOwned for UsernameRef {
|
||||||
|
type Owned = Username;
|
||||||
|
|
||||||
|
fn to_owned(&self) -> Self::Owned {
|
||||||
|
Username(self.0.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Username {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(s: String) -> Result<Self, Error> {
|
||||||
|
if !PROXMOX_USER_NAME_REGEX.is_match(&s) {
|
||||||
|
bail!("invalid user name");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for &'a UsernameRef {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(s: &'a str) -> Result<&'a UsernameRef, Error> {
|
||||||
|
if !PROXMOX_USER_NAME_REGEX.is_match(s) {
|
||||||
|
bail!("invalid name in user id");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(UsernameRef::new(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(schema: PROXMOX_AUTH_REALM_SCHEMA)]
|
||||||
|
/// An authentication realm.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct Realm(String);
|
||||||
|
|
||||||
|
/// A reference to an authentication realm.
|
||||||
|
///
|
||||||
|
/// This is like a `str` to the `String` of a `Realm`.
|
||||||
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
||||||
|
pub struct RealmRef(str);
|
||||||
|
|
||||||
|
impl RealmRef {
|
||||||
|
fn new(s: &str) -> &Self {
|
||||||
|
unsafe { &*(s as *const str as *const RealmRef) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Realm {
|
||||||
|
type Target = RealmRef;
|
||||||
|
|
||||||
|
fn deref(&self) -> &RealmRef {
|
||||||
|
self.borrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<RealmRef> for Realm {
|
||||||
|
fn borrow(&self) -> &RealmRef {
|
||||||
|
RealmRef::new(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<RealmRef> for Realm {
|
||||||
|
fn as_ref(&self) -> &RealmRef {
|
||||||
|
RealmRef::new(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToOwned for RealmRef {
|
||||||
|
type Owned = Realm;
|
||||||
|
|
||||||
|
fn to_owned(&self) -> Self::Owned {
|
||||||
|
Realm(self.0.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Realm {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(s: String) -> Result<Self, Error> {
|
||||||
|
PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(&s)
|
||||||
|
.map_err(|_| format_err!("invalid realm"))?;
|
||||||
|
|
||||||
|
Ok(Self(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for &'a RealmRef {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(s: &'a str) -> Result<&'a RealmRef, Error> {
|
||||||
|
PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(s)
|
||||||
|
.map_err(|_| format_err!("invalid realm"))?;
|
||||||
|
|
||||||
|
Ok(RealmRef::new(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for Realm {
|
||||||
|
fn eq(&self, rhs: &str) -> bool {
|
||||||
|
self.0 == rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<&str> for Realm {
|
||||||
|
fn eq(&self, rhs: &&str) -> bool {
|
||||||
|
self.0 == *rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for RealmRef {
|
||||||
|
fn eq(&self, rhs: &str) -> bool {
|
||||||
|
self.0 == *rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<&str> for RealmRef {
|
||||||
|
fn eq(&self, rhs: &&str) -> bool {
|
||||||
|
self.0 == **rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A complete user id consting of a user name and a realm.
|
||||||
|
#[derive(Clone, Debug, Hash)]
|
||||||
|
pub struct Userid {
|
||||||
|
data: String,
|
||||||
|
name_len: usize,
|
||||||
|
//name: Username,
|
||||||
|
//realm: Realm,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Userid {
|
||||||
|
pub const API_SCHEMA: Schema = StringSchema::new("User ID")
|
||||||
|
.format(&PROXMOX_USER_ID_FORMAT)
|
||||||
|
.min_length(3)
|
||||||
|
.max_length(64)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
const fn new(data: String, name_len: usize) -> Self {
|
||||||
|
Self { data, name_len }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &UsernameRef {
|
||||||
|
UsernameRef::new(&self.data[..self.name_len])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn realm(&self) -> &RealmRef {
|
||||||
|
RealmRef::new(&self.data[(self.name_len + 1)..])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the "backup@pam" user id.
|
||||||
|
pub fn backup_userid() -> &'static Self {
|
||||||
|
&*BACKUP_USERID
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the "root@pam" user id.
|
||||||
|
pub fn root_userid() -> &'static Self {
|
||||||
|
&*ROOT_USERID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref BACKUP_USERID: Userid = Userid::new("backup@pam".to_string(), 6);
|
||||||
|
pub static ref ROOT_USERID: Userid = Userid::new("root@pam".to_string(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Userid {}
|
||||||
|
|
||||||
|
impl PartialEq for Userid {
|
||||||
|
fn eq(&self, rhs: &Self) -> bool {
|
||||||
|
self.data == rhs.data && self.name_len == rhs.name_len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(Username, Realm)> for Userid {
|
||||||
|
fn from(parts: (Username, Realm)) -> Self {
|
||||||
|
Self::from((parts.0.as_ref(), parts.1.as_ref()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&UsernameRef, &RealmRef)> for Userid {
|
||||||
|
fn from(parts: (&UsernameRef, &RealmRef)) -> Self {
|
||||||
|
let data = format!("{}@{}", parts.0.as_str(), parts.1.as_str());
|
||||||
|
let name_len = parts.0.as_str().len();
|
||||||
|
Self { data, name_len }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Userid {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.data.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Userid {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(id: &str) -> Result<Self, Error> {
|
||||||
|
let (name, realm) = match id.as_bytes().iter().rposition(|&b| b == b'@') {
|
||||||
|
Some(pos) => (&id[..pos], &id[(pos + 1)..]),
|
||||||
|
None => bail!("not a valid user id"),
|
||||||
|
};
|
||||||
|
|
||||||
|
PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(realm)
|
||||||
|
.map_err(|_| format_err!("invalid realm in user id"))?;
|
||||||
|
|
||||||
|
Ok(Self::from((UsernameRef::new(name), RealmRef::new(realm))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Userid {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(data: String) -> Result<Self, Error> {
|
||||||
|
let name_len = data
|
||||||
|
.as_bytes()
|
||||||
|
.iter()
|
||||||
|
.rposition(|&b| b == b'@')
|
||||||
|
.ok_or_else(|| format_err!("not a valid user id"))?;
|
||||||
|
|
||||||
|
PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(&data[(name_len + 1)..])
|
||||||
|
.map_err(|_| format_err!("invalid realm in user id"))?;
|
||||||
|
|
||||||
|
Ok(Self { data, name_len })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for Userid {
|
||||||
|
fn eq(&self, rhs: &str) -> bool {
|
||||||
|
rhs.len() > self.name_len + 2 // make sure range access below is allowed
|
||||||
|
&& rhs.starts_with(self.name().as_str())
|
||||||
|
&& rhs.as_bytes()[self.name_len] == b'@'
|
||||||
|
&& &rhs[(self.name_len + 1)..] == self.realm().as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<&str> for Userid {
|
||||||
|
fn eq(&self, rhs: &&str) -> bool {
|
||||||
|
*self == **rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<String> for Userid {
|
||||||
|
fn eq(&self, rhs: &String) -> bool {
|
||||||
|
self == rhs.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proxmox::forward_deserialize_to_from_str!(Userid);
|
||||||
|
proxmox::forward_serialize_to_display!(Userid);
|
67
src/auth.rs
67
src/auth.rs
|
@ -10,39 +10,54 @@ use base64;
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, format_err, Error};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::api2::types::{Userid, UsernameRef, RealmRef};
|
||||||
|
|
||||||
pub trait ProxmoxAuthenticator {
|
pub trait ProxmoxAuthenticator {
|
||||||
fn authenticate_user(&self, username: &str, password: &str) -> Result<(), Error>;
|
fn authenticate_user(&self, username: &UsernameRef, password: &str) -> Result<(), Error>;
|
||||||
fn store_password(&self, username: &str, password: &str) -> Result<(), Error>;
|
fn store_password(&self, username: &UsernameRef, password: &str) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PAM();
|
pub struct PAM();
|
||||||
|
|
||||||
impl ProxmoxAuthenticator for PAM {
|
impl ProxmoxAuthenticator for PAM {
|
||||||
|
|
||||||
fn authenticate_user(&self, username: &str, password: &str) -> Result<(), Error> {
|
fn authenticate_user(&self, username: &UsernameRef, password: &str) -> Result<(), Error> {
|
||||||
let mut auth = pam::Authenticator::with_password("proxmox-backup-auth").unwrap();
|
let mut auth = pam::Authenticator::with_password("proxmox-backup-auth").unwrap();
|
||||||
auth.get_handler().set_credentials(username, password);
|
auth.get_handler().set_credentials(username.as_str(), password);
|
||||||
auth.authenticate()?;
|
auth.authenticate()?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_password(&self, username: &str, password: &str) -> Result<(), Error> {
|
fn store_password(&self, username: &UsernameRef, password: &str) -> Result<(), Error> {
|
||||||
let mut child = Command::new("passwd")
|
let mut child = Command::new("passwd")
|
||||||
.arg(username)
|
.arg(username.as_str())
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.or_else(|err| Err(format_err!("unable to set password for '{}' - execute passwd failed: {}", username, err)))?;
|
.map_err(|err| format_err!(
|
||||||
|
"unable to set password for '{}' - execute passwd failed: {}",
|
||||||
|
username.as_str(),
|
||||||
|
err,
|
||||||
|
))?;
|
||||||
|
|
||||||
// Note: passwd reads password twice from stdin (for verify)
|
// Note: passwd reads password twice from stdin (for verify)
|
||||||
writeln!(child.stdin.as_mut().unwrap(), "{}\n{}", password, password)?;
|
writeln!(child.stdin.as_mut().unwrap(), "{}\n{}", password, password)?;
|
||||||
|
|
||||||
let output = child.wait_with_output()
|
let output = child
|
||||||
.or_else(|err| Err(format_err!("unable to set password for '{}' - wait failed: {}", username, err)))?;
|
.wait_with_output()
|
||||||
|
.map_err(|err| format_err!(
|
||||||
|
"unable to set password for '{}' - wait failed: {}",
|
||||||
|
username.as_str(),
|
||||||
|
err,
|
||||||
|
))?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
bail!("unable to set password for '{}' - {}", username, String::from_utf8_lossy(&output.stderr));
|
bail!(
|
||||||
|
"unable to set password for '{}' - {}",
|
||||||
|
username.as_str(),
|
||||||
|
String::from_utf8_lossy(&output.stderr),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -90,23 +105,23 @@ pub fn verify_crypt_pw(password: &str, enc_password: &str) -> Result<(), Error>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
const SHADOW_CONFIG_FILENAME: &str = "/etc/proxmox-backup/shadow.json";
|
const SHADOW_CONFIG_FILENAME: &str = configdir!("/shadow.json");
|
||||||
|
|
||||||
impl ProxmoxAuthenticator for PBS {
|
impl ProxmoxAuthenticator for PBS {
|
||||||
|
|
||||||
fn authenticate_user(&self, username: &str, password: &str) -> Result<(), Error> {
|
fn authenticate_user(&self, username: &UsernameRef, password: &str) -> Result<(), Error> {
|
||||||
let data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?;
|
let data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?;
|
||||||
match data[username].as_str() {
|
match data[username.as_str()].as_str() {
|
||||||
None => bail!("no password set"),
|
None => bail!("no password set"),
|
||||||
Some(enc_password) => verify_crypt_pw(password, enc_password)?,
|
Some(enc_password) => verify_crypt_pw(password, enc_password)?,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_password(&self, username: &str, password: &str) -> Result<(), Error> {
|
fn store_password(&self, username: &UsernameRef, password: &str) -> Result<(), Error> {
|
||||||
let enc_password = encrypt_pw(password)?;
|
let enc_password = encrypt_pw(password)?;
|
||||||
let mut data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?;
|
let mut data = proxmox::tools::fs::file_get_json(SHADOW_CONFIG_FILENAME, Some(json!({})))?;
|
||||||
data[username] = enc_password.into();
|
data[username.as_str()] = enc_password.into();
|
||||||
|
|
||||||
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
|
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
|
||||||
let options = proxmox::tools::fs::CreateOptions::new()
|
let options = proxmox::tools::fs::CreateOptions::new()
|
||||||
|
@ -121,28 +136,18 @@ impl ProxmoxAuthenticator for PBS {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_userid(userid: &str) -> Result<(String, String), Error> {
|
|
||||||
let data: Vec<&str> = userid.rsplitn(2, '@').collect();
|
|
||||||
|
|
||||||
if data.len() != 2 {
|
|
||||||
bail!("userid '{}' has no realm", userid);
|
|
||||||
}
|
|
||||||
Ok((data[1].to_owned(), data[0].to_owned()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lookup the autenticator for the specified realm
|
/// Lookup the autenticator for the specified realm
|
||||||
pub fn lookup_authenticator(realm: &str) -> Result<Box<dyn ProxmoxAuthenticator>, Error> {
|
pub fn lookup_authenticator(realm: &RealmRef) -> Result<Box<dyn ProxmoxAuthenticator>, Error> {
|
||||||
match realm {
|
match realm.as_str() {
|
||||||
"pam" => Ok(Box::new(PAM())),
|
"pam" => Ok(Box::new(PAM())),
|
||||||
"pbs" => Ok(Box::new(PBS())),
|
"pbs" => Ok(Box::new(PBS())),
|
||||||
_ => bail!("unknown realm '{}'", realm),
|
_ => bail!("unknown realm '{}'", realm.as_str()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticate users
|
/// Authenticate users
|
||||||
pub fn authenticate_user(userid: &str, password: &str) -> Result<(), Error> {
|
pub fn authenticate_user(userid: &Userid, password: &str) -> Result<(), Error> {
|
||||||
let (username, realm) = parse_userid(userid)?;
|
|
||||||
|
|
||||||
lookup_authenticator(&realm)?
|
lookup_authenticator(userid.realm())?
|
||||||
.authenticate_user(&username, password)
|
.authenticate_user(userid.name(), password)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,17 @@ use std::path::PathBuf;
|
||||||
use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
|
use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
|
||||||
use proxmox::try_block;
|
use proxmox::try_block;
|
||||||
|
|
||||||
|
use crate::api2::types::Userid;
|
||||||
use crate::tools::epoch_now_u64;
|
use crate::tools::epoch_now_u64;
|
||||||
|
|
||||||
fn compute_csrf_secret_digest(
|
fn compute_csrf_secret_digest(
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
secret: &[u8],
|
secret: &[u8],
|
||||||
username: &str,
|
userid: &Userid,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
|
||||||
let mut hasher = sha::Sha256::new();
|
let mut hasher = sha::Sha256::new();
|
||||||
let data = format!("{:08X}:{}:", timestamp, username);
|
let data = format!("{:08X}:{}:", timestamp, userid);
|
||||||
hasher.update(data.as_bytes());
|
hasher.update(data.as_bytes());
|
||||||
hasher.update(secret);
|
hasher.update(secret);
|
||||||
|
|
||||||
|
@ -28,19 +29,19 @@ fn compute_csrf_secret_digest(
|
||||||
|
|
||||||
pub fn assemble_csrf_prevention_token(
|
pub fn assemble_csrf_prevention_token(
|
||||||
secret: &[u8],
|
secret: &[u8],
|
||||||
username: &str,
|
userid: &Userid,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
|
||||||
let epoch = epoch_now_u64().unwrap() as i64;
|
let epoch = epoch_now_u64().unwrap() as i64;
|
||||||
|
|
||||||
let digest = compute_csrf_secret_digest(epoch, secret, username);
|
let digest = compute_csrf_secret_digest(epoch, secret, userid);
|
||||||
|
|
||||||
format!("{:08X}:{}", epoch, digest)
|
format!("{:08X}:{}", epoch, digest)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_csrf_prevention_token(
|
pub fn verify_csrf_prevention_token(
|
||||||
secret: &[u8],
|
secret: &[u8],
|
||||||
username: &str,
|
userid: &Userid,
|
||||||
token: &str,
|
token: &str,
|
||||||
min_age: i64,
|
min_age: i64,
|
||||||
max_age: i64,
|
max_age: i64,
|
||||||
|
@ -62,7 +63,7 @@ pub fn verify_csrf_prevention_token(
|
||||||
let ttime = i64::from_str_radix(timestamp, 16).
|
let ttime = i64::from_str_radix(timestamp, 16).
|
||||||
map_err(|err| format_err!("timestamp format error - {}", err))?;
|
map_err(|err| format_err!("timestamp format error - {}", err))?;
|
||||||
|
|
||||||
let digest = compute_csrf_secret_digest(ttime, secret, username);
|
let digest = compute_csrf_secret_digest(ttime, secret, userid);
|
||||||
|
|
||||||
if digest != sig {
|
if digest != sig {
|
||||||
bail!("invalid signature.");
|
bail!("invalid signature.");
|
||||||
|
|
|
@ -21,7 +21,7 @@ use super::{DataBlob, ArchiveType, archive_type};
|
||||||
use crate::config::datastore;
|
use crate::config::datastore;
|
||||||
use crate::server::WorkerTask;
|
use crate::server::WorkerTask;
|
||||||
use crate::tools;
|
use crate::tools;
|
||||||
use crate::api2::types::GarbageCollectionStatus;
|
use crate::api2::types::{GarbageCollectionStatus, Userid};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref DATASTORE_MAP: Mutex<HashMap<String, Arc<DataStore>>> = Mutex::new(HashMap::new());
|
static ref DATASTORE_MAP: Mutex<HashMap<String, Arc<DataStore>>> = Mutex::new(HashMap::new());
|
||||||
|
@ -287,16 +287,21 @@ impl DataStore {
|
||||||
/// Returns the backup owner.
|
/// Returns the backup owner.
|
||||||
///
|
///
|
||||||
/// The backup owner is the user who first created the backup group.
|
/// The backup owner is the user who first created the backup group.
|
||||||
pub fn get_owner(&self, backup_group: &BackupGroup) -> Result<String, Error> {
|
pub fn get_owner(&self, backup_group: &BackupGroup) -> Result<Userid, Error> {
|
||||||
let mut full_path = self.base_path();
|
let mut full_path = self.base_path();
|
||||||
full_path.push(backup_group.group_path());
|
full_path.push(backup_group.group_path());
|
||||||
full_path.push("owner");
|
full_path.push("owner");
|
||||||
let owner = proxmox::tools::fs::file_read_firstline(full_path)?;
|
let owner = proxmox::tools::fs::file_read_firstline(full_path)?;
|
||||||
Ok(owner.trim_end().to_string()) // remove trailing newline
|
Ok(owner.trim_end().parse()?) // remove trailing newline
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the backup owner.
|
/// Set the backup owner.
|
||||||
pub fn set_owner(&self, backup_group: &BackupGroup, userid: &str, force: bool) -> Result<(), Error> {
|
pub fn set_owner(
|
||||||
|
&self,
|
||||||
|
backup_group: &BackupGroup,
|
||||||
|
userid: &Userid,
|
||||||
|
force: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let mut path = self.base_path();
|
let mut path = self.base_path();
|
||||||
path.push(backup_group.group_path());
|
path.push(backup_group.group_path());
|
||||||
path.push("owner");
|
path.push("owner");
|
||||||
|
@ -326,8 +331,11 @@ impl DataStore {
|
||||||
/// current owner (instead of setting the owner).
|
/// current owner (instead of setting the owner).
|
||||||
///
|
///
|
||||||
/// This also aquires an exclusive lock on the directory and returns the lock guard.
|
/// This also aquires an exclusive lock on the directory and returns the lock guard.
|
||||||
pub fn create_locked_backup_group(&self, backup_group: &BackupGroup, userid: &str) -> Result<(String, BackupGroupGuard), Error> {
|
pub fn create_locked_backup_group(
|
||||||
|
&self,
|
||||||
|
backup_group: &BackupGroup,
|
||||||
|
userid: &Userid,
|
||||||
|
) -> Result<(Userid, BackupGroupGuard), Error> {
|
||||||
// create intermediate path first:
|
// create intermediate path first:
|
||||||
let base_path = self.base_path();
|
let base_path = self.base_path();
|
||||||
|
|
||||||
|
|
|
@ -184,7 +184,7 @@ pub fn complete_repository(_arg: &str, _param: &HashMap<String, String>) -> Vec<
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect(server: &str, userid: &str) -> Result<HttpClient, Error> {
|
fn connect(server: &str, userid: &Userid) -> Result<HttpClient, Error> {
|
||||||
|
|
||||||
let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
|
let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
|
||||||
|
|
||||||
|
|
|
@ -59,12 +59,17 @@ fn connect() -> Result<HttpClient, Error> {
|
||||||
.verify_cert(false); // not required for connection to localhost
|
.verify_cert(false); // not required for connection to localhost
|
||||||
|
|
||||||
let client = if uid.is_root() {
|
let client = if uid.is_root() {
|
||||||
let ticket = assemble_rsa_ticket(private_auth_key(), "PBS", Some("root@pam"), None)?;
|
let ticket = assemble_rsa_ticket(
|
||||||
|
private_auth_key(),
|
||||||
|
"PBS",
|
||||||
|
Some(Userid::root_userid()),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
options = options.password(Some(ticket));
|
options = options.password(Some(ticket));
|
||||||
HttpClient::new("localhost", "root@pam", options)?
|
HttpClient::new("localhost", Userid::root_userid(), options)?
|
||||||
} else {
|
} else {
|
||||||
options = options.ticket_cache(true).interactive(true);
|
options = options.ticket_cache(true).interactive(true);
|
||||||
HttpClient::new("localhost", "root@pam", options)?
|
HttpClient::new("localhost", Userid::root_userid(), options)?
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(client)
|
Ok(client)
|
||||||
|
|
|
@ -9,6 +9,7 @@ use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
||||||
use proxmox::try_block;
|
use proxmox::try_block;
|
||||||
use proxmox::api::RpcEnvironmentType;
|
use proxmox::api::RpcEnvironmentType;
|
||||||
|
|
||||||
|
use proxmox_backup::api2::types::Userid;
|
||||||
use proxmox_backup::configdir;
|
use proxmox_backup::configdir;
|
||||||
use proxmox_backup::buildcfg;
|
use proxmox_backup::buildcfg;
|
||||||
use proxmox_backup::server;
|
use proxmox_backup::server;
|
||||||
|
@ -318,7 +319,7 @@ async fn schedule_datastore_garbage_collection() {
|
||||||
if let Err(err) = WorkerTask::new_thread(
|
if let Err(err) = WorkerTask::new_thread(
|
||||||
worker_type,
|
worker_type,
|
||||||
Some(store.clone()),
|
Some(store.clone()),
|
||||||
"backup@pam",
|
Userid::backup_userid().clone(),
|
||||||
false,
|
false,
|
||||||
move |worker| {
|
move |worker| {
|
||||||
worker.log(format!("starting garbage collection on store {}", store));
|
worker.log(format!("starting garbage collection on store {}", store));
|
||||||
|
@ -429,7 +430,7 @@ async fn schedule_datastore_prune() {
|
||||||
if let Err(err) = WorkerTask::new_thread(
|
if let Err(err) = WorkerTask::new_thread(
|
||||||
worker_type,
|
worker_type,
|
||||||
Some(store.clone()),
|
Some(store.clone()),
|
||||||
"backup@pam",
|
Userid::backup_userid().clone(),
|
||||||
false,
|
false,
|
||||||
move |worker| {
|
move |worker| {
|
||||||
worker.log(format!("Starting datastore prune on store \"{}\"", store));
|
worker.log(format!("Starting datastore prune on store \"{}\"", store));
|
||||||
|
@ -568,14 +569,14 @@ async fn schedule_datastore_sync_jobs() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let username = String::from("backup@pam");
|
let userid = Userid::backup_userid().clone();
|
||||||
|
|
||||||
let delete = job_config.remove_vanished.unwrap_or(true);
|
let delete = job_config.remove_vanished.unwrap_or(true);
|
||||||
|
|
||||||
if let Err(err) = WorkerTask::spawn(
|
if let Err(err) = WorkerTask::spawn(
|
||||||
worker_type,
|
worker_type,
|
||||||
Some(job_id.clone()),
|
Some(job_id.clone()),
|
||||||
&username.clone(),
|
userid.clone(),
|
||||||
false,
|
false,
|
||||||
move |worker| async move {
|
move |worker| async move {
|
||||||
worker.log(format!("Starting datastore sync job '{}'", job_id));
|
worker.log(format!("Starting datastore sync job '{}'", job_id));
|
||||||
|
@ -594,7 +595,7 @@ async fn schedule_datastore_sync_jobs() {
|
||||||
|
|
||||||
let src_repo = BackupRepository::new(Some(remote.userid), Some(remote.host), job_config.remote_store);
|
let src_repo = BackupRepository::new(Some(remote.userid), Some(remote.host), job_config.remote_store);
|
||||||
|
|
||||||
pull_store(&worker, &client, &src_repo, tgt_store, delete, username).await?;
|
pull_store(&worker, &client, &src_repo, tgt_store, delete, userid).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::{format_err, Error};
|
use anyhow::{format_err, Error};
|
||||||
|
@ -15,7 +16,7 @@ pub const BACKUP_REPO_URL: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_RE
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BackupRepository {
|
pub struct BackupRepository {
|
||||||
/// The user name used for Authentication
|
/// The user name used for Authentication
|
||||||
user: Option<String>,
|
user: Option<Userid>,
|
||||||
/// The host name or IP address
|
/// The host name or IP address
|
||||||
host: Option<String>,
|
host: Option<String>,
|
||||||
/// The name of the datastore
|
/// The name of the datastore
|
||||||
|
@ -24,15 +25,15 @@ pub struct BackupRepository {
|
||||||
|
|
||||||
impl BackupRepository {
|
impl BackupRepository {
|
||||||
|
|
||||||
pub fn new(user: Option<String>, host: Option<String>, store: String) -> Self {
|
pub fn new(user: Option<Userid>, host: Option<String>, store: String) -> Self {
|
||||||
Self { user, host, store }
|
Self { user, host, store }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user(&self) -> &str {
|
pub fn user(&self) -> &Userid {
|
||||||
if let Some(ref user) = self.user {
|
if let Some(ref user) = self.user {
|
||||||
return user;
|
return &user;
|
||||||
}
|
}
|
||||||
"root@pam"
|
Userid::root_userid()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn host(&self) -> &str {
|
pub fn host(&self) -> &str {
|
||||||
|
@ -73,7 +74,7 @@ impl std::str::FromStr for BackupRepository {
|
||||||
.ok_or_else(|| format_err!("unable to parse repository url '{}'", url))?;
|
.ok_or_else(|| format_err!("unable to parse repository url '{}'", url))?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
user: cap.get(1).map(|m| m.as_str().to_owned()),
|
user: cap.get(1).map(|m| Userid::try_from(m.as_str().to_owned())).transpose()?,
|
||||||
host: cap.get(2).map(|m| m.as_str().to_owned()),
|
host: cap.get(2).map(|m| m.as_str().to_owned()),
|
||||||
store: cap[3].to_owned(),
|
store: cap[3].to_owned(),
|
||||||
})
|
})
|
||||||
|
|
|
@ -24,6 +24,7 @@ use proxmox::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::pipe_to_stream::PipeToSendStream;
|
use super::pipe_to_stream::PipeToSendStream;
|
||||||
|
use crate::api2::types::Userid;
|
||||||
use crate::tools::async_io::EitherStream;
|
use crate::tools::async_io::EitherStream;
|
||||||
use crate::tools::{self, BroadcastFuture, DEFAULT_ENCODE_SET};
|
use crate::tools::{self, BroadcastFuture, DEFAULT_ENCODE_SET};
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ pub struct HttpClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete stored ticket data (logout)
|
/// Delete stored ticket data (logout)
|
||||||
pub fn delete_ticket_info(prefix: &str, server: &str, username: &str) -> Result<(), Error> {
|
pub fn delete_ticket_info(prefix: &str, server: &str, username: &Userid) -> Result<(), Error> {
|
||||||
|
|
||||||
let base = BaseDirectories::with_prefix(prefix)?;
|
let base = BaseDirectories::with_prefix(prefix)?;
|
||||||
|
|
||||||
|
@ -116,7 +117,7 @@ pub fn delete_ticket_info(prefix: &str, server: &str, username: &str) -> Result<
|
||||||
let mut data = file_get_json(&path, Some(json!({})))?;
|
let mut data = file_get_json(&path, Some(json!({})))?;
|
||||||
|
|
||||||
if let Some(map) = data[server].as_object_mut() {
|
if let Some(map) = data[server].as_object_mut() {
|
||||||
map.remove(username);
|
map.remove(username.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
replace_file(path, data.to_string().as_bytes(), CreateOptions::new().perm(mode))?;
|
replace_file(path, data.to_string().as_bytes(), CreateOptions::new().perm(mode))?;
|
||||||
|
@ -223,7 +224,7 @@ fn store_ticket_info(prefix: &str, server: &str, username: &str, ticket: &str, t
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_ticket_info(prefix: &str, server: &str, username: &str) -> Option<(String, String)> {
|
fn load_ticket_info(prefix: &str, server: &str, userid: &Userid) -> Option<(String, String)> {
|
||||||
let base = BaseDirectories::with_prefix(prefix).ok()?;
|
let base = BaseDirectories::with_prefix(prefix).ok()?;
|
||||||
|
|
||||||
// usually /run/user/<uid>/...
|
// usually /run/user/<uid>/...
|
||||||
|
@ -231,7 +232,7 @@ fn load_ticket_info(prefix: &str, server: &str, username: &str) -> Option<(Strin
|
||||||
let data = file_get_json(&path, None).ok()?;
|
let data = file_get_json(&path, None).ok()?;
|
||||||
let now = Utc::now().timestamp();
|
let now = Utc::now().timestamp();
|
||||||
let ticket_lifetime = tools::ticket::TICKET_LIFETIME - 60;
|
let ticket_lifetime = tools::ticket::TICKET_LIFETIME - 60;
|
||||||
let uinfo = data[server][username].as_object()?;
|
let uinfo = data[server][userid.as_str()].as_object()?;
|
||||||
let timestamp = uinfo["timestamp"].as_i64()?;
|
let timestamp = uinfo["timestamp"].as_i64()?;
|
||||||
let age = now - timestamp;
|
let age = now - timestamp;
|
||||||
|
|
||||||
|
@ -245,8 +246,11 @@ fn load_ticket_info(prefix: &str, server: &str, username: &str) -> Option<(Strin
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpClient {
|
impl HttpClient {
|
||||||
|
pub fn new(
|
||||||
pub fn new(server: &str, username: &str, mut options: HttpClientOptions) -> Result<Self, Error> {
|
server: &str,
|
||||||
|
userid: &Userid,
|
||||||
|
mut options: HttpClientOptions,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
|
||||||
let verified_fingerprint = Arc::new(Mutex::new(None));
|
let verified_fingerprint = Arc::new(Mutex::new(None));
|
||||||
|
|
||||||
|
@ -306,20 +310,20 @@ impl HttpClient {
|
||||||
} else {
|
} else {
|
||||||
let mut ticket_info = None;
|
let mut ticket_info = None;
|
||||||
if use_ticket_cache {
|
if use_ticket_cache {
|
||||||
ticket_info = load_ticket_info(options.prefix.as_ref().unwrap(), server, username);
|
ticket_info = load_ticket_info(options.prefix.as_ref().unwrap(), server, userid);
|
||||||
}
|
}
|
||||||
if let Some((ticket, _token)) = ticket_info {
|
if let Some((ticket, _token)) = ticket_info {
|
||||||
ticket
|
ticket
|
||||||
} else {
|
} else {
|
||||||
Self::get_password(&username, options.interactive)?
|
Self::get_password(userid, options.interactive)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let login_future = Self::credentials(
|
let login_future = Self::credentials(
|
||||||
client.clone(),
|
client.clone(),
|
||||||
server.to_owned(),
|
server.to_owned(),
|
||||||
username.to_owned(),
|
userid.to_owned(),
|
||||||
password,
|
password.to_owned(),
|
||||||
).map_ok({
|
).map_ok({
|
||||||
let server = server.to_string();
|
let server = server.to_string();
|
||||||
let prefix = options.prefix.clone();
|
let prefix = options.prefix.clone();
|
||||||
|
@ -355,7 +359,7 @@ impl HttpClient {
|
||||||
(*self.fingerprint.lock().unwrap()).clone()
|
(*self.fingerprint.lock().unwrap()).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_password(username: &str, interactive: bool) -> Result<String, Error> {
|
fn get_password(username: &Userid, interactive: bool) -> Result<String, Error> {
|
||||||
// If we're on a TTY, query the user for a password
|
// If we're on a TTY, query the user for a password
|
||||||
if interactive && tty::stdin_isatty() {
|
if interactive && tty::stdin_isatty() {
|
||||||
let msg = format!("Password for \"{}\": ", username);
|
let msg = format!("Password for \"{}\": ", username);
|
||||||
|
@ -579,7 +583,7 @@ impl HttpClient {
|
||||||
async fn credentials(
|
async fn credentials(
|
||||||
client: Client<HttpsConnector>,
|
client: Client<HttpsConnector>,
|
||||||
server: String,
|
server: String,
|
||||||
username: String,
|
username: Userid,
|
||||||
password: String,
|
password: String,
|
||||||
) -> Result<AuthInfo, Error> {
|
) -> Result<AuthInfo, Error> {
|
||||||
let data = json!({ "username": username, "password": password });
|
let data = json!({ "username": username, "password": password });
|
||||||
|
|
|
@ -401,7 +401,7 @@ pub async fn pull_store(
|
||||||
src_repo: &BackupRepository,
|
src_repo: &BackupRepository,
|
||||||
tgt_store: Arc<DataStore>,
|
tgt_store: Arc<DataStore>,
|
||||||
delete: bool,
|
delete: bool,
|
||||||
username: String,
|
userid: Userid,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
// explicit create shared lock to prevent GC on newly created chunks
|
// explicit create shared lock to prevent GC on newly created chunks
|
||||||
|
@ -432,11 +432,11 @@ pub async fn pull_store(
|
||||||
for item in list {
|
for item in list {
|
||||||
let group = BackupGroup::new(&item.backup_type, &item.backup_id);
|
let group = BackupGroup::new(&item.backup_type, &item.backup_id);
|
||||||
|
|
||||||
let (owner, _lock_guard) = tgt_store.create_locked_backup_group(&group, &username)?;
|
let (owner, _lock_guard) = tgt_store.create_locked_backup_group(&group, &userid)?;
|
||||||
// permission check
|
// permission check
|
||||||
if owner != username { // only the owner is allowed to create additional snapshots
|
if userid != owner { // only the owner is allowed to create additional snapshots
|
||||||
worker.log(format!("sync group {}/{} failed - owner check failed ({} != {})",
|
worker.log(format!("sync group {}/{} failed - owner check failed ({} != {})",
|
||||||
item.backup_type, item.backup_id, username, owner));
|
item.backup_type, item.backup_id, userid, owner));
|
||||||
errors = true;
|
errors = true;
|
||||||
continue; // do not stop here, instead continue
|
continue; // do not stop here, instead continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ use proxmox::tools::{fs::replace_file, fs::CreateOptions};
|
||||||
use proxmox::constnamemap;
|
use proxmox::constnamemap;
|
||||||
use proxmox::api::{api, schema::*};
|
use proxmox::api::{api, schema::*};
|
||||||
|
|
||||||
|
use crate::api2::types::Userid;
|
||||||
|
|
||||||
// define Privilege bitfield
|
// define Privilege bitfield
|
||||||
|
|
||||||
constnamemap! {
|
constnamemap! {
|
||||||
|
@ -224,7 +226,7 @@ pub struct AclTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AclTreeNode {
|
pub struct AclTreeNode {
|
||||||
pub users: HashMap<String, HashMap<String, bool>>,
|
pub users: HashMap<Userid, HashMap<String, bool>>,
|
||||||
pub groups: HashMap<String, HashMap<String, bool>>,
|
pub groups: HashMap<String, HashMap<String, bool>>,
|
||||||
pub children: BTreeMap<String, AclTreeNode>,
|
pub children: BTreeMap<String, AclTreeNode>,
|
||||||
}
|
}
|
||||||
|
@ -239,7 +241,7 @@ impl AclTreeNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_roles(&self, user: &str, all: bool) -> HashSet<String> {
|
pub fn extract_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
|
||||||
let user_roles = self.extract_user_roles(user, all);
|
let user_roles = self.extract_user_roles(user, all);
|
||||||
if !user_roles.is_empty() {
|
if !user_roles.is_empty() {
|
||||||
// user privs always override group privs
|
// user privs always override group privs
|
||||||
|
@ -249,7 +251,7 @@ impl AclTreeNode {
|
||||||
self.extract_group_roles(user, all)
|
self.extract_group_roles(user, all)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_user_roles(&self, user: &str, all: bool) -> HashSet<String> {
|
pub fn extract_user_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
|
||||||
|
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
|
|
||||||
|
@ -273,7 +275,7 @@ impl AclTreeNode {
|
||||||
set
|
set
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_group_roles(&self, _user: &str, all: bool) -> HashSet<String> {
|
pub fn extract_group_roles(&self, _user: &Userid, all: bool) -> HashSet<String> {
|
||||||
|
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
|
|
||||||
|
@ -305,7 +307,7 @@ impl AclTreeNode {
|
||||||
roles.remove(role);
|
roles.remove(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_user_role(&mut self, userid: &str, role: &str) {
|
pub fn delete_user_role(&mut self, userid: &Userid, role: &str) {
|
||||||
let roles = match self.users.get_mut(userid) {
|
let roles = match self.users.get_mut(userid) {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
None => return,
|
None => return,
|
||||||
|
@ -324,7 +326,7 @@ impl AclTreeNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_user_role(&mut self, user: String, role: String, propagate: bool) {
|
pub fn insert_user_role(&mut self, user: Userid, role: String, propagate: bool) {
|
||||||
let map = self.users.entry(user).or_insert_with(|| HashMap::new());
|
let map = self.users.entry(user).or_insert_with(|| HashMap::new());
|
||||||
if role == ROLE_NAME_NO_ACCESS {
|
if role == ROLE_NAME_NO_ACCESS {
|
||||||
map.clear();
|
map.clear();
|
||||||
|
@ -376,7 +378,7 @@ impl AclTree {
|
||||||
node.delete_group_role(group, role);
|
node.delete_group_role(group, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_user_role(&mut self, path: &str, userid: &str, role: &str) {
|
pub fn delete_user_role(&mut self, path: &str, userid: &Userid, role: &str) {
|
||||||
let path = split_acl_path(path);
|
let path = split_acl_path(path);
|
||||||
let node = match self.get_node(&path) {
|
let node = match self.get_node(&path) {
|
||||||
Some(n) => n,
|
Some(n) => n,
|
||||||
|
@ -391,10 +393,10 @@ impl AclTree {
|
||||||
node.insert_group_role(group.to_string(), role.to_string(), propagate);
|
node.insert_group_role(group.to_string(), role.to_string(), propagate);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_user_role(&mut self, path: &str, user: &str, role: &str, propagate: bool) {
|
pub fn insert_user_role(&mut self, path: &str, user: &Userid, role: &str, propagate: bool) {
|
||||||
let path = split_acl_path(path);
|
let path = split_acl_path(path);
|
||||||
let node = self.get_or_insert_node(&path);
|
let node = self.get_or_insert_node(&path);
|
||||||
node.insert_user_role(user.to_string(), role.to_string(), propagate);
|
node.insert_user_role(user.to_owned(), role.to_string(), propagate);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_node_config(
|
fn write_node_config(
|
||||||
|
@ -521,7 +523,7 @@ impl AclTree {
|
||||||
let group = &user_or_group[1..];
|
let group = &user_or_group[1..];
|
||||||
node.insert_group_role(group.to_string(), role.to_string(), propagate);
|
node.insert_group_role(group.to_string(), role.to_string(), propagate);
|
||||||
} else {
|
} else {
|
||||||
node.insert_user_role(user_or_group.to_string(), role.to_string(), propagate);
|
node.insert_user_role(user_or_group.parse()?, role.to_string(), propagate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -569,7 +571,7 @@ impl AclTree {
|
||||||
Ok(tree)
|
Ok(tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn roles(&self, userid: &str, path: &[&str]) -> HashSet<String> {
|
pub fn roles(&self, userid: &Userid, path: &[&str]) -> HashSet<String> {
|
||||||
|
|
||||||
let mut node = &self.root;
|
let mut node = &self.root;
|
||||||
let mut role_set = node.extract_roles(userid, path.is_empty());
|
let mut role_set = node.extract_roles(userid, path.is_empty());
|
||||||
|
@ -665,13 +667,14 @@ pub fn save_config(acl: &AclTree) -> Result<(), Error> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use anyhow::{Error};
|
use anyhow::{Error};
|
||||||
use super::AclTree;
|
use super::AclTree;
|
||||||
|
|
||||||
|
use crate::api2::types::Userid;
|
||||||
|
|
||||||
fn check_roles(
|
fn check_roles(
|
||||||
tree: &AclTree,
|
tree: &AclTree,
|
||||||
user: &str,
|
user: &Userid,
|
||||||
path: &str,
|
path: &str,
|
||||||
expected_roles: &str,
|
expected_roles: &str,
|
||||||
) {
|
) {
|
||||||
|
@ -686,22 +689,23 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_acl_line_compression() -> Result<(), Error> {
|
fn test_acl_line_compression() {
|
||||||
|
|
||||||
let tree = AclTree::from_raw(r###"
|
let tree = AclTree::from_raw(
|
||||||
acl:0:/store/store2:user1:Admin
|
"\
|
||||||
acl:0:/store/store2:user2:Admin
|
acl:0:/store/store2:user1@pbs:Admin\n\
|
||||||
acl:0:/store/store2:user1:DatastoreBackup
|
acl:0:/store/store2:user2@pbs:Admin\n\
|
||||||
acl:0:/store/store2:user2:DatastoreBackup
|
acl:0:/store/store2:user1@pbs:DatastoreBackup\n\
|
||||||
"###)?;
|
acl:0:/store/store2:user2@pbs:DatastoreBackup\n\
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.expect("failed to parse acl tree");
|
||||||
|
|
||||||
let mut raw: Vec<u8> = Vec::new();
|
let mut raw: Vec<u8> = Vec::new();
|
||||||
tree.write_config(&mut raw)?;
|
tree.write_config(&mut raw).expect("failed to write acl tree");
|
||||||
let raw = std::str::from_utf8(&raw)?;
|
let raw = std::str::from_utf8(&raw).expect("acl tree is not valid utf8");
|
||||||
|
|
||||||
assert_eq!(raw, "acl:0:/store/store2:user1,user2:Admin,DatastoreBackup\n");
|
assert_eq!(raw, "acl:0:/store/store2:user1@pbs,user2@pbs:Admin,DatastoreBackup\n");
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -712,15 +716,17 @@ acl:1:/storage:user1@pbs:Admin
|
||||||
acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||||
acl:1:/storage/store2:user2@pbs:DatastoreBackup
|
acl:1:/storage/store2:user2@pbs:DatastoreBackup
|
||||||
"###)?;
|
"###)?;
|
||||||
check_roles(&tree, "user1@pbs", "/", "");
|
let user1: Userid = "user1@pbs".parse()?;
|
||||||
check_roles(&tree, "user1@pbs", "/storage", "Admin");
|
check_roles(&tree, &user1, "/", "");
|
||||||
check_roles(&tree, "user1@pbs", "/storage/store1", "DatastoreBackup");
|
check_roles(&tree, &user1, "/storage", "Admin");
|
||||||
check_roles(&tree, "user1@pbs", "/storage/store2", "Admin");
|
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
|
||||||
|
check_roles(&tree, &user1, "/storage/store2", "Admin");
|
||||||
|
|
||||||
check_roles(&tree, "user2@pbs", "/", "");
|
let user2: Userid = "user2@pbs".parse()?;
|
||||||
check_roles(&tree, "user2@pbs", "/storage", "");
|
check_roles(&tree, &user2, "/", "");
|
||||||
check_roles(&tree, "user2@pbs", "/storage/store1", "");
|
check_roles(&tree, &user2, "/storage", "");
|
||||||
check_roles(&tree, "user2@pbs", "/storage/store2", "DatastoreBackup");
|
check_roles(&tree, &user2, "/storage/store1", "");
|
||||||
|
check_roles(&tree, &user2, "/storage/store2", "DatastoreBackup");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -733,22 +739,23 @@ acl:1:/:user1@pbs:Admin
|
||||||
acl:1:/storage:user1@pbs:NoAccess
|
acl:1:/storage:user1@pbs:NoAccess
|
||||||
acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||||
"###)?;
|
"###)?;
|
||||||
check_roles(&tree, "user1@pbs", "/", "Admin");
|
let user1: Userid = "user1@pbs".parse()?;
|
||||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
check_roles(&tree, &user1, "/", "Admin");
|
||||||
check_roles(&tree, "user1@pbs", "/storage/store1", "DatastoreBackup");
|
check_roles(&tree, &user1, "/storage", "NoAccess");
|
||||||
check_roles(&tree, "user1@pbs", "/storage/store2", "NoAccess");
|
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
|
||||||
check_roles(&tree, "user1@pbs", "/system", "Admin");
|
check_roles(&tree, &user1, "/storage/store2", "NoAccess");
|
||||||
|
check_roles(&tree, &user1, "/system", "Admin");
|
||||||
|
|
||||||
let tree = AclTree::from_raw(r###"
|
let tree = AclTree::from_raw(r###"
|
||||||
acl:1:/:user1@pbs:Admin
|
acl:1:/:user1@pbs:Admin
|
||||||
acl:0:/storage:user1@pbs:NoAccess
|
acl:0:/storage:user1@pbs:NoAccess
|
||||||
acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||||
"###)?;
|
"###)?;
|
||||||
check_roles(&tree, "user1@pbs", "/", "Admin");
|
check_roles(&tree, &user1, "/", "Admin");
|
||||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
check_roles(&tree, &user1, "/storage", "NoAccess");
|
||||||
check_roles(&tree, "user1@pbs", "/storage/store1", "DatastoreBackup");
|
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
|
||||||
check_roles(&tree, "user1@pbs", "/storage/store2", "Admin");
|
check_roles(&tree, &user1, "/storage/store2", "Admin");
|
||||||
check_roles(&tree, "user1@pbs", "/system", "Admin");
|
check_roles(&tree, &user1, "/system", "Admin");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -758,13 +765,15 @@ acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||||
|
|
||||||
let mut tree = AclTree::new();
|
let mut tree = AclTree::new();
|
||||||
|
|
||||||
tree.insert_user_role("/", "user1@pbs", "Admin", true);
|
let user1: Userid = "user1@pbs".parse()?;
|
||||||
tree.insert_user_role("/", "user1@pbs", "Audit", true);
|
|
||||||
|
|
||||||
check_roles(&tree, "user1@pbs", "/", "Admin,Audit");
|
tree.insert_user_role("/", &user1, "Admin", true);
|
||||||
|
tree.insert_user_role("/", &user1, "Audit", true);
|
||||||
|
|
||||||
tree.insert_user_role("/", "user1@pbs", "NoAccess", true);
|
check_roles(&tree, &user1, "/", "Admin,Audit");
|
||||||
check_roles(&tree, "user1@pbs", "/", "NoAccess");
|
|
||||||
|
tree.insert_user_role("/", &user1, "NoAccess", true);
|
||||||
|
check_roles(&tree, &user1, "/", "NoAccess");
|
||||||
|
|
||||||
let mut raw: Vec<u8> = Vec::new();
|
let mut raw: Vec<u8> = Vec::new();
|
||||||
tree.write_config(&mut raw)?;
|
tree.write_config(&mut raw)?;
|
||||||
|
@ -780,20 +789,21 @@ acl:1:/storage/store1:user1@pbs:DatastoreBackup
|
||||||
|
|
||||||
let mut tree = AclTree::new();
|
let mut tree = AclTree::new();
|
||||||
|
|
||||||
tree.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
|
let user1: Userid = "user1@pbs".parse()?;
|
||||||
|
|
||||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
tree.insert_user_role("/storage", &user1, "NoAccess", true);
|
||||||
|
|
||||||
tree.insert_user_role("/storage", "user1@pbs", "Admin", true);
|
check_roles(&tree, &user1, "/storage", "NoAccess");
|
||||||
tree.insert_user_role("/storage", "user1@pbs", "Audit", true);
|
|
||||||
|
|
||||||
check_roles(&tree, "user1@pbs", "/storage", "Admin,Audit");
|
tree.insert_user_role("/storage", &user1, "Admin", true);
|
||||||
|
tree.insert_user_role("/storage", &user1, "Audit", true);
|
||||||
|
|
||||||
tree.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
|
check_roles(&tree, &user1, "/storage", "Admin,Audit");
|
||||||
|
|
||||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
tree.insert_user_role("/storage", &user1, "NoAccess", true);
|
||||||
|
|
||||||
|
check_roles(&tree, &user1, "/storage", "NoAccess");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use proxmox::api::UserInformation;
|
||||||
|
|
||||||
use super::acl::{AclTree, ROLE_NAMES, ROLE_ADMIN};
|
use super::acl::{AclTree, ROLE_NAMES, ROLE_ADMIN};
|
||||||
use super::user::User;
|
use super::user::User;
|
||||||
|
use crate::api2::types::Userid;
|
||||||
|
|
||||||
/// Cache User/Group/Acl configuration data for fast permission tests
|
/// Cache User/Group/Acl configuration data for fast permission tests
|
||||||
pub struct CachedUserInfo {
|
pub struct CachedUserInfo {
|
||||||
|
@ -57,8 +58,8 @@ impl CachedUserInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if a user account is enabled and not expired
|
/// Test if a user account is enabled and not expired
|
||||||
pub fn is_active_user(&self, userid: &str) -> bool {
|
pub fn is_active_user(&self, userid: &Userid) -> bool {
|
||||||
if let Ok(info) = self.user_cfg.lookup::<User>("user", &userid) {
|
if let Ok(info) = self.user_cfg.lookup::<User>("user", userid.as_str()) {
|
||||||
if !info.enable.unwrap_or(true) {
|
if !info.enable.unwrap_or(true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -77,12 +78,12 @@ impl CachedUserInfo {
|
||||||
|
|
||||||
pub fn check_privs(
|
pub fn check_privs(
|
||||||
&self,
|
&self,
|
||||||
userid: &str,
|
userid: &Userid,
|
||||||
path: &[&str],
|
path: &[&str],
|
||||||
required_privs: u64,
|
required_privs: u64,
|
||||||
partial: bool,
|
partial: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let user_privs = self.lookup_privs(userid, path);
|
let user_privs = self.lookup_privs(&userid, path);
|
||||||
let allowed = if partial {
|
let allowed = if partial {
|
||||||
(user_privs & required_privs) != 0
|
(user_privs & required_privs) != 0
|
||||||
} else {
|
} else {
|
||||||
|
@ -97,18 +98,20 @@ impl CachedUserInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserInformation for CachedUserInfo {
|
impl CachedUserInfo {
|
||||||
fn is_superuser(&self, userid: &str) -> bool {
|
pub fn is_superuser(&self, userid: &Userid) -> bool {
|
||||||
userid == "root@pam"
|
userid == "root@pam"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_group_member(&self, _userid: &str, _group: &str) -> bool {
|
pub fn is_group_member(&self, _userid: &Userid, _group: &str) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_privs(&self, userid: &str, path: &[&str]) -> u64 {
|
pub fn lookup_privs(&self, userid: &Userid, path: &[&str]) -> u64 {
|
||||||
|
|
||||||
if self.is_superuser(userid) { return ROLE_ADMIN; }
|
if self.is_superuser(userid) {
|
||||||
|
return ROLE_ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
let roles = self.acl_tree.roles(userid, path);
|
let roles = self.acl_tree.roles(userid, path);
|
||||||
let mut privs: u64 = 0;
|
let mut privs: u64 = 0;
|
||||||
|
@ -120,3 +123,20 @@ impl UserInformation for CachedUserInfo {
|
||||||
privs
|
privs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UserInformation for CachedUserInfo {
|
||||||
|
fn is_superuser(&self, userid: &str) -> bool {
|
||||||
|
userid == "root@pam"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_group_member(&self, _userid: &str, _group: &str) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_privs(&self, userid: &str, path: &[&str]) -> u64 {
|
||||||
|
match userid.parse::<Userid>() {
|
||||||
|
Ok(userid) => Self::lookup_privs(self, &userid, path),
|
||||||
|
Err(_) => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth t
|
||||||
schema: DNS_NAME_OR_IP_SCHEMA,
|
schema: DNS_NAME_OR_IP_SCHEMA,
|
||||||
},
|
},
|
||||||
userid: {
|
userid: {
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
schema: REMOTE_PASSWORD_SCHEMA,
|
schema: REMOTE_PASSWORD_SCHEMA,
|
||||||
|
@ -58,7 +58,7 @@ pub struct Remote {
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub userid: String,
|
pub userid: Userid,
|
||||||
#[serde(skip_serializing_if="String::is_empty")]
|
#[serde(skip_serializing_if="String::is_empty")]
|
||||||
#[serde(with = "proxmox::tools::serde::string_as_base64")]
|
#[serde(with = "proxmox::tools::serde::string_as_base64")]
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
|
||||||
#[api(
|
#[api(
|
||||||
properties: {
|
properties: {
|
||||||
userid: {
|
userid: {
|
||||||
schema: PROXMOX_USER_ID_SCHEMA,
|
type: Userid,
|
||||||
},
|
},
|
||||||
comment: {
|
comment: {
|
||||||
optional: true,
|
optional: true,
|
||||||
|
@ -87,7 +87,7 @@ pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
|
||||||
#[derive(Serialize,Deserialize)]
|
#[derive(Serialize,Deserialize)]
|
||||||
/// User properties.
|
/// User properties.
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub userid: String,
|
pub userid: Userid,
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
@ -109,7 +109,7 @@ fn init() -> SectionConfig {
|
||||||
};
|
};
|
||||||
|
|
||||||
let plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), obj_schema);
|
let plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), obj_schema);
|
||||||
let mut config = SectionConfig::new(&PROXMOX_USER_ID_SCHEMA);
|
let mut config = SectionConfig::new(&Userid::API_SCHEMA);
|
||||||
|
|
||||||
config.register_plugin(plugin);
|
config.register_plugin(plugin);
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> {
|
||||||
|
|
||||||
if data.sections.get("root@pam").is_none() {
|
if data.sections.get("root@pam").is_none() {
|
||||||
let user: User = User {
|
let user: User = User {
|
||||||
userid: "root@pam".to_string(),
|
userid: Userid::root_userid().clone(),
|
||||||
comment: Some("Superuser".to_string()),
|
comment: Some("Superuser".to_string()),
|
||||||
enable: None,
|
enable: None,
|
||||||
expire: None,
|
expire: None,
|
||||||
|
|
|
@ -27,6 +27,7 @@ use super::formatter::*;
|
||||||
use super::ApiConfig;
|
use super::ApiConfig;
|
||||||
|
|
||||||
use crate::auth_helpers::*;
|
use crate::auth_helpers::*;
|
||||||
|
use crate::api2::types::Userid;
|
||||||
use crate::tools;
|
use crate::tools;
|
||||||
use crate::config::cached_user_info::CachedUserInfo;
|
use crate::config::cached_user_info::CachedUserInfo;
|
||||||
|
|
||||||
|
@ -311,10 +312,10 @@ pub async fn handle_api_request<Env: RpcEnvironment, S: 'static + BuildHasher +
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_index(username: Option<String>, token: Option<String>, api: &Arc<ApiConfig>, parts: Parts) -> Response<Body> {
|
fn get_index(userid: Option<Userid>, token: Option<String>, api: &Arc<ApiConfig>, parts: Parts) -> Response<Body> {
|
||||||
|
|
||||||
let nodename = proxmox::tools::nodename();
|
let nodename = proxmox::tools::nodename();
|
||||||
let username = username.unwrap_or_else(|| String::from(""));
|
let userid = userid.as_ref().map(|u| u.as_str()).unwrap_or("");
|
||||||
|
|
||||||
let token = token.unwrap_or_else(|| String::from(""));
|
let token = token.unwrap_or_else(|| String::from(""));
|
||||||
|
|
||||||
|
@ -333,7 +334,7 @@ fn get_index(username: Option<String>, token: Option<String>, api: &Arc<ApiConfi
|
||||||
|
|
||||||
let data = json!({
|
let data = json!({
|
||||||
"NodeName": nodename,
|
"NodeName": nodename,
|
||||||
"UserName": username,
|
"UserName": userid,
|
||||||
"CSRFPreventionToken": token,
|
"CSRFPreventionToken": token,
|
||||||
"debug": debug,
|
"debug": debug,
|
||||||
});
|
});
|
||||||
|
@ -461,33 +462,33 @@ fn check_auth(
|
||||||
ticket: &Option<String>,
|
ticket: &Option<String>,
|
||||||
token: &Option<String>,
|
token: &Option<String>,
|
||||||
user_info: &CachedUserInfo,
|
user_info: &CachedUserInfo,
|
||||||
) -> Result<String, Error> {
|
) -> Result<Userid, Error> {
|
||||||
|
|
||||||
let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
|
let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
|
||||||
|
|
||||||
let username = match ticket {
|
let userid = match ticket {
|
||||||
Some(ticket) => match tools::ticket::verify_rsa_ticket(public_auth_key(), "PBS", &ticket, None, -300, ticket_lifetime) {
|
Some(ticket) => match tools::ticket::verify_rsa_ticket(public_auth_key(), "PBS", &ticket, None, -300, ticket_lifetime) {
|
||||||
Ok((_age, Some(username))) => username.to_owned(),
|
Ok((_age, Some(userid))) => userid,
|
||||||
Ok((_, None)) => bail!("ticket without username."),
|
Ok((_, None)) => bail!("ticket without username."),
|
||||||
Err(err) => return Err(err),
|
Err(err) => return Err(err),
|
||||||
}
|
}
|
||||||
None => bail!("missing ticket"),
|
None => bail!("missing ticket"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !user_info.is_active_user(&username) {
|
if !user_info.is_active_user(&userid) {
|
||||||
bail!("user account disabled or expired.");
|
bail!("user account disabled or expired.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if method != hyper::Method::GET {
|
if method != hyper::Method::GET {
|
||||||
if let Some(token) = token {
|
if let Some(token) = token {
|
||||||
println!("CSRF prevention token: {:?}", token);
|
println!("CSRF prevention token: {:?}", token);
|
||||||
verify_csrf_prevention_token(csrf_secret(), &username, &token, -300, ticket_lifetime)?;
|
verify_csrf_prevention_token(csrf_secret(), &userid, &token, -300, ticket_lifetime)?;
|
||||||
} else {
|
} else {
|
||||||
bail!("missing CSRF prevention token");
|
bail!("missing CSRF prevention token");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(username)
|
Ok(userid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<Response<Body>, Error> {
|
pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<Response<Body>, Error> {
|
||||||
|
@ -532,7 +533,7 @@ pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<R
|
||||||
} else {
|
} else {
|
||||||
let (ticket, token) = extract_auth_data(&parts.headers);
|
let (ticket, token) = extract_auth_data(&parts.headers);
|
||||||
match check_auth(&method, &ticket, &token, &user_info) {
|
match check_auth(&method, &ticket, &token, &user_info) {
|
||||||
Ok(username) => rpcenv.set_user(Some(username)),
|
Ok(userid) => rpcenv.set_user(Some(userid.to_string())),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// always delay unauthorized calls by 3 seconds (from start of request)
|
// always delay unauthorized calls by 3 seconds (from start of request)
|
||||||
let err = http_err!(UNAUTHORIZED, "authentication failed - {}", err);
|
let err = http_err!(UNAUTHORIZED, "authentication failed - {}", err);
|
||||||
|
@ -580,9 +581,9 @@ pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<R
|
||||||
let (ticket, token) = extract_auth_data(&parts.headers);
|
let (ticket, token) = extract_auth_data(&parts.headers);
|
||||||
if ticket != None {
|
if ticket != None {
|
||||||
match check_auth(&method, &ticket, &token, &user_info) {
|
match check_auth(&method, &ticket, &token, &user_info) {
|
||||||
Ok(username) => {
|
Ok(userid) => {
|
||||||
let new_token = assemble_csrf_prevention_token(csrf_secret(), &username);
|
let new_token = assemble_csrf_prevention_token(csrf_secret(), &userid);
|
||||||
return Ok(get_index(Some(username), Some(new_token), &api, parts));
|
return Ok(get_index(Some(userid), Some(new_token), &api, parts));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await;
|
tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await;
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
use anyhow::{bail, Error};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
|
||||||
use chrono::Local;
|
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use anyhow::{bail, Error};
|
||||||
|
use chrono::Local;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use proxmox::sys::linux::procfs;
|
use proxmox::sys::linux::procfs;
|
||||||
|
|
||||||
|
use crate::api2::types::Userid;
|
||||||
|
|
||||||
/// Unique Process/Task Identifier
|
/// Unique Process/Task Identifier
|
||||||
///
|
///
|
||||||
/// We use this to uniquely identify worker task. UPIDs have a short
|
/// We use this to uniquely identify worker task. UPIDs have a short
|
||||||
/// string repesentaion, which gives additional information about the
|
/// string repesentaion, which gives additional information about the
|
||||||
/// type of the task. for example:
|
/// type of the task. for example:
|
||||||
/// ```text
|
/// ```text
|
||||||
/// UPID:{node}:{pid}:{pstart}:{task_id}:{starttime}:{worker_type}:{worker_id}:{username}:
|
/// UPID:{node}:{pid}:{pstart}:{task_id}:{starttime}:{worker_type}:{worker_id}:{userid}:
|
||||||
/// UPID:elsa:00004F37:0039E469:00000000:5CA78B83:garbage_collection::root@pam:
|
/// UPID:elsa:00004F37:0039E469:00000000:5CA78B83:garbage_collection::root@pam:
|
||||||
/// ```
|
/// ```
|
||||||
/// Please note that we use tokio, so a single thread can run multiple
|
/// Please note that we use tokio, so a single thread can run multiple
|
||||||
|
@ -33,7 +35,7 @@ pub struct UPID {
|
||||||
/// Worker ID (arbitrary ASCII string)
|
/// Worker ID (arbitrary ASCII string)
|
||||||
pub worker_id: Option<String>,
|
pub worker_id: Option<String>,
|
||||||
/// The user who started the task
|
/// The user who started the task
|
||||||
pub username: String,
|
pub userid: Userid,
|
||||||
/// The node name.
|
/// The node name.
|
||||||
pub node: String,
|
pub node: String,
|
||||||
}
|
}
|
||||||
|
@ -41,7 +43,11 @@ pub struct UPID {
|
||||||
impl UPID {
|
impl UPID {
|
||||||
|
|
||||||
/// Create a new UPID
|
/// Create a new UPID
|
||||||
pub fn new(worker_type: &str, worker_id: Option<String>, username: &str) -> Result<Self, Error> {
|
pub fn new(
|
||||||
|
worker_type: &str,
|
||||||
|
worker_id: Option<String>,
|
||||||
|
userid: Userid,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
|
||||||
let pid = unsafe { libc::getpid() };
|
let pid = unsafe { libc::getpid() };
|
||||||
|
|
||||||
|
@ -67,7 +73,7 @@ impl UPID {
|
||||||
task_id,
|
task_id,
|
||||||
worker_type: worker_type.to_owned(),
|
worker_type: worker_type.to_owned(),
|
||||||
worker_id,
|
worker_id,
|
||||||
username: username.to_owned(),
|
userid,
|
||||||
node: proxmox::tools::nodename().to_owned(),
|
node: proxmox::tools::nodename().to_owned(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -91,7 +97,7 @@ impl std::str::FromStr for UPID {
|
||||||
static ref REGEX: Regex = Regex::new(concat!(
|
static ref REGEX: Regex = Regex::new(concat!(
|
||||||
r"^UPID:(?P<node>[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?):(?P<pid>[0-9A-Fa-f]{8}):",
|
r"^UPID:(?P<node>[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?):(?P<pid>[0-9A-Fa-f]{8}):",
|
||||||
r"(?P<pstart>[0-9A-Fa-f]{8,9}):(?P<task_id>[0-9A-Fa-f]{8,16}):(?P<starttime>[0-9A-Fa-f]{8}):",
|
r"(?P<pstart>[0-9A-Fa-f]{8,9}):(?P<task_id>[0-9A-Fa-f]{8,16}):(?P<starttime>[0-9A-Fa-f]{8}):",
|
||||||
r"(?P<wtype>[^:\s]+):(?P<wid>[^:\s]*):(?P<username>[^:\s]+):$"
|
r"(?P<wtype>[^:\s]+):(?P<wid>[^:\s]*):(?P<userid>[^:\s]+):$"
|
||||||
)).unwrap();
|
)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +110,7 @@ impl std::str::FromStr for UPID {
|
||||||
task_id: usize::from_str_radix(&cap["task_id"], 16).unwrap(),
|
task_id: usize::from_str_radix(&cap["task_id"], 16).unwrap(),
|
||||||
worker_type: cap["wtype"].to_string(),
|
worker_type: cap["wtype"].to_string(),
|
||||||
worker_id: if cap["wid"].is_empty() { None } else { Some(cap["wid"].to_string()) },
|
worker_id: if cap["wid"].is_empty() { None } else { Some(cap["wid"].to_string()) },
|
||||||
username: cap["username"].to_string(),
|
userid: cap["userid"].parse()?,
|
||||||
node: cap["node"].to_string(),
|
node: cap["node"].to_string(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -124,6 +130,6 @@ impl std::fmt::Display for UPID {
|
||||||
// more that 8 characters for pstart
|
// more that 8 characters for pstart
|
||||||
|
|
||||||
write!(f, "UPID:{}:{:08X}:{:08X}:{:08X}:{:08X}:{}:{}:{}:",
|
write!(f, "UPID:{}:{:08X}:{:08X}:{:08X}:{:08X}:{}:{}:{}:",
|
||||||
self.node, self.pid, self.pstart, self.task_id, self.starttime, self.worker_type, wid, self.username)
|
self.node, self.pid, self.pstart, self.task_id, self.starttime, self.worker_type, wid, self.userid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ use proxmox::tools::fs::{create_path, open_file_locked, replace_file, CreateOpti
|
||||||
use super::UPID;
|
use super::UPID;
|
||||||
|
|
||||||
use crate::tools::FileLogger;
|
use crate::tools::FileLogger;
|
||||||
|
use crate::api2::types::Userid;
|
||||||
|
|
||||||
macro_rules! PROXMOX_BACKUP_VAR_RUN_DIR_M { () => ("/run/proxmox-backup") }
|
macro_rules! PROXMOX_BACKUP_VAR_RUN_DIR_M { () => ("/run/proxmox-backup") }
|
||||||
macro_rules! PROXMOX_BACKUP_LOG_DIR_M { () => ("/var/log/proxmox-backup") }
|
macro_rules! PROXMOX_BACKUP_LOG_DIR_M { () => ("/var/log/proxmox-backup") }
|
||||||
|
@ -394,10 +395,10 @@ impl Drop for WorkerTask {
|
||||||
|
|
||||||
impl WorkerTask {
|
impl WorkerTask {
|
||||||
|
|
||||||
pub fn new(worker_type: &str, worker_id: Option<String>, username: &str, to_stdout: bool) -> Result<Arc<Self>, Error> {
|
pub fn new(worker_type: &str, worker_id: Option<String>, userid: Userid, to_stdout: bool) -> Result<Arc<Self>, Error> {
|
||||||
println!("register worker");
|
println!("register worker");
|
||||||
|
|
||||||
let upid = UPID::new(worker_type, worker_id, username)?;
|
let upid = UPID::new(worker_type, worker_id, userid)?;
|
||||||
let task_id = upid.task_id;
|
let task_id = upid.task_id;
|
||||||
|
|
||||||
let mut path = std::path::PathBuf::from(PROXMOX_BACKUP_TASK_DIR);
|
let mut path = std::path::PathBuf::from(PROXMOX_BACKUP_TASK_DIR);
|
||||||
|
@ -442,14 +443,14 @@ impl WorkerTask {
|
||||||
pub fn spawn<F, T>(
|
pub fn spawn<F, T>(
|
||||||
worker_type: &str,
|
worker_type: &str,
|
||||||
worker_id: Option<String>,
|
worker_id: Option<String>,
|
||||||
username: &str,
|
userid: Userid,
|
||||||
to_stdout: bool,
|
to_stdout: bool,
|
||||||
f: F,
|
f: F,
|
||||||
) -> Result<String, Error>
|
) -> Result<String, Error>
|
||||||
where F: Send + 'static + FnOnce(Arc<WorkerTask>) -> T,
|
where F: Send + 'static + FnOnce(Arc<WorkerTask>) -> T,
|
||||||
T: Send + 'static + Future<Output = Result<(), Error>>,
|
T: Send + 'static + Future<Output = Result<(), Error>>,
|
||||||
{
|
{
|
||||||
let worker = WorkerTask::new(worker_type, worker_id, username, to_stdout)?;
|
let worker = WorkerTask::new(worker_type, worker_id, userid, to_stdout)?;
|
||||||
let upid_str = worker.upid.to_string();
|
let upid_str = worker.upid.to_string();
|
||||||
let f = f(worker.clone());
|
let f = f(worker.clone());
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
@ -464,7 +465,7 @@ impl WorkerTask {
|
||||||
pub fn new_thread<F>(
|
pub fn new_thread<F>(
|
||||||
worker_type: &str,
|
worker_type: &str,
|
||||||
worker_id: Option<String>,
|
worker_id: Option<String>,
|
||||||
username: &str,
|
userid: Userid,
|
||||||
to_stdout: bool,
|
to_stdout: bool,
|
||||||
f: F,
|
f: F,
|
||||||
) -> Result<String, Error>
|
) -> Result<String, Error>
|
||||||
|
@ -474,7 +475,7 @@ impl WorkerTask {
|
||||||
|
|
||||||
let (p, c) = oneshot::channel::<()>();
|
let (p, c) = oneshot::channel::<()>();
|
||||||
|
|
||||||
let worker = WorkerTask::new(worker_type, worker_id, username, to_stdout)?;
|
let worker = WorkerTask::new(worker_type, worker_id, userid, to_stdout)?;
|
||||||
let upid_str = worker.upid.to_string();
|
let upid_str = worker.upid.to_string();
|
||||||
|
|
||||||
let _child = std::thread::Builder::new().name(upid_str.clone()).spawn(move || {
|
let _child = std::thread::Builder::new().name(upid_str.clone()).spawn(move || {
|
||||||
|
|
|
@ -7,6 +7,7 @@ use openssl::pkey::{PKey, Public, Private};
|
||||||
use openssl::sign::{Signer, Verifier};
|
use openssl::sign::{Signer, Verifier};
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
|
|
||||||
|
use crate::api2::types::Userid;
|
||||||
use crate::tools::epoch_now_u64;
|
use crate::tools::epoch_now_u64;
|
||||||
|
|
||||||
pub const TICKET_LIFETIME: i64 = 3600*2; // 2 hours
|
pub const TICKET_LIFETIME: i64 = 3600*2; // 2 hours
|
||||||
|
@ -15,7 +16,7 @@ const TERM_PREFIX: &str = "PBSTERM";
|
||||||
|
|
||||||
pub fn assemble_term_ticket(
|
pub fn assemble_term_ticket(
|
||||||
keypair: &PKey<Private>,
|
keypair: &PKey<Private>,
|
||||||
username: &str,
|
userid: &Userid,
|
||||||
path: &str,
|
path: &str,
|
||||||
port: u16,
|
port: u16,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
|
@ -23,22 +24,22 @@ pub fn assemble_term_ticket(
|
||||||
keypair,
|
keypair,
|
||||||
TERM_PREFIX,
|
TERM_PREFIX,
|
||||||
None,
|
None,
|
||||||
Some(&format!("{}{}{}", username, path, port)),
|
Some(&format!("{}{}{}", userid, path, port)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_term_ticket(
|
pub fn verify_term_ticket(
|
||||||
keypair: &PKey<Public>,
|
keypair: &PKey<Public>,
|
||||||
username: &str,
|
userid: &Userid,
|
||||||
path: &str,
|
path: &str,
|
||||||
port: u16,
|
port: u16,
|
||||||
ticket: &str,
|
ticket: &str,
|
||||||
) -> Result<(i64, Option<String>), Error> {
|
) -> Result<(i64, Option<Userid>), Error> {
|
||||||
verify_rsa_ticket(
|
verify_rsa_ticket(
|
||||||
keypair,
|
keypair,
|
||||||
TERM_PREFIX,
|
TERM_PREFIX,
|
||||||
ticket,
|
ticket,
|
||||||
Some(&format!("{}{}{}", username, path, port)),
|
Some(&format!("{}{}{}", userid, path, port)),
|
||||||
-300,
|
-300,
|
||||||
TICKET_LIFETIME,
|
TICKET_LIFETIME,
|
||||||
)
|
)
|
||||||
|
@ -47,7 +48,7 @@ pub fn verify_term_ticket(
|
||||||
pub fn assemble_rsa_ticket(
|
pub fn assemble_rsa_ticket(
|
||||||
keypair: &PKey<Private>,
|
keypair: &PKey<Private>,
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
data: Option<&str>,
|
data: Option<&Userid>,
|
||||||
secret_data: Option<&str>,
|
secret_data: Option<&str>,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
|
|
||||||
|
@ -59,7 +60,8 @@ pub fn assemble_rsa_ticket(
|
||||||
plain.push(':');
|
plain.push(':');
|
||||||
|
|
||||||
if let Some(data) = data {
|
if let Some(data) = data {
|
||||||
plain.push_str(data);
|
use std::fmt::Write;
|
||||||
|
write!(plain, "{}", data)?;
|
||||||
plain.push(':');
|
plain.push(':');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +89,7 @@ pub fn verify_rsa_ticket(
|
||||||
secret_data: Option<&str>,
|
secret_data: Option<&str>,
|
||||||
min_age: i64,
|
min_age: i64,
|
||||||
max_age: i64,
|
max_age: i64,
|
||||||
) -> Result<(i64, Option<String>), Error> {
|
) -> Result<(i64, Option<Userid>), Error> {
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
@ -145,5 +147,5 @@ pub fn verify_rsa_ticket(
|
||||||
bail!("invalid ticket - timestamp too old.");
|
bail!("invalid ticket - timestamp too old.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((age, data))
|
Ok((age, data.map(|s| s.parse()).transpose()?))
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,12 @@ fn worker_task_abort() -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let errmsg = errmsg1.clone();
|
let errmsg = errmsg1.clone();
|
||||||
let res = server::WorkerTask::new_thread("garbage_collection", None, "root@pam", true, move |worker| {
|
let res = server::WorkerTask::new_thread(
|
||||||
|
"garbage_collection",
|
||||||
|
None,
|
||||||
|
proxmox_backup::api2::types::Userid::root_userid().clone(),
|
||||||
|
true,
|
||||||
|
move |worker| {
|
||||||
println!("WORKER {}", worker);
|
println!("WORKER {}", worker);
|
||||||
|
|
||||||
let result = garbage_collection(&worker);
|
let result = garbage_collection(&worker);
|
||||||
|
@ -68,7 +73,8 @@ fn worker_task_abort() -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
Loading…
Reference in New Issue