api2/access: implement term ticket
modeled after pves/pmgs vncticket (i substituted the vnc with term) by putting the path and username as secret data in the ticket when sending the ticket to /access/ticket it only verifies it, checks the privs on the path and does not generate a new ticket Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
parent
2ab5acac5a
commit
a4d1675513
@ -13,15 +13,22 @@ use crate::auth_helpers::*;
|
||||
use crate::api2::types::*;
|
||||
|
||||
use crate::config::cached_user_info::CachedUserInfo;
|
||||
use crate::config::acl::PRIV_PERMISSIONS_MODIFY;
|
||||
use crate::config::acl::{PRIVILEGES, PRIV_PERMISSIONS_MODIFY};
|
||||
|
||||
pub mod user;
|
||||
pub mod domain;
|
||||
pub mod acl;
|
||||
pub mod role;
|
||||
|
||||
fn authenticate_user(username: &str, password: &str) -> Result<(), Error> {
|
||||
|
||||
/// returns Ok(true) if a ticket has to be created
|
||||
/// and Ok(false) if not
|
||||
fn authenticate_user(
|
||||
username: &str,
|
||||
password: &str,
|
||||
path: Option<String>,
|
||||
privs: Option<String>,
|
||||
port: Option<u16>,
|
||||
) -> Result<bool, Error> {
|
||||
let user_info = CachedUserInfo::new()?;
|
||||
|
||||
if !user_info.is_active_user(&username) {
|
||||
@ -33,14 +40,43 @@ fn authenticate_user(username: &str, password: &str) -> Result<(), Error> {
|
||||
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 ticket_username == username {
|
||||
return Ok(());
|
||||
return Ok(true);
|
||||
} else {
|
||||
bail!("ticket login failed - wrong username");
|
||||
}
|
||||
}
|
||||
} 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");
|
||||
}
|
||||
|
||||
crate::auth::authenticate_user(username, password)
|
||||
let path = path.unwrap();
|
||||
let privilege_name = privs.unwrap();
|
||||
let port = port.unwrap();
|
||||
|
||||
if let Ok((_age, _data)) =
|
||||
tools::ticket::verify_term_ticket(public_auth_key(), &username, &path, port, password)
|
||||
{
|
||||
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(username, &path_vec, *privilege, false)?;
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
bail!("No such privilege");
|
||||
}
|
||||
}
|
||||
|
||||
let _ = crate::auth::authenticate_user(username, password)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[api(
|
||||
@ -52,6 +88,21 @@ fn authenticate_user(username: &str, password: &str) -> Result<(), Error> {
|
||||
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: {
|
||||
@ -78,11 +129,16 @@ fn authenticate_user(username: &str, password: &str) -> Result<(), Error> {
|
||||
/// Create or verify authentication ticket.
|
||||
///
|
||||
/// Returns: An authentication ticket with additional infos.
|
||||
fn create_ticket(username: String, password: String) -> Result<Value, Error> {
|
||||
match authenticate_user(&username, &password) {
|
||||
Ok(_) => {
|
||||
|
||||
let ticket = assemble_rsa_ticket( private_auth_key(), "PBS", Some(&username), None)?;
|
||||
fn create_ticket(
|
||||
username: String,
|
||||
password: String,
|
||||
path: Option<String>,
|
||||
privs: Option<String>,
|
||||
port: Option<u16>,
|
||||
) -> Result<Value, Error> {
|
||||
match authenticate_user(&username, &password, path, privs, port) {
|
||||
Ok(true) => {
|
||||
let ticket = assemble_rsa_ticket(private_auth_key(), "PBS", Some(&username), None)?;
|
||||
|
||||
let token = assemble_csrf_prevention_token(csrf_secret(), &username);
|
||||
|
||||
@ -94,6 +150,9 @@ fn create_ticket(username: String, password: String) -> Result<Value, Error> {
|
||||
"CSRFPreventionToken": token,
|
||||
}))
|
||||
}
|
||||
Ok(false) => Ok(json!({
|
||||
"username": username,
|
||||
})),
|
||||
Err(err) => {
|
||||
let client_ip = "unknown"; // $rpcenv->get_client_ip() || '';
|
||||
log::error!("authentication failure; rhost={} user={} msg={}", client_ip, username, err.to_string());
|
||||
|
@ -11,6 +11,38 @@ use crate::tools::epoch_now_u64;
|
||||
|
||||
pub const TICKET_LIFETIME: i64 = 3600*2; // 2 hours
|
||||
|
||||
const TERM_PREFIX: &str = "PBSTERM";
|
||||
|
||||
pub fn assemble_term_ticket(
|
||||
keypair: &PKey<Private>,
|
||||
username: &str,
|
||||
path: &str,
|
||||
port: u16,
|
||||
) -> Result<String, Error> {
|
||||
assemble_rsa_ticket(
|
||||
keypair,
|
||||
TERM_PREFIX,
|
||||
None,
|
||||
Some(&format!("{}{}{}", username, path, port)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn verify_term_ticket(
|
||||
keypair: &PKey<Public>,
|
||||
username: &str,
|
||||
path: &str,
|
||||
port: u16,
|
||||
ticket: &str,
|
||||
) -> Result<(i64, Option<String>), Error> {
|
||||
verify_rsa_ticket(
|
||||
keypair,
|
||||
TERM_PREFIX,
|
||||
ticket,
|
||||
Some(&format!("{}{}{}", username, path, port)),
|
||||
-300,
|
||||
TICKET_LIFETIME,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assemble_rsa_ticket(
|
||||
keypair: &PKey<Private>,
|
||||
|
Loading…
Reference in New Issue
Block a user