add acme commands to proxmox-backup-manager
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
09989d9963
commit
72bd8293e3
|
@ -355,6 +355,7 @@ fn main() {
|
||||||
.insert("user", user_commands())
|
.insert("user", user_commands())
|
||||||
.insert("remote", remote_commands())
|
.insert("remote", remote_commands())
|
||||||
.insert("garbage-collection", garbage_collection_commands())
|
.insert("garbage-collection", garbage_collection_commands())
|
||||||
|
.insert("acme", acme_mgmt_cli())
|
||||||
.insert("cert", cert_mgmt_cli())
|
.insert("cert", cert_mgmt_cli())
|
||||||
.insert("subscription", subscription_commands())
|
.insert("subscription", subscription_commands())
|
||||||
.insert("sync-job", sync_job_commands())
|
.insert("sync-job", sync_job_commands())
|
||||||
|
|
|
@ -0,0 +1,415 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use anyhow::{bail, Error};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use proxmox::api::{api, cli::*, ApiHandler, RpcEnvironment};
|
||||||
|
use proxmox::tools::fs::file_get_contents;
|
||||||
|
|
||||||
|
use proxmox_backup::acme::AcmeClient;
|
||||||
|
use proxmox_backup::api2;
|
||||||
|
use proxmox_backup::config::acme::plugin::DnsPluginCoreUpdater;
|
||||||
|
use proxmox_backup::config::acme::{AccountName, KNOWN_ACME_DIRECTORIES};
|
||||||
|
|
||||||
|
pub fn acme_mgmt_cli() -> CommandLineInterface {
|
||||||
|
let cmd_def = CliCommandMap::new()
|
||||||
|
.insert("account", account_cli())
|
||||||
|
.insert("cert", cert_cli())
|
||||||
|
.insert("plugin", plugin_cli());
|
||||||
|
|
||||||
|
cmd_def.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
"output-format": {
|
||||||
|
schema: OUTPUT_FORMAT,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
/// List acme accounts.
|
||||||
|
fn list_accounts(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
|
||||||
|
let output_format = get_output_format(¶m);
|
||||||
|
|
||||||
|
let info = &api2::config::acme::API_METHOD_LIST_ACCOUNTS;
|
||||||
|
let mut data = match info.handler {
|
||||||
|
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let options = default_table_format_options();
|
||||||
|
format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
name: { type: AccountName },
|
||||||
|
"output-format": {
|
||||||
|
schema: OUTPUT_FORMAT,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
/// Show acme account information.
|
||||||
|
async fn get_account(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
|
||||||
|
let output_format = get_output_format(¶m);
|
||||||
|
|
||||||
|
let info = &api2::config::acme::API_METHOD_GET_ACCOUNT;
|
||||||
|
let mut data = match info.handler {
|
||||||
|
ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let options = default_table_format_options()
|
||||||
|
.column(
|
||||||
|
ColumnConfig::new("account")
|
||||||
|
.renderer(|value, _record| Ok(serde_json::to_string_pretty(value)?)),
|
||||||
|
)
|
||||||
|
.column(ColumnConfig::new("directory"))
|
||||||
|
.column(ColumnConfig::new("location"))
|
||||||
|
.column(ColumnConfig::new("tos"));
|
||||||
|
format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
name: { type: AccountName },
|
||||||
|
contact: {
|
||||||
|
description: "List of email addresses.",
|
||||||
|
},
|
||||||
|
directory: {
|
||||||
|
type: String,
|
||||||
|
description: "The ACME Directory.",
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
/// Register an ACME account.
|
||||||
|
async fn register_account(
|
||||||
|
name: AccountName,
|
||||||
|
contact: String,
|
||||||
|
directory: Option<String>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let directory = match directory {
|
||||||
|
Some(directory) => directory,
|
||||||
|
None => {
|
||||||
|
println!("Directory endpoints:");
|
||||||
|
for (i, dir) in KNOWN_ACME_DIRECTORIES.iter().enumerate() {
|
||||||
|
println!("{}) {}", i, dir.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}) Custom", KNOWN_ACME_DIRECTORIES.len());
|
||||||
|
let mut attempt = 0;
|
||||||
|
loop {
|
||||||
|
print!("Enter selection: ");
|
||||||
|
std::io::stdout().flush()?;
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
std::io::stdin().read_line(&mut input)?;
|
||||||
|
|
||||||
|
match input.trim().parse::<usize>() {
|
||||||
|
Ok(n) if n < KNOWN_ACME_DIRECTORIES.len() => {
|
||||||
|
break KNOWN_ACME_DIRECTORIES[n].url.to_owned();
|
||||||
|
}
|
||||||
|
Ok(n) if n == KNOWN_ACME_DIRECTORIES.len() => {
|
||||||
|
input.clear();
|
||||||
|
std::io::stdin().read_line(&mut input)?;
|
||||||
|
break input.trim().to_owned();
|
||||||
|
}
|
||||||
|
_ => eprintln!("Invalid selection."),
|
||||||
|
}
|
||||||
|
|
||||||
|
attempt += 1;
|
||||||
|
if attempt >= 3 {
|
||||||
|
bail!("Aborting.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Attempting to fetch Terms of Service from {:?}", directory);
|
||||||
|
let mut client = AcmeClient::new(directory.clone());
|
||||||
|
let tos_agreed = if let Some(tos_url) = client.terms_of_service_url().await? {
|
||||||
|
println!("Terms of Service: {}", tos_url);
|
||||||
|
print!("Do you agree to the above terms? [y|N]: ");
|
||||||
|
std::io::stdout().flush()?;
|
||||||
|
let mut input = String::new();
|
||||||
|
std::io::stdin().read_line(&mut input)?;
|
||||||
|
if input.trim().eq_ignore_ascii_case("y") {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Attempting to register account with {:?}...", directory);
|
||||||
|
|
||||||
|
let account =
|
||||||
|
api2::config::acme::do_register_account(&mut client, &name, tos_agreed, contact, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Registration successful, account URL: {}", account.location);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
name: { type: AccountName },
|
||||||
|
contact: {
|
||||||
|
description: "List of email addresses.",
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
/// Update an ACME account.
|
||||||
|
async fn update_account(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
|
||||||
|
let info = &api2::config::acme::API_METHOD_UPDATE_ACCOUNT;
|
||||||
|
let result = match info.handler {
|
||||||
|
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::wait_for_local_worker(result.as_str().unwrap()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
name: { type: AccountName },
|
||||||
|
force: {
|
||||||
|
description:
|
||||||
|
"Delete account data even if the server refuses to deactivate the account.",
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
/// Deactivate an ACME account.
|
||||||
|
async fn deactivate_account(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
|
||||||
|
let info = &api2::config::acme::API_METHOD_DEACTIVATE_ACCOUNT;
|
||||||
|
let result = match info.handler {
|
||||||
|
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::wait_for_local_worker(result.as_str().unwrap()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn account_cli() -> CommandLineInterface {
|
||||||
|
let cmd_def = CliCommandMap::new()
|
||||||
|
.insert("list", CliCommand::new(&API_METHOD_LIST_ACCOUNTS))
|
||||||
|
.insert(
|
||||||
|
"register",
|
||||||
|
CliCommand::new(&API_METHOD_REGISTER_ACCOUNT).arg_param(&["name", "contact"]),
|
||||||
|
)
|
||||||
|
.insert(
|
||||||
|
"deactivate",
|
||||||
|
CliCommand::new(&API_METHOD_DEACTIVATE_ACCOUNT)
|
||||||
|
.arg_param(&["name"])
|
||||||
|
.completion_cb("name", crate::config::acme::complete_acme_account),
|
||||||
|
)
|
||||||
|
.insert(
|
||||||
|
"info",
|
||||||
|
CliCommand::new(&API_METHOD_GET_ACCOUNT)
|
||||||
|
.arg_param(&["name"])
|
||||||
|
.completion_cb("name", crate::config::acme::complete_acme_account),
|
||||||
|
)
|
||||||
|
.insert(
|
||||||
|
"update",
|
||||||
|
CliCommand::new(&API_METHOD_UPDATE_ACCOUNT)
|
||||||
|
.arg_param(&["name"])
|
||||||
|
.completion_cb("name", crate::config::acme::complete_acme_account),
|
||||||
|
);
|
||||||
|
|
||||||
|
cmd_def.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
"output-format": {
|
||||||
|
schema: OUTPUT_FORMAT,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
/// List acme plugins.
|
||||||
|
fn list_plugins(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
|
||||||
|
let output_format = get_output_format(¶m);
|
||||||
|
|
||||||
|
let info = &api2::config::acme::API_METHOD_LIST_PLUGINS;
|
||||||
|
let mut data = match info.handler {
|
||||||
|
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let options = default_table_format_options();
|
||||||
|
format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
description: "Plugin ID",
|
||||||
|
},
|
||||||
|
"output-format": {
|
||||||
|
schema: OUTPUT_FORMAT,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
/// Show acme account information.
|
||||||
|
fn get_plugin(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
|
||||||
|
let output_format = get_output_format(¶m);
|
||||||
|
|
||||||
|
let info = &api2::config::acme::API_METHOD_GET_PLUGIN;
|
||||||
|
let mut data = match info.handler {
|
||||||
|
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let options = default_table_format_options();
|
||||||
|
format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
description: "The ACME challenge plugin type.",
|
||||||
|
},
|
||||||
|
core: {
|
||||||
|
type: DnsPluginCoreUpdater,
|
||||||
|
flatten: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: String,
|
||||||
|
description: "File containing the plugin data.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
/// Show acme account information.
|
||||||
|
fn add_plugin(r#type: String, core: DnsPluginCoreUpdater, data: String) -> Result<(), Error> {
|
||||||
|
let data = base64::encode(&file_get_contents(&data)?);
|
||||||
|
api2::config::acme::add_plugin(r#type, core, data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plugin_cli() -> CommandLineInterface {
|
||||||
|
use proxmox_backup::api2::config::acme;
|
||||||
|
let cmd_def = CliCommandMap::new()
|
||||||
|
.insert("list", CliCommand::new(&API_METHOD_LIST_PLUGINS))
|
||||||
|
.insert(
|
||||||
|
"config", // name comes from pve/pmg
|
||||||
|
CliCommand::new(&API_METHOD_GET_PLUGIN)
|
||||||
|
.arg_param(&["id"])
|
||||||
|
.completion_cb("id", crate::config::acme::complete_acme_plugin),
|
||||||
|
)
|
||||||
|
.insert(
|
||||||
|
"add",
|
||||||
|
CliCommand::new(&API_METHOD_ADD_PLUGIN)
|
||||||
|
.arg_param(&["type", "id"])
|
||||||
|
.completion_cb("id", crate::config::acme::complete_acme_plugin)
|
||||||
|
.completion_cb("type", crate::config::acme::complete_acme_plugin_type),
|
||||||
|
)
|
||||||
|
.insert(
|
||||||
|
"remove",
|
||||||
|
CliCommand::new(&acme::API_METHOD_DELETE_PLUGIN)
|
||||||
|
.arg_param(&["id"])
|
||||||
|
.completion_cb("id", crate::config::acme::complete_acme_plugin),
|
||||||
|
)
|
||||||
|
.insert(
|
||||||
|
"set",
|
||||||
|
CliCommand::new(&acme::API_METHOD_UPDATE_PLUGIN)
|
||||||
|
.arg_param(&["id"])
|
||||||
|
.completion_cb("id", crate::config::acme::complete_acme_plugin),
|
||||||
|
);
|
||||||
|
|
||||||
|
cmd_def.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
force: {
|
||||||
|
description: "Force renewal even if the certificate does not expire soon.",
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Order a new ACME certificate.
|
||||||
|
async fn order_acme_cert(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
|
||||||
|
if !param["force"].as_bool().unwrap_or(false) && !api2::node::certificates::cert_expires_soon()?
|
||||||
|
{
|
||||||
|
println!("Certificate does not expire within the next 30 days, not renewing.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = &api2::node::certificates::API_METHOD_RENEW_ACME_CERT;
|
||||||
|
let result = match info.handler {
|
||||||
|
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::wait_for_local_worker(result.as_str().unwrap()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api]
|
||||||
|
/// Order a new ACME certificate.
|
||||||
|
async fn revoke_acme_cert(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
|
||||||
|
let info = &api2::node::certificates::API_METHOD_REVOKE_ACME_CERT;
|
||||||
|
let result = match info.handler {
|
||||||
|
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::wait_for_local_worker(result.as_str().unwrap()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cert_cli() -> CommandLineInterface {
|
||||||
|
let cmd_def = CliCommandMap::new()
|
||||||
|
.insert("order", CliCommand::new(&API_METHOD_ORDER_ACME_CERT))
|
||||||
|
.insert("revoke", CliCommand::new(&API_METHOD_REVOKE_ACME_CERT));
|
||||||
|
|
||||||
|
cmd_def.into()
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
mod acl;
|
mod acl;
|
||||||
pub use acl::*;
|
pub use acl::*;
|
||||||
|
mod acme;
|
||||||
|
pub use acme::*;
|
||||||
mod cert;
|
mod cert;
|
||||||
pub use cert::*;
|
pub use cert::*;
|
||||||
mod datastore;
|
mod datastore;
|
||||||
|
|
Loading…
Reference in New Issue