[pbs-devel] [PATCH proxmox-backup 2/2] api2/access: implement term ticket

Dominik Csapak d.csapak at proxmox.com
Fri Jul 17 15:38:38 CEST 2020


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 at proxmox.com>
---
 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<String>,
+    privs: Option<String>,
+) -> Result<bool, Error> {
     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<Value, Error> {
-    match authenticate_user(&username, &password) {
-        Ok(_) => {
+fn create_ticket(
+    username: String,
+    password: String,
+    path: Option<String>,
+    privs: Option<String>,
+) -> Result<Value, Error> {
+    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<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());
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<Private>,
+    username: &str,
+    path: &str,
+) -> Result<String, Error> {
+    assemble_rsa_ticket(keypair, TERM_PREFIX, None, Some(&format!("{}{}", username, path)))
+}
+
+pub fn verify_term_ticket(
+    keypair: &PKey<Public>,
+    username: &str,
+    path: &str,
+    ticket: &str,
+) -> Result<(i64, Option<String>), Error> {
+    verify_rsa_ticket(keypair, TERM_PREFIX, ticket, Some(&format!("{}{}", username, path)), -300, TICKET_LIFETIME)
+}
 
 pub fn assemble_rsa_ticket(
     keypair: &PKey<Private>,
-- 
2.20.1






More information about the pbs-devel mailing list