proxmox-backup/src/api2/node/subscription.rs
Fabian Grünbichler da7b6af61c manager: add 'subscription set-offline-key' command
and disallow updating offline subscription keys via the regular
check/update code path.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-07-25 10:03:10 +02:00

252 lines
7.4 KiB
Rust

use anyhow::{bail, format_err, Error};
use serde_json::Value;
use proxmox_http::client::{SimpleHttp, SimpleHttpOptions};
use proxmox_router::{Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use proxmox_subscription::{SubscriptionInfo, SubscriptionStatus};
use proxmox_sys::fs::{file_get_contents, CreateOptions};
use pbs_api_types::{
Authid, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, SUBSCRIPTION_KEY_SCHEMA,
};
use crate::config::node;
use crate::tools::{DEFAULT_USER_AGENT_STRING, PROXMOX_BACKUP_TCP_KEEPALIVE_TIME};
use pbs_buildcfg::{PROXMOX_BACKUP_SUBSCRIPTION_FN, PROXMOX_BACKUP_SUBSCRIPTION_SIGNATURE_KEY_FN};
use pbs_config::CachedUserInfo;
const PRODUCT_URL: &str = "https://www.proxmox.com/en/proxmox-backup-server/pricing";
const APT_AUTH_FN: &str = "/etc/apt/auth.conf.d/pbs.conf";
const APT_AUTH_URL: &str = "enterprise.proxmox.com/debian/pbs";
pub fn subscription_file_opts() -> Result<CreateOptions, Error> {
let backup_user = pbs_config::backup_user()?;
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
Ok(CreateOptions::new()
.perm(mode)
.owner(nix::unistd::ROOT)
.group(backup_user.gid))
}
fn apt_auth_file_opts() -> CreateOptions {
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
CreateOptions::new().perm(mode).owner(nix::unistd::ROOT)
}
pub fn subscription_signature_key() -> Result<openssl::pkey::PKey<openssl::pkey::Public>, Error> {
let key = file_get_contents(PROXMOX_BACKUP_SUBSCRIPTION_SIGNATURE_KEY_FN)?;
openssl::pkey::PKey::public_key_from_pem(&key).map_err(|err| {
format_err!(
"Failed parsing public key from '{}' - {}",
PROXMOX_BACKUP_SUBSCRIPTION_SIGNATURE_KEY_FN,
err
)
})
}
fn check_and_write_subscription(key: String, server_id: String) -> Result<(), Error> {
let proxy_config = if let Ok((node_config, _digest)) = node::config() {
node_config.http_proxy()
} else {
None
};
let client = SimpleHttp::with_options(SimpleHttpOptions {
proxy_config,
user_agent: Some(DEFAULT_USER_AGENT_STRING.to_string()),
tcp_keepalive: Some(PROXMOX_BACKUP_TCP_KEEPALIVE_TIME),
});
let info = proxmox_subscription::check::check_subscription(
key,
server_id,
PRODUCT_URL.to_string(),
client,
)?;
proxmox_subscription::files::write_subscription(
PROXMOX_BACKUP_SUBSCRIPTION_FN,
subscription_file_opts()?,
&info,
)
.map_err(|e| format_err!("Error writing updated subscription status - {}", e))?;
proxmox_subscription::files::update_apt_auth(
APT_AUTH_FN,
apt_auth_file_opts(),
APT_AUTH_URL,
info.key,
info.serverid,
)
}
#[api(
input: {
properties: {
node: {
schema: NODE_SCHEMA,
},
force: {
description: "Always connect to server, even if information in cache is up to date.",
type: bool,
optional: true,
default: false,
},
},
},
protected: true,
access: {
permission: &Permission::Privilege(&["system"], PRIV_SYS_MODIFY, false),
},
)]
/// Check and update subscription status.
pub fn check_subscription(force: bool) -> Result<(), Error> {
let mut info = match proxmox_subscription::files::read_subscription(
PROXMOX_BACKUP_SUBSCRIPTION_FN,
&subscription_signature_key()?,
) {
Err(err) => bail!("could not read subscription status: {}", err),
Ok(Some(info)) => info,
Ok(None) => return Ok(()),
};
let server_id = proxmox_subscription::get_hardware_address()?;
let key = if let Some(key) = info.key.as_ref() {
// always update apt auth if we have a key to ensure user can access enterprise repo
proxmox_subscription::files::update_apt_auth(
APT_AUTH_FN,
apt_auth_file_opts(),
APT_AUTH_URL,
Some(key.to_owned()),
Some(server_id.to_owned()),
)?;
key.to_owned()
} else {
String::new()
};
if info.is_signed() {
bail!("Updating offline key not possible - please remove and re-add subscription key to switch to online key.");
}
if !force && info.status == SubscriptionStatus::Active {
// will set to INVALID if last check too long ago
info.check_age(true);
if info.status == SubscriptionStatus::Active {
return Ok(());
}
}
check_and_write_subscription(key, server_id)
}
#[api(
input: {
properties: {
node: {
schema: NODE_SCHEMA,
},
},
},
returns: { type: SubscriptionInfo },
access: {
permission: &Permission::Anybody,
},
)]
/// Read subscription info.
pub fn get_subscription(
_param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<SubscriptionInfo, Error> {
let info = match proxmox_subscription::files::read_subscription(
PROXMOX_BACKUP_SUBSCRIPTION_FN,
&subscription_signature_key()?,
) {
Err(err) => bail!("could not read subscription status: {}", err),
Ok(Some(info)) => info,
Ok(None) => SubscriptionInfo {
status: SubscriptionStatus::NotFound,
message: Some("There is no subscription key".into()),
serverid: Some(proxmox_subscription::get_hardware_address()?),
url: Some(PRODUCT_URL.into()),
..Default::default()
},
};
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
let user_privs = user_info.lookup_privs(&auth_id, &[]);
if (user_privs & PRIV_SYS_AUDIT) == 0 {
// not enough privileges for full state
return Ok(SubscriptionInfo {
status: info.status,
message: info.message,
url: info.url,
..Default::default()
});
};
Ok(info)
}
#[api(
input: {
properties: {
node: {
schema: NODE_SCHEMA,
},
key: {
schema: SUBSCRIPTION_KEY_SCHEMA,
},
},
},
protected: true,
access: {
permission: &Permission::Privilege(&["system"], PRIV_SYS_MODIFY, false),
},
)]
/// Set a subscription key and check it.
pub fn set_subscription(key: String) -> Result<(), Error> {
let server_id = proxmox_subscription::get_hardware_address()?;
check_and_write_subscription(key, server_id)
}
#[api(
input: {
properties: {
node: {
schema: NODE_SCHEMA,
},
},
},
protected: true,
access: {
permission: &Permission::Privilege(&["system"], PRIV_SYS_MODIFY, false),
},
)]
/// Delete subscription info.
pub fn delete_subscription() -> Result<(), Error> {
proxmox_subscription::files::delete_subscription(PROXMOX_BACKUP_SUBSCRIPTION_FN)
.map_err(|err| format_err!("Deleting subscription failed: {}", err))?;
proxmox_subscription::files::update_apt_auth(
APT_AUTH_FN,
apt_auth_file_opts(),
APT_AUTH_URL,
None,
None,
)?;
Ok(())
}
pub const ROUTER: Router = Router::new()
.post(&API_METHOD_CHECK_SUBSCRIPTION)
.put(&API_METHOD_SET_SUBSCRIPTION)
.delete(&API_METHOD_DELETE_SUBSCRIPTION)
.get(&API_METHOD_GET_SUBSCRIPTION);