start impl. access permissions

This commit is contained in:
Dietmar Maurer 2020-04-16 10:01:59 +02:00
parent 423e656163
commit 4b40148caa
9 changed files with 139 additions and 105 deletions

View File

@ -2,7 +2,7 @@ use failure::*;
use serde_json::{json, Value}; use serde_json::{json, Value};
use proxmox::api::{api, RpcEnvironment}; use proxmox::api::{api, RpcEnvironment, Permission};
use proxmox::api::router::{Router, SubdirMap}; use proxmox::api::router::{Router, SubdirMap};
use proxmox::{sortable, identity}; use proxmox::{sortable, identity};
use proxmox::{http_err, list_subdirs_api_method}; use proxmox::{http_err, list_subdirs_api_method};
@ -11,6 +11,7 @@ use crate::tools;
use crate::tools::ticket::*; use crate::tools::ticket::*;
use crate::auth_helpers::*; use crate::auth_helpers::*;
use crate::api2::types::*; use crate::api2::types::*;
use crate::config::cached_user_info::CachedUserInfo;
pub mod user; pub mod user;
pub mod domain; pub mod domain;
@ -18,6 +19,12 @@ pub mod acl;
fn authenticate_user(username: &str, password: &str) -> Result<(), Error> { 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; let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
if password.starts_with("PBS:") { if password.starts_with("PBS:") {
@ -61,6 +68,9 @@ fn authenticate_user(username: &str, password: &str) -> Result<(), Error> {
}, },
}, },
protected: true, protected: true,
access: {
permission: &Permission::World,
},
)] )]
/// Create or verify authentication ticket. /// Create or verify authentication ticket.
/// ///
@ -100,6 +110,11 @@ fn create_ticket(username: String, password: String) -> Result<Value, Error> {
}, },
}, },
}, },
access: {
description: "Anybody is allowed to change there own password. The Superuser may change any password.",
permission: &Permission::Anybody,
},
)] )]
/// Change user password /// Change user password
/// ///

View File

@ -2,7 +2,7 @@ use failure::*;
use serde_json::{json, Value}; use serde_json::{json, Value};
use proxmox::api::api; use proxmox::api::{api, Permission};
use proxmox::api::router::Router; use proxmox::api::router::Router;
use crate::api2::types::*; 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. /// 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<Value, Error> { fn list_domains() -> Result<Value, Error> {
let mut list = Vec::new(); let mut list = Vec::new();
list.push(json!({ "realm": "pam", "comment": "Linux PAM standard authentication", "default": true })); list.push(json!({ "realm": "pam", "comment": "Linux PAM standard authentication", "default": true }));

View File

