move basic ACME types into src/api2/types/acme.rs
And rename AccountName into AcmeAccountName.
This commit is contained in:
		@ -17,7 +17,8 @@ use proxmox_acme_rs::order::{Order, OrderData};
 | 
				
			|||||||
use proxmox_acme_rs::Request as AcmeRequest;
 | 
					use proxmox_acme_rs::Request as AcmeRequest;
 | 
				
			||||||
use proxmox_acme_rs::{Account, Authorization, Challenge, Directory, Error, ErrorResponse};
 | 
					use proxmox_acme_rs::{Account, Authorization, Challenge, Directory, Error, ErrorResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::config::acme::{account_path, AccountName};
 | 
					use crate::api2::types::AcmeAccountName;
 | 
				
			||||||
 | 
					use crate::config::acme::account_path;
 | 
				
			||||||
use crate::tools::http::SimpleHttp;
 | 
					use crate::tools::http::SimpleHttp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Our on-disk format inherited from PVE's proxmox-acme code.
 | 
					/// Our on-disk format inherited from PVE's proxmox-acme code.
 | 
				
			||||||
@ -76,7 +77,7 @@ impl AcmeClient {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Load an existing ACME account by name.
 | 
					    /// Load an existing ACME account by name.
 | 
				
			||||||
    pub async fn load(account_name: &AccountName) -> Result<Self, anyhow::Error> {
 | 
					    pub async fn load(account_name: &AcmeAccountName) -> Result<Self, anyhow::Error> {
 | 
				
			||||||
        Self::load_path(account_path(account_name.as_ref())).await
 | 
					        Self::load_path(account_path(account_name.as_ref())).await
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -98,7 +99,7 @@ impl AcmeClient {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    pub async fn new_account<'a>(
 | 
					    pub async fn new_account<'a>(
 | 
				
			||||||
        &'a mut self,
 | 
					        &'a mut self,
 | 
				
			||||||
        account_name: &AccountName,
 | 
					        account_name: &AcmeAccountName,
 | 
				
			||||||
        tos_agreed: bool,
 | 
					        tos_agreed: bool,
 | 
				
			||||||
        contact: Vec<String>,
 | 
					        contact: Vec<String>,
 | 
				
			||||||
        rsa_bits: Option<u32>,
 | 
					        rsa_bits: Option<u32>,
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ use tokio::process::Command;
 | 
				
			|||||||
use proxmox_acme_rs::{Authorization, Challenge};
 | 
					use proxmox_acme_rs::{Authorization, Challenge};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::acme::AcmeClient;
 | 
					use crate::acme::AcmeClient;
 | 
				
			||||||
use crate::config::acme::AcmeDomain;
 | 
					use crate::api2::types::AcmeDomain;
 | 
				
			||||||
use crate::server::WorkerTask;
 | 
					use crate::server::WorkerTask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::config::acme::plugin::{DnsPlugin, PluginData};
 | 
					use crate::config::acme::plugin::{DnsPlugin, PluginData};
 | 
				
			||||||
 | 
				
			|||||||
@ -14,12 +14,11 @@ use proxmox_acme_rs::account::AccountData as AcmeAccountData;
 | 
				
			|||||||
use proxmox_acme_rs::Account;
 | 
					use proxmox_acme_rs::Account;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::acme::AcmeClient;
 | 
					use crate::acme::AcmeClient;
 | 
				
			||||||
use crate::api2::types::Authid;
 | 
					 | 
				
			||||||
use crate::config::acl::PRIV_SYS_MODIFY;
 | 
					use crate::config::acl::PRIV_SYS_MODIFY;
 | 
				
			||||||
use crate::config::acme::plugin::{
 | 
					use crate::config::acme::plugin::{
 | 
				
			||||||
    DnsPlugin, DnsPluginCore, DnsPluginCoreUpdater, PLUGIN_ID_SCHEMA,
 | 
					    DnsPlugin, DnsPluginCore, DnsPluginCoreUpdater, PLUGIN_ID_SCHEMA,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use crate::config::acme::{AccountName, KnownAcmeDirectory};
 | 
					use crate::api2::types::{Authid, KnownAcmeDirectory, AcmeAccountName};
 | 
				
			||||||
use crate::server::WorkerTask;
 | 
					use crate::server::WorkerTask;
 | 
				
			||||||
use crate::tools::ControlFlow;
 | 
					use crate::tools::ControlFlow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -65,7 +64,7 @@ const PLUGIN_ITEM_ROUTER: Router = Router::new()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    properties: {
 | 
					    properties: {
 | 
				
			||||||
        name: { type: AccountName },
 | 
					        name: { type: AcmeAccountName },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
/// An ACME Account entry.
 | 
					/// An ACME Account entry.
 | 
				
			||||||
@ -73,7 +72,7 @@ const PLUGIN_ITEM_ROUTER: Router = Router::new()
 | 
				
			|||||||
/// Currently only contains a 'name' property.
 | 
					/// Currently only contains a 'name' property.
 | 
				
			||||||
#[derive(Serialize)]
 | 
					#[derive(Serialize)]
 | 
				
			||||||
pub struct AccountEntry {
 | 
					pub struct AccountEntry {
 | 
				
			||||||
    name: AccountName,
 | 
					    name: AcmeAccountName,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
@ -128,7 +127,7 @@ pub struct AccountInfo {
 | 
				
			|||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
            name: { type: AccountName },
 | 
					            name: { type: AcmeAccountName },
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    access: {
 | 
					    access: {
 | 
				
			||||||
@ -138,7 +137,7 @@ pub struct AccountInfo {
 | 
				
			|||||||
    protected: true,
 | 
					    protected: true,
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
/// Return existing ACME account information.
 | 
					/// Return existing ACME account information.
 | 
				
			||||||
pub async fn get_account(name: AccountName) -> Result<AccountInfo, Error> {
 | 
					pub async fn get_account(name: AcmeAccountName) -> Result<AccountInfo, Error> {
 | 
				
			||||||
    let client = AcmeClient::load(&name).await?;
 | 
					    let client = AcmeClient::load(&name).await?;
 | 
				
			||||||
    let account = client.account()?;
 | 
					    let account = client.account()?;
 | 
				
			||||||
    Ok(AccountInfo {
 | 
					    Ok(AccountInfo {
 | 
				
			||||||
@ -162,7 +161,7 @@ fn account_contact_from_string(s: &str) -> Vec<String> {
 | 
				
			|||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
            name: {
 | 
					            name: {
 | 
				
			||||||
                type: AccountName,
 | 
					                type: AcmeAccountName,
 | 
				
			||||||
                optional: true,
 | 
					                optional: true,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            contact: {
 | 
					            contact: {
 | 
				
			||||||
@ -186,7 +185,7 @@ fn account_contact_from_string(s: &str) -> Vec<String> {
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Register an ACME account.
 | 
					/// Register an ACME account.
 | 
				
			||||||
fn register_account(
 | 
					fn register_account(
 | 
				
			||||||
    name: Option<AccountName>,
 | 
					    name: Option<AcmeAccountName>,
 | 
				
			||||||
    // Todo: email & email-list schema
 | 
					    // Todo: email & email-list schema
 | 
				
			||||||
    contact: String,
 | 
					    contact: String,
 | 
				
			||||||
    tos_url: Option<String>,
 | 
					    tos_url: Option<String>,
 | 
				
			||||||
@ -196,7 +195,7 @@ fn register_account(
 | 
				
			|||||||
    let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
 | 
					    let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let name = name
 | 
					    let name = name
 | 
				
			||||||
        .unwrap_or_else(|| unsafe { AccountName::from_string_unchecked("default".to_string()) });
 | 
					        .unwrap_or_else(|| unsafe { AcmeAccountName::from_string_unchecked("default".to_string()) });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if Path::new(&crate::config::acme::account_path(&name)).exists() {
 | 
					    if Path::new(&crate::config::acme::account_path(&name)).exists() {
 | 
				
			||||||
        http_bail!(BAD_REQUEST, "account {:?} already exists", name);
 | 
					        http_bail!(BAD_REQUEST, "account {:?} already exists", name);
 | 
				
			||||||
@ -233,7 +232,7 @@ fn register_account(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub async fn do_register_account<'a>(
 | 
					pub async fn do_register_account<'a>(
 | 
				
			||||||
    client: &'a mut AcmeClient,
 | 
					    client: &'a mut AcmeClient,
 | 
				
			||||||
    name: &AccountName,
 | 
					    name: &AcmeAccountName,
 | 
				
			||||||
    agree_to_tos: bool,
 | 
					    agree_to_tos: bool,
 | 
				
			||||||
    contact: String,
 | 
					    contact: String,
 | 
				
			||||||
    rsa_bits: Option<u32>,
 | 
					    rsa_bits: Option<u32>,
 | 
				
			||||||
@ -247,7 +246,7 @@ pub async fn do_register_account<'a>(
 | 
				
			|||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
            name: { type: AccountName },
 | 
					            name: { type: AcmeAccountName },
 | 
				
			||||||
            contact: {
 | 
					            contact: {
 | 
				
			||||||
                description: "List of email addresses.",
 | 
					                description: "List of email addresses.",
 | 
				
			||||||
                optional: true,
 | 
					                optional: true,
 | 
				
			||||||
@ -261,7 +260,7 @@ pub async fn do_register_account<'a>(
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Update an ACME account.
 | 
					/// Update an ACME account.
 | 
				
			||||||
pub fn update_account(
 | 
					pub fn update_account(
 | 
				
			||||||
    name: AccountName,
 | 
					    name: AcmeAccountName,
 | 
				
			||||||
    // Todo: email & email-list schema
 | 
					    // Todo: email & email-list schema
 | 
				
			||||||
    contact: Option<String>,
 | 
					    contact: Option<String>,
 | 
				
			||||||
    rpcenv: &mut dyn RpcEnvironment,
 | 
					    rpcenv: &mut dyn RpcEnvironment,
 | 
				
			||||||
@ -291,7 +290,7 @@ pub fn update_account(
 | 
				
			|||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
            name: { type: AccountName },
 | 
					            name: { type: AcmeAccountName },
 | 
				
			||||||
            force: {
 | 
					            force: {
 | 
				
			||||||
                description:
 | 
					                description:
 | 
				
			||||||
                    "Delete account data even if the server refuses to deactivate the account.",
 | 
					                    "Delete account data even if the server refuses to deactivate the account.",
 | 
				
			||||||
@ -307,7 +306,7 @@ pub fn update_account(
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Deactivate an ACME account.
 | 
					/// Deactivate an ACME account.
 | 
				
			||||||
pub fn deactivate_account(
 | 
					pub fn deactivate_account(
 | 
				
			||||||
    name: AccountName,
 | 
					    name: AcmeAccountName,
 | 
				
			||||||
    force: bool,
 | 
					    force: bool,
 | 
				
			||||||
    rpcenv: &mut dyn RpcEnvironment,
 | 
					    rpcenv: &mut dyn RpcEnvironment,
 | 
				
			||||||
) -> Result<String, Error> {
 | 
					) -> Result<String, Error> {
 | 
				
			||||||
 | 
				
			|||||||
@ -14,8 +14,8 @@ use proxmox::list_subdirs_api_method;
 | 
				
			|||||||
use crate::acme::AcmeClient;
 | 
					use crate::acme::AcmeClient;
 | 
				
			||||||
use crate::api2::types::Authid;
 | 
					use crate::api2::types::Authid;
 | 
				
			||||||
use crate::api2::types::NODE_SCHEMA;
 | 
					use crate::api2::types::NODE_SCHEMA;
 | 
				
			||||||
 | 
					use crate::api2::types::AcmeDomain;
 | 
				
			||||||
use crate::config::acl::PRIV_SYS_MODIFY;
 | 
					use crate::config::acl::PRIV_SYS_MODIFY;
 | 
				
			||||||
use crate::config::acme::AcmeDomain;
 | 
					 | 
				
			||||||
use crate::config::node::NodeConfig;
 | 
					use crate::config::node::NodeConfig;
 | 
				
			||||||
use crate::server::WorkerTask;
 | 
					use crate::server::WorkerTask;
 | 
				
			||||||
use crate::tools::cert;
 | 
					use crate::tools::cert;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										126
									
								
								src/api2/types/acme.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/api2/types/acme.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					use std::fmt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use anyhow::Error;
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use proxmox::api::{api, schema::{Schema, StringSchema, ApiStringFormat}};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::api2::types::{
 | 
				
			||||||
 | 
					    DNS_ALIAS_FORMAT, DNS_NAME_FORMAT, PROXMOX_SAFE_ID_FORMAT,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[api(
 | 
				
			||||||
 | 
					    properties: {
 | 
				
			||||||
 | 
					        "domain": { format: &DNS_NAME_FORMAT },
 | 
				
			||||||
 | 
					        "alias": {
 | 
				
			||||||
 | 
					            optional: true,
 | 
				
			||||||
 | 
					            format: &DNS_ALIAS_FORMAT,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "plugin": {
 | 
				
			||||||
 | 
					            optional: true,
 | 
				
			||||||
 | 
					            format: &PROXMOX_SAFE_ID_FORMAT,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    default_key: "domain",
 | 
				
			||||||
 | 
					)]
 | 
				
			||||||
 | 
					#[derive(Deserialize, Serialize)]
 | 
				
			||||||
 | 
					/// A domain entry for an ACME certificate.
 | 
				
			||||||
 | 
					pub struct AcmeDomain {
 | 
				
			||||||
 | 
					    /// The domain to certify for.
 | 
				
			||||||
 | 
					    pub domain: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The domain to use for challenges instead of the default acme challenge domain.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// This is useful if you use CNAME entries to redirect `_acme-challenge.*` domains to a
 | 
				
			||||||
 | 
					    /// different DNS server.
 | 
				
			||||||
 | 
					    #[serde(skip_serializing_if = "Option::is_none")]
 | 
				
			||||||
 | 
					    pub alias: Option<String>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The plugin to use to validate this domain.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Empty means standalone HTTP validation is used.
 | 
				
			||||||
 | 
					    #[serde(skip_serializing_if = "Option::is_none")]
 | 
				
			||||||
 | 
					    pub plugin: Option<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const ACME_DOMAIN_PROPERTY_SCHEMA: Schema = StringSchema::new(
 | 
				
			||||||
 | 
					    "ACME domain configuration string")
 | 
				
			||||||
 | 
					    .format(&ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA))
 | 
				
			||||||
 | 
					    .schema();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[api(
 | 
				
			||||||
 | 
					    properties: {
 | 
				
			||||||
 | 
					        name: { type: String },
 | 
				
			||||||
 | 
					        url: { type: String },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					)]
 | 
				
			||||||
 | 
					/// An ACME directory endpoint with a name and URL.
 | 
				
			||||||
 | 
					#[derive(Serialize)]
 | 
				
			||||||
 | 
					pub struct KnownAcmeDirectory {
 | 
				
			||||||
 | 
					    /// The ACME directory's name.
 | 
				
			||||||
 | 
					    pub name: &'static str,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The ACME directory's endpoint URL.
 | 
				
			||||||
 | 
					    pub url: &'static str,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[api(format: &PROXMOX_SAFE_ID_FORMAT)]
 | 
				
			||||||
 | 
					/// ACME account name.
 | 
				
			||||||
 | 
					#[derive(Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
 | 
				
			||||||
 | 
					#[serde(transparent)]
 | 
				
			||||||
 | 
					pub struct AcmeAccountName(String);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl AcmeAccountName {
 | 
				
			||||||
 | 
					    pub fn into_string(self) -> String {
 | 
				
			||||||
 | 
					        self.0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn from_string(name: String) -> Result<Self, Error> {
 | 
				
			||||||
 | 
					        match &Self::API_SCHEMA {
 | 
				
			||||||
 | 
					            Schema::String(s) => s.check_constraints(&name)?,
 | 
				
			||||||
 | 
					            _ => unreachable!(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(Self(name))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub unsafe fn from_string_unchecked(name: String) -> Self {
 | 
				
			||||||
 | 
					        Self(name)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::ops::Deref for AcmeAccountName {
 | 
				
			||||||
 | 
					    type Target = str;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
 | 
					    fn deref(&self) -> &str {
 | 
				
			||||||
 | 
					        &self.0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::ops::DerefMut for AcmeAccountName {
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
 | 
					    fn deref_mut(&mut self) -> &mut str {
 | 
				
			||||||
 | 
					        &mut self.0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl AsRef<str> for AcmeAccountName {
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
 | 
					    fn as_ref(&self) -> &str {
 | 
				
			||||||
 | 
					        self.0.as_ref()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Debug for AcmeAccountName {
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
				
			||||||
 | 
					        fmt::Debug::fmt(&self.0, f)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for AcmeAccountName {
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
				
			||||||
 | 
					        fmt::Display::fmt(&self.0, f)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -37,6 +37,9 @@ pub use tape::*;
 | 
				
			|||||||
mod file_restore;
 | 
					mod file_restore;
 | 
				
			||||||
pub use file_restore::*;
 | 
					pub use file_restore::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod acme;
 | 
				
			||||||
 | 
					pub use acme::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// File names: may not contain slashes, may not start with "."
 | 
					// File names: may not contain slashes, may not start with "."
 | 
				
			||||||
pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
 | 
					pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
 | 
				
			||||||
    if name.starts_with('.') {
 | 
					    if name.starts_with('.') {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,8 +8,9 @@ use proxmox::tools::fs::file_get_contents;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use proxmox_backup::acme::AcmeClient;
 | 
					use proxmox_backup::acme::AcmeClient;
 | 
				
			||||||
use proxmox_backup::api2;
 | 
					use proxmox_backup::api2;
 | 
				
			||||||
 | 
					use proxmox_backup::api2::types::AcmeAccountName;
 | 
				
			||||||
use proxmox_backup::config::acme::plugin::DnsPluginCoreUpdater;
 | 
					use proxmox_backup::config::acme::plugin::DnsPluginCoreUpdater;
 | 
				
			||||||
use proxmox_backup::config::acme::{AccountName, KNOWN_ACME_DIRECTORIES};
 | 
					use proxmox_backup::config::acme::KNOWN_ACME_DIRECTORIES;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn acme_mgmt_cli() -> CommandLineInterface {
 | 
					pub fn acme_mgmt_cli() -> CommandLineInterface {
 | 
				
			||||||
    let cmd_def = CliCommandMap::new()
 | 
					    let cmd_def = CliCommandMap::new()
 | 
				
			||||||
@ -49,7 +50,7 @@ fn list_accounts(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Er
 | 
				
			|||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
            name: { type: AccountName },
 | 
					            name: { type: AcmeAccountName },
 | 
				
			||||||
            "output-format": {
 | 
					            "output-format": {
 | 
				
			||||||
                schema: OUTPUT_FORMAT,
 | 
					                schema: OUTPUT_FORMAT,
 | 
				
			||||||
                optional: true,
 | 
					                optional: true,
 | 
				
			||||||
@ -83,7 +84,7 @@ async fn get_account(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<()
 | 
				
			|||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
            name: { type: AccountName },
 | 
					            name: { type: AcmeAccountName },
 | 
				
			||||||
            contact: {
 | 
					            contact: {
 | 
				
			||||||
                description: "List of email addresses.",
 | 
					                description: "List of email addresses.",
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
@ -97,7 +98,7 @@ async fn get_account(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<()
 | 
				
			|||||||
)]
 | 
					)]
 | 
				
			||||||
/// Register an ACME account.
 | 
					/// Register an ACME account.
 | 
				
			||||||
async fn register_account(
 | 
					async fn register_account(
 | 
				
			||||||
    name: AccountName,
 | 
					    name: AcmeAccountName,
 | 
				
			||||||
    contact: String,
 | 
					    contact: String,
 | 
				
			||||||
    directory: Option<String>,
 | 
					    directory: Option<String>,
 | 
				
			||||||
) -> Result<(), Error> {
 | 
					) -> Result<(), Error> {
 | 
				
			||||||
@ -169,7 +170,7 @@ async fn register_account(
 | 
				
			|||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
            name: { type: AccountName },
 | 
					            name: { type: AcmeAccountName },
 | 
				
			||||||
            contact: {
 | 
					            contact: {
 | 
				
			||||||
                description: "List of email addresses.",
 | 
					                description: "List of email addresses.",
 | 
				
			||||||
                type: String,
 | 
					                type: String,
 | 
				
			||||||
@ -194,7 +195,7 @@ async fn update_account(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result
 | 
				
			|||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
            name: { type: AccountName },
 | 
					            name: { type: AcmeAccountName },
 | 
				
			||||||
            force: {
 | 
					            force: {
 | 
				
			||||||
                description:
 | 
					                description:
 | 
				
			||||||
                    "Delete account data even if the server refuses to deactivate the account.",
 | 
					                    "Delete account data even if the server refuses to deactivate the account.",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,15 @@
 | 
				
			|||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::fmt;
 | 
					 | 
				
			||||||
use std::path::Path;
 | 
					use std::path::Path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::{bail, format_err, Error};
 | 
					use anyhow::{bail, format_err, Error};
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use proxmox::api::{api, schema::{Schema, StringSchema, ApiStringFormat}};
 | 
					 | 
				
			||||||
use proxmox::sys::error::SysError;
 | 
					use proxmox::sys::error::SysError;
 | 
				
			||||||
use proxmox::tools::fs::CreateOptions;
 | 
					use proxmox::tools::fs::CreateOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::api2::types::{
 | 
					use crate::api2::types::{
 | 
				
			||||||
    DNS_ALIAS_FORMAT, DNS_NAME_FORMAT, PROXMOX_SAFE_ID_FORMAT, PROXMOX_SAFE_ID_REGEX,
 | 
					    PROXMOX_SAFE_ID_REGEX,
 | 
				
			||||||
 | 
					    KnownAcmeDirectory,
 | 
				
			||||||
 | 
					    AcmeAccountName,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use crate::tools::ControlFlow;
 | 
					use crate::tools::ControlFlow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,61 +43,6 @@ pub(crate) fn make_acme_account_dir() -> nix::Result<()> {
 | 
				
			|||||||
    create_acme_subdir(ACME_ACCOUNT_DIR)
 | 
					    create_acme_subdir(ACME_ACCOUNT_DIR)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[api(
 | 
					 | 
				
			||||||
    properties: {
 | 
					 | 
				
			||||||
        "domain": { format: &DNS_NAME_FORMAT },
 | 
					 | 
				
			||||||
        "alias": {
 | 
					 | 
				
			||||||
            optional: true,
 | 
					 | 
				
			||||||
            format: &DNS_ALIAS_FORMAT,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "plugin": {
 | 
					 | 
				
			||||||
            optional: true,
 | 
					 | 
				
			||||||
            format: &PROXMOX_SAFE_ID_FORMAT,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    default_key: "domain",
 | 
					 | 
				
			||||||
)]
 | 
					 | 
				
			||||||
#[derive(Deserialize, Serialize)]
 | 
					 | 
				
			||||||
/// A domain entry for an ACME certificate.
 | 
					 | 
				
			||||||
pub struct AcmeDomain {
 | 
					 | 
				
			||||||
    /// The domain to certify for.
 | 
					 | 
				
			||||||
    pub domain: String,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// The domain to use for challenges instead of the default acme challenge domain.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// This is useful if you use CNAME entries to redirect `_acme-challenge.*` domains to a
 | 
					 | 
				
			||||||
    /// different DNS server.
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub alias: Option<String>,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// The plugin to use to validate this domain.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// Empty means standalone HTTP validation is used.
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub plugin: Option<String>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const ACME_DOMAIN_PROPERTY_SCHEMA: Schema = StringSchema::new(
 | 
					 | 
				
			||||||
    "ACME domain configuration string")
 | 
					 | 
				
			||||||
    .format(&ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA))
 | 
					 | 
				
			||||||
    .schema();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[api(
 | 
					 | 
				
			||||||
    properties: {
 | 
					 | 
				
			||||||
        name: { type: String },
 | 
					 | 
				
			||||||
        url: { type: String },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
)]
 | 
					 | 
				
			||||||
/// An ACME directory endpoint with a name and URL.
 | 
					 | 
				
			||||||
#[derive(Serialize)]
 | 
					 | 
				
			||||||
pub struct KnownAcmeDirectory {
 | 
					 | 
				
			||||||
    /// The ACME directory's name.
 | 
					 | 
				
			||||||
    pub name: &'static str,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// The ACME directory's endpoint URL.
 | 
					 | 
				
			||||||
    pub url: &'static str,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const KNOWN_ACME_DIRECTORIES: &[KnownAcmeDirectory] = &[
 | 
					pub const KNOWN_ACME_DIRECTORIES: &[KnownAcmeDirectory] = &[
 | 
				
			||||||
    KnownAcmeDirectory {
 | 
					    KnownAcmeDirectory {
 | 
				
			||||||
        name: "Let's Encrypt V2",
 | 
					        name: "Let's Encrypt V2",
 | 
				
			||||||
@ -116,70 +60,10 @@ pub fn account_path(name: &str) -> String {
 | 
				
			|||||||
    format!("{}/{}", ACME_ACCOUNT_DIR, name)
 | 
					    format!("{}/{}", ACME_ACCOUNT_DIR, name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[api(format: &PROXMOX_SAFE_ID_FORMAT)]
 | 
					 | 
				
			||||||
/// ACME account name.
 | 
					 | 
				
			||||||
#[derive(Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
 | 
					 | 
				
			||||||
#[serde(transparent)]
 | 
					 | 
				
			||||||
pub struct AccountName(String);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl AccountName {
 | 
					 | 
				
			||||||
    pub fn into_string(self) -> String {
 | 
					 | 
				
			||||||
        self.0
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn from_string(name: String) -> Result<Self, Error> {
 | 
					 | 
				
			||||||
        match &Self::API_SCHEMA {
 | 
					 | 
				
			||||||
            Schema::String(s) => s.check_constraints(&name)?,
 | 
					 | 
				
			||||||
            _ => unreachable!(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Ok(Self(name))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub unsafe fn from_string_unchecked(name: String) -> Self {
 | 
					 | 
				
			||||||
        Self(name)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::ops::Deref for AccountName {
 | 
					 | 
				
			||||||
    type Target = str;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    fn deref(&self) -> &str {
 | 
					 | 
				
			||||||
        &self.0
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::ops::DerefMut for AccountName {
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    fn deref_mut(&mut self) -> &mut str {
 | 
					 | 
				
			||||||
        &mut self.0
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl AsRef<str> for AccountName {
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    fn as_ref(&self) -> &str {
 | 
					 | 
				
			||||||
        self.0.as_ref()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl fmt::Debug for AccountName {
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
					 | 
				
			||||||
        fmt::Debug::fmt(&self.0, f)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl fmt::Display for AccountName {
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
					 | 
				
			||||||
        fmt::Display::fmt(&self.0, f)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn foreach_acme_account<F>(mut func: F) -> Result<(), Error>
 | 
					pub fn foreach_acme_account<F>(mut func: F) -> Result<(), Error>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    F: FnMut(AccountName) -> ControlFlow<Result<(), Error>>,
 | 
					    F: FnMut(AcmeAccountName) -> ControlFlow<Result<(), Error>>,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    match crate::tools::fs::scan_subdir(-1, ACME_ACCOUNT_DIR, &PROXMOX_SAFE_ID_REGEX) {
 | 
					    match crate::tools::fs::scan_subdir(-1, ACME_ACCOUNT_DIR, &PROXMOX_SAFE_ID_REGEX) {
 | 
				
			||||||
        Ok(files) => {
 | 
					        Ok(files) => {
 | 
				
			||||||
@ -191,7 +75,10 @@ where
 | 
				
			|||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let account_name = AccountName(file_name.to_owned());
 | 
					                let account_name = match AcmeAccountName::from_string(file_name.to_owned()) {
 | 
				
			||||||
 | 
					                    Ok(account_name) => account_name,
 | 
				
			||||||
 | 
					                    Err(_) => continue,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if let ControlFlow::Break(result) = func(account_name) {
 | 
					                if let ControlFlow::Break(result) = func(account_name) {
 | 
				
			||||||
                    return result;
 | 
					                    return result;
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,8 @@ use crate::api2::types::PROXMOX_SAFE_ID_FORMAT;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub const PLUGIN_ID_SCHEMA: Schema = StringSchema::new("ACME Challenge Plugin ID.")
 | 
					pub const PLUGIN_ID_SCHEMA: Schema = StringSchema::new("ACME Challenge Plugin ID.")
 | 
				
			||||||
    .format(&PROXMOX_SAFE_ID_FORMAT)
 | 
					    .format(&PROXMOX_SAFE_ID_FORMAT)
 | 
				
			||||||
 | 
					    .min_length(1)
 | 
				
			||||||
 | 
					    .max_length(32)
 | 
				
			||||||
    .schema();
 | 
					    .schema();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lazy_static! {
 | 
					lazy_static! {
 | 
				
			||||||
 | 
				
			|||||||
@ -10,8 +10,8 @@ use proxmox::api::api;
 | 
				
			|||||||
use proxmox::api::schema::{ApiStringFormat, Updater};
 | 
					use proxmox::api::schema::{ApiStringFormat, Updater};
 | 
				
			||||||
use proxmox::tools::fs::{replace_file, CreateOptions};
 | 
					use proxmox::tools::fs::{replace_file, CreateOptions};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::api2::types::{AcmeDomain, AcmeAccountName, ACME_DOMAIN_PROPERTY_SCHEMA};
 | 
				
			||||||
use crate::acme::AcmeClient;
 | 
					use crate::acme::AcmeClient;
 | 
				
			||||||
use crate::config::acme::{AccountName, AcmeDomain, ACME_DOMAIN_PROPERTY_SCHEMA};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CONF_FILE: &str = configdir!("/node.cfg");
 | 
					const CONF_FILE: &str = configdir!("/node.cfg");
 | 
				
			||||||
const LOCK_FILE: &str = configdir!("/.node.lck");
 | 
					const LOCK_FILE: &str = configdir!("/.node.lck");
 | 
				
			||||||
@ -49,7 +49,7 @@ pub fn save_config(config: &NodeConfig) -> Result<(), Error> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    properties: {
 | 
					    properties: {
 | 
				
			||||||
        account: { type: AccountName },
 | 
					        account: { type: AcmeAccountName },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
#[derive(Deserialize, Serialize)]
 | 
					#[derive(Deserialize, Serialize)]
 | 
				
			||||||
@ -58,7 +58,7 @@ pub fn save_config(config: &NodeConfig) -> Result<(), Error> {
 | 
				
			|||||||
/// Currently only contains the name of the account use.
 | 
					/// Currently only contains the name of the account use.
 | 
				
			||||||
pub struct AcmeConfig {
 | 
					pub struct AcmeConfig {
 | 
				
			||||||
    /// Account to use to acquire ACME certificates.
 | 
					    /// Account to use to acquire ACME certificates.
 | 
				
			||||||
    account: AccountName,
 | 
					    account: AcmeAccountName,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user