proxmox-backup/src/api2/access.rs

259 lines
7.3 KiB
Rust
Raw Normal View History

use anyhow::{bail, format_err, Error};
2019-01-30 14:14:20 +00:00
use serde_json::{json, Value};
use proxmox::api::{api, RpcEnvironment, Permission};
use proxmox::api::router::{Router, SubdirMap};
2020-04-09 11:34:07 +00:00
use proxmox::{sortable, identity};
use proxmox::{http_err, list_subdirs_api_method};
use crate::tools::ticket::{self, Empty, Ticket};
2019-01-30 14:14:20 +00:00
use crate::auth_helpers::*;
use crate::api2::types::*;
use crate::tools::{FileLogOptions, FileLogger};
2020-04-16 08:01:59 +00:00
use crate::config::cached_user_info::CachedUserInfo;
use crate::config::acl::{PRIVILEGES, PRIV_PERMISSIONS_MODIFY};
pub mod user;
2020-04-09 09:36:45 +00:00
pub mod domain;
2020-04-13 09:09:44 +00:00
pub mod acl;
pub mod role;
2019-01-30 14:14:20 +00:00
/// returns Ok(true) if a ticket has to be created
/// and Ok(false) if not
fn authenticate_user(
userid: &Userid,
password: &str,
path: Option<String>,
privs: Option<String>,
port: Option<u16>,
) -> Result<bool, Error> {
2020-04-16 08:01:59 +00:00
let user_info = CachedUserInfo::new()?;
if !user_info.is_active_user(&userid) {
2020-04-16 08:01:59 +00:00
bail!("user account disabled or expired.");
}
if password.starts_with("PBS:") {
if let Ok(ticket_userid) = Ticket::<Userid>::parse(password)
.and_then(|ticket| ticket.verify(public_auth_key(), "PBS", None))
{
if *userid == ticket_userid {
return Ok(true);
}
bail!("ticket login failed - wrong userid");
}
} else if password.starts_with("PBSTERM:") {
if path.is_none() || privs.is_none() || port.is_none() {
bail!("cannot check termnal ticket without path, priv and port");
}
let path = path.ok_or_else(|| format_err!("missing path for termproxy ticket"))?;
let privilege_name = privs
.ok_or_else(|| format_err!("missing privilege name for termproxy ticket"))?;
let port = port.ok_or_else(|| format_err!("missing port for termproxy ticket"))?;
if let Ok(Empty) = Ticket::parse(password)
.and_then(|ticket| ticket.verify(
public_auth_key(),
ticket::TERM_PREFIX,
Some(&ticket::term_aad(userid, &path, port)),
))
{
for (name, privilege) in PRIVILEGES {
if *name == privilege_name {
let mut path_vec = Vec::new();
for part in path.split('/') {
if part != "" {
path_vec.push(part);
}
}
user_info.check_privs(userid, &path_vec, *privilege, false)?;
return Ok(false);
}
}
bail!("No such privilege");
}
}
let _ = crate::auth::authenticate_user(userid, password)?;
Ok(true)
2019-01-30 14:14:20 +00:00
}
#[api(
input: {
properties: {
username: {
type: Userid,
},
password: {
schema: PASSWORD_SCHEMA,
},
path: {
type: String,
description: "Path for verifying terminal tickets.",
optional: true,
},
privs: {
type: String,
description: "Privilege for verifying terminal tickets.",
optional: true,
},
port: {
type: Integer,
description: "Port for verifying terminal tickets.",
optional: true,
},
},
},
returns: {
properties: {
username: {
type: String,
description: "User name.",
},
ticket: {
type: String,
description: "Auth ticket.",
},
CSRFPreventionToken: {
type: String,
description: "Cross Site Request Forgery Prevention Token.",
},
},
},
protected: true,
2020-04-16 08:01:59 +00:00
access: {
permission: &Permission::World,
},
)]
/// Create or verify authentication ticket.
///
/// Returns: An authentication ticket with additional infos.
fn create_ticket(
username: Userid,
password: String,
path: Option<String>,
privs: Option<String>,
port: Option<u16>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let logger_options = FileLogOptions {
append: true,
prefix_time: true,
..Default::default()
};
let mut auth_log = FileLogger::new("/var/log/proxmox-backup/api/auth.log", logger_options)?;
match authenticate_user(&username, &password, path, privs, port) {
Ok(true) => {
let ticket = Ticket::new("PBS", &username)?.sign(private_auth_key(), None)?;
2019-01-30 14:14:20 +00:00
let token = assemble_csrf_prevention_token(csrf_secret(), &username);
2019-01-30 14:14:20 +00:00
auth_log.log(format!("successful auth for user '{}'", username));
2019-01-30 14:14:20 +00:00
2019-10-26 09:36:01 +00:00
Ok(json!({
2019-01-30 14:14:20 +00:00
"username": username,
"ticket": ticket,
"CSRFPreventionToken": token,
2019-10-26 09:36:01 +00:00
}))
2019-01-30 14:14:20 +00:00
}
Ok(false) => Ok(json!({
"username": username,
})),
2019-01-30 14:14:20 +00:00
Err(err) => {
let client_ip = match rpcenv.get_client_ip().map(|addr| addr.ip()) {
Some(ip) => format!("{}", ip),
None => "unknown".into(),
};
let msg = format!(
"authentication failure; rhost={} user={} msg={}",
client_ip,
username,
err.to_string()
);
auth_log.log(&msg);
log::error!("{}", msg);
Err(http_err!(UNAUTHORIZED, "permission check failed."))
2019-01-30 14:14:20 +00:00
}
}
}
#[api(
input: {
properties: {
userid: {
type: Userid,
},
password: {
schema: PASSWORD_SCHEMA,
},
},
},
2020-04-16 08:01:59 +00:00
access: {
description: "Anybody is allowed to change there own password. In addition, users with 'Permissions:Modify' privilege may change any password.",
2020-04-16 08:01:59 +00:00
permission: &Permission::Anybody,
},
)]
/// Change user password
///
/// Each user is allowed to change his own password. Superuser
/// can change all passwords.
fn change_password(
userid: Userid,
password: String,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let current_user: Userid = rpcenv
.get_user()
.ok_or_else(|| format_err!("unknown user"))?
.parse()?;
let mut allowed = userid == current_user;
if userid == "root@pam" { allowed = true; }
if !allowed {
let user_info = CachedUserInfo::new()?;
let privs = user_info.lookup_privs(&current_user, &[]);
if (privs & PRIV_PERMISSIONS_MODIFY) != 0 { allowed = true; }
}
if !allowed {
bail!("you are not authorized to change the password.");
}
let authenticator = crate::auth::lookup_authenticator(userid.realm())?;
authenticator.store_password(userid.name(), &password)?;
Ok(Value::Null)
}
#[sortable]
2020-04-09 11:34:07 +00:00
const SUBDIRS: SubdirMap = &sorted!([
2020-04-13 09:09:44 +00:00
("acl", &acl::ROUTER),
(
"password", &Router::new()
.put(&API_METHOD_CHANGE_PASSWORD)
),
2019-11-21 08:36:41 +00:00
(
"ticket", &Router::new()
.post(&API_METHOD_CREATE_TICKET)
),
2020-04-09 09:36:45 +00:00
("domains", &domain::ROUTER),
("roles", &role::ROUTER),
("users", &user::ROUTER),
2020-04-09 11:34:07 +00:00
]);
2019-11-21 08:36:41 +00:00
pub const ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(SUBDIRS))
.subdirs(SUBDIRS);