From a4d1675513118c3549ba0267485cb38595e30e36 Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Tue, 21 Jul 2020 11:10:37 +0200 Subject: [PATCH] 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 --- src/api2/access.rs | 79 +++++++++++++++++++++++++++++++++++++++------ src/tools/ticket.rs | 32 ++++++++++++++++++ 2 files changed, 101 insertions(+), 10 deletions(-) diff --git a/src/api2/access.rs b/src/api2/access.rs index f5855ede..46fbd991 100644 --- a/src/api2/access.rs +++ b/src/api2/access.rs @@ -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, + privs: Option, + port: Option, +) -> Result { 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"); + } + + 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"); + } } - crate::auth::authenticate_user(username, password) + 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 { - 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, + privs: Option, + port: Option, +) -> Result { + 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 { "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()); diff --git a/src/tools/ticket.rs b/src/tools/ticket.rs index 4727b1ef..ff639ce1 100644 --- a/src/tools/ticket.rs +++ b/src/tools/ticket.rs @@ -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, + username: &str, + path: &str, + port: u16, +) -> Result { + assemble_rsa_ticket( + keypair, + TERM_PREFIX, + None, + Some(&format!("{}{}{}", username, path, port)), + ) +} + +pub fn verify_term_ticket( + keypair: &PKey, + username: &str, + path: &str, + port: u16, + ticket: &str, +) -> Result<(i64, Option), Error> { + verify_rsa_ticket( + keypair, + TERM_PREFIX, + ticket, + Some(&format!("{}{}{}", username, path, port)), + -300, + TICKET_LIFETIME, + ) +} pub fn assemble_rsa_ticket( keypair: &PKey,