5
0
mirror of git://git.proxmox.com/git/proxmox-backup.git synced 2025-01-05 09:17:59 +03:00

extract proxmox-subscription crate

and add support for signed subscription keys.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
Fabian Grünbichler 2022-06-21 14:35:28 +02:00
parent 9b3b3c88a9
commit dd16e1dac8
8 changed files with 135 additions and 451 deletions

View File

@ -93,7 +93,7 @@ zstd = { version = "0.6", features = [ "bindgen" ] }
pathpatterns = "0.1.2"
pxar = { version = "0.10.1", features = [ "tokio-io" ] }
proxmox-http = { version = "0.6.4", features = [ "client", "http-helpers", "websocket" ] }
proxmox-http = { version = "0.6.4", features = [ "client", "client-trait", "http-helpers", "proxmox-async", "websocket" ] }
proxmox-io = "1"
proxmox-lang = "1.1"
proxmox-metrics = "0.2"
@ -105,6 +105,7 @@ proxmox-time = "1.1.2"
proxmox-uuid = "1"
proxmox-serde = { version = "0.1.1", features = [ "serde_json" ] }
proxmox-shared-memory = "0.2"
proxmox-subscription = { version = "0.1", features = [ "api-types" ] }
proxmox-sys = { version = "0.3.1", features = [ "sortable-macro" ] }
proxmox-compression = "0.1"

2
debian/control vendored
View File

@ -65,6 +65,8 @@ Build-Depends: debhelper (>= 12),
librust-proxmox-serde-0.1+default-dev (>= 0.1.1-~~),
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~),
librust-proxmox-shared-memory-0.2+default-dev,
librust-proxmox-subscription-0.1+default-dev,
librust-proxmox-subscription-0.1+api-types-dev,
librust-proxmox-sys-0.3+default-dev (>= 0.3.1-~~),
librust-proxmox-sys-0.3+logrotate-dev (>= 0.3.1-~~),
librust-proxmox-sys-0.3+sortable-macro-dev (>= 0.3.1-~~),

View File

