2 use lazy_static
::lazy_static
;
3 use serde
::{Deserialize, Serialize}
;
9 section_config
::{SectionConfig, SectionConfigData, SectionConfigPlugin}
,
12 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
14 use crate::api2
::types
::PROXMOX_SAFE_ID_FORMAT
;
16 pub const PLUGIN_ID_SCHEMA
: Schema
= StringSchema
::new("ACME Challenge Plugin ID.")
17 .format(&PROXMOX_SAFE_ID_FORMAT
)
23 pub static ref CONFIG
: SectionConfig
= init();
28 id
: { schema: PLUGIN_ID_SCHEMA }
,
31 #[derive(Deserialize, Serialize)]
32 /// Standalone ACME Plugin for the http-1 challenge.
33 pub struct StandalonePlugin
{
38 impl Default
for StandalonePlugin
{
39 fn default() -> Self {
41 id
: "standalone".to_string(),
48 id
: { schema: PLUGIN_ID_SCHEMA }
,
57 maximum
: 2 * 24 * 60 * 60,
61 /// DNS ACME Challenge Plugin core data.
62 #[derive(Deserialize, Serialize, Updater)]
63 #[serde(rename_all = "kebab-case")]
64 pub struct DnsPluginCore
{
66 pub(crate) id
: String
,
68 /// DNS API Plugin Id.
69 pub(crate) api
: String
,
71 /// Extra delay in seconds to wait before requesting validation.
73 /// Allows to cope with long TTL of DNS records.
74 #[serde(skip_serializing_if = "Option::is_none", default)]
75 validation_delay
: Option
<u32>,
77 /// Flag to disable the config.
78 #[serde(skip_serializing_if = "Option::is_none", default)]
79 disable
: Option
<bool
>,
84 core
: { type: DnsPluginCore }
,
87 /// DNS ACME Challenge Plugin.
88 #[derive(Deserialize, Serialize)]
89 #[serde(rename_all = "kebab-case")]
90 pub struct DnsPlugin
{
92 pub(crate) core
: DnsPluginCore
,
94 // FIXME: The `Updater` should allow:
95 // * having different descriptions for this and the Updater version
96 // * having different `#[serde]` attributes for the Updater
97 // * or, well, leaving fields out completely in teh Updater but this means we may need to
98 // separate Updater and Builder deriving.
99 // We handle this property separately in the API calls.
100 /// DNS plugin data (base64url encoded without padding).
101 #[serde(with = "proxmox::tools::serde::string_as_base64url_nopad")]
102 pub(crate) data
: String
,
106 pub fn decode_data(&self, output
: &mut Vec
<u8>) -> Result
<(), Error
> {
107 Ok(base64
::decode_config_buf(
109 base64
::URL_SAFE_NO_PAD
,
115 fn init() -> SectionConfig
{
116 let mut config
= SectionConfig
::new(&PLUGIN_ID_SCHEMA
);
118 let standalone_schema
= match &StandalonePlugin
::API_SCHEMA
{
119 Schema
::Object(schema
) => schema
,
122 let standalone_plugin
= SectionConfigPlugin
::new(
123 "standalone".to_string(),
124 Some("id".to_string()),
127 config
.register_plugin(standalone_plugin
);
129 let dns_challenge_schema
= match DnsPlugin
::API_SCHEMA
{
130 Schema
::AllOf(ref schema
) => schema
,
133 let dns_challenge_plugin
= SectionConfigPlugin
::new(
135 Some("id".to_string()),
136 dns_challenge_schema
,
138 config
.register_plugin(dns_challenge_plugin
);
143 const ACME_PLUGIN_CFG_FILENAME
: &str = configdir
!("/acme/plugins.cfg");
144 const ACME_PLUGIN_CFG_LOCKFILE
: &str = configdir
!("/acme/.plugins.lck");
145 const LOCK_TIMEOUT
: std
::time
::Duration
= std
::time
::Duration
::from_secs(10);
147 pub fn lock() -> Result
<std
::fs
::File
, Error
> {
148 super::make_acme_dir()?
;
149 proxmox
::tools
::fs
::open_file_locked(ACME_PLUGIN_CFG_LOCKFILE
, LOCK_TIMEOUT
, true)
152 pub fn config() -> Result
<(PluginData
, [u8; 32]), Error
> {
153 let content
= proxmox
::tools
::fs
::file_read_optional_string(ACME_PLUGIN_CFG_FILENAME
)?
154 .unwrap_or_else(|| "".to_string());
156 let digest
= openssl
::sha
::sha256(content
.as_bytes());
157 let mut data
= CONFIG
.parse(ACME_PLUGIN_CFG_FILENAME
, &content
)?
;
159 if data
.sections
.get("standalone").is_none() {
160 let standalone
= StandalonePlugin
::default();
161 data
.set_data("standalone", "standalone", &standalone
)
165 Ok((PluginData { data }
, digest
))
168 pub fn save_config(config
: &PluginData
) -> Result
<(), Error
> {
169 super::make_acme_dir()?
;
170 let raw
= CONFIG
.write(ACME_PLUGIN_CFG_FILENAME
, &config
.data
)?
;
172 let backup_user
= crate::backup
::backup_user()?
;
173 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640);
174 // set the correct owner/group/permissions while saving file
175 // owner(rw) = root, group(r)= backup
176 let options
= CreateOptions
::new()
178 .owner(nix
::unistd
::ROOT
)
179 .group(backup_user
.gid
);
181 replace_file(ACME_PLUGIN_CFG_FILENAME
, raw
.as_bytes(), options
)?
;
186 pub struct PluginData
{
187 data
: SectionConfigData
,
190 // And some convenience helpers.
192 pub fn remove(&mut self, name
: &str) -> Option
<(String
, Value
)> {
193 self.data
.sections
.remove(name
)
196 pub fn contains_key(&mut self, name
: &str) -> bool
{
197 self.data
.sections
.contains_key(name
)
200 pub fn get(&self, name
: &str) -> Option
<&(String
, Value
)> {
201 self.data
.sections
.get(name
)
204 pub fn get_mut(&mut self, name
: &str) -> Option
<&mut (String
, Value
)> {
205 self.data
.sections
.get_mut(name
)
208 pub fn insert(&mut self, id
: String
, ty
: String
, plugin
: Value
) {
209 self.data
.sections
.insert(id
, (ty
, plugin
));
212 pub fn iter(&self) -> impl Iterator
<Item
= (&String
, &(String
, Value
))> + Send
{
213 self.data
.sections
.iter()