From b51a3daebf732039fe47e612235215bf3917a717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 17 Apr 2026 11:07:25 +0200 Subject: [PATCH 1/2] Check certificate expiration --- ...da41a9d6c3716261637bf9d26b2231d94cbae.json | 161 ++++++++++++++++++ crates/defguard_certs/src/lib.rs | 2 +- .../src/db/models/certificates.rs | 6 +- crates/defguard_core/src/utility_thread.rs | 88 +++++++--- 4 files changed, 233 insertions(+), 24 deletions(-) create mode 100644 .sqlx/query-fcf4fce68b9353dd5720d326554da41a9d6c3716261637bf9d26b2231d94cbae.json diff --git a/.sqlx/query-fcf4fce68b9353dd5720d326554da41a9d6c3716261637bf9d26b2231d94cbae.json b/.sqlx/query-fcf4fce68b9353dd5720d326554da41a9d6c3716261637bf9d26b2231d94cbae.json new file mode 100644 index 000000000..432263fad --- /dev/null +++ b/.sqlx/query-fcf4fce68b9353dd5720d326554da41a9d6c3716261637bf9d26b2231d94cbae.json @@ -0,0 +1,161 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE aclrule SET state = 'expired'::aclrule_state, modified_at = NOW(), modified_by = $1 WHERE state = 'applied'::aclrule_state AND expires < NOW() RETURNING id, parent_id, state \"state: _\", name, allow_all_users, deny_all_users, allow_all_groups, deny_all_groups, allow_all_network_devices, deny_all_network_devices, all_locations, addresses, ports, protocols, enabled, expires, any_address, any_port, any_protocol, use_manual_destination_settings, modified_at, modified_by", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "parent_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "state: _", + "type_info": { + "Custom": { + "name": "aclrule_state", + "kind": { + "Enum": [ + "applied", + "new", + "modified", + "deleted", + "expired" + ] + } + } + } + }, + { + "ordinal": 3, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "allow_all_users", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "deny_all_users", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "allow_all_groups", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "deny_all_groups", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "allow_all_network_devices", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "deny_all_network_devices", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "all_locations", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "addresses", + "type_info": "InetArray" + }, + { + "ordinal": 12, + "name": "ports", + "type_info": "Int4RangeArray" + }, + { + "ordinal": 13, + "name": "protocols", + "type_info": "Int4Array" + }, + { + "ordinal": 14, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "expires", + "type_info": "Timestamp" + }, + { + "ordinal": 16, + "name": "any_address", + "type_info": "Bool" + }, + { + "ordinal": 17, + "name": "any_port", + "type_info": "Bool" + }, + { + "ordinal": 18, + "name": "any_protocol", + "type_info": "Bool" + }, + { + "ordinal": 19, + "name": "use_manual_destination_settings", + "type_info": "Bool" + }, + { + "ordinal": 20, + "name": "modified_at", + "type_info": "Timestamp" + }, + { + "ordinal": 21, + "name": "modified_by", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "fcf4fce68b9353dd5720d326554da41a9d6c3716261637bf9d26b2231d94cbae" +} diff --git a/crates/defguard_certs/src/lib.rs b/crates/defguard_certs/src/lib.rs index 2751875d8..9d0c34d13 100644 --- a/crates/defguard_certs/src/lib.rs +++ b/crates/defguard_certs/src/lib.rs @@ -247,7 +247,7 @@ impl Csr<'_> { } } -#[derive(Debug, Copy, Clone)] +#[derive(Clone, Copy)] pub enum PemLabel { Certificate, PrivateKey, diff --git a/crates/defguard_common/src/db/models/certificates.rs b/crates/defguard_common/src/db/models/certificates.rs index 1d000a1ae..8b2f96c8e 100644 --- a/crates/defguard_common/src/db/models/certificates.rs +++ b/crates/defguard_common/src/db/models/certificates.rs @@ -41,7 +41,7 @@ pub enum CoreCertSource { /// /// Holds the Core CA (used to sign gRPC TLS certs for gateways/proxies), /// the proxy HTTP/HTTPS cert, and the core web server HTTPS cert. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Default)] pub struct Certificates { // Core CA pub ca_cert_der: Option>, @@ -75,13 +75,13 @@ impl Certificates { ca_cert_der, \ ca_key_der, \ ca_expiry, \ - proxy_http_cert_source AS \"proxy_http_cert_source: ProxyCertSource\", \ + proxy_http_cert_source \"proxy_http_cert_source: ProxyCertSource\", \ proxy_http_cert_pem, \ proxy_http_cert_key_pem, \ proxy_http_cert_expiry, \ acme_domain, \ acme_account_credentials, \ - core_http_cert_source AS \"core_http_cert_source: CoreCertSource\", \ + core_http_cert_source \"core_http_cert_source: CoreCertSource\", \ core_http_cert_pem, \ core_http_cert_key_pem, \ core_http_cert_expiry \ diff --git a/crates/defguard_core/src/utility_thread.rs b/crates/defguard_core/src/utility_thread.rs index 15da196d6..53b9abfec 100644 --- a/crates/defguard_core/src/utility_thread.rs +++ b/crates/defguard_core/src/utility_thread.rs @@ -1,11 +1,8 @@ use std::{collections::HashSet, time::Duration}; -use chrono::Utc; -use defguard_common::db::{ - Id, - models::{WireguardNetwork, wireguard::ServiceLocationMode}, -}; -use sqlx::PgPool; +use chrono::{NaiveDateTime, TimeDelta, Utc}; +use defguard_common::db::models::{Certificates, WireguardNetwork, wireguard::ServiceLocationMode}; +use sqlx::{PgPool, query_as}; use tokio::{ sync::broadcast::Sender, time::{Instant, sleep}, @@ -95,11 +92,18 @@ pub async fn run_utility_thread( } }; + // let certificates_task = || async { + // if let Err(err) = check_certificates(pool) { + // error!("Failed to check certificates: {err}"); + // } + // }; + directory_sync_task().await; count_update_task().await; updates_check_task().await; ldap_sync_task().await; expired_acl_rules_task().await; + check_certificates(pool).await; loop { sleep(UTILITY_THREAD_MAIN_SLEEP_TIME).await; @@ -142,7 +146,7 @@ pub async fn run_utility_thread( } debug!( "Enterprise feature status changed from {enterprise_enabled} to \ - {new_enterprise_enabled}" + {new_enterprise_enabled}" ); if let Err(err) = enterprise_status_check(pool, wireguard_tx.clone(), new_enterprise_enabled) @@ -231,21 +235,21 @@ async fn expired_acl_rules_check( wireguard_tx: Sender, ) -> Result<(), anyhow::Error> { // mark relevant rules as expired - let updated_rules = sqlx::query_as::<_, AclRule>( - "UPDATE aclrule SET state = 'expired'::aclrule_state, modified_at = $1, modified_by = $2 \ + let updated_rules = query_as!( + AclRule, + "UPDATE aclrule SET state = 'expired'::aclrule_state, modified_at = NOW(), \ + modified_by = $1 \ WHERE state = 'applied'::aclrule_state AND expires < NOW() \ - RETURNING id, parent_id, state, name, allow_all_users, deny_all_users, allow_all_groups, \ - deny_all_groups, allow_all_network_devices, deny_all_network_devices, all_locations, \ - addresses, ports, protocols, enabled, expires, any_address, any_port, any_protocol, \ - use_manual_destination_settings, modified_at, modified_by", + RETURNING id, parent_id, state \"state: _\", name, allow_all_users, deny_all_users, \ + allow_all_groups, deny_all_groups, allow_all_network_devices, deny_all_network_devices, \ + all_locations, addresses, ports, protocols, enabled, expires, any_address, any_port, \ + any_protocol, use_manual_destination_settings, modified_at, modified_by", + ACL_EXPIRY_SYSTEM_ACTOR ) - .bind(Utc::now().naive_utc()) - .bind(ACL_EXPIRY_SYSTEM_ACTOR) .fetch_all(pool) .await?; - // send firewall config updates to locations which have been affected by updated - // rules + // Send firewall config updates to locations which have been affected by updated rules. debug!( "Marked {} ACL rules as expired. Sending firewall config updates to affected locations.", updated_rules.len() @@ -260,10 +264,10 @@ async fn expired_acl_rules_check( } } - let affected_locations: Vec> = affected_locations.into_iter().collect(); + let affected_locations = affected_locations.into_iter().collect::>(); debug!( - "{} locations affected by expired ACL rules. Sending gateway firewall update events \ - for each location", + "{} locations affected by expired ACL rules. Sending gateway firewall update events for \ + each location", affected_locations.len() ); @@ -288,3 +292,47 @@ async fn expired_acl_rules_check( Ok(()) } + +fn expiry_check(expiry: NaiveDateTime) { + const TIME_CHECK: &[TimeDelta] = &[ + TimeDelta::days(14), + TimeDelta::days(7), + TimeDelta::days(3), + TimeDelta::days(1), + ]; + + let now = Utc::now().naive_utc(); + let time_delta = now - expiry; + for check in TIME_CHECK { + if check.num_days() == time_delta.num_days() { + // Send email + } + } +} + +/// Check if certificates are about to expire, or got expired. +async fn check_certificates(pool: &PgPool) { + let cert = match Certificates::get(pool).await { + Ok(Some(cert)) => cert, + Ok(None) => { + debug!("No certificates in the databae"); + return; + } + Err(err) => { + error!("Failed to fetch certificates {err}"); + return; + } + }; + + if let Some(ca_expiry) = cert.ca_expiry { + expiry_check(ca_expiry); + } + + if let Some(proxy_http_cert_expiry) = cert.proxy_http_cert_expiry { + expiry_check(proxy_http_cert_expiry); + } + + if let Some(core_http_cert_expiry) = cert.core_http_cert_expiry { + expiry_check(core_http_cert_expiry); + } +} From ba9355bd75f43aa38423b040d633bd44621897fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 17 Apr 2026 14:36:16 +0200 Subject: [PATCH 2/2] fmt --- crates/defguard_core/src/lib.rs | 10 ++++------ crates/defguard_core/src/version.rs | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 59522c074..13b410d64 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -697,12 +697,10 @@ pub async fn run_web_server( let app = webapp .clone() .into_make_service_with_connect_info::(); - let current_tls_cert_pair = Certificates::get_or_default(&pool) - .await - .map_or(None, |c| { - c.core_http_cert_pair() - .map(|(cert, key)| (cert.to_owned(), key.to_owned())) - }); + let current_tls_cert_pair = Certificates::get_or_default(&pool).await.map_or(None, |c| { + c.core_http_cert_pair() + .map(|(cert, key)| (cert.to_owned(), key.to_owned())) + }); let mut server_task = tokio::spawn(async move { if let Some((cert_pem, key_pem)) = current_tls_cert_pair { diff --git a/crates/defguard_core/src/version.rs b/crates/defguard_core/src/version.rs index ce4a48ae7..c2521942f 100644 --- a/crates/defguard_core/src/version.rs +++ b/crates/defguard_core/src/version.rs @@ -183,7 +183,9 @@ impl IncompatibleComponents { .read() .expect("Failed to read-lock IncompatibleComponents") .proxy - .as_ref().as_ref().is_some_and(|proxy| (now - proxy.created) > OUTDATED_COMPONENT_LIFETIME) + .as_ref() + .as_ref() + .is_some_and(|proxy| (now - proxy.created) > OUTDATED_COMPONENT_LIFETIME) { return true; }