@ -90,6 +90,10 @@ pub const PROXMOX_BACKUP_INITRAMFS_DBG_FN: &str = concat!(
pub const PROXMOX_BACKUP_KERNEL_FN: &str =
concat!(PROXMOX_BACKUP_FILE_RESTORE_BIN_DIR_M!(), "/bzImage");
pub const PROXMOX_BACKUP_SUBSCRIPTION_FN: &str = configdir!("/subscription");
pub const PROXMOX_BACKUP_SUBSCRIPTION_SIGNATURE_KEY_FN: &str =
"/usr/share/keyrings/proxmox-offline-signing-key.pub";
/// Prepend configuration directory to a file name
///
/// This is a simply way to get the full path for configuration files.

View File

@ -19,9 +19,10 @@ use pbs_api_types::{
APTUpdateInfo, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
UPID_SCHEMA,
};
use pbs_buildcfg::PROXMOX_BACKUP_SUBSCRIPTION_FN;
use crate::config::node;
use crate::tools::{apt, pbs_simple_http, subscription};
use crate::tools::{apt, pbs_simple_http};
use proxmox_rest_server::WorkerTask;
#[api(
@ -255,7 +256,10 @@ fn apt_get_changelog(param: Value) -> Result<Value, Error> {
})?;
Ok(json!(changelog))
} else if changelog_url.starts_with("https://enterprise.proxmox.com/") {
let sub = match subscription::read_subscription()? {
let sub = match proxmox_subscription::files::read_subscription(
PROXMOX_BACKUP_SUBSCRIPTION_FN,
&super::subscription::subscription_signature_key()?,
)? {
Some(sub) => sub,
None => {
bail!("cannot retrieve changelog from enterprise repo: no subscription info found")

View File

@ -1,17 +1,87 @@
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::tools;
use crate::tools::subscription::{self, SubscriptionInfo, SubscriptionStatus};
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";
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(crate) 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: {
@ -33,34 +103,39 @@ use pbs_config::CachedUserInfo;
)]
/// Check and update subscription status.
pub fn check_subscription(force: bool) -> Result<(), Error> {
let info = match subscription::read_subscription() {
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 = tools::get_hardware_address()?;
let key = if let Some(key) = info.key {
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
subscription::update_apt_auth(Some(key.to_owned()), Some(server_id.to_owned()))?;
key
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 !force && info.status == SubscriptionStatus::ACTIVE {
let age = proxmox_time::epoch_i64() - info.checktime.unwrap_or(i64::MAX);
if age < subscription::MAX_LOCAL_KEY_AGE {
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(());
}
}
let info = subscription::check_subscription(key, server_id)?;
subscription::write_subscription(info)
.map_err(|e| format_err!("Error writing updated subscription status - {}", e))?;
Ok(())
check_and_write_subscription(key, server_id)
}
#[api(
@ -81,16 +156,17 @@ pub fn get_subscription(
_param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<SubscriptionInfo, Error> {
let url = "https://www.proxmox.com/en/proxmox-backup-server/pricing";
let info = match subscription::read_subscription() {
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,
status: SubscriptionStatus::NotFound,
message: Some("There is no subscription key".into()),
serverid: Some(tools::get_hardware_address()?),
url: Some(url.into()),
serverid: Some(proxmox_subscription::get_hardware_address()?),
url: Some(PRODUCT_URL.into()),
..Default::default()
},
};
@ -130,14 +206,9 @@ pub fn get_subscription(
)]
/// Set a subscription key and check it.
pub fn set_subscription(key: String) -> Result<(), Error> {
let server_id = tools::get_hardware_address()?;
let server_id = proxmox_subscription::get_hardware_address()?;
let info = subscription::check_subscription(key, server_id)?;
subscription::write_subscription(info)
.map_err(|e| format_err!("Error writing subscription status - {}", e))?;
Ok(())
check_and_write_subscription(key, server_id)
}
#[api(
@ -155,9 +226,17 @@ pub fn set_subscription(key: String) -> Result<(), Error> {
)]
/// Delete subscription info.
pub fn delete_subscription() -> Result<(), Error> {
subscription::delete_subscription()
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(())
}

View File

@ -5,7 +5,6 @@ use proxmox_router::{cli::*, ApiHandler, RpcEnvironment};
use proxmox_sys::fs::CreateOptions;
use proxmox_backup::api2;
use proxmox_backup::tools::subscription;
async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> {
let upid: pbs_api_types::UPID = upid_str.parse()?;
@ -27,20 +26,22 @@ async fn do_update(rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
let method = &api2::node::subscription::API_METHOD_CHECK_SUBSCRIPTION;
match method.handler {
ApiHandler::Sync(handler) => {
if let Err(err) = (handler)(param, method, rpcenv) {
if let Err(err) = (handler)(param.clone(), method, rpcenv) {
log::error!("Error checking subscription - {}", err);
}
}
_ => unreachable!(),
}
let notify = match subscription::read_subscription() {
Ok(Some(subscription)) => subscription.status == subscription::SubscriptionStatus::ACTIVE,
Ok(None) => false,
Err(err) => {
log::error!("Error reading subscription - {}", err);
false
}
let method = &api2::node::subscription::API_METHOD_GET_SUBSCRIPTION;
let notify = match method.handler {
ApiHandler::Sync(handler) => match (handler)(param, method, rpcenv) {
Ok(value) => !value.is_null(),
Err(err) => {
log::error!("Error reading subscription - {}", err);
false
}
},
_ => unreachable!(),
};
let param = json!({

View File

@ -3,8 +3,7 @@
//! This is a collection of small and useful tools.
use std::any::Any;
use anyhow::{bail, format_err, Error};
use openssl::hash::{hash, DigestBytes, MessageDigest};
use anyhow::{bail, Error};
use proxmox_http::{client::SimpleHttp, client::SimpleHttpOptions, ProxyConfig};
@ -17,27 +16,11 @@ mod shared_rate_limiter;
pub use shared_rate_limiter::SharedRateLimiter;
pub mod statistics;
pub mod subscription;
pub mod systemd;
pub mod ticket;
pub mod parallel_handler;
/// Shortcut for md5 sums.
pub fn md5sum(data: &[u8]) -> Result<DigestBytes, Error> {
hash(MessageDigest::md5(), data).map_err(Error::from)
}
pub fn get_hardware_address() -> Result<String, Error> {
static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
let contents = proxmox_sys::fs::file_get_contents(FILENAME)
.map_err(|e| format_err!("Error getting host key - {}", e))?;
let digest = md5sum(&contents).map_err(|e| format_err!("Error digesting host key - {}", e))?;
Ok(hex::encode(&digest).to_uppercase())
}
pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> {
if digest1 != digest2 {
bail!("detected modified configuration - file changed by other user? Try again.");

View File

@ -1,390 +0,0 @@
use anyhow::{bail, format_err, Error};
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::json;
use proxmox_schema::api;
use proxmox_http::client::SimpleHttp;
use proxmox_http::uri::json_object_to_query;
use proxmox_sys::fs::{replace_file, CreateOptions};
use crate::config::node;
use crate::tools::{self, pbs_simple_http};
/// How long the local key is valid for in between remote checks
pub const MAX_LOCAL_KEY_AGE: i64 = 15 * 24 * 3600;
const MAX_KEY_CHECK_FAILURE_AGE: i64 = 5 * 24 * 3600;
const SHARED_KEY_DATA: &str = "kjfdlskfhiuewhfk947368";
const SUBSCRIPTION_FN: &str = "/etc/proxmox-backup/subscription";
const APT_AUTH_FN: &str = "/etc/apt/auth.conf.d/pbs.conf";
#[api()]
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// Subscription status
pub enum SubscriptionStatus {
// FIXME: remove?
/// newly set subscription, not yet checked
NEW,
/// no subscription set
NOTFOUND,
/// subscription set and active
ACTIVE,
/// subscription set but invalid for this server
INVALID,
}
impl Default for SubscriptionStatus {
fn default() -> Self {
SubscriptionStatus::NOTFOUND
}
}
impl std::fmt::Display for SubscriptionStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SubscriptionStatus::NEW => write!(f, "New"),
SubscriptionStatus::NOTFOUND => write!(f, "NotFound"),
SubscriptionStatus::ACTIVE => write!(f, "Active"),
SubscriptionStatus::INVALID => write!(f, "Invalid"),
}
}
}
#[api(
properties: {
status: {
type: SubscriptionStatus,
},
},
)]
#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Proxmox subscription information
pub struct SubscriptionInfo {
/// Subscription status from the last check
pub status: SubscriptionStatus,
/// the server ID, if permitted to access
#[serde(skip_serializing_if = "Option::is_none")]
pub serverid: Option<String>,
/// timestamp of the last check done
#[serde(skip_serializing_if = "Option::is_none")]
pub checktime: Option<i64>,
/// the subscription key, if set and permitted to access
#[serde(skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
/// a more human readable status message
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
/// human readable productname of the set subscription
#[serde(skip_serializing_if = "Option::is_none")]
pub productname: Option<String>,
/// register date of the set subscription
#[serde(skip_serializing_if = "Option::is_none")]
pub regdate: Option<String>,
/// next due date of the set subscription
#[serde(skip_serializing_if = "Option::is_none")]
pub nextduedate: Option<String>,
/// URL to the web shop
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
async fn register_subscription(
key: &str,
server_id: &str,
checktime: i64,
) -> Result<(String, String), Error> {
// WHCMS sample code feeds the key into this, but it's just a challenge, so keep it simple
let rand = hex::encode(&proxmox_sys::linux::random_data(16)?);
let challenge = format!("{}{}", checktime, rand);
let params = json!({
"licensekey": key,
"dir": server_id,
"domain": "www.proxmox.com",
"ip": "localhost",
"check_token": challenge,
});
let proxy_config = if let Ok((node_config, _digest)) = node::config() {
node_config.http_proxy()
} else {
None
};
let client = pbs_simple_http(proxy_config);
let uri = "https://shop.proxmox.com/modules/servers/licensing/verify.php";
let query = json_object_to_query(params)?;
let response = client
.post(uri, Some(query), Some("application/x-www-form-urlencoded"))
.await?;
let body = SimpleHttp::response_body_string(response).await?;
Ok((body, challenge))
}
fn parse_status(value: &str) -> SubscriptionStatus {
match value.to_lowercase().as_str() {
"active" => SubscriptionStatus::ACTIVE,
"new" => SubscriptionStatus::NEW,
"notfound" => SubscriptionStatus::NOTFOUND,
"invalid" => SubscriptionStatus::INVALID,
_ => SubscriptionStatus::INVALID,
}
}
fn parse_register_response(
body: &str,
key: String,
server_id: String,
checktime: i64,
challenge: &str,
) -> Result<SubscriptionInfo, Error> {
lazy_static! {
static ref ATTR_RE: Regex = Regex::new(r"<([^>]+)>([^<]+)</[^>]+>").unwrap();
}
let mut info = SubscriptionInfo {
key: Some(key),
status: SubscriptionStatus::NOTFOUND,
checktime: Some(checktime),
url: Some("https://www.proxmox.com/en/proxmox-backup-server/pricing".into()),
..Default::default()
};
let mut md5hash = String::new();
let is_server_id = |id: &&str| *id == server_id;
for caps in ATTR_RE.captures_iter(body) {
let (key, value) = (&caps[1], &caps[2]);
match key {
"status" => info.status = parse_status(value),
"productname" => info.productname = Some(value.into()),
"regdate" => info.regdate = Some(value.into()),
"nextduedate" => info.nextduedate = Some(value.into()),
"message" if value == "Directory Invalid" => {
info.message = Some("Invalid Server ID".into())
}
"message" => info.message = Some(value.into()),
"validdirectory" => {
if value.split(',').find(is_server_id) == None {
bail!("Server ID does not match");
}
info.serverid = Some(server_id.to_owned());
}
"md5hash" => md5hash = value.to_owned(),
_ => (),
}
}
if let SubscriptionStatus::ACTIVE = info.status {
let response_raw = format!("{}{}", SHARED_KEY_DATA, challenge);
let expected = hex::encode(&tools::md5sum(response_raw.as_bytes())?);
if expected != md5hash {
bail!(
"Subscription API challenge failed, expected {} != got {}",
expected,
md5hash
);
}
}
Ok(info)
}
#[test]
fn test_parse_register_response() -> Result<(), Error> {
let response = r#"
<status>Active</status>
<companyname>Proxmox</companyname>
<serviceid>41108</serviceid>
<productid>71</productid>
<productname>Proxmox Backup Server Test Subscription -1 year</productname>
<regdate>2020-09-19 00:00:00</regdate>
<nextduedate>2021-09-19</nextduedate>
<billingcycle>Annually</billingcycle>
<validdomain>proxmox.com,www.proxmox.com</validdomain>
<validdirectory>830000000123456789ABCDEF00000042</validdirectory>
<customfields>Notes=Test Key!</customfields>
<addons></addons>
<md5hash>969f4df84fe157ee4f5a2f71950ad154</md5hash>
"#;
let key = "pbst-123456789a".to_string();
let server_id = "830000000123456789ABCDEF00000042".to_string();
let checktime = 1600000000;
let salt = "cf44486bddb6ad0145732642c45b2957";
let info = parse_register_response(
response,
key.to_owned(),
server_id.to_owned(),
checktime,
salt,
)?;
assert_eq!(
info,
SubscriptionInfo {
key: Some(key),
serverid: Some(server_id),
status: SubscriptionStatus::ACTIVE,
checktime: Some(checktime),
url: Some("https://www.proxmox.com/en/proxmox-backup-server/pricing".into()),
message: None,
nextduedate: Some("2021-09-19".into()),
regdate: Some("2020-09-19 00:00:00".into()),
productname: Some("Proxmox Backup Server Test Subscription -1 year".into()),
}
);
Ok(())
}
/// queries the up to date subscription status and parses the response
pub fn check_subscription(key: String, server_id: String) -> Result<SubscriptionInfo, Error> {
let now = proxmox_time::epoch_i64();
let (response, challenge) =
proxmox_async::runtime::block_on(register_subscription(&key, &server_id, now))
.map_err(|err| format_err!("Error checking subscription: {}", err))?;
parse_register_response(&response, key, server_id, now, &challenge)
.map_err(|err| format_err!("Error parsing subscription check response: {}", err))
}
/// reads in subscription information and does a basic integrity verification
pub fn read_subscription() -> Result<Option<SubscriptionInfo>, Error> {
let cfg = proxmox_sys::fs::file_read_optional_string(&SUBSCRIPTION_FN)?;
let cfg = if let Some(cfg) = cfg {
cfg
} else {
return Ok(None);
};
let mut cfg = cfg.lines();
// first line is key in plain
let _key = if let Some(key) = cfg.next() {
key
} else {
return Ok(None);
};
// second line is checksum of encoded data
let checksum = if let Some(csum) = cfg.next() {
csum
} else {
return Ok(None);
};
let encoded: String = cfg.collect::<String>();
let decoded = base64::decode(&encoded)?;
let decoded = std::str::from_utf8(&decoded)?;
let info: SubscriptionInfo = serde_json::from_str(decoded)?;
let new_checksum = format!(
"{}{}{}",
info.checktime.unwrap_or(0),
encoded,
SHARED_KEY_DATA
);
let new_checksum = base64::encode(tools::md5sum(new_checksum.as_bytes())?);
if checksum != new_checksum {
return Ok(Some(SubscriptionInfo {
status: SubscriptionStatus::INVALID,
message: Some("checksum mismatch".to_string()),
..info
}));
}
let age = proxmox_time::epoch_i64() - info.checktime.unwrap_or(0);
if age < -5400 {
// allow some delta for DST changes or time syncs, 1.5h
return Ok(Some(SubscriptionInfo {
status: SubscriptionStatus::INVALID,
message: Some("last check date too far in the future".to_string()),
..info
}));
} else if age > MAX_LOCAL_KEY_AGE + MAX_KEY_CHECK_FAILURE_AGE {
if let SubscriptionStatus::ACTIVE = info.status {
return Ok(Some(SubscriptionInfo {
status: SubscriptionStatus::INVALID,
message: Some("subscription information too old".to_string()),
..info
}));
}
}
Ok(Some(info))
}
/// writes out subscription status
pub fn write_subscription(info: SubscriptionInfo) -> Result<(), Error> {
let key = info.key.to_owned();
let server_id = info.serverid.to_owned();
let raw = if info.key == None || info.checktime == None {
String::new()
} else if let SubscriptionStatus::NEW = info.status {
format!("{}\n", info.key.unwrap())
} else {
let encoded = base64::encode(serde_json::to_string(&info)?);
let csum = format!(
"{}{}{}",
info.checktime.unwrap_or(0),
encoded,
SHARED_KEY_DATA
);
let csum = base64::encode(tools::md5sum(csum.as_bytes())?);
format!("{}\n{}\n{}\n", info.key.unwrap(), csum, encoded)
};
let backup_user = pbs_config::backup_user()?;
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
let file_opts = CreateOptions::new()
.perm(mode)
.owner(nix::unistd::ROOT)
.group(backup_user.gid);
let subscription_file = std::path::Path::new(SUBSCRIPTION_FN);
replace_file(subscription_file, raw.as_bytes(), file_opts, true)?;
update_apt_auth(key, server_id)?;
Ok(())
}
/// deletes subscription from server
pub fn delete_subscription() -> Result<(), Error> {
let subscription_file = std::path::Path::new(SUBSCRIPTION_FN);
nix::unistd::unlink(subscription_file)?;
update_apt_auth(None, None)?;
Ok(())
}
/// updates apt authentication for repo access
pub fn update_apt_auth(key: Option<String>, password: Option<String>) -> Result<(), Error> {
let auth_conf = std::path::Path::new(APT_AUTH_FN);
match (key, password) {
(Some(key), Some(password)) => {
let conf = format!(
"machine enterprise.proxmox.com/debian/pbs\n login {}\n password {}\n",
key, password,
);
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
let file_opts = CreateOptions::new().perm(mode).owner(nix::unistd::ROOT);
// we use a namespaced .conf file, so just overwrite..
replace_file(auth_conf, conf.as_bytes(), file_opts, true)
.map_err(|e| format_err!("Error saving apt auth config - {}", e))?;
}
_ => match nix::unistd::unlink(auth_conf) {
Ok(()) => Ok(()),
Err(nix::errno::Errno::ENOENT) => Ok(()), // ignore not existing
Err(err) => Err(err),
}
.map_err(|e| format_err!("Error clearing apt auth config - {}", e))?,
}
Ok(())
}