aboutsummaryrefslogtreecommitdiff
path: root/src/cert_store.rs
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2023-11-30 17:34:07 +0100
committerQuentin Dufour <quentin@deuxfleurs.fr>2023-11-30 17:35:12 +0100
commitb9b035034ff7dee1089d8c629296391fe0539515 (patch)
tree13e9b022c640edc91aba86e101b5275b48d6cc43 /src/cert_store.rs
parent753903ee021e7aac7c0914883ae8d547148b4d95 (diff)
downloadtricot-b9b035034ff7dee1089d8c629296391fe0539515.tar.gz
tricot-b9b035034ff7dee1089d8c629296391fe0539515.zip
centralize all the checks in the same placedomain-check
Diffstat (limited to 'src/cert_store.rs')
-rw-r--r--src/cert_store.rs102
1 files changed, 52 insertions, 50 deletions
diff --git a/src/cert_store.rs b/src/cert_store.rs
index 7c6b4b3..edbd0a1 100644
--- a/src/cert_store.rs
+++ b/src/cert_store.rs
@@ -22,12 +22,19 @@ pub struct CertStore {
consul: Consul,
node_name: String,
letsencrypt_email: String,
+
certs: RwLock<HashMap<String, Arc<Cert>>>,
self_signed_certs: RwLock<HashMap<String, Arc<Cert>>>,
+
rx_proxy_config: watch::Receiver<Arc<ProxyConfig>>,
tx_need_cert: mpsc::UnboundedSender<String>,
}
+struct ProcessedDomains {
+ static_domains: HashSet<String>,
+ on_demand_domains: Vec<(glob::Pattern, Option<String>)>,
+}
+
impl CertStore {
pub fn new(
consul: Consul,
@@ -41,10 +48,10 @@ impl CertStore {
let cert_store = Arc::new(Self {
consul,
node_name,
+ letsencrypt_email,
certs: RwLock::new(HashMap::new()),
self_signed_certs: RwLock::new(HashMap::new()),
rx_proxy_config,
- letsencrypt_email,
tx_need_cert: tx,
});
@@ -66,57 +73,55 @@ impl CertStore {
let mut rx_proxy_config = self.rx_proxy_config.clone();
let mut t_last_check: HashMap<String, Instant> = HashMap::new();
-
- // Collect data from proxy config
- let mut static_domains: HashSet<String> = HashSet::new();
- let mut on_demand_checks: Vec<(glob::Pattern, Option<String>)> = vec![];
+ let mut proc_domains: Option<ProcessedDomains> = None;
loop {
- // Collect domains that need a TLS certificate
- // either from the proxy configuration (eagerly)
- // or on reaction to a user request (lazily)
let domains = select! {
+ // Refresh some internal states, schedule static_domains for renew
res = rx_proxy_config.changed() => {
if res.is_err() {
bail!("rx_proxy_config closed");
}
- on_demand_checks.clear();
+ let mut static_domains: HashSet<String> = HashSet::new();
+ let mut on_demand_domains: Vec<(glob::Pattern, Option<String>)> = vec![];
+
let proxy_config: Arc<ProxyConfig> = rx_proxy_config.borrow().clone();
+
for ent in proxy_config.entries.iter() {
- // Eagerly generate certificates for domains that
- // are not patterns
- match &ent.url_prefix.host {
- HostDescription::Hostname(domain) => {
- if let Some((host, _port)) = domain.split_once(':') {
- static_domains.insert(host.to_string());
- //domains.insert(host.to_string());
- } else {
- static_domains.insert(domain.clone());
- //domains.insert(domain.clone());
- }
+ // Eagerly generate certificates for domains that
+ // are not patterns
+ match &ent.url_prefix.host {
+ HostDescription::Hostname(domain) => {
+ if let Some((host, _port)) = domain.split_once(':') {
+ static_domains.insert(host.to_string());
+ } else {
+ static_domains.insert(domain.clone());
+ }
+ },
+ HostDescription::Pattern(pattern) => {
+ on_demand_domains.push((pattern.clone(), ent.on_demand_tls_ask.clone()));
},
- HostDescription::Pattern(pattern) => {
- on_demand_checks.push((pattern.clone(), ent.on_demand_tls_ask.clone()));
- }
- }
+ }
}
- // only static_domains are refreshed
- static_domains.clone()
+ // only static_domains are refreshed
+ proc_domains = Some(ProcessedDomains { static_domains: static_domains.clone(), on_demand_domains });
+ self.domain_validation(static_domains, proc_domains.as_ref()).await
}
+ // renew static and on-demand domains
need_cert = rx_need_cert.recv() => {
match need_cert {
Some(dom) => {
- let mut candidates: HashSet<String> = HashSet::new();
+ let mut candidates: HashSet<String> = HashSet::new();
- // collect certificates as much as possible
+ // collect certificates as much as possible
candidates.insert(dom);
while let Ok(dom2) = rx_need_cert.try_recv() {
candidates.insert(dom2);
}
- self.domain_validation(candidates, &static_domains, on_demand_checks.as_slice()).await
+ self.domain_validation(candidates, proc_domains.as_ref()).await
}
None => bail!("rx_need_cert closed"),
}
@@ -145,28 +150,36 @@ impl CertStore {
async fn domain_validation(
&self,
candidates: HashSet<String>,
- static_domains: &HashSet<String>,
- checks: &[(glob::Pattern, Option<String>)],
+ maybe_proc_domains: Option<&ProcessedDomains>,
) -> HashSet<String> {
let mut domains: HashSet<String> = HashSet::new();
+ // Handle initialization
+ let proc_domains = match maybe_proc_domains {
+ None => {
+ warn!("Proxy config is not yet loaded, refusing all certificate generation");
+ return domains;
+ }
+ Some(proc) => proc,
+ };
+
// Filter certificates...
- for candidate in candidates.into_iter() {
+ 'outer: for candidate in candidates.into_iter() {
// Disallow obvious wrong domains...
if !candidate.contains('.') || candidate.ends_with(".local") {
- warn!("Probably not a publicly accessible domain, skipping (a self-signed certificate will be used)");
+ warn!("{} is probably not a publicly accessible domain, skipping (a self-signed certificate will be used)", candidate);
continue;
}
// Try to register domain as a static domain
- if static_domains.contains(&candidate) {
+ if proc_domains.static_domains.contains(&candidate) {
trace!("domain {} validated as static domain", candidate);
domains.insert(candidate);
continue;
}
// It's not a static domain, maybe an on-demand domain?
- for (pattern, maybe_check_url) in checks.iter() {
+ for (pattern, maybe_check_url) in proc_domains.on_demand_domains.iter() {
// check glob pattern
if pattern.matches(&candidate) {
// if no check url is set, accept domain as long as it matches the pattern
@@ -178,12 +191,14 @@ impl CertStore {
pattern
);
domains.insert(candidate);
- break;
+ continue 'outer;
}
Some(url) => url,
};
// if a check url is set, call it
+ // -- avoid DDoSing a backend
+ tokio::time::sleep(Duration::from_secs(2)).await;
match self.on_demand_tls_ask(check_url, &candidate).await {
Ok(()) => {
trace!(
@@ -193,7 +208,7 @@ impl CertStore {
check_url
);
domains.insert(candidate);
- break;
+ continue 'outer;
}
Err(e) => {
warn!("domain {} validation refused on glob pattern {} and on check url {} with error: {}", candidate, pattern, check_url, e);
@@ -201,8 +216,6 @@ impl CertStore {
}
}
}
- // Avoid DDoSing a backend
- tokio::time::sleep(Duration::from_secs(2)).await;
}
return domains;
@@ -210,17 +223,6 @@ impl CertStore {
/// This function is also in charge of the refresh of the domain names
fn get_cert_for_https(self: &Arc<Self>, domain: &str) -> Result<Arc<Cert>> {
- // Check if domain is authorized
- if !self
- .rx_proxy_config
- .borrow()
- .entries
- .iter()
- .any(|ent| ent.url_prefix.host.matches(domain))
- {
- bail!("Domain {} should not have a TLS certificate.", domain);
- }
-
// Check in local memory if it exists
if let Some(cert) = self.certs.read().unwrap().get(domain) {
if cert.is_old() {