start impl. access permissions
This commit is contained in:
parent
423e656163
commit
4b40148caa
@ -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<Value, Error> {
|
||||
},
|
||||
},
|
||||
},
|
||||
access: {
|
||||
description: "Anybody is allowed to change there own password. The Superuser may change any password.",
|
||||
permission: &Permission::Anybody,
|
||||
},
|
||||
|
||||
)]
|
||||
/// Change user password
|
||||
///
|
||||
|
@ -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<Value, Error> {
|
||||
let mut list = Vec::new();
|
||||
list.push(json!({ "realm": "pam", "comment": "Linux PAM standard authentication", "default": true }));
|
||||
|
@ -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<Value, Error> {
|
||||
},
|
||||
},
|
||||
},
|
||||
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(
|
||||
|
@ -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<Value, Error> {
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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<String, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_time(
|
||||
_param: Value,
|
||||
_info: &ApiMethod,
|
||||
_rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<Value, Error> {
|
||||
#[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<Value, Error> {
|
||||
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<Value, Error> {
|
||||
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);
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
);
|
||||
|
||||
|
21
src/auth.rs
21
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<Box<dyn ProxmoxAuthenticator>, Error> {
|
||||
match realm {
|
||||
"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> {
|
||||
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)?
|
||||
.authenticate_user(&username, password)
|
||||
}
|
||||
|
@ -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<String>, Option<Strin
|
||||
(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;
|
||||
|
||||
@ -481,6 +487,10 @@ fn check_auth(method: &hyper::Method, ticket: &Option<String>, token: &Option<St
|
||||
None => 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<ApiConfig>, req: Request<Body>) -> Result<R
|
||||
let env_type = api.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);
|
||||
|
||||
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
|
||||
} else {
|
||||
let (ticket, token) = extract_auth_data(&parts.headers);
|
||||
match check_auth(&method, &ticket, &token) {
|
||||
Ok(username) => {
|
||||
|
||||
// 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<ApiConfig>, req: Request<Body>) -> Result<R
|
||||
return Ok((formatter.format_error)(err));
|
||||
}
|
||||
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 {
|
||||
proxy_protected_request(api_method, parts, body).await
|
||||
} else {
|
||||
@ -577,7 +591,7 @@ pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<R
|
||||
if comp_len == 0 {
|
||||
let (ticket, token) = extract_auth_data(&parts.headers);
|
||||
if ticket != None {
|
||||
match check_auth(&method, &ticket, &token) {
|
||||
match check_auth(&method, &ticket, &token, &user_info) {
|
||||
Ok(username) => {
|
||||
let new_token = assemble_csrf_prevention_token(csrf_secret(), &username);
|
||||
return Ok(get_index(Some(username), Some(new_token)));
|
||||
|
Loading…
Reference in New Issue
Block a user