Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defguard_version = { path = "./crates/defguard_version", version = "0.0.0" }
defguard_vpn_stats_purge = { path = "./crates/defguard_vpn_stats_purge", version = "0.0.0" }
defguard_web_ui = { path = "./crates/defguard_web_ui", version = "0.0.0" }
defguard_certs = { path = "./crates/defguard_certs", version = "0.0.0" }
defguard_setup = { path = "./crates/defguard_setup", version = "0.0.0" }
model_derive = { path = "./crates/model_derive", version = "0.0.0" }

# external dependencies
Expand Down
1 change: 1 addition & 0 deletions crates/defguard/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ defguard_session_manager = { workspace = true }
defguard_version = { workspace = true }
defguard_vpn_stats_purge = { workspace = true }
defguard_certs = { workspace = true }
defguard_setup = { workspace = true }

# external dependencies
anyhow = { workspace = true }
Expand Down
37 changes: 23 additions & 14 deletions crates/defguard/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use defguard_common::{
db::{
init_db,
models::{
Settings, User,
Settings,
settings::{initialize_current_settings, update_current_settings},
},
},
Expand Down Expand Up @@ -40,6 +40,7 @@ use defguard_event_router::{RouterReceiverSet, run_event_router};
use defguard_mail::{Mail, run_mail_handler};
use defguard_proxy_manager::{ProxyManager, ProxyTxSet};
use defguard_session_manager::{events::SessionManagerEvent, run_session_manager};
use defguard_setup::setup::run_setup_web_server;
use defguard_vpn_stats_purge::run_periodic_stats_purge;
use secrecy::ExposeSecret;
use tokio::sync::{
Expand All @@ -56,10 +57,7 @@ async fn main() -> Result<(), anyhow::Error> {
if dotenvy::from_filename(".env.local").is_err() {
dotenvy::dotenv().ok();
}
let config = DefGuardConfig::new();
SERVER_CONFIG
.set(config.clone())
.expect("Failed to initialize server config.");
let mut config = DefGuardConfig::new();

let subscriber = tracing_subscriber::registry();
defguard_version::tracing::with_version_formatters(
Expand Down Expand Up @@ -103,6 +101,26 @@ async fn main() -> Result<(), anyhow::Error> {
info!("Using HMAC OpenID signing key");
}

// initialize default settings
Settings::init_defaults(&pool).await?;
// initialize global settings struct
initialize_current_settings(&pool).await?;
let mut settings = Settings::get_current_settings();

if !settings.initial_setup_completed {
if let Err(err) =
run_setup_web_server(pool.clone(), config.http_bind_address, config.http_port).await
{
error!("Setup web server exited with error: {err}");
}
}

config.initialize_post_settings();

SERVER_CONFIG
.set(config.clone())
.expect("Failed to initialize server config.");

// create event channels for services
let (api_event_tx, api_event_rx) = unbounded_channel::<ApiEvent>();
let (bidi_event_tx, bidi_event_rx) = unbounded_channel::<BidiStreamEvent>();
Expand All @@ -125,15 +143,6 @@ async fn main() -> Result<(), anyhow::Error> {

let incompatible_components: Arc<RwLock<IncompatibleComponents>> = Arc::default();

// initialize admin user
User::init_admin_user(&pool, config.default_admin_password.expose_secret()).await?;

// initialize default settings
Settings::init_defaults(&pool).await?;
// initialize global settings struct
initialize_current_settings(&pool).await?;

let mut settings = Settings::get_current_settings();
if settings.ca_cert_der.is_none() || settings.ca_key_der.is_none() {
info!(
"No gRPC TLS certificate or key found in settings, generating self-signed certificate for gRPC server."
Expand Down
71 changes: 59 additions & 12 deletions crates/defguard_certs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use x509_parser::parse_x509_certificate;

const CA_NAME: &str = "Defguard CA";
const NOT_BEFORE_OFFSET_SECS: Duration = Duration::minutes(5);
const DEFAULT_CERT_VALIDITY_DAYS: i64 = 365;
const DEFAULT_CERT_VALIDITY_DAYS: i64 = 1825;

#[derive(Debug, Error)]
pub enum CertificateError {
Expand Down Expand Up @@ -137,23 +137,53 @@ impl CertificateAuthority<'_> {
}

pub fn expiry(&self) -> Result<NaiveDateTime, CertificateError> {
get_certificate_expiry(&self.cert_der)
let CertificateInfo { not_after, .. } = parse_certificate_info(&self.cert_der)?;
Ok(not_after)
}
}

/// Extract the expiry date (not_after) from a certificate.
pub fn get_certificate_expiry(cert_der: &[u8]) -> Result<NaiveDateTime, CertificateError> {
pub struct CertificateInfo {
pub subject_common_name: String,
pub not_before: NaiveDateTime,
pub not_after: NaiveDateTime,
}

pub fn parse_certificate_info(cert_der: &[u8]) -> Result<CertificateInfo, CertificateError> {
let (_, parsed) = parse_x509_certificate(cert_der)
.map_err(|e| CertificateError::ParsingError(format!("Failed to parse certificate: {e}")))?;

let expiry = parsed.tbs_certificate.validity.not_after.to_datetime();
Ok(chrono::DateTime::from_timestamp(expiry.unix_timestamp(), 0)
.ok_or_else(|| {
CertificateError::ParsingError(format!(
"Failed to convert certificate expiry {expiry} to NaiveDateTime",
))
})?
.naive_utc())
let subject = &parsed.tbs_certificate.subject;

let cn = subject
.iter_common_name()
.next()
.ok_or_else(|| CertificateError::ParsingError("Common Name not found".to_string()))?
.as_str()
.map_err(|e| {
CertificateError::ParsingError(format!("Failed to parse CN as string: {e}"))
})?;

let validity = &parsed.tbs_certificate.validity;
let not_before = validity.not_before.to_datetime();
let not_after = validity.not_after.to_datetime();

Ok(CertificateInfo {
subject_common_name: cn.to_string(),
not_before: chrono::DateTime::from_timestamp(not_before.unix_timestamp(), 0)
.ok_or_else(|| {
CertificateError::ParsingError(format!(
"Failed to convert certificate not_before {not_before} to NaiveDateTime",
))
})?
.naive_utc(),
not_after: chrono::DateTime::from_timestamp(not_after.unix_timestamp(), 0)
.ok_or_else(|| {
CertificateError::ParsingError(format!(
"Failed to convert certificate not_after {not_after} to NaiveDateTime",
))
})?
.naive_utc(),
})
}

pub struct Csr<'a> {
Expand Down Expand Up @@ -235,6 +265,12 @@ pub fn generate_key_pair() -> Result<KeyPair, CertificateError> {
Ok(key_pair)
}

pub fn parse_pem_certificate(pem_str: &str) -> Result<CertificateDer<'_>, CertificateError> {
let cert_der = CertificateDer::from_pem_slice(pem_str.as_bytes())
.map_err(|e| CertificateError::ParsingError(e.to_string()))?;
Ok(cert_der)
}

pub type DnType = rcgen::DnType;
pub type RcGenKeyPair = rcgen::KeyPair;

Expand Down Expand Up @@ -409,4 +445,15 @@ mod tests {
expected_email
);
}

#[test]
fn test_parse_pem_certificate() {
// Create a CA and get its PEM representation
let ca = CertificateAuthority::new("Defguard CA", "test@example.com", 365).unwrap();
let pem = ca.cert_pem().unwrap();

// Parse the PEM back to DER and ensure it matches the original
let parsed = parse_pem_certificate(&pem).unwrap();
assert_eq!(parsed, ca.cert_der);
}
}
1 change: 1 addition & 0 deletions crates/defguard_common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ utoipa.workspace = true
uuid.workspace = true
webauthn-rs.workspace = true
x25519-dalek.workspace = true
url = "2.5"

[dev-dependencies]
matches.workspace = true
Expand Down
Loading
Loading