]> git.proxmox.com Git - proxmox-backup.git/blame - src/config/acme/mod.rs
completion: ACME plugin type: comment out http type for now, not useful
[proxmox-backup.git] / src / config / acme / mod.rs
CommitLineData
cb67ecad 1use std::collections::HashMap;
cb67ecad
WB
2use std::path::Path;
3
4use anyhow::{bail, format_err, Error};
d308dc8a 5use serde_json::Value;
cb67ecad 6
cb67ecad 7use proxmox::sys::error::SysError;
d308dc8a 8use proxmox::tools::fs::{CreateOptions, file_read_string};
cb67ecad
WB
9
10use crate::api2::types::{
39c5db7f 11 PROXMOX_SAFE_ID_REGEX,
d308dc8a 12 AcmeChallengeSchema,
39c5db7f
DM
13 KnownAcmeDirectory,
14 AcmeAccountName,
cb67ecad
WB
15};
16use crate::tools::ControlFlow;
17
18pub(crate) const ACME_DIR: &str = configdir!("/acme");
19pub(crate) const ACME_ACCOUNT_DIR: &str = configdir!("/acme/accounts");
20
d308dc8a
TL
21pub(crate) const ACME_DNS_SCHEMA_FN: &str = "/usr/share/proxmox-acme/dns-challenge-schema.json";
22
cb67ecad
WB
23pub mod plugin;
24
25// `const fn`ify this once it is supported in `proxmox`
26fn root_only() -> CreateOptions {
27 CreateOptions::new()
28 .owner(nix::unistd::ROOT)
29 .group(nix::unistd::Gid::from_raw(0))
30 .perm(nix::sys::stat::Mode::from_bits_truncate(0o700))
31}
32
33fn create_acme_subdir(dir: &str) -> nix::Result<()> {
34 match proxmox::tools::fs::create_dir(dir, root_only()) {
35 Ok(()) => Ok(()),
36 Err(err) if err.already_exists() => Ok(()),
37 Err(err) => Err(err),
38 }
39}
40
41pub(crate) fn make_acme_dir() -> nix::Result<()> {
42 create_acme_subdir(ACME_DIR)
43}
44
45pub(crate) fn make_acme_account_dir() -> nix::Result<()> {
46 make_acme_dir()?;
47 create_acme_subdir(ACME_ACCOUNT_DIR)
48}
49
cb67ecad
WB
50pub const KNOWN_ACME_DIRECTORIES: &[KnownAcmeDirectory] = &[
51 KnownAcmeDirectory {
52 name: "Let's Encrypt V2",
53 url: "https://acme-v02.api.letsencrypt.org/directory",
54 },
55 KnownAcmeDirectory {
56 name: "Let's Encrypt V2 Staging",
57 url: "https://acme-staging-v02.api.letsencrypt.org/directory",
58 },
59];
60
61pub const DEFAULT_ACME_DIRECTORY_ENTRY: &KnownAcmeDirectory = &KNOWN_ACME_DIRECTORIES[0];
62
63pub fn account_path(name: &str) -> String {
64 format!("{}/{}", ACME_ACCOUNT_DIR, name)
65}
66
cb67ecad
WB
67
68pub fn foreach_acme_account<F>(mut func: F) -> Result<(), Error>
69where
39c5db7f 70 F: FnMut(AcmeAccountName) -> ControlFlow<Result<(), Error>>,
cb67ecad
WB
71{
72 match crate::tools::fs::scan_subdir(-1, ACME_ACCOUNT_DIR, &PROXMOX_SAFE_ID_REGEX) {
73 Ok(files) => {
74 for file in files {
75 let file = file?;
76 let file_name = unsafe { file.file_name_utf8_unchecked() };
77
78 if file_name.starts_with('_') {
79 continue;
80 }
81
39c5db7f
DM
82 let account_name = match AcmeAccountName::from_string(file_name.to_owned()) {
83 Ok(account_name) => account_name,
84 Err(_) => continue,
85 };
cb67ecad
WB
86
87 if let ControlFlow::Break(result) = func(account_name) {
88 return result;
89 }
90 }
91 Ok(())
cb67ecad
WB
92 }
93 Err(err) if err.not_found() => Ok(()),
94 Err(err) => Err(err.into()),
95 }
96}
97
98pub fn mark_account_deactivated(name: &str) -> Result<(), Error> {
99 let from = account_path(name);
100 for i in 0..100 {
101 let to = account_path(&format!("_deactivated_{}_{}", name, i));
102 if !Path::new(&to).exists() {
103 return std::fs::rename(&from, &to).map_err(|err| {
104 format_err!(
105 "failed to move account path {:?} to {:?} - {}",
106 from,
107 to,
108 err
109 )
110 });
111 }
112 }
113 bail!(
114 "No free slot to rename deactivated account {:?}, please cleanup {:?}",
115 from,
116 ACME_ACCOUNT_DIR
117 );
118}
119
d308dc8a
TL
120pub fn load_dns_challenge_schema() -> Result<Vec<AcmeChallengeSchema>, Error> {
121 let raw = file_read_string(&ACME_DNS_SCHEMA_FN)?;
122 let schemas: serde_json::Map<String, Value> = serde_json::from_str(&raw)?;
123
124 Ok(schemas
125 .iter()
126 .map(|(id, schema)| AcmeChallengeSchema {
127 id: id.to_owned(),
128 name: schema
129 .get("name")
130 .and_then(Value::as_str)
131 .unwrap_or(id)
132 .to_owned(),
133 ty: "dns",
134 schema: schema.to_owned(),
135 })
136 .collect())
137}
138
cb67ecad
WB
139pub fn complete_acme_account(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
140 let mut out = Vec::new();
141 let _ = foreach_acme_account(|name| {
142 out.push(name.into_string());
143 ControlFlow::CONTINUE
144 });
145 out
146}
147
148pub fn complete_acme_plugin(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
149 match plugin::config() {
150 Ok((config, _digest)) => config
151 .iter()
152 .map(|(id, (_type, _cfg))| id.clone())
153 .collect(),
154 Err(_) => Vec::new(),
155 }
156}
157
158pub fn complete_acme_plugin_type(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
e857f1fa
TL
159 vec![
160 "dns".to_string(),
161 //"http".to_string(), // makes currently not realyl sense to create or the like
162 ]
cb67ecad 163}