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 5288363E9E for ; Fri, 17 Jul 2020 15:38:42 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4D2D11C98C for ; Fri, 17 Jul 2020 15:38:42 +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 4C9401C97B for ; Fri, 17 Jul 2020 15:38: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 18EA443146 for ; Fri, 17 Jul 2020 15:38:41 +0200 (CEST) From: Dominik Csapak To: pbs-devel@lists.proxmox.com Date: Fri, 17 Jul 2020 15:38:38 +0200 Message-Id: <20200717133838.7244-3-d.csapak@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200717133838.7244-1-d.csapak@proxmox.com> References: <20200717133838.7244-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.007 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/2] 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: Fri, 17 Jul 2020 13:38:42 -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 --- src/api2/access.rs | 70 +++++++++++++++++++++++++++++++++++++++------ src/tools/ticket.rs | 18 ++++++++++++ 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/api2/access.rs b/src/api2/access.rs index f5855ed..780fa9b 100644 --- a/src/api2/access.rs +++ b/src/api2/access.rs @@ -13,15 +13,21 @@ 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, +) -> Result { let user_info = CachedUserInfo::new()?; if !user_info.is_active_user(&username) { @@ -33,14 +39,42 @@ 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() { + bail!("cannot check termnal ticket without path and priv"); + } + + let path = path.unwrap(); + let privilege_name = privs.unwrap(); + + if let Ok((_age, _data)) = tools::ticket::verify_term_ticket(public_auth_key(), &username, &path, 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 +86,16 @@ fn authenticate_user(username: &str, password: &str) -> Result<(), Error> { password: { schema: PASSWORD_SCHEMA, }, + path: { + type: String, + description: "path", + optional: true, + }, + privs: { + type: String, + description: "privs", + optional: true, + }, }, }, returns: { @@ -78,9 +122,14 @@ 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(_) => { +fn create_ticket( + username: String, + password: String, + path: Option, + privs: Option, +) -> Result { + match authenticate_user(&username, &password, path, privs) { + Ok(true) => { let ticket = assemble_rsa_ticket( private_auth_key(), "PBS", Some(&username), None)?; @@ -94,6 +143,11 @@ 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..fc8750e 100644 --- a/src/tools/ticket.rs +++ b/src/tools/ticket.rs @@ -11,6 +11,24 @@ 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, +) -> Result { + assemble_rsa_ticket(keypair, TERM_PREFIX, None, Some(&format!("{}{}", username, path))) +} + +pub fn verify_term_ticket( + keypair: &PKey, + username: &str, + path: &str, + ticket: &str, +) -> Result<(i64, Option), Error> { + verify_rsa_ticket(keypair, TERM_PREFIX, ticket, Some(&format!("{}{}", username, path)), -300, TICKET_LIFETIME) +} pub fn assemble_rsa_ticket( keypair: &PKey, -- 2.20.1