@ -6,11 +6,12 @@ use openssl::sha;
use regex::Regex; use regex::Regex;
use serde_json::{json, Value}; 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::tools::fs::{file_get_contents, replace_file, CreateOptions};
use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32}; use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
use crate::api2::types::*; use crate::api2::types::*;
use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
static RESOLV_CONF_FN: &str = "/etc/resolv.conf"; static RESOLV_CONF_FN: &str = "/etc/resolv.conf";
@ -77,6 +78,9 @@ pub fn read_etc_resolv_conf() -> Result<Value, Error> {
}, },
}, },
}, },
access: {
permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
}
)] )]
/// Update DNS settings /// Update DNS settings
fn update_dns( fn update_dns(
@ -158,6 +162,9 @@ fn update_dns(
}, },
}, },
}, },
access: {
permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false),
}
)] )]
/// Read DNS settings. /// Read DNS settings.
fn get_dns( fn get_dns(

View File

@ -1,28 +1,37 @@
use failure::*; use failure::*;
use serde_json::{json, Value}; use serde_json::{json, Value};
use proxmox::api::{ApiHandler, ApiMethod, Router, RpcEnvironment}; use proxmox::api::{api, Router, Permission};
use proxmox::api::schema::ObjectSchema;
use crate::api2::types::*; 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( fn get_network_config(
_param: Value, _param: Value,
_info: &ApiMethod,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
Ok(json!({})) Ok(json!({}))
} }
pub const ROUTER: Router = Router::new() pub const ROUTER: Router = Router::new()
.get( .get(&API_METHOD_GET_NETWORK_CONFIG);
&ApiMethod::new(
&ApiHandler::Sync(&get_network_config),
&ObjectSchema::new(
"Read network configuration.",
&[ ("node", false, &NODE_SCHEMA) ],
)
)
);

View File

@ -4,9 +4,7 @@ use chrono::prelude::*;
use failure::*; use failure::*;
use serde_json::{json, Value}; use serde_json::{json, Value};
use proxmox::{sortable, identity}; use proxmox::api::{api, Router, Permission};
use proxmox::api::{ApiHandler, ApiMethod, Router, RpcEnvironment};
use proxmox::api::schema::*;
use proxmox::tools::fs::{file_read_firstline, replace_file, CreateOptions}; use proxmox::tools::fs::{file_read_firstline, replace_file, CreateOptions};
use crate::api2::types::*; use crate::api2::types::*;
@ -41,11 +39,38 @@ fn read_etc_localtime() -> Result<String, Error> {
} }
} }
fn get_time( #[api(
_param: Value, input: {
_info: &ApiMethod, properties: {
_rpcenv: &mut dyn RpcEnvironment, node: {
) -> Result<Value, Error> { 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<Value, Error> {
let datetime = Local::now(); let datetime = Local::now();
let offset = datetime.offset(); let offset = datetime.offset();
let time = datetime.timestamp(); 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( fn set_timezone(
param: Value, timezone: String,
_info: &ApiMethod, _param: Value,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
let timezone = crate::tools::required_string_param(&param, "timezone")?;
let path = std::path::PathBuf::from(format!("/usr/share/zoneinfo/{}", timezone)); let path = std::path::PathBuf::from(format!("/usr/share/zoneinfo/{}", timezone));
if !path.exists() { if !path.exists() {
@ -81,45 +118,6 @@ fn set_timezone(
Ok(Value::Null) Ok(Value::Null)
} }
#[sortable]
pub const ROUTER: Router = Router::new() pub const ROUTER: Router = Router::new()
.get( .get(&API_METHOD_GET_TIME)
&ApiMethod::new( .put(&API_METHOD_SET_TIMEZONE);
&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)
);

View File

@ -164,6 +164,13 @@ pub const THIRD_DNS_SERVER_SCHEMA: Schema =
.format(&IP_FORMAT) .format(&IP_FORMAT)
.schema(); .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 = pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema =
StringSchema::new("Backup archive name.") StringSchema::new("Backup archive name.")
.format(&PROXMOX_SAFE_ID_FORMAT) .format(&PROXMOX_SAFE_ID_FORMAT)

View File

@ -1,7 +1,7 @@
use failure::*; use failure::*;
use serde_json::{json, Value}; 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; use proxmox::api::schema::ObjectSchema;
pub const PROXMOX_PKG_VERSION: &str = pub const PROXMOX_PKG_VERSION: &str =
@ -31,6 +31,6 @@ pub const ROUTER: Router = Router::new()
&ApiMethod::new( &ApiMethod::new(
&ApiHandler::Sync(&get_version), &ApiHandler::Sync(&get_version),
&ObjectSchema::new("Proxmox Backup Server API version.", &[]) &ObjectSchema::new("Proxmox Backup Server API version.", &[])
) ).access(None, &Permission::Anybody)
); );

View File

@ -130,6 +130,7 @@ pub fn parse_userid(userid: &str) -> Result<(String, String), Error> {
Ok((data[1].to_owned(), data[0].to_owned())) Ok((data[1].to_owned(), data[0].to_owned()))
} }
/// Lookup the autenticator for the specified realm
pub fn lookup_authenticator(realm: &str) -> Result<Box<dyn ProxmoxAuthenticator>, Error> { pub fn lookup_authenticator(realm: &str) -> Result<Box<dyn ProxmoxAuthenticator>, Error> {
match realm { match realm {
"pam" => Ok(Box::new(PAM())), "pam" => Ok(Box::new(PAM())),
@ -138,28 +139,10 @@ pub fn lookup_authenticator(realm: &str) -> Result<Box<dyn ProxmoxAuthenticator>
} }
} }
/// Authenticate users
pub fn authenticate_user(userid: &str, password: &str) -> Result<(), Error> { pub fn authenticate_user(userid: &str, password: &str) -> Result<(), Error> {
let (username, realm) = parse_userid(userid)?; let (username, realm) = parse_userid(userid)?;
let (user_config, _digest) = crate::config::user::config()?;
let user: Result<crate::config::user::User, Error> = 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)? lookup_authenticator(&realm)?
.authenticate_user(&username, password) .authenticate_user(&username, password)
} }

View File

@ -19,7 +19,7 @@ use url::form_urlencoded;
use proxmox::http_err; use proxmox::http_err;
use proxmox::api::{ApiHandler, ApiMethod, HttpError}; 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 proxmox::api::schema::{ObjectSchema, parse_simple_value, verify_json_object, parse_parameter_strings};
use super::environment::RestEnvironment; use super::environment::RestEnvironment;
@ -28,6 +28,7 @@ use super::ApiConfig;
use crate::auth_helpers::*; use crate::auth_helpers::*;
use crate::tools; use crate::tools;
use crate::config::cached_user_info::CachedUserInfo;
extern "C" { fn tzset(); } extern "C" { fn tzset(); }
@ -468,7 +469,12 @@ fn extract_auth_data(headers: &http::HeaderMap) -> (Option<String>, Option<Strin
(ticket, token) (ticket, token)
} }
fn check_auth(method: &hyper::Method, ticket: &Option<String>, token: &Option<String>) -> Result<String, Error> { fn check_auth(
method: &hyper::Method,
ticket: &Option<String>,
token: &Option<String>,
user_info: &CachedUserInfo,
) -> Result<String, Error> {
let ticket_lifetime = tools::ticket::TICKET_LIFETIME; let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
@ -481,6 +487,10 @@ fn check_auth(method: &hyper::Method, ticket: &Option<String>, token: &Option<St
None => bail!("missing ticket"), None => bail!("missing ticket"),
}; };
if !user_info.is_active_user(&username) {
bail!("user account disabled or expired.");
}
if method != hyper::Method::GET { if method != hyper::Method::GET {
if let Some(token) = token { if let Some(token) = token {
println!("CSRF prevention token: {:?}", token); println!("CSRF prevention token: {:?}", token);
@ -508,6 +518,8 @@ pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<R
let env_type = api.env_type(); let env_type = api.env_type();
let mut rpcenv = RestEnvironment::new(env_type); let mut rpcenv = RestEnvironment::new(env_type);
let user_info = CachedUserInfo::new()?;
let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000); let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000);
if comp_len >= 1 && components[0] == "api2" { if comp_len >= 1 && components[0] == "api2" {
@ -531,16 +543,11 @@ pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<R
// explicitly allow those calls without auth // explicitly allow those calls without auth
} else { } else {
let (ticket, token) = extract_auth_data(&parts.headers); let (ticket, token) = extract_auth_data(&parts.headers);
match check_auth(&method, &ticket, &token) { match check_auth(&method, &ticket, &token, &user_info) {
Ok(username) => { Ok(username) => rpcenv.set_user(Some(username)),
// fixme: check permissions
rpcenv.set_user(Some(username));
}
Err(err) => { Err(err) => {
// always delay unauthorized calls by 3 seconds (from start of request) // 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; tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await;
return Ok((formatter.format_error)(err)); return Ok((formatter.format_error)(err));
} }
@ -553,6 +560,13 @@ pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<R
return Ok((formatter.format_error)(err)); return Ok((formatter.format_error)(err));
} }
Some(api_method) => { Some(api_method) => {
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 { let result = if api_method.protected && env_type == RpcEnvironmentType::PUBLIC {
proxy_protected_request(api_method, parts, body).await proxy_protected_request(api_method, parts, body).await
} else { } else {
@ -577,7 +591,7 @@ pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<R
if comp_len == 0 { if comp_len == 0 {
let (ticket, token) = extract_auth_data(&parts.headers); let (ticket, token) = extract_auth_data(&parts.headers);
if ticket != None { if ticket != None {
match check_auth(&method, &ticket, &token) { match check_auth(&method, &ticket, &token, &user_info) {
Ok(username) => { Ok(username) => {
let new_token = assemble_csrf_prevention_token(csrf_secret(), &username); let new_token = assemble_csrf_prevention_token(csrf_secret(), &username);
return Ok(get_index(Some(username), Some(new_token))); return Ok(get_index(Some(username), Some(new_token)));