diff --git a/README.md b/README.md index b16f2a4..85d609b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Sentry and opentelemetry are optional, they are supported in the full-featured v [Examples](./examples/README.md) +[Docs](./docs/README.md) + ```mermaid flowchart LR internet("Internet") -- request --> pingap["Pingap"] diff --git a/README_zh.md b/README_zh.md index 818876e..a27ef39 100644 --- a/README_zh.md +++ b/README_zh.md @@ -6,6 +6,8 @@ Sentry与opentelemetry为可选的特性,在全功能版本中支持。 [例子](./examples/README.md) +[文档](./docs/README.md) + ```mermaid flowchart LR diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8bcaef3 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +# Documents + +- [acme chart](./acme_chart.md) diff --git a/docs/acme_chart.md b/docs/acme_chart.md new file mode 100644 index 0000000..b8e7837 --- /dev/null +++ b/docs/acme_chart.md @@ -0,0 +1,36 @@ +# ACME + +### Acme Service + +```mermaid +graph TD; + start("new acme service") --> validate_certificate{{Load certificate and validate}}; + validate_certificate -- not found or expired --> new_acme_order; + + new_acme_order --> get_authorizations; + + get_authorizations -- load all authorization and save to files --> set_challenges; + + set_challenges -- wait for ready --> order_fresh; + + order_fresh -- ready --> done; + order_fresh -- not ready --> delay; + + delay -- xms*2 --> order_fresh + + validate_certificate -- valid --> done("wait for next task"); +``` + + +### Http 01 Challenge + +```mermaid +graph TD; + start("new http 80") -- wait for http-01 challenge request --> handle_request; + + handle_request -- get token from file --> validate_token; + + validate_token -- fail(not found) --> done; + + validate_token -- success --> done; +``` diff --git a/src/acme/lets_encrypt.rs b/src/acme/lets_encrypt.rs index 2336364..756f07a 100644 --- a/src/acme/lets_encrypt.rs +++ b/src/acme/lets_encrypt.rs @@ -14,8 +14,8 @@ use super::{get_token_path, Certificate, Error, Result}; use crate::config::{ - get_current_config, load_config, save_config, LoadConfigOptions, - PingapConf, CATEGORY_CERTIFICATE, + get_config_storage, get_current_config, load_config, save_config, + LoadConfigOptions, PingapConf, CATEGORY_CERTIFICATE, }; use crate::http_extra::HttpResponse; use crate::proxy::init_certificates; @@ -32,7 +32,6 @@ use instant_acme::{ use pingora::proxy::Session; use std::time::Duration; use substring::Substring; -use tokio::fs; use tracing::{error, info}; struct LetsEncryptService { @@ -151,19 +150,22 @@ pub async fn handle_lets_encrypt( if path.starts_with(WELL_KNOWN_PATH_PREFIX) { // token auth let token = path.substring(WELL_KNOWN_PATH_PREFIX.len(), path.len()); + let Some(storage) = get_config_storage() else { + return Err(util::new_internal_error( + 500, + "get config storage fail".to_string(), + )); + }; - let value = match fs::read_to_string(get_token_path().join(token)).await - { - Ok(value) => Ok(value), - Err(e) => { + let value = + storage.load(&get_token_path(token)).await.map_err(|e| { error!( token, err = e.to_string(), "let't encrypt http-01 fail" ); - Err(util::new_internal_error(500, e.to_string())) - }, - }?; + util::new_internal_error(500, e.to_string()) + })?; info!(token, "let't encrypt http-01 success"); HttpResponse { status: StatusCode::OK, @@ -235,10 +237,11 @@ async fn new_lets_encrypt( })?; let mut challenges = Vec::with_capacity(authorizations.len()); - let token_path = get_token_path(); - fs::create_dir_all(&token_path) - .await - .map_err(|e| Error::Io { source: e })?; + let Some(storage) = get_config_storage() else { + return Err(Error::NotFound { + message: "storage not found".to_string(), + }); + }; for authz in &authorizations { info!( @@ -262,11 +265,17 @@ async fn new_lets_encrypt( let instant_acme::Identifier::Dns(identifier) = &authz.identifier; let key_auth = order.key_authorization(challenge); - - fs::write(token_path.join(&challenge.token), key_auth.as_str()) + storage + .save( + &get_token_path(&challenge.token), + key_auth.as_str().as_bytes(), + ) .await - .map_err(|e| Error::Io { source: e })?; - // http://your-domain/.well-known/acme-challenge/ + .map_err(|e| Error::Fail { + category: "save_token".to_string(), + message: e.to_string(), + })?; + let well_known_path = format!("{WELL_KNOWN_PATH_PREFIX}{}", challenge.token); info!(well_known_path, "let's encrypt well known path",); diff --git a/src/acme/mod.rs b/src/acme/mod.rs index 1102129..119247f 100644 --- a/src/acme/mod.rs +++ b/src/acme/mod.rs @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{config::get_current_config, util}; +use crate::util; use serde::{Deserialize, Serialize}; use snafu::Snafu; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::path::{Path, PathBuf}; #[derive(Debug, Snafu)] pub enum Error { @@ -36,8 +35,6 @@ pub enum Error { Fail { category: String, message: String }, #[snafu(display("X509 error, category: {category}, {message}"))] X509 { category: String, message: String }, - #[snafu(display("Io error, {source}"))] - Io { source: std::io::Error }, } type Result = std::result::Result; @@ -131,15 +128,8 @@ impl Certificate { } } -pub fn get_token_path() -> PathBuf { - let token_path = util::resolve_path( - &get_current_config() - .basic - .acme_token_directory - .clone() - .unwrap_or("/tmp/pingap-acme".to_string()), - ); - Path::new(&token_path).to_path_buf() +pub fn get_token_path(key: &str) -> String { + format!("pingap-acme-tokens/${key}") } mod lets_encrypt; diff --git a/src/config/common.rs b/src/config/common.rs index 2b5c134..84cd320 100644 --- a/src/config/common.rs +++ b/src/config/common.rs @@ -503,7 +503,6 @@ pub struct BasicConf { pub auto_restart_check_interval: Option, pub cache_directory: Option, pub cache_max_size: Option, - pub acme_token_directory: Option, } impl BasicConf { diff --git a/src/config/etcd.rs b/src/config/etcd.rs index 981521d..a5d5e3f 100644 --- a/src/config/etcd.rs +++ b/src/config/etcd.rs @@ -120,14 +120,13 @@ impl ConfigStorage for EtcdStorage { category: &str, name: Option<&str>, ) -> Result<()> { - let filepath = self.path.clone(); conf.validate()?; let (path, toml_value) = if self.separation && name.is_some() { conf.get_toml(category, name)? } else { conf.get_toml(category, None)? }; - let key = format!("{filepath}{path}"); + let key = util::path_join(&self.path, &path); let mut c = self.connect().await?; if toml_value.is_empty() { c.delete(key, None) @@ -158,6 +157,25 @@ impl ConfigStorage for EtcdStorage { etcd_watch_stream: Some(stream), }) } + async fn save(&self, key: &str, data: &[u8]) -> Result<()> { + let key = util::path_join(&self.path, key); + let mut c = self.connect().await?; + c.put(key, data, None) + .await + .map_err(|e| Error::Etcd { source: e })?; + Ok(()) + } + async fn load(&self, key: &str) -> Result> { + let key = util::path_join(&self.path, key); + let mut c = self.connect().await?; + let arr = c + .get(key, None) + .await + .map_err(|e| Error::Etcd { source: e })? + .take_kvs(); + let buf = if arr.is_empty() { b"" } else { arr[0].value() }; + Ok(buf.into()) + } } #[cfg(test)] diff --git a/src/config/file.rs b/src/config/file.rs index ddaca15..9690fca 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -141,7 +141,7 @@ impl ConfigStorage for FileStorage { conf.get_toml(category, None)? }; - let filepath = format!("{filepath}{path}"); + let filepath = util::path_join(&filepath, &path); let target_file = Path::new(&filepath); if let Some(p) = Path::new(&target_file).parent() { fs::create_dir_all(p).await.map_err(|e| Error::Io { @@ -167,6 +167,30 @@ impl ConfigStorage for FileStorage { Ok(()) } + async fn save(&self, key: &str, data: &[u8]) -> Result<()> { + let key = util::path_join(&self.path, key); + let path = Path::new(&key); + if let Some(p) = path.parent() { + fs::create_dir_all(p).await.map_err(|e| Error::Io { + source: e, + file: key.to_string(), + })?; + } + fs::write(path, data).await.map_err(|e| Error::Io { + source: e, + file: key.to_string(), + })?; + Ok(()) + } + async fn load(&self, key: &str) -> Result> { + let key = util::path_join(&self.path, key); + let path = Path::new(&key); + let buf = fs::read(path).await.map_err(|e| Error::Io { + source: e, + file: key.to_string(), + })?; + Ok(buf) + } } #[cfg(test)] diff --git a/src/config/mod.rs b/src/config/mod.rs index 05cce1a..ba27864 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -105,6 +105,8 @@ pub trait ConfigStorage { etcd_watch_stream: None, }) } + async fn save(&self, key: &str, data: &[u8]) -> Result<()>; + async fn load(&self, key: &str) -> Result>; } static CONFIG_STORAGE: OnceCell> = diff --git a/src/proxy/dynamic_certificate.rs b/src/proxy/dynamic_certificate.rs index 0ca063a..e8608c4 100644 --- a/src/proxy/dynamic_certificate.rs +++ b/src/proxy/dynamic_certificate.rs @@ -89,7 +89,6 @@ fn parse_certificate( "" }; - let hash_key = certificate_config.hash_key(); let tls_chain = diff --git a/src/util/mod.rs b/src/util/mod.rs index dacc5a8..742e1f6 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -477,6 +477,14 @@ pub fn toml_omit_empty_value(value: &str) -> Result { }) } +pub fn path_join(value1: &str, value2: &str) -> String { + if value2.starts_with("/") { + format!("{value1}{value2}") + } else { + format!("{value1}/{value2}") + } +} + #[cfg(test)] mod tests { use super::{ diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 66da7cb..51401c8 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -106,8 +106,6 @@ export default { cacheMaxSizePlaceholder: "Input max size of cache(e.g. 100mb)", upgradeSock: "Upgrade Sock For Daemon", upgradeSockPlaceholder: "Input upgrade unix sock for daemon", - acmeTokenDirectory: "ACME Token Directory", - acmeTokenDirectoryPlaceholder: "Input the directory for save acme token", user: "User For Daemon", userPlaceholder: "Input user for daemon", group: "Group For Daemon", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index a054421..e14d9db 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -104,8 +104,6 @@ export default { cacheMaxSizePlaceholder: "输入最大缓存空间限制(如100mb)", upgradeSock: "更新配置使用的sock", upgradeSockPlaceholder: "输入后台服务更新配置使用的sock", - acmeTokenDirectory: "ACME协议认证Token", - acmeTokenDirectoryPlaceholder: "输入保存ACME协议认证Token的目录路径", user: "启用后台服务的用户", userPlaceholder: "输入后台服务的用户", group: "启用后台服务的用户组", diff --git a/web/src/pages/Basic.tsx b/web/src/pages/Basic.tsx index 1efec9d..e7ad19e 100644 --- a/web/src/pages/Basic.tsx +++ b/web/src/pages/Basic.tsx @@ -111,14 +111,6 @@ export default function Basic() { span: 3, category: ExFormItemCategory.TEXT, }, - { - name: "acme_token_directory", - label: basicI18n("acmeTokenDirectory"), - placeholder: basicI18n("acmeTokenDirectoryPlaceholder"), - defaultValue: basic.acme_token_directory, - span: 3, - category: ExFormItemCategory.TEXT, - }, { name: "cache_directory", label: basicI18n("cacheDirectory"), diff --git a/web/src/states/config.ts b/web/src/states/config.ts index bef3046..99288c1 100644 --- a/web/src/states/config.ts +++ b/web/src/states/config.ts @@ -132,7 +132,6 @@ interface Basic { auto_restart_check_interval?: string; cache_max_size?: number; cache_directory?: string; - acme_token_directory?: string; sentry?: string; pyroscope?: string; webhook?: string;