From a75af2ae8069dbcbe106a1d196fc67e064468d78 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 17 Dec 2024 23:17:23 +0800 Subject: [PATCH] refactor: remove stale self signed certificate interval --- src/config/common.rs | 2 +- src/main.rs | 9 +++- src/proxy/dynamic_certificate.rs | 92 +++++++++++++++++++++++++++----- src/proxy/mod.rs | 5 +- web/src/i18n/en.ts | 2 +- web/src/i18n/zh.ts | 2 +- web/src/pages/Certificates.tsx | 6 +-- web/src/states/config.ts | 2 +- 8 files changed, 97 insertions(+), 23 deletions(-) diff --git a/src/config/common.rs b/src/config/common.rs index 3aaba167..1039fd05 100644 --- a/src/config/common.rs +++ b/src/config/common.rs @@ -143,7 +143,7 @@ pub struct CertificateConf { pub tls_key: Option, pub tls_chain: Option, pub is_default: Option, - pub is_root: Option, + pub is_ca: Option, pub acme: Option, pub remark: Option, } diff --git a/src/main.rs b/src/main.rs index f4f130e0..04370549 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,10 @@ use otel::TracerService; use pingora::server; use pingora::server::configuration::Opt; use pingora::services::background::background_service; -use proxy::{new_upstream_health_check_task, Server, ServerConf}; +use proxy::{ + new_self_signed_cert_validity_service, new_upstream_health_check_task, + Server, ServerConf, +}; use state::{get_admin_addr, get_start_time, set_admin_addr}; use std::collections::HashMap; use std::error::Error; @@ -555,6 +558,10 @@ fn run() -> Result<(), Box> { "TlsValidity", new_tls_validity_service(), )); + my_server.add_service(background_service( + "SelfSignedStale", + new_self_signed_cert_validity_service(), + )); my_server.add_service(background_service( "UpstreamHc", new_upstream_health_check_task(Duration::from_secs(10)), diff --git a/src/proxy/dynamic_certificate.rs b/src/proxy/dynamic_certificate.rs index 14ddca40..60106c02 100644 --- a/src/proxy/dynamic_certificate.rs +++ b/src/proxy/dynamic_certificate.rs @@ -14,6 +14,7 @@ use crate::acme::Certificate; use crate::config::CertificateConf; +use crate::service::{CommonServiceTask, ServiceTask}; use crate::{util, webhook}; use ahash::AHashMap; use arc_swap::ArcSwap; @@ -26,7 +27,9 @@ use pingora::tls::ssl::{NameType, SslRef}; use pingora::tls::x509::X509; use snafu::Snafu; use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::Arc; +use std::time::Duration; use substring::Substring; use tracing::{debug, error, info}; @@ -43,10 +46,52 @@ type DynamicCertificates = AHashMap>; static DYNAMIC_CERTIFICATE_MAP: Lazy> = Lazy::new(|| ArcSwap::from_pointee(AHashMap::new())); -type SelfSignedCertKey = AHashMap)>>; +struct SelfSignedCert { + x509: X509, + key: PKey, + stale: AtomicBool, + count: AtomicU32, +} +type SelfSignedCertKey = AHashMap>; static SELF_SIGNED_CERT_KEY_MAP: Lazy> = Lazy::new(|| ArcSwap::from_pointee(AHashMap::new())); +struct SelfSiginedStaleCertChecker {} + +#[async_trait] +impl ServiceTask for SelfSiginedStaleCertChecker { + async fn run(&self) -> Option { + let mut m = AHashMap::new(); + for (k, v) in SELF_SIGNED_CERT_KEY_MAP.load().iter() { + let count = v.count.load(Ordering::Relaxed); + let stale = v.stale.load(Ordering::Relaxed); + if stale && count == 0 { + continue; + } + if count == 0 { + v.stale.store(true, Ordering::Relaxed); + } else { + v.stale.store(false, Ordering::Relaxed); + v.count.store(0, Ordering::Relaxed); + } + m.insert(k.to_string(), v.clone()); + } + SELF_SIGNED_CERT_KEY_MAP.store(Arc::new(m)); + Some(false) + } + fn description(&self) -> String { + "Self signed certificate stale checker".to_string() + } +} + +pub fn new_self_signed_cert_validity_service() -> CommonServiceTask { + CommonServiceTask::new( + // check interval: one day + Duration::from_secs(24 * 60 * 60), + SelfSiginedStaleCertChecker {}, + ) +} + // https://letsencrypt.org/certificates/ const E5: &[u8] = include_bytes!("../assets/e5.pem"); const E6: &[u8] = include_bytes!("../assets/e6.pem"); @@ -130,7 +175,7 @@ fn parse_certificate( domains: info.domains.clone(), certificate: Some((cert, key)), info: Some(info), - is_root: certificate_config.is_root.unwrap_or_default(), + is_ca: certificate_config.is_ca.unwrap_or_default(), ..Default::default() }) } @@ -224,7 +269,7 @@ pub struct DynamicCertificate { domains: Vec, info: Option, hash_key: String, - is_root: bool, + is_ca: bool, } pub struct TlsSettingParams { @@ -263,6 +308,8 @@ fn new_certificate_with_ca( message: e.to_string(), category: "parse_ca_key".to_string(), })?; + let not_before = ca_params.not_before; + let not_after = ca_params.not_after; let ca_cert = ca_params.self_signed(&ca_kp).map_err(|e| Error::Invalid { message: e.to_string(), @@ -276,13 +323,24 @@ fn new_certificate_with_ca( })?; let mut dn = rcgen::DistinguishedName::new(); dn.push(rcgen::DnType::CommonName, cn.to_string()); - dn.push(rcgen::DnType::OrganizationName, "Pingap".to_string()); - dn.push(rcgen::DnType::OrganizationalUnitName, "Pingap".to_string()); + if let Some(organ) = ca_cert + .params() + .distinguished_name + .get(&rcgen::DnType::OrganizationName) + { + dn.push(rcgen::DnType::OrganizationName, organ.clone()); + }; + if let Some(unit) = ca_cert + .params() + .distinguished_name + .get(&rcgen::DnType::OrganizationalUnitName) + { + dn.push(rcgen::DnType::OrganizationalUnitName, unit.clone()); + }; params.distinguished_name = dn; - params.not_before = time::OffsetDateTime::now_utc(); - params.not_after = - time::OffsetDateTime::now_utc() + time::Duration::days(365 * 20); + params.not_before = not_before; + params.not_after = not_after; let cert_key = rcgen::KeyPair::generate().map_err(|e| Error::Invalid { message: e.to_string(), @@ -384,7 +442,7 @@ impl DynamicCertificate { fn get_self_signed_cert( &self, server_name: &str, - ) -> Result)>> { + ) -> Result> { let arr: Vec<&str> = server_name.split('.').collect(); let cn = if arr.len() > 2 { format!("*.{}", arr[1..].join(".")) @@ -401,7 +459,12 @@ impl DynamicCertificate { for (k, v) in SELF_SIGNED_CERT_KEY_MAP.load().iter() { m.insert(k.to_string(), v.clone()); } - let v = Arc::new((cert, key)); + let v = Arc::new(SelfSignedCert { + x509: cert, + key, + stale: AtomicBool::new(false), + count: AtomicU32::new(0), + }); m.insert(k, v.clone()); SELF_SIGNED_CERT_KEY_MAP.store(Arc::new(m)); @@ -480,16 +543,17 @@ impl pingora::listeners::TlsAccept for DynamicCertificate { return; }; - // root ca - if d.is_root { + // ca + if d.is_ca { match d.get_self_signed_cert(server_name.unwrap_or_default()) { Ok(result) => { ssl_certificate( ssl, - &result.0, - &result.1, + &result.x509, + &result.key, &d.chain_certificate, ); + result.count.fetch_add(1, Ordering::Relaxed); }, Err(err) => { error!( diff --git a/src/proxy/mod.rs b/src/proxy/mod.rs index b65872dd..13741cc5 100644 --- a/src/proxy/mod.rs +++ b/src/proxy/mod.rs @@ -23,7 +23,10 @@ mod upstream; #[allow(unused_imports)] pub use location::Location; -pub use dynamic_certificate::{get_certificate_info_list, init_certificates}; +pub use dynamic_certificate::{ + get_certificate_info_list, init_certificates, + new_self_signed_cert_validity_service, +}; pub use location::try_init_locations; pub use logger::Parser; pub use server::*; diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index c1d40a61..e57aefff 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -251,7 +251,7 @@ export default { "Input the domains of acme, multiple domains separated by comma", acme: "Acme", isDefault: "Default Certificate", - isRoot: "Root Certificate Authority", + isCa: "Certificate Authority", }, plugin: { name: "Name", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 3e39e113..7a07cfb5 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -236,7 +236,7 @@ export default { domainsPlaceholder: "输入使用acme的域名列表,多个域名以`,`分隔", acme: "acme", isDefault: "是否默认证书", - isRoot: "是否根证书", + isCa: "CA证书", }, plugin: { name: "名称", diff --git a/web/src/pages/Certificates.tsx b/web/src/pages/Certificates.tsx index 56c5aeea..542f06ab 100644 --- a/web/src/pages/Certificates.tsx +++ b/web/src/pages/Certificates.tsx @@ -123,10 +123,10 @@ export default function Certificates() { options: newBooleanOptions(), }, { - name: "is_root", - label: certificateI18n("isRoot"), + name: "is_ca", + label: certificateI18n("isCa"), placeholder: "", - defaultValue: certificateConfig.is_root, + defaultValue: certificateConfig.is_ca, span: 3, category: ExFormItemCategory.RADIOS, options: newBooleanOptions(), diff --git a/web/src/states/config.ts b/web/src/states/config.ts index cc5a09fb..67e9d33b 100644 --- a/web/src/states/config.ts +++ b/web/src/states/config.ts @@ -104,7 +104,7 @@ export interface Certificate { certificate_file?: string; acme?: string; is_default?: boolean; - is_root?: boolean; + is_ca?: boolean; remark?: string; }