From 4b40148caa113d478d9ba60a304fe02f04651a0c Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Thu, 16 Apr 2020 10:01:59 +0200 Subject: [PATCH] start impl. access permissions --- src/api2/access.rs | 17 +++++- src/api2/access/domain.rs | 9 ++-- src/api2/node/dns.rs | 9 +++- src/api2/node/network.rs | 35 ++++++++----- src/api2/node/time.rs | 106 +++++++++++++++++++------------------- src/api2/types.rs | 7 +++ src/api2/version.rs | 4 +- src/auth.rs | 21 +------- src/server/rest.rs | 36 +++++++++---- 9 files changed, 139 insertions(+), 105 deletions(-) diff --git a/src/api2/access.rs b/src/api2/access.rs index 6d819349..f4ee79cf 100644 --- a/src/api2/access.rs +++ b/src/api2/access.rs @@ -2,7 +2,7 @@ use failure::*; use serde_json::{json, Value}; -use proxmox::api::{api, RpcEnvironment}; +use proxmox::api::{api, RpcEnvironment, Permission}; use proxmox::api::router::{Router, SubdirMap}; use proxmox::{sortable, identity}; use proxmox::{http_err, list_subdirs_api_method}; @@ -11,6 +11,7 @@ use crate::tools; use crate::tools::ticket::*; use crate::auth_helpers::*; use crate::api2::types::*; +use crate::config::cached_user_info::CachedUserInfo; pub mod user; pub mod domain; @@ -18,6 +19,12 @@ pub mod acl; fn authenticate_user(username: &str, password: &str) -> Result<(), Error> { + let user_info = CachedUserInfo::new()?; + + if !user_info.is_active_user(&username) { + bail!("user account disabled or expired."); + } + let ticket_lifetime = tools::ticket::TICKET_LIFETIME; if password.starts_with("PBS:") { @@ -61,6 +68,9 @@ fn authenticate_user(username: &str, password: &str) -> Result<(), Error> { }, }, protected: true, + access: { + permission: &Permission::World, + }, )] /// Create or verify authentication ticket. /// @@ -100,6 +110,11 @@ fn create_ticket(username: String, password: String) -> Result { }, }, }, + access: { + description: "Anybody is allowed to change there own password. The Superuser may change any password.", + permission: &Permission::Anybody, + }, + )] /// Change user password /// diff --git a/src/api2/access/domain.rs b/src/api2/access/domain.rs index 02555923..f899e59f 100644 --- a/src/api2/access/domain.rs +++ b/src/api2/access/domain.rs @@ -2,7 +2,7 @@ use failure::*; use serde_json::{json, Value}; -use proxmox::api::api; +use proxmox::api::{api, Permission}; use proxmox::api::router::Router; use crate::api2::types::*; @@ -29,12 +29,13 @@ use crate::api2::types::*; } }, } + }, + access: { + description: "Anyone can access this, because we need that list for the login box (before the user is authenticated).", + permission: &Permission::World, } )] /// Authentication domain/realm index. -/// -/// Anyone can access this, because we need that list for the login -/// box (before the user is authenticated). fn list_domains() -> Result { let mut list = Vec::new(); list.push(json!({ "realm": "pam", "comment": "Linux PAM standard authentication", "default": true })); diff --git a/src/api2/node/dns.rs b/src/api2/node/dns.rs index 31e53595..419973e9 100644 --- a/src/api2/node/dns.rs +++ b/src/api2/node/dns.rs @@ -6,11 +6,12 @@ use openssl::sha; use regex::Regex; use serde_json::{json, Value}; -use proxmox::api::{api, ApiMethod, Router, RpcEnvironment}; +use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32}; use crate::api2::types::*; +use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; static RESOLV_CONF_FN: &str = "/etc/resolv.conf"; @@ -77,6 +78,9 @@ pub fn read_etc_resolv_conf() -> Result { }, }, }, + access: { + permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false), + } )] /// Update DNS settings fn update_dns( @@ -158,6 +162,9 @@ fn update_dns( }, }, }, + access: { + permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), + } )] /// Read DNS settings. fn get_dns( diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs index 0cef10d2..42627e78 100644 --- a/src/api2/node/network.rs +++ b/src/api2/node/network.rs @@ -1,28 +1,37 @@ use failure::*; use serde_json::{json, Value}; -use proxmox::api::{ApiHandler, ApiMethod, Router, RpcEnvironment}; -use proxmox::api::schema::ObjectSchema; +use proxmox::api::{api, Router, Permission}; use crate::api2::types::*; +use crate::config::acl::{PRIV_SYS_AUDIT}; +#[api( + input: { + properties: { + node: { + schema: NODE_SCHEMA, + }, + }, + }, + returns: { + description: "The network configuration from /etc/network/interfaces.", + properties: { + // fixme + }, + }, + access: { + permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), + }, +)] +/// Read network configuration. fn get_network_config( _param: Value, - _info: &ApiMethod, - _rpcenv: &mut dyn RpcEnvironment, ) -> Result { Ok(json!({})) } pub const ROUTER: Router = Router::new() - .get( - &ApiMethod::new( - &ApiHandler::Sync(&get_network_config), - &ObjectSchema::new( - "Read network configuration.", - &[ ("node", false, &NODE_SCHEMA) ], - ) - ) - ); + .get(&API_METHOD_GET_NETWORK_CONFIG); diff --git a/src/api2/node/time.rs b/src/api2/node/time.rs index 855dcc2a..a87e8a0e 100644 --- a/src/api2/node/time.rs +++ b/src/api2/node/time.rs @@ -4,9 +4,7 @@ use chrono::prelude::*; use failure::*; use serde_json::{json, Value}; -use proxmox::{sortable, identity}; -use proxmox::api::{ApiHandler, ApiMethod, Router, RpcEnvironment}; -use proxmox::api::schema::*; +use proxmox::api::{api, Router, Permission}; use proxmox::tools::fs::{file_read_firstline, replace_file, CreateOptions}; use crate::api2::types::*; @@ -41,11 +39,38 @@ fn read_etc_localtime() -> Result { } } -fn get_time( - _param: Value, - _info: &ApiMethod, - _rpcenv: &mut dyn RpcEnvironment, -) -> Result { +#[api( + input: { + properties: { + node: { + schema: NODE_SCHEMA, + }, + }, + }, + returns: { + description: "Returns server time and timezone.", + properties: { + timezone: { + schema: TIME_ZONE_SCHEMA, + }, + time: { + type: i64, + description: "Seconds since 1970-01-01 00:00:00 UTC.", + minimum: 1_297_163_644, + }, + localtime: { + type: i64, + description: "Seconds since 1970-01-01 00:00:00 UTC. (local time)", + minimum: 1_297_163_644, + }, + } + }, + access: { + permission: &Permission::Anybody, + }, +)] +/// Read server time and time zone settings. +fn get_time(_param: Value) -> Result { let datetime = Local::now(); let offset = datetime.offset(); let time = datetime.timestamp(); @@ -58,13 +83,25 @@ fn get_time( })) } +#[api( + protected: true, + reload_timezone: true, + input: { + properties: { + node: { + schema: NODE_SCHEMA, + }, + timezone: { + schema: TIME_ZONE_SCHEMA, + }, + }, + }, +)] +/// Set time zone fn set_timezone( - param: Value, - _info: &ApiMethod, - _rpcenv: &mut dyn RpcEnvironment, + timezone: String, + _param: Value, ) -> Result { - let timezone = crate::tools::required_string_param(¶m, "timezone")?; - let path = std::path::PathBuf::from(format!("/usr/share/zoneinfo/{}", timezone)); if !path.exists() { @@ -81,45 +118,6 @@ fn set_timezone( Ok(Value::Null) } -#[sortable] pub const ROUTER: Router = Router::new() - .get( - &ApiMethod::new( - &ApiHandler::Sync(&get_time), - &ObjectSchema::new( - "Read server time and time zone settings.", - &sorted!([ ("node", false, &NODE_SCHEMA) ]), - ) - ).returns( - &ObjectSchema::new( - "Returns server time and timezone.", - &sorted!([ - ("timezone", false, &StringSchema::new("Time zone").schema()), - ("time", false, &IntegerSchema::new("Seconds since 1970-01-01 00:00:00 UTC.") - .minimum(1_297_163_644) - .schema() - ), - ("localtime", false, &IntegerSchema::new("Seconds since 1970-01-01 00:00:00 UTC. (local time)") - .minimum(1_297_163_644) - .schema() - ), - ]), - ).schema() - ) - ) - .put( - &ApiMethod::new( - &ApiHandler::Sync(&set_timezone), - &ObjectSchema::new( - "Set time zone.", - &sorted!([ - ("node", false, &NODE_SCHEMA), - ("timezone", false, &StringSchema::new( - "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.") - .schema() - ), - ]), - ) - ).protected(true).reload_timezone(true) - ); - + .get(&API_METHOD_GET_TIME) + .put(&API_METHOD_SET_TIMEZONE); diff --git a/src/api2/types.rs b/src/api2/types.rs index 9e153d89..5cb48435 100644 --- a/src/api2/types.rs +++ b/src/api2/types.rs @@ -164,6 +164,13 @@ pub const THIRD_DNS_SERVER_SCHEMA: Schema = .format(&IP_FORMAT) .schema(); +pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new( + "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.") + .format(&SINGLE_LINE_COMMENT_FORMAT) + .min_length(2) + .max_length(64) + .schema(); + pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema = StringSchema::new("Backup archive name.") .format(&PROXMOX_SAFE_ID_FORMAT) diff --git a/src/api2/version.rs b/src/api2/version.rs index 2e0d20e7..4935990f 100644 --- a/src/api2/version.rs +++ b/src/api2/version.rs @@ -1,7 +1,7 @@ use failure::*; use serde_json::{json, Value}; -use proxmox::api::{ApiHandler, ApiMethod, Router, RpcEnvironment}; +use proxmox::api::{ApiHandler, ApiMethod, Router, RpcEnvironment, Permission}; use proxmox::api::schema::ObjectSchema; pub const PROXMOX_PKG_VERSION: &str = @@ -31,6 +31,6 @@ pub const ROUTER: Router = Router::new() &ApiMethod::new( &ApiHandler::Sync(&get_version), &ObjectSchema::new("Proxmox Backup Server API version.", &[]) - ) + ).access(None, &Permission::Anybody) ); diff --git a/src/auth.rs b/src/auth.rs index 6a5d8a96..e011cdcc 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -130,6 +130,7 @@ pub fn parse_userid(userid: &str) -> Result<(String, String), Error> { Ok((data[1].to_owned(), data[0].to_owned())) } +/// Lookup the autenticator for the specified realm pub fn lookup_authenticator(realm: &str) -> Result, Error> { match realm { "pam" => Ok(Box::new(PAM())), @@ -138,28 +139,10 @@ pub fn lookup_authenticator(realm: &str) -> Result } } +/// Authenticate users pub fn authenticate_user(userid: &str, password: &str) -> Result<(), Error> { let (username, realm) = parse_userid(userid)?; - let (user_config, _digest) = crate::config::user::config()?; - let user: Result = user_config.lookup("user", userid); - match user { - Ok(user) => { - if let Some(false) = user.enable { - bail!("account disabled"); - } - if let Some(expire) = user.expire { - if expire > 0 { - let now = unsafe { libc::time(std::ptr::null_mut()) }; - if expire <= now { - bail!("account expired"); - } - } - } - }, - Err(_) => bail!("no such user"), - } - lookup_authenticator(&realm)? .authenticate_user(&username, password) } diff --git a/src/server/rest.rs b/src/server/rest.rs index 39bccfd7..45067b41 100644 --- a/src/server/rest.rs +++ b/src/server/rest.rs @@ -19,7 +19,7 @@ use url::form_urlencoded; use proxmox::http_err; use proxmox::api::{ApiHandler, ApiMethod, HttpError}; -use proxmox::api::{RpcEnvironment, RpcEnvironmentType}; +use proxmox::api::{RpcEnvironment, RpcEnvironmentType, check_api_permission}; use proxmox::api::schema::{ObjectSchema, parse_simple_value, verify_json_object, parse_parameter_strings}; use super::environment::RestEnvironment; @@ -28,6 +28,7 @@ use super::ApiConfig; use crate::auth_helpers::*; use crate::tools; +use crate::config::cached_user_info::CachedUserInfo; extern "C" { fn tzset(); } @@ -468,7 +469,12 @@ fn extract_auth_data(headers: &http::HeaderMap) -> (Option, Option, token: &Option) -> Result { +fn check_auth( + method: &hyper::Method, + ticket: &Option, + token: &Option, + user_info: &CachedUserInfo, +) -> Result { let ticket_lifetime = tools::ticket::TICKET_LIFETIME; @@ -481,6 +487,10 @@ fn check_auth(method: &hyper::Method, ticket: &Option, token: &Option bail!("missing ticket"), }; + if !user_info.is_active_user(&username) { + bail!("user account disabled or expired."); + } + if method != hyper::Method::GET { if let Some(token) = token { println!("CSRF prevention token: {:?}", token); @@ -508,6 +518,8 @@ pub async fn handle_request(api: Arc, req: Request) -> Result= 1 && components[0] == "api2" { @@ -531,16 +543,11 @@ pub async fn handle_request(api: Arc, req: Request) -> Result { - - // fixme: check permissions - - rpcenv.set_user(Some(username)); - } + match check_auth(&method, &ticket, &token, &user_info) { + Ok(username) => rpcenv.set_user(Some(username)), Err(err) => { // always delay unauthorized calls by 3 seconds (from start of request) - let err = http_err!(UNAUTHORIZED, format!("permission check failed - {}", err)); + let err = http_err!(UNAUTHORIZED, format!("authentication failed - {}", err)); tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await; return Ok((formatter.format_error)(err)); } @@ -553,6 +560,13 @@ pub async fn handle_request(api: Arc, req: Request) -> Result { + let user = rpcenv.get_user(); + if !check_api_permission(api_method.access.permission, user.as_deref(), &uri_param, &user_info) { + let err = http_err!(FORBIDDEN, format!("permission check failed")); + tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await; + return Ok((formatter.format_error)(err)); + } + let result = if api_method.protected && env_type == RpcEnvironmentType::PUBLIC { proxy_protected_request(api_method, parts, body).await } else { @@ -577,7 +591,7 @@ pub async fn handle_request(api: Arc, req: Request) -> Result { let new_token = assemble_csrf_prevention_token(csrf_secret(), &username); return Ok(get_index(Some(username), Some(new_token)));