From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id B8E3F64D14 for ; Tue, 21 Jul 2020 11:10:43 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id AE86916514 for ; Tue, 21 Jul 2020 11:10:43 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 8626B164E1 for ; Tue, 21 Jul 2020 11:10:41 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 4AF8D43302 for ; Tue, 21 Jul 2020 11:10:41 +0200 (CEST) From: Dominik Csapak To: pbs-devel@lists.proxmox.com Date: Tue, 21 Jul 2020 11:10:37 +0200 Message-Id: <20200721091040.7632-3-d.csapak@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200721091040.7632-1-d.csapak@proxmox.com> References: <20200721091040.7632-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.011 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods NO_DNS_FOR_FROM 0.379 Envelope sender has no MX or A DNS records RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [access.rs, ticket.rs] Subject: [pbs-devel] [PATCH proxmox-backup 2/5] api2/access: implement term ticket X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 21 Jul 2020 09:10:43 -0000 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 --- changes from last version: * include the port in the termticket * use rstfmt 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 f5855ed..46fbd99 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 4727b1e..ff639ce 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, -- 2.20.1