diff --git a/.sqlx/query-7ddef79c85c3e85b979d5a8a5e50660bcae531c2b8342ae2feffea7454450f10.json b/.sqlx/query-4a1fba6c990265bc278d4e1534f06a96461ecb5edf023ab88d71f9887fc0f2f2.json similarity index 94% rename from .sqlx/query-7ddef79c85c3e85b979d5a8a5e50660bcae531c2b8342ae2feffea7454450f10.json rename to .sqlx/query-4a1fba6c990265bc278d4e1534f06a96461ecb5edf023ab88d71f9887fc0f2f2.json index 9f60163eac..fa1068e52b 100644 --- a/.sqlx/query-7ddef79c85c3e85b979d5a8a5e50660bcae531c2b8342ae2feffea7454450f10.json +++ b/.sqlx/query-4a1fba6c990265bc278d4e1534f06a96461ecb5edf023ab88d71f9887fc0f2f2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT openid_enabled, wireguard_enabled, webhooks_enabled, worker_enabled, challenge_template, instance_name, main_logo_url, nav_logo_url, smtp_server, smtp_port, smtp_encryption \"smtp_encryption: _\", smtp_user, smtp_password \"smtp_password?: SecretStringWrapper\", smtp_sender, enrollment_vpn_step_optional, enrollment_welcome_message, enrollment_welcome_email, enrollment_welcome_email_subject, enrollment_use_welcome_message_as_email, uuid, ldap_url, ldap_bind_username, ldap_bind_password \"ldap_bind_password?: SecretStringWrapper\", ldap_group_search_base, ldap_user_search_base, ldap_user_obj_class, ldap_group_obj_class, ldap_username_attr, ldap_groupname_attr, ldap_group_member_attr, ldap_member_attr, openid_create_account, license, gateway_disconnect_notifications_enabled, ldap_use_starttls, ldap_tls_verify_cert, gateway_disconnect_notifications_inactivity_threshold, gateway_disconnect_notifications_reconnect_notification_enabled, ldap_sync_status \"ldap_sync_status: SyncStatus\", ldap_enabled, ldap_sync_enabled, ldap_is_authoritative, ldap_sync_interval, ldap_user_auxiliary_obj_classes, ldap_uses_ad, ldap_user_rdn_attr, ldap_sync_groups, openid_username_handling \"openid_username_handling: OpenidUsernameHandling\" FROM \"settings\" WHERE id = 1", + "query": "SELECT openid_enabled, wireguard_enabled, webhooks_enabled, worker_enabled, challenge_template, instance_name, main_logo_url, nav_logo_url, smtp_server, smtp_port, smtp_encryption \"smtp_encryption: _\", smtp_user, smtp_password \"smtp_password?: SecretStringWrapper\", smtp_sender, enrollment_vpn_step_optional, enrollment_welcome_message, enrollment_welcome_email, enrollment_welcome_email_subject, enrollment_use_welcome_message_as_email, uuid, ldap_url, ldap_bind_username, ldap_bind_password \"ldap_bind_password?: SecretStringWrapper\", ldap_group_search_base, ldap_user_search_base, ldap_user_obj_class, ldap_group_obj_class, ldap_username_attr, ldap_groupname_attr, ldap_group_member_attr, ldap_member_attr, openid_create_account, license, gateway_disconnect_notifications_enabled, ldap_use_starttls, ldap_tls_verify_cert, gateway_disconnect_notifications_inactivity_threshold, gateway_disconnect_notifications_reconnect_notification_enabled, ldap_sync_status \"ldap_sync_status: LdapSyncStatus\", ldap_enabled, ldap_sync_enabled, ldap_is_authoritative, ldap_sync_interval, ldap_user_auxiliary_obj_classes, ldap_uses_ad, ldap_user_rdn_attr, ldap_sync_groups, openid_username_handling \"openid_username_handling: OpenidUsernameHandling\" FROM \"settings\" WHERE id = 1", "describe": { "columns": [ { @@ -206,7 +206,7 @@ }, { "ordinal": 38, - "name": "ldap_sync_status: SyncStatus", + "name": "ldap_sync_status: LdapSyncStatus", "type_info": { "Custom": { "name": "ldap_sync_status", @@ -330,5 +330,5 @@ false ] }, - "hash": "7ddef79c85c3e85b979d5a8a5e50660bcae531c2b8342ae2feffea7454450f10" + "hash": "4a1fba6c990265bc278d4e1534f06a96461ecb5edf023ab88d71f9887fc0f2f2" } diff --git a/Cargo.lock b/Cargo.lock index 7215fcceea..913382c6d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1066,9 +1066,11 @@ version = "0.0.0" dependencies = [ "anyhow", "bytes", + "defguard_common", "defguard_core", "defguard_event_logger", "defguard_event_router", + "defguard_mail", "defguard_version", "dotenvy", "secrecy", @@ -1077,8 +1079,38 @@ dependencies = [ ] [[package]] -name = "defguard_core" +name = "defguard_common" version = "1.5.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "chrono", + "clap", + "ed25519-dalek", + "humantime", + "ipnetwork", + "jsonwebtoken", + "matches", + "model_derive", + "openidconnect", + "rand 0.8.5", + "reqwest", + "rsa", + "secrecy", + "serde", + "sqlx", + "struct-patch", + "thiserror 2.0.16", + "tonic", + "tracing", + "utoipa", + "uuid", + "vergen-git2", +] + +[[package]] +name = "defguard_core" +version = "0.0.0" dependencies = [ "ammonia", "anyhow", @@ -1091,10 +1123,11 @@ dependencies = [ "bytes", "chrono", "claims", - "clap", + "defguard_common", + "defguard_mail", + "defguard_proto", "defguard_version", "defguard_web_ui", - "ed25519-dalek", "humantime", "hyper-util", "ipnetwork", @@ -1110,7 +1143,6 @@ dependencies = [ "paste", "pgp", "prost", - "pulldown-cmark", "rand 0.8.5", "regex", "reqwest", @@ -1149,7 +1181,6 @@ dependencies = [ "utoipa", "utoipa-swagger-ui", "uuid", - "vergen-git2", "webauthn-authenticator-rs", "webauthn-rs", "webauthn-rs-proto", @@ -1162,6 +1193,7 @@ version = "0.0.0" dependencies = [ "bytes", "chrono", + "defguard_common", "defguard_core", "serde_json", "sqlx", @@ -1176,11 +1208,42 @@ version = "0.0.0" dependencies = [ "defguard_core", "defguard_event_logger", + "defguard_mail", "thiserror 2.0.16", "tokio", "tracing", ] +[[package]] +name = "defguard_mail" +version = "0.0.0" +dependencies = [ + "chrono", + "claims", + "defguard_common", + "lettre", + "pulldown-cmark", + "reqwest", + "serde", + "serde_json", + "sqlx", + "tera", + "thiserror 2.0.16", + "tokio", + "tracing", +] + +[[package]] +name = "defguard_proto" +version = "0.0.0" +dependencies = [ + "prost", + "serde", + "tonic", + "tonic-prost", + "tonic-prost-build", +] + [[package]] name = "defguard_version" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index d6405991da..513ecf7cf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,12 @@ resolver = "2" [workspace.dependencies] # internal crates -defguard_core = { path = "./crates/defguard_core", version = "1.5.0" } +defguard_common = { path = "./crates/defguard_common", version = "1.5.0" } +defguard_core = { path = "./crates/defguard_core", version = "0.0.0" } defguard_event_logger = { path = "./crates/defguard_event_logger", version = "0.0.0" } defguard_event_router = { path = "./crates/defguard_event_router", version = "0.0.0" } +defguard_mail = { path = "./crates/defguard_mail", version = "0.0.0" } +defguard_proto = { path = "./crates/defguard_proto", version = "0.0.0" } defguard_version = { path = "./crates/defguard_version", version = "0.0.0" } defguard_web_ui = { path = "./crates/defguard_web_ui", version = "0.0.0" } model_derive = { path = "./crates/model_derive", version = "0.0.0" } @@ -35,6 +38,7 @@ chrono = { version = "0.4", default-features = false, features = [ "clock", "serde", ] } +claims = "0.8" clap = { version = "4.5", features = ["derive", "env"] } humantime = "2.1" # match version used by sqlx @@ -43,10 +47,15 @@ jsonwebkey = { version = "0.3", features = ["pkcs-convert"] } jsonwebtoken = "9.3" ldap3 = { version = "0.11", default-features = false, features = ["tls"] } lettre = { version = "0.11", features = ["tokio1-native-tls"] } +matches = "0.1" md4 = "0.10" +openidconnect = { version = "4.0", default-features = false, features = [ + "reqwest", +] } parse_link_header = "0.4" paste = "1.0" pgp = { version = "0.16", default-features = false } +prost = "0.14" pulldown-cmark = "0.13" # match version used by sqlx rand = "0.8" @@ -93,6 +102,8 @@ tonic = { version = "0.14", features = [ "tls-ring", ] } tonic-health = "0.14" +tonic-prost = "0.14" +tonic-prost-build = "0.14" totp-lite = { version = "2.0" } tower-http = { version = "0.6", features = ["fs", "trace", "set-header"] } tracing = "0.1" diff --git a/crates/defguard/Cargo.toml b/crates/defguard/Cargo.toml index a9837db037..8399bb0ae5 100644 --- a/crates/defguard/Cargo.toml +++ b/crates/defguard/Cargo.toml @@ -9,9 +9,11 @@ rust-version.workspace = true [dependencies] # internal crates +defguard_common = { workspace = true } defguard_core = { workspace = true } defguard_event_router = { workspace = true } defguard_event_logger = { workspace = true } +defguard_mail = { workspace = true } defguard_version = { workspace = true } # external dependencies diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index d59c944b97..3c7576a2ee 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -4,14 +4,17 @@ use std::{ }; use bytes::Bytes; -use defguard_core::{ - SERVER_CONFIG, VERSION, - auth::failed_login::FailedLoginMap, - config::{Command, DefGuardConfig}, +use defguard_common::{ + VERSION, + config::{Command, DefGuardConfig, SERVER_CONFIG}, db::{ - AppEvent, GatewayEvent, Settings, User, init_db, - models::settings::initialize_current_settings, + init_db, + models::{Settings, settings::initialize_current_settings}, }, +}; +use defguard_core::{ + auth::failed_login::FailedLoginMap, + db::{AppEvent, GatewayEvent, User}, enterprise::{ activity_log_stream::activity_log_stream_manager::run_activity_log_stream_manager, license::{License, run_periodic_license_check, set_cached_license}, @@ -23,9 +26,7 @@ use defguard_core::{ gateway::{client_state::ClientMap, map::GatewayMap}, run_grpc_bidi_stream, run_grpc_server, }, - init_dev_env, init_vpn_location, - mail::{Mail, run_mail_handler}, - run_web_server, + init_dev_env, init_vpn_location, run_web_server, utility_thread::run_utility_thread, version::IncompatibleComponents, wireguard_peer_disconnect::run_periodic_peer_disconnect, @@ -33,6 +34,7 @@ use defguard_core::{ }; use defguard_event_logger::{message::EventLoggerMessage, run_event_logger}; use defguard_event_router::{RouterReceiverSet, run_event_router}; +use defguard_mail::{Mail, run_mail_handler}; use secrecy::ExposeSecret; use tokio::sync::{broadcast, mpsc::unbounded_channel}; diff --git a/crates/defguard_common/Cargo.toml b/crates/defguard_common/Cargo.toml new file mode 100644 index 0000000000..e035598ecf --- /dev/null +++ b/crates/defguard_common/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "defguard_common" +version = "1.5.0" +edition.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +model_derive.workspace = true + +anyhow.workspace = true +base64.workspace = true +chrono.workspace = true +clap.workspace = true +ed25519-dalek = { version = "2.2", features = ["rand_core"] } +humantime.workspace = true +ipnetwork.workspace = true +jsonwebtoken.workspace = true +openidconnect.workspace = true +rand.workspace = true +reqwest.workspace = true +rsa.workspace = true +secrecy.workspace = true +serde.workspace = true +sqlx.workspace = true +struct-patch.workspace = true +thiserror.workspace = true +tonic.workspace = true +tracing.workspace = true +utoipa.workspace = true +uuid.workspace = true + +[dev-dependencies] +matches.workspace = true + +[build-dependencies] +vergen-git2 = { version = "1.0", features = ["build"] } diff --git a/crates/defguard_common/build.rs b/crates/defguard_common/build.rs new file mode 100644 index 0000000000..d1e71d8bbe --- /dev/null +++ b/crates/defguard_common/build.rs @@ -0,0 +1,10 @@ +use vergen_git2::{Emitter, Git2Builder}; + +fn main() -> Result<(), Box> { + // set VERGEN_GIT_SHA env variable based on git commit hash + let git2 = Git2Builder::default().branch(true).sha(true).build()?; + Emitter::default().add_instructions(&git2)?.emit()?; + + println!("cargo:rerun-if-changed=../../migrations"); + Ok(()) +} diff --git a/crates/defguard_common/src/auth/claims.rs b/crates/defguard_common/src/auth/claims.rs new file mode 100644 index 0000000000..bca84e18e7 --- /dev/null +++ b/crates/defguard_common/src/auth/claims.rs @@ -0,0 +1,98 @@ +use std::{ + env, + time::{Duration, SystemTime}, +}; + +use jsonwebtoken::{ + DecodingKey, EncodingKey, Header, Validation, decode, encode, errors::Error as JWTError, +}; +use serde::{Deserialize, Serialize}; + +pub static JWT_ISSUER: &str = "DefGuard"; +pub static AUTH_SECRET_ENV: &str = "DEFGUARD_AUTH_SECRET"; +pub static GATEWAY_SECRET_ENV: &str = "DEFGUARD_GATEWAY_SECRET"; +pub static YUBIBRIDGE_SECRET_ENV: &str = "DEFGUARD_YUBIBRIDGE_SECRET"; + +#[derive(Clone, Copy, Default)] +pub enum ClaimsType { + #[default] + Auth, + Gateway, + YubiBridge, + DesktopClient, +} + +/// Standard claims: https://www.iana.org/assignments/jwt/jwt.xhtml +#[derive(Deserialize, Serialize)] +pub struct Claims { + #[serde(skip_serializing, skip_deserializing)] + secret: String, + // issuer + pub iss: String, + // subject + pub sub: String, + // client identifier + pub client_id: String, + // expiration time + pub exp: u64, + // not before + pub nbf: u64, +} + +impl Claims { + #[must_use] + pub fn new(claims_type: ClaimsType, sub: String, client_id: String, duration: u64) -> Self { + let now = SystemTime::now(); + let exp = now + .checked_add(Duration::from_secs(duration)) + .expect("valid time") + .duration_since(SystemTime::UNIX_EPOCH) + .expect("valid timestamp") + .as_secs(); + let nbf = now + .duration_since(SystemTime::UNIX_EPOCH) + .expect("valid timestamp") + .as_secs(); + Self { + secret: Self::get_secret(claims_type), + iss: JWT_ISSUER.to_string(), + sub, + client_id, + exp, + nbf, + } + } + + fn get_secret(claims_type: ClaimsType) -> String { + let env_var = match claims_type { + ClaimsType::Auth | ClaimsType::DesktopClient => AUTH_SECRET_ENV, + ClaimsType::Gateway => GATEWAY_SECRET_ENV, + ClaimsType::YubiBridge => YUBIBRIDGE_SECRET_ENV, + }; + env::var(env_var).unwrap_or_default() + } + + /// Convert claims to JWT. + pub fn to_jwt(&self) -> Result { + encode( + &Header::default(), + self, + &EncodingKey::from_secret(self.secret.as_bytes()), + ) + } + + /// Verify JWT and, if successful, convert it to claims. + pub fn from_jwt(claims_type: ClaimsType, token: &str) -> Result { + let secret = Self::get_secret(claims_type); + let mut validation = Validation::default(); + validation.validate_nbf = true; + validation.set_issuer(&[JWT_ISSUER]); + validation.set_required_spec_claims(&["iss", "sub", "exp", "nbf"]); + decode::( + token, + &DecodingKey::from_secret(secret.as_bytes()), + &validation, + ) + .map(|data| data.claims) + } +} diff --git a/crates/defguard_common/src/auth/mod.rs b/crates/defguard_common/src/auth/mod.rs new file mode 100644 index 0000000000..c0e708bb12 --- /dev/null +++ b/crates/defguard_common/src/auth/mod.rs @@ -0,0 +1 @@ +pub mod claims; diff --git a/crates/defguard_core/src/config.rs b/crates/defguard_common/src/config.rs similarity index 97% rename from crates/defguard_core/src/config.rs rename to crates/defguard_common/src/config.rs index e3cf365da5..2549ce610b 100644 --- a/crates/defguard_core/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -1,4 +1,4 @@ -use std::net::IpAddr; +use std::{net::IpAddr, sync::OnceLock}; use clap::{Args, Parser, Subcommand}; use humantime::Duration; @@ -12,6 +12,15 @@ use rsa::{ traits::PublicKeyParts, }; use secrecy::{ExposeSecret, SecretString}; +use serde::Serialize; + +pub static SERVER_CONFIG: OnceLock = OnceLock::new(); + +pub fn server_config() -> &'static DefGuardConfig { + SERVER_CONFIG + .get() + .expect("Server configuration not set yet") +} #[derive(Clone, Parser, Serialize, Debug)] #[command(version)] @@ -281,7 +290,7 @@ impl DefGuardConfig { /// Returns configured URL with "auth/callback" appended to the path. #[must_use] - pub(crate) fn callback_url(&self) -> Url { + pub fn callback_url(&self) -> Url { let mut url = self.url.clone(); // Append "auth/callback" to the URL. if let Ok(mut path_segments) = url.path_segments_mut() { diff --git a/crates/defguard_common/src/csv.rs b/crates/defguard_common/src/csv.rs new file mode 100644 index 0000000000..1695bafaba --- /dev/null +++ b/crates/defguard_common/src/csv.rs @@ -0,0 +1,17 @@ +pub trait AsCsv { + fn as_csv(&self) -> String; +} + +impl AsCsv for I +where + I: ?Sized + std::iter::IntoIterator, + for<'a> &'a I: IntoIterator, + T: ToString, +{ + fn as_csv(&self) -> String { + self.into_iter() + .map(ToString::to_string) + .collect::>() + .join(",") + } +} diff --git a/crates/defguard_common/src/db/mod.rs b/crates/defguard_common/src/db/mod.rs new file mode 100644 index 0000000000..d7ca63d055 --- /dev/null +++ b/crates/defguard_common/src/db/mod.rs @@ -0,0 +1,47 @@ +use serde::{Deserialize, Serialize}; +use sqlx::{ + PgPool, + postgres::{PgConnectOptions, PgPoolOptions}, +}; +use tracing::info; +use utoipa::ToSchema; + +pub mod models; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, ToSchema, Eq, Default, Hash)] +pub struct NoId; +pub type Id = i64; + +// helper for easier migration handling with a custom `migration` folder location +// reference: https://docs.rs/sqlx/latest/sqlx/attr.test.html#automatic-migrations-requires-migrate-feature +pub static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("../../migrations"); + +/// Initializes and migrates postgres database. Returns DB pool object. +pub async fn init_db(host: &str, port: u16, name: &str, user: &str, password: &str) -> PgPool { + info!("Initializing DB pool"); + let opts = PgConnectOptions::new() + .host(host) + .port(port) + .username(user) + .password(password) + .database(name); + let pool = PgPool::connect_with(opts) + .await + .expect("Database connection failed"); + MIGRATOR + .run(&pool) + .await + .expect("Cannot run database migrations."); + pool +} + +// Helper function to instantiate pool manually as a workaround for issues with `sqlx::test` macro +// reference: https://github.com/launchbadge/sqlx/issues/2567#issuecomment-2009849261 +pub async fn setup_pool(options: PgConnectOptions) -> PgPool { + let pool = PgPoolOptions::new().connect_with(options).await.unwrap(); + MIGRATOR + .run(&pool) + .await + .expect("Cannot run database migrations."); + pool +} diff --git a/crates/defguard_core/src/db/models/auth_code.rs b/crates/defguard_common/src/db/models/auth_code.rs similarity index 82% rename from crates/defguard_core/src/db/models/auth_code.rs rename to crates/defguard_common/src/db/models/auth_code.rs index 5cf60f13dd..b57774c708 100644 --- a/crates/defguard_core/src/db/models/auth_code.rs +++ b/crates/defguard_common/src/db/models/auth_code.rs @@ -9,22 +9,22 @@ use crate::{ #[derive(Model)] #[table(authorization_code)] -pub(crate) struct AuthCode { +pub struct AuthCode { #[allow(dead_code)] id: I, - pub(crate) user_id: Id, - pub(crate) client_id: String, - pub(crate) code: String, - pub(crate) redirect_uri: String, - pub(crate) scope: String, - pub(crate) auth_time: i64, - pub(crate) nonce: Option, - pub(crate) code_challenge: Option, + pub user_id: Id, + pub client_id: String, + pub code: String, + pub redirect_uri: String, + pub scope: String, + pub auth_time: i64, + pub nonce: Option, + pub code_challenge: Option, } impl AuthCode { #[must_use] - pub(crate) fn new( + pub fn new( user_id: Id, client_id: String, redirect_uri: String, @@ -66,7 +66,7 @@ impl From> for AuthCode { impl AuthCode { /// Find by code. /// If found, delete `AuthCode` from the database right away, so it can't be reused. - pub(crate) async fn find_code<'e, E>( + pub async fn find_code<'e, E>( executor: E, code: &str, ) -> Result>, sqlx::Error> diff --git a/crates/defguard_core/src/db/models/authentication_key.rs b/crates/defguard_common/src/db/models/authentication_key.rs similarity index 94% rename from crates/defguard_core/src/db/models/authentication_key.rs rename to crates/defguard_common/src/db/models/authentication_key.rs index 84ddd50183..a1bc189586 100644 --- a/crates/defguard_core/src/db/models/authentication_key.rs +++ b/crates/defguard_common/src/db/models/authentication_key.rs @@ -1,6 +1,7 @@ use std::fmt::Display; use model_derive::Model; +use serde::{Deserialize, Serialize}; use sqlx::{Error as SqlxError, PgExecutor, Type, query_as}; use crate::db::{Id, NoId}; @@ -25,11 +26,11 @@ impl Display for AuthenticationKeyType { #[derive(Clone, Debug, Deserialize, Model, Serialize)] #[table(authentication_key)] pub struct AuthenticationKey { - pub(crate) id: I, - pub(crate) yubikey_id: Option, + pub id: I, + pub yubikey_id: Option, pub name: Option, - pub(crate) user_id: Id, - pub(crate) key: String, + pub user_id: Id, + pub key: String, #[model(enum)] pub key_type: AuthenticationKeyType, } diff --git a/crates/defguard_core/src/db/models/biometric_auth.rs b/crates/defguard_common/src/db/models/biometric_auth.rs similarity index 95% rename from crates/defguard_core/src/db/models/biometric_auth.rs rename to crates/defguard_common/src/db/models/biometric_auth.rs index 1ac93a4e7a..8f477b5fc0 100644 --- a/crates/defguard_core/src/db/models/biometric_auth.rs +++ b/crates/defguard_common/src/db/models/biometric_auth.rs @@ -57,7 +57,7 @@ impl BiometricAuth { } impl BiometricAuth { - pub(crate) async fn find_by_device_id<'e, E>( + pub async fn find_by_device_id<'e, E>( executor: E, device_id: Id, ) -> Result, sqlx::Error> @@ -73,7 +73,7 @@ impl BiometricAuth { .await } - pub(crate) async fn verify_owner<'e, E>( + pub async fn verify_owner<'e, E>( executor: E, user_id: Id, pub_key: &str, @@ -91,10 +91,7 @@ impl BiometricAuth { Ok(q_result.is_some()) } - pub(crate) async fn find_by_user_id<'e, E>( - executor: E, - user_id: Id, - ) -> Result, sqlx::Error> + pub async fn find_by_user_id<'e, E>(executor: E, user_id: Id) -> Result, sqlx::Error> where E: PgExecutor<'e>, { @@ -124,6 +121,12 @@ fn decode_pub_key(public_key: &str) -> Result Ok(verifying_key) } +impl Default for BiometricChallenge { + fn default() -> Self { + Self::new() + } +} + impl BiometricChallenge { pub fn new_with_owner(pub_key: &str) -> Result { let _ = decode_pub_key(pub_key)?; diff --git a/crates/defguard_core/src/db/models/device_login.rs b/crates/defguard_common/src/db/models/device_login.rs similarity index 93% rename from crates/defguard_core/src/db/models/device_login.rs rename to crates/defguard_common/src/db/models/device_login.rs index e3a8e5b7d6..09b7d52eeb 100644 --- a/crates/defguard_core/src/db/models/device_login.rs +++ b/crates/defguard_common/src/db/models/device_login.rs @@ -2,6 +2,7 @@ use std::fmt; use chrono::{NaiveDateTime, Utc}; use model_derive::Model; +use serde::{Deserialize, Serialize}; use sqlx::{Error as SqlxError, PgPool, query_as}; use crate::db::{Id, NoId}; @@ -34,6 +35,7 @@ impl fmt::Display for DeviceLoginEvent { } impl DeviceLoginEvent { + #[allow(clippy::too_many_arguments)] #[must_use] pub fn new( user_id: Id, @@ -59,7 +61,7 @@ impl DeviceLoginEvent { } } - pub(crate) async fn check_if_device_already_logged_in( + pub async fn check_if_device_already_logged_in( self, pool: &PgPool, ) -> Result>, anyhow::Error> { @@ -72,7 +74,7 @@ impl DeviceLoginEvent { } } - pub(crate) async fn find_device_login_event( + pub async fn find_device_login_event( &self, pool: &PgPool, ) -> Result>, SqlxError> { diff --git a/crates/defguard_core/src/db/models/error.rs b/crates/defguard_common/src/db/models/error.rs similarity index 100% rename from crates/defguard_core/src/db/models/error.rs rename to crates/defguard_common/src/db/models/error.rs diff --git a/crates/defguard_common/src/db/models/mod.rs b/crates/defguard_common/src/db/models/mod.rs new file mode 100644 index 0000000000..0aa3a601bb --- /dev/null +++ b/crates/defguard_common/src/db/models/mod.rs @@ -0,0 +1,15 @@ +pub mod auth_code; +pub mod authentication_key; +pub mod biometric_auth; +pub mod device_login; +pub mod error; +pub mod settings; +pub mod user; + +pub use auth_code::AuthCode; +pub use authentication_key::{AuthenticationKey, AuthenticationKeyType}; +pub use biometric_auth::{BiometricAuth, BiometricChallenge}; +pub use device_login::DeviceLoginEvent; +pub use error::ModelError; +pub use settings::{Settings, SettingsEssentials}; +pub use user::MFAMethod; diff --git a/crates/defguard_core/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs similarity index 95% rename from crates/defguard_core/src/db/models/settings.rs rename to crates/defguard_common/src/db/models/settings.rs index d74ebd49ee..ad900d7831 100644 --- a/crates/defguard_core/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; +use crate::{global_value, secret::SecretStringWrapper}; +use serde::{Deserialize, Serialize}; use sqlx::{PgExecutor, PgPool, Type, query, query_as}; use struct_patch::Patch; use thiserror::Error; +use tracing::{debug, info, warn}; use uuid::Uuid; -use crate::{enterprise::ldap::sync::SyncStatus, global_value, secret::SecretStringWrapper}; - global_value!(SETTINGS, Option, None, set_settings, get_settings); /// Initializes global `SETTINGS` struct at program startup @@ -61,6 +62,21 @@ pub enum OpenidUsernameHandling { PruneEmailDomain, } +#[derive(Clone, Debug, Copy, Eq, PartialEq, Deserialize, Serialize, Default, Type)] +#[sqlx(type_name = "ldap_sync_status", rename_all = "lowercase")] +pub enum LdapSyncStatus { + InSync, + #[default] + OutOfSync, +} + +impl LdapSyncStatus { + #[must_use] + pub fn is_out_of_sync(&self) -> bool { + matches!(self, LdapSyncStatus::OutOfSync) + } +} + #[derive(Clone, Debug, Deserialize, PartialEq, Patch, Serialize, Default)] #[patch(attribute(derive(Deserialize, Serialize, Debug)))] pub struct Settings { @@ -107,7 +123,7 @@ pub struct Settings { pub ldap_member_attr: Option, pub ldap_use_starttls: bool, pub ldap_tls_verify_cert: bool, - pub ldap_sync_status: SyncStatus, + pub ldap_sync_status: LdapSyncStatus, pub ldap_enabled: bool, pub ldap_sync_enabled: bool, pub ldap_is_authoritative: bool, @@ -149,7 +165,7 @@ impl Settings { license, gateway_disconnect_notifications_enabled, ldap_use_starttls, \ ldap_tls_verify_cert, gateway_disconnect_notifications_inactivity_threshold, \ gateway_disconnect_notifications_reconnect_notification_enabled, \ - ldap_sync_status \"ldap_sync_status: SyncStatus\", \ + ldap_sync_status \"ldap_sync_status: LdapSyncStatus\", \ ldap_enabled, ldap_sync_enabled, ldap_is_authoritative, \ ldap_sync_interval, ldap_user_auxiliary_obj_classes, ldap_uses_ad, \ ldap_user_rdn_attr, ldap_sync_groups, \ @@ -270,7 +286,7 @@ impl Settings { self.gateway_disconnect_notifications_enabled, self.gateway_disconnect_notifications_inactivity_threshold, self.gateway_disconnect_notifications_reconnect_notification_enabled, - &self.ldap_sync_status as &SyncStatus, + &self.ldap_sync_status as &LdapSyncStatus, self.ldap_enabled, self.ldap_sync_enabled, self.ldap_is_authoritative, @@ -352,7 +368,7 @@ pub struct SettingsEssentials { } impl SettingsEssentials { - pub(crate) async fn get_settings_essentials<'e, E>(executor: E) -> Result + pub async fn get_settings_essentials<'e, E>(executor: E) -> Result where E: PgExecutor<'e>, { diff --git a/crates/defguard_common/src/db/models/user.rs b/crates/defguard_common/src/db/models/user.rs new file mode 100644 index 0000000000..d632bb9483 --- /dev/null +++ b/crates/defguard_common/src/db/models/user.rs @@ -0,0 +1,30 @@ +use std::fmt; + +use serde::{Deserialize, Serialize}; +use sqlx::Type; +use utoipa::ToSchema; + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, ToSchema, Type)] +#[sqlx(type_name = "mfa_method", rename_all = "snake_case")] +pub enum MFAMethod { + None, + OneTimePassword, + Webauthn, + Email, +} + +// Web MFA methods +impl fmt::Display for MFAMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + MFAMethod::None => "None", + MFAMethod::OneTimePassword => "TOTP", + MFAMethod::Webauthn => "WebAuthn", + MFAMethod::Email => "Email", + } + ) + } +} diff --git a/crates/defguard_core/src/globals.rs b/crates/defguard_common/src/globals.rs similarity index 100% rename from crates/defguard_core/src/globals.rs rename to crates/defguard_common/src/globals.rs diff --git a/crates/defguard_core/src/hex.rs b/crates/defguard_common/src/hex.rs similarity index 100% rename from crates/defguard_core/src/hex.rs rename to crates/defguard_common/src/hex.rs diff --git a/crates/defguard_common/src/lib.rs b/crates/defguard_common/src/lib.rs new file mode 100644 index 0000000000..c326bfc3c3 --- /dev/null +++ b/crates/defguard_common/src/lib.rs @@ -0,0 +1,11 @@ +pub mod auth; +pub mod config; +pub mod csv; +pub mod db; +pub mod globals; +pub mod hex; +pub mod random; +pub mod secret; + +pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "+", env!("VERGEN_GIT_SHA")); +pub const CARGO_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/crates/defguard_core/src/random.rs b/crates/defguard_common/src/random.rs similarity index 77% rename from crates/defguard_core/src/random.rs rename to crates/defguard_common/src/random.rs index 30edce4459..ce12448d41 100644 --- a/crates/defguard_core/src/random.rs +++ b/crates/defguard_common/src/random.rs @@ -2,7 +2,7 @@ use rand::{Rng, distributions::Alphanumeric, thread_rng}; /// Generate random alphanumeric string. #[must_use] -pub(crate) fn gen_alphanumeric(n: usize) -> String { +pub fn gen_alphanumeric(n: usize) -> String { thread_rng() .sample_iter(Alphanumeric) .take(n) @@ -12,6 +12,6 @@ pub(crate) fn gen_alphanumeric(n: usize) -> String { /// Generate random 20-byte secret for TOTP. #[must_use] -pub(crate) fn gen_totp_secret() -> Vec { +pub fn gen_totp_secret() -> Vec { thread_rng().r#gen::<[u8; 20]>().to_vec() } diff --git a/crates/defguard_core/src/secret.rs b/crates/defguard_common/src/secret.rs similarity index 100% rename from crates/defguard_core/src/secret.rs rename to crates/defguard_common/src/secret.rs diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index 3ee6c10de5..2d57b13188 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard_core" -version = "1.5.0" +version = "0.0.0" edition.workspace = true license-file.workspace = true homepage.workspace = true @@ -9,6 +9,9 @@ rust-version.workspace = true [dependencies] # internal crates +defguard_common = { workspace = true } +defguard_mail = { workspace = true } +defguard_proto = { workspace = true } defguard_web_ui = { workspace = true } defguard_version = { workspace = true } model_derive = { workspace = true } @@ -22,7 +25,6 @@ axum-extra = { workspace = true } base32 = { workspace = true } base64 = { workspace = true } chrono = { workspace = true } -clap = { workspace = true } humantime = { workspace = true } # match version used by sqlx ipnetwork = { workspace = true } @@ -31,14 +33,11 @@ jsonwebtoken = { workspace = true } ldap3 = { workspace = true } lettre = { workspace = true } md4 = { workspace = true } -openidconnect = { version = "4.0", default-features = false, optional = true, features = [ - "reqwest", -] } +openidconnect.workspace = true parse_link_header = { workspace = true } paste = { workspace = true } pgp = { workspace = true } -prost = "0.14" -pulldown-cmark = { workspace = true } +prost.workspace = true # match version used by sqlx rand = { workspace = true } reqwest = { workspace = true } @@ -65,7 +64,7 @@ tokio-stream = { workspace = true } tokio-util = { workspace = true } tonic = { workspace = true } tonic-health = { workspace = true } -tonic-prost = "0.14" +tonic-prost.workspace = true totp-lite = { workspace = true } tower-http = { workspace = true } tracing = { workspace = true } @@ -82,16 +81,15 @@ x25519-dalek = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } bytes = { workspace = true } -ed25519-dalek = { version = "2.2", features = ["rand_core"] } tower = "0.5" regex = "1.10" ammonia = "4.1.1" [dev-dependencies] bytes = "1.6" -claims = "0.8" +claims.workspace = true hyper-util = "0.1" -matches = "0.1" +matches.workspace = true regex = "1.10" reqwest = { version = "0.12", features = [ "cookies", @@ -105,11 +103,4 @@ tower = "0.5" webauthn-authenticator-rs = { version = "0.5", features = ["softpasskey"] } [build-dependencies] -tonic-prost-build = "0.14" -vergen-git2 = { version = "1.0", features = ["build"] } - -[features] -default = ["openid", "wireguard", "worker"] -openid = ["dep:openidconnect"] -worker = [] -wireguard = [] +tonic-prost-build.workspace = true diff --git a/crates/defguard_core/build.rs b/crates/defguard_core/build.rs index 9551d43add..96c212192d 100644 --- a/crates/defguard_core/build.rs +++ b/crates/defguard_core/build.rs @@ -1,51 +1,14 @@ -use vergen_git2::{Emitter, Git2Builder}; - fn main() -> Result<(), Box> { - // set VERGEN_GIT_SHA env variable based on git commit hash - let git2 = Git2Builder::default().branch(true).sha(true).build()?; - Emitter::default().add_instructions(&git2)?.emit()?; - tonic_prost_build::configure() - // These types contain sensitive data. - .skip_debug([ - "ActivateUserRequest", - "AuthInfoResponse", - "AuthenticateRequest", - "AuthenticateResponse", - "ClientMfaFinishResponse", - "CodeMfaSetupStartResponse", - "CodeMfaSetupFinishResponse", - "CoreRequest", - "CoreResponse", - "DeviceConfigResponse", - "InstanceInfoResponse", - "NewDevice", - "PasswordResetRequest", - ]) .protoc_arg("--experimental_allow_proto3_optional") .type_attribute( "LicenseLimits", "#[derive(serde::Serialize, serde::Deserialize)]", ) .compile_protos( - &[ - "../../proto/core/auth.proto", - "../../proto/core/proxy.proto", - "../../proto/worker/worker.proto", - "../../proto/wireguard/gateway.proto", - "../../proto/enterprise/firewall/firewall.proto", - "src/enterprise/proto/license.proto", - ], - &[ - "../../proto/core", - "../../proto/worker", - "../../proto/wireguard", - "../../proto/enterprise/firewall", - "src/enterprise/proto", - ], + &["src/enterprise/proto/license.proto"], + &["src/enterprise/proto"], )?; - println!("cargo:rerun-if-changed=../../migrations"); - println!("cargo:rerun-if-changed=../../proto"); println!("cargo:rerun-if-changed=src/enterprise"); Ok(()) } diff --git a/crates/defguard_core/src/appstate.rs b/crates/defguard_core/src/appstate.rs index 3930b72c6a..7483b0b725 100644 --- a/crates/defguard_core/src/appstate.rs +++ b/crates/defguard_core/src/appstate.rs @@ -2,6 +2,8 @@ use std::sync::{Arc, Mutex, RwLock}; use axum::extract::FromRef; use axum_extra::extract::cookie::Key; +use defguard_common::config::server_config; +use defguard_mail::Mail; use reqwest::Client; use secrecy::ExposeSecret; use serde_json::json; @@ -21,8 +23,6 @@ use crate::{ error::WebError, events::ApiEvent, grpc::gateway::{send_multiple_wireguard_events, send_wireguard_event}, - mail::Mail, - server_config, version::IncompatibleComponents, }; diff --git a/crates/defguard_core/src/auth/mod.rs b/crates/defguard_core/src/auth/mod.rs index 41b01e2fd4..5abf56809f 100644 --- a/crates/defguard_core/src/auth/mod.rs +++ b/crates/defguard_core/src/auth/mod.rs @@ -1,10 +1,5 @@ pub mod failed_login; -use std::{ - env, - time::{Duration, SystemTime}, -}; - use axum::{ extract::{FromRef, FromRequestParts, OptionalFromRequestParts}, http::{header::AUTHORIZATION, request::Parts}, @@ -15,15 +10,12 @@ use axum_extra::{ extract::cookie::CookieJar, headers::{Authorization, authorization::Bearer}, }; -use jsonwebtoken::{ - DecodingKey, EncodingKey, Header, Validation, decode, encode, errors::Error as JWTError, -}; -use serde::{Deserialize, Serialize}; +use defguard_common::db::Id; use crate::{ appstate::AppState, db::{ - Group, Id, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User, + Group, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User, models::{group::Permission, oauth2client::OAuth2Client}, }, enterprise::{db::models::api_tokens::ApiToken, is_enterprise_enabled}, @@ -31,98 +23,10 @@ use crate::{ handlers::SESSION_COOKIE_NAME, }; -pub static JWT_ISSUER: &str = "DefGuard"; -pub static AUTH_SECRET_ENV: &str = "DEFGUARD_AUTH_SECRET"; -pub static GATEWAY_SECRET_ENV: &str = "DEFGUARD_GATEWAY_SECRET"; -pub static YUBIBRIDGE_SECRET_ENV: &str = "DEFGUARD_YUBIBRIDGE_SECRET"; pub const TOTP_CODE_VALIDITY_PERIOD: u64 = 30; pub const EMAIL_CODE_DIGITS: u32 = 6; pub const TOTP_CODE_DIGITS: u32 = 6; -#[derive(Clone, Copy, Default)] -pub enum ClaimsType { - #[default] - Auth, - Gateway, - YubiBridge, - DesktopClient, -} - -/// Standard claims: https://www.iana.org/assignments/jwt/jwt.xhtml -#[derive(Deserialize, Serialize)] -pub struct Claims { - #[serde(skip_serializing, skip_deserializing)] - secret: String, - // issuer - pub iss: String, - // subject - pub sub: String, - // client identifier - pub client_id: String, - // expiration time - pub exp: u64, - // not before - pub nbf: u64, -} - -impl Claims { - #[must_use] - pub fn new(claims_type: ClaimsType, sub: String, client_id: String, duration: u64) -> Self { - let now = SystemTime::now(); - let exp = now - .checked_add(Duration::from_secs(duration)) - .expect("valid time") - .duration_since(SystemTime::UNIX_EPOCH) - .expect("valid timestamp") - .as_secs(); - let nbf = now - .duration_since(SystemTime::UNIX_EPOCH) - .expect("valid timestamp") - .as_secs(); - Self { - secret: Self::get_secret(claims_type), - iss: JWT_ISSUER.to_string(), - sub, - client_id, - exp, - nbf, - } - } - - fn get_secret(claims_type: ClaimsType) -> String { - let env_var = match claims_type { - ClaimsType::Auth | ClaimsType::DesktopClient => AUTH_SECRET_ENV, - ClaimsType::Gateway => GATEWAY_SECRET_ENV, - ClaimsType::YubiBridge => YUBIBRIDGE_SECRET_ENV, - }; - env::var(env_var).unwrap_or_default() - } - - /// Convert claims to JWT. - pub fn to_jwt(&self) -> Result { - encode( - &Header::default(), - self, - &EncodingKey::from_secret(self.secret.as_bytes()), - ) - } - - /// Verify JWT and, if successful, convert it to claims. - pub fn from_jwt(claims_type: ClaimsType, token: &str) -> Result { - let secret = Self::get_secret(claims_type); - let mut validation = Validation::default(); - validation.validate_nbf = true; - validation.set_issuer(&[JWT_ISSUER]); - validation.set_required_spec_claims(&["iss", "sub", "exp", "nbf"]); - decode::( - token, - &DecodingKey::from_secret(secret.as_bytes()), - &validation, - ) - .map(|data| data.claims) - } -} - impl FromRequestParts for Session where S: Send + Sync, @@ -398,7 +302,6 @@ where None } }) { - // TODO: #[cfg(feature = "openid")] match OAuth2Token::find_access_token(&appstate.pool, token).await { Ok(Some(oauth2token)) => { match OAuth2AuthorizedApp::find_by_id( diff --git a/crates/defguard_core/src/db/mod.rs b/crates/defguard_core/src/db/mod.rs index 1dcc96ec1c..37bcc961f0 100644 --- a/crates/defguard_core/src/db/mod.rs +++ b/crates/defguard_core/src/db/mod.rs @@ -1,33 +1,5 @@ pub mod models; -use sqlx::postgres::{PgConnectOptions, PgPool, PgPoolOptions}; -use utoipa::ToSchema; - -use crate::MIGRATOR; - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, ToSchema, Eq, Default, Hash)] -pub struct NoId; -pub type Id = i64; - -/// Initializes and migrates postgres database. Returns DB pool object. -pub async fn init_db(host: &str, port: u16, name: &str, user: &str, password: &str) -> PgPool { - info!("Initializing DB pool"); - let opts = PgConnectOptions::new() - .host(host) - .port(port) - .username(user) - .password(password) - .database(name); - let pool = PgPool::connect_with(opts) - .await - .expect("Database connection failed"); - MIGRATOR - .run(&pool) - .await - .expect("Cannot run database migrations."); - pool -} - pub use models::{ MFAInfo, UserDetails, UserInfo, device::{AddDevice, Device}, @@ -35,21 +7,9 @@ pub use models::{ oauth2authorizedapp::OAuth2AuthorizedApp, oauth2token::OAuth2Token, session::{Session, SessionState}, - settings::Settings, - user::{MFAMethod, User}, + user::User, webauthn::WebAuthn, webhook::{AppEvent, HWKeyUserData, WebHook}, wireguard::{GatewayEvent, WireguardNetwork}, yubikey::YubiKey, }; - -// Helper function to instantiate pool manually as a workaround for issues with `sqlx::test` macro -// reference: https://github.com/launchbadge/sqlx/issues/2567#issuecomment-2009849261 -pub async fn setup_pool(options: PgConnectOptions) -> PgPool { - let pool = PgPoolOptions::new().connect_with(options).await.unwrap(); - MIGRATOR - .run(&pool) - .await - .expect("Cannot run database migrations."); - pool -} diff --git a/crates/defguard_core/src/db/models/activity_log/metadata.rs b/crates/defguard_core/src/db/models/activity_log/metadata.rs index c1c1032cce..e358560fe2 100644 --- a/crates/defguard_core/src/db/models/activity_log/metadata.rs +++ b/crates/defguard_core/src/db/models/activity_log/metadata.rs @@ -1,22 +1,22 @@ use chrono::NaiveDateTime; +use defguard_common::db::{ + Id, + models::{ + AuthenticationKey, AuthenticationKeyType, MFAMethod, Settings, + settings::{LdapSyncStatus, OpenidUsernameHandling, SmtpEncryption}, + }, +}; use crate::{ db::{ - Device, Group, Id, MFAMethod, Settings, User, WebAuthn, WebHook, WireguardNetwork, - models::{ - authentication_key::{AuthenticationKey, AuthenticationKeyType}, - oauth2client::OAuth2Client, - settings::{OpenidUsernameHandling, SmtpEncryption}, - }, + Device, Group, User, WebAuthn, WebHook, WireguardNetwork, + models::oauth2client::OAuth2Client, }, - enterprise::{ - db::models::{ - activity_log_stream::{ActivityLogStream, ActivityLogStreamType}, - api_tokens::ApiToken, - openid_provider::{DirectorySyncTarget, DirectorySyncUserBehavior, OpenIdProvider}, - snat::UserSnatBinding, - }, - ldap::sync::SyncStatus, + enterprise::db::models::{ + activity_log_stream::{ActivityLogStream, ActivityLogStreamType}, + api_tokens::ApiToken, + openid_provider::{DirectorySyncTarget, DirectorySyncUserBehavior, OpenIdProvider}, + snat::UserSnatBinding, }, events::ClientMFAMethod, }; @@ -377,7 +377,7 @@ pub struct SettingsNoSecrets { pub ldap_member_attr: Option, pub ldap_use_starttls: bool, pub ldap_tls_verify_cert: bool, - pub ldap_sync_status: SyncStatus, + pub ldap_sync_status: LdapSyncStatus, pub ldap_enabled: bool, pub ldap_sync_enabled: bool, pub ldap_is_authoritative: bool, diff --git a/crates/defguard_core/src/db/models/activity_log/mod.rs b/crates/defguard_core/src/db/models/activity_log/mod.rs index a175e50c2e..62cf59154c 100644 --- a/crates/defguard_core/src/db/models/activity_log/mod.rs +++ b/crates/defguard_core/src/db/models/activity_log/mod.rs @@ -3,7 +3,7 @@ use ipnetwork::IpNetwork; use model_derive::Model; use sqlx::{FromRow, Type}; -use crate::db::{Id, NoId}; +use defguard_common::db::{Id, NoId}; pub mod metadata; diff --git a/crates/defguard_core/src/db/models/device.rs b/crates/defguard_core/src/db/models/device.rs index 8977f70288..00c895ff15 100644 --- a/crates/defguard_core/src/db/models/device.rs +++ b/crates/defguard_core/src/db/models/device.rs @@ -4,6 +4,10 @@ use base64::{Engine, prelude::BASE64_STANDARD}; #[cfg(test)] use chrono::NaiveDate; use chrono::{NaiveDateTime, Utc}; +use defguard_common::{ + csv::AsCsv, + db::{Id, NoId, models::ModelError}, +}; use ipnetwork::IpNetwork; use model_derive::Model; #[cfg(test)] @@ -19,14 +23,10 @@ use sqlx::{ use thiserror::Error; use utoipa::ToSchema; -use super::{ - error::ModelError, - wireguard::{LocationMfaMode, NetworkAddressError, WIREGUARD_MAX_HANDSHAKE, WireguardNetwork}, -}; -use crate::{ - AsCsv, KEY_LENGTH, - db::{Id, NoId, User}, +use super::wireguard::{ + LocationMfaMode, NetworkAddressError, WIREGUARD_MAX_HANDSHAKE, WireguardNetwork, }; +use crate::{KEY_LENGTH, db::User}; #[derive(Serialize, ToSchema)] pub struct DeviceConfig { @@ -1010,10 +1010,11 @@ mod test { use std::str::FromStr; use claims::{assert_err, assert_ok}; + use defguard_common::db::setup_pool; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use super::*; - use crate::db::{User, setup_pool}; + use crate::db::User; impl Device { /// Create new device and assign IP in a given network diff --git a/crates/defguard_core/src/db/models/enrollment.rs b/crates/defguard_core/src/db/models/enrollment.rs index 8204991123..7c9237c3ca 100644 --- a/crates/defguard_core/src/db/models/enrollment.rs +++ b/crates/defguard_core/src/db/models/enrollment.rs @@ -1,4 +1,14 @@ use chrono::{NaiveDateTime, TimeDelta, Utc}; +use defguard_common::{ + VERSION, + config::server_config, + db::{Id, models::Settings}, + random::gen_alphanumeric, +}; +use defguard_mail::{ + Mail, + templates::{self, TemplateError, safe_tera}, +}; use reqwest::Url; use sqlx::{Error as SqlxError, PgConnection, PgExecutor, PgPool, query, query_as}; use tera::Context; @@ -6,15 +16,7 @@ use thiserror::Error; use tokio::sync::mpsc::UnboundedSender; use tonic::{Code, Status}; -use super::{User, settings::Settings}; -use crate::{ - VERSION, - db::Id, - mail::Mail, - random::gen_alphanumeric, - server_config, - templates::{self, TemplateError, safe_tera}, -}; +use super::User; pub static ENROLLMENT_TOKEN_TYPE: &str = "ENROLLMENT"; pub static PASSWORD_RESET_TOKEN_TYPE: &str = "PASSWORD_RESET"; @@ -356,7 +358,7 @@ impl Token { // load configured content as template let mut tera = safe_tera(); - tera.add_raw_template("welcome_page", &settings.enrollment_welcome_message()?)?; + tera.add_raw_template("welcome_page", &enrollment_welcome_message(&settings)?)?; let context = self.get_welcome_message_context(&mut *transaction).await?; @@ -374,7 +376,7 @@ impl Token { // load configured content as template let mut tera = safe_tera(); - tera.add_raw_template("welcome_email", &settings.enrollment_welcome_email()?)?; + tera.add_raw_template("welcome_email", &enrollment_welcome_email(&settings)?)?; let context = self.get_welcome_message_context(&mut *transaction).await?; let content = tera.render("welcome_email", &context)?; @@ -616,21 +618,19 @@ impl User { } } -impl Settings { - pub fn enrollment_welcome_message(&self) -> Result { - self.enrollment_welcome_message.clone().ok_or_else(|| { - error!("Enrollment welcome message not configured"); - TokenError::WelcomeMsgNotConfigured - }) - } +pub fn enrollment_welcome_message(settings: &Settings) -> Result { + settings.enrollment_welcome_message.clone().ok_or_else(|| { + error!("Enrollment welcome message not configured"); + TokenError::WelcomeMsgNotConfigured + }) +} - pub fn enrollment_welcome_email(&self) -> Result { - if self.enrollment_use_welcome_message_as_email { - return self.enrollment_welcome_message(); - } - self.enrollment_welcome_email.clone().ok_or_else(|| { - error!("Enrollment welcome email not configured"); - TokenError::WelcomeEmailNotConfigured - }) +pub fn enrollment_welcome_email(settings: &Settings) -> Result { + if settings.enrollment_use_welcome_message_as_email { + return enrollment_welcome_message(settings); } + settings.enrollment_welcome_email.clone().ok_or_else(|| { + error!("Enrollment welcome email not configured"); + TokenError::WelcomeEmailNotConfigured + }) } diff --git a/crates/defguard_core/src/db/models/group.rs b/crates/defguard_core/src/db/models/group.rs index 6dadfbf904..b9734fd4f6 100644 --- a/crates/defguard_core/src/db/models/group.rs +++ b/crates/defguard_core/src/db/models/group.rs @@ -1,10 +1,11 @@ use std::fmt; +use defguard_common::db::{Id, NoId, models::ModelError}; use model_derive::Model; use sqlx::{Error as SqlxError, FromRow, PgConnection, PgExecutor, query, query_as, query_scalar}; use utoipa::ToSchema; -use crate::db::{Id, NoId, User, WireguardNetwork, models::error::ModelError}; +use crate::db::{User, WireguardNetwork}; #[derive(Debug)] pub enum Permission { @@ -296,10 +297,11 @@ impl WireguardNetwork { #[cfg(test)] mod test { + use defguard_common::db::setup_pool; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use super::*; - use crate::db::{User, setup_pool}; + use crate::db::User; #[sqlx::test] async fn test_group(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/src/db/models/mod.rs b/crates/defguard_core/src/db/models/mod.rs index 083645bff4..df2faac41b 100644 --- a/crates/defguard_core/src/db/models/mod.rs +++ b/crates/defguard_core/src/db/models/mod.rs @@ -1,22 +1,12 @@ pub mod activity_log; -#[cfg(feature = "openid")] -pub mod auth_code; -pub mod authentication_key; -pub mod biometric_auth; pub mod device; -pub mod device_login; pub mod enrollment; -pub mod error; pub mod group; -#[cfg(feature = "openid")] pub mod oauth2authorizedapp; -#[cfg(feature = "openid")] pub mod oauth2client; -#[cfg(feature = "openid")] pub mod oauth2token; pub mod polling_token; pub mod session; -pub mod settings; pub mod user; pub mod webauthn; pub mod webhook; @@ -26,17 +16,16 @@ pub mod yubikey; use std::collections::HashSet; +use defguard_common::db::{ + Id, + models::{BiometricAuth, MFAMethod}, +}; use sqlx::{Error as SqlxError, PgConnection, PgPool, query_as}; use utoipa::ToSchema; -use self::{ - device::UserDevice, - user::{MFAMethod, User}, -}; -use super::{Group, Id}; -use crate::db::models::biometric_auth::BiometricAuth; +use self::{device::UserDevice, user::User}; +use super::Group; -#[cfg(feature = "openid")] #[derive(Deserialize, Serialize)] pub struct NewOpenIDClient { pub name: String, @@ -283,10 +272,10 @@ impl MFAInfo { #[cfg(test)] mod test { + use defguard_common::db::setup_pool; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use super::*; - use crate::db::setup_pool; #[sqlx::test] async fn test_user_info(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/src/db/models/oauth2authorizedapp.rs b/crates/defguard_core/src/db/models/oauth2authorizedapp.rs index 421a93437b..0039265a65 100644 --- a/crates/defguard_core/src/db/models/oauth2authorizedapp.rs +++ b/crates/defguard_core/src/db/models/oauth2authorizedapp.rs @@ -1,7 +1,7 @@ use model_derive::Model; use sqlx::{Error as SqlxError, PgPool, query_as}; -use crate::db::{Id, NoId}; +use defguard_common::db::{Id, NoId}; #[derive(Model)] pub struct OAuth2AuthorizedApp { diff --git a/crates/defguard_core/src/db/models/oauth2client.rs b/crates/defguard_core/src/db/models/oauth2client.rs index 4a886c23d7..5b362d22c6 100644 --- a/crates/defguard_core/src/db/models/oauth2client.rs +++ b/crates/defguard_core/src/db/models/oauth2client.rs @@ -2,10 +2,8 @@ use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query_as}; use super::NewOpenIDClient; -use crate::{ - db::{Id, NoId}, - random::gen_alphanumeric, -}; +use defguard_common::db::{Id, NoId}; +use defguard_common::random::gen_alphanumeric; #[derive(Clone, Debug, Deserialize, Model, Serialize)] pub struct OAuth2Client { diff --git a/crates/defguard_core/src/db/models/oauth2token.rs b/crates/defguard_core/src/db/models/oauth2token.rs index 3e183ffb06..abc400d711 100644 --- a/crates/defguard_core/src/db/models/oauth2token.rs +++ b/crates/defguard_core/src/db/models/oauth2token.rs @@ -1,8 +1,7 @@ use chrono::{TimeDelta, Utc}; +use defguard_common::{config::server_config, db::Id, random::gen_alphanumeric}; use sqlx::{Error as SqlxError, PgPool, query, query_as}; -use crate::{db::Id, random::gen_alphanumeric, server_config}; - pub struct OAuth2Token { pub oauth2authorizedapp_id: Id, pub access_token: String, diff --git a/crates/defguard_core/src/db/models/polling_token.rs b/crates/defguard_core/src/db/models/polling_token.rs index 59fcaba079..8b19e98dda 100644 --- a/crates/defguard_core/src/db/models/polling_token.rs +++ b/crates/defguard_core/src/db/models/polling_token.rs @@ -2,10 +2,8 @@ use chrono::{NaiveDateTime, Utc}; use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query_as}; -use crate::{ - db::{Id, NoId}, - random::gen_alphanumeric, -}; +use defguard_common::db::{Id, NoId}; +use defguard_common::random::gen_alphanumeric; // Token used for polling requests. #[derive(Clone, Debug, Model)] diff --git a/crates/defguard_core/src/db/models/session.rs b/crates/defguard_core/src/db/models/session.rs index 8fe3945aa0..52d7a0fd9b 100644 --- a/crates/defguard_core/src/db/models/session.rs +++ b/crates/defguard_core/src/db/models/session.rs @@ -1,8 +1,9 @@ use chrono::{NaiveDateTime, TimeDelta, Utc}; +use defguard_common::{config::server_config, db::Id, random::gen_alphanumeric}; use sqlx::{Error as SqlxError, PgExecutor, PgPool, Type, query, query_as}; use webauthn_rs::prelude::{PasskeyAuthentication, PasskeyRegistration}; -use crate::{db::Id, random::gen_alphanumeric, server_config}; +use defguard_mail::templates::SessionContext; #[derive(Clone, PartialEq, Type)] #[repr(i16)] @@ -27,6 +28,15 @@ pub struct Session { pub device_info: Option, } +impl From for SessionContext { + fn from(value: Session) -> Self { + Self { + ip_address: value.ip_address, + device_info: value.device_info, + } + } +} + impl Session { #[must_use] pub fn new( diff --git a/crates/defguard_core/src/db/models/user.rs b/crates/defguard_core/src/db/models/user.rs index d2e02ee1d5..e011a6e3b2 100644 --- a/crates/defguard_core/src/db/models/user.rs +++ b/crates/defguard_core/src/db/models/user.rs @@ -8,6 +8,7 @@ use argon2::{ }, }; use axum::http::StatusCode; +use defguard_mail::templates::UserContext; use model_derive::Model; #[cfg(test)] use rand::{ @@ -17,12 +18,10 @@ use rand::{ }; use serde::Serialize; use sqlx::{ - Error as SqlxError, FromRow, PgConnection, PgExecutor, PgPool, Type, query, query_as, - query_scalar, + Error as SqlxError, FromRow, PgConnection, PgExecutor, PgPool, query, query_as, query_scalar, }; use tokio::sync::broadcast::Sender; use totp_lite::{Sha1, totp_custom}; -use utoipa::ToSchema; use super::{ MFAInfo, OAuth2AuthorizedAppInfo, SecurityKey, @@ -32,61 +31,19 @@ use super::{ }; use crate::{ auth::{EMAIL_CODE_DIGITS, TOTP_CODE_DIGITS, TOTP_CODE_VALIDITY_PERIOD}, - db::{GatewayEvent, Id, NoId, Session, WireguardNetwork, models::group::Permission}, + db::{GatewayEvent, Session, WireguardNetwork, models::group::Permission}, enterprise::limits::update_counts, error::WebError, - grpc::{ - gateway::{send_multiple_wireguard_events, send_wireguard_event}, - proto::proxy::MfaMethod, - }, + grpc::gateway::{send_multiple_wireguard_events, send_wireguard_event}, +}; +use defguard_common::{ + config::server_config, + db::{Id, NoId, models::MFAMethod}, random::{gen_alphanumeric, gen_totp_secret}, - server_config, }; const RECOVERY_CODES_COUNT: usize = 8; -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, ToSchema, Type)] -#[sqlx(type_name = "mfa_method", rename_all = "snake_case")] -pub enum MFAMethod { - None, - OneTimePassword, - Webauthn, - Email, -} - -// Web MFA methods -impl fmt::Display for MFAMethod { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - MFAMethod::None => "None", - MFAMethod::OneTimePassword => "TOTP", - MFAMethod::Webauthn => "WebAuthn", - MFAMethod::Email => "Email", - } - ) - } -} - -// Client MFA methods -impl fmt::Display for MfaMethod { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - MfaMethod::Totp => "TOTP", - MfaMethod::Email => "Email", - MfaMethod::Oidc => "OIDC", - MfaMethod::Biometric => "Biometric", - MfaMethod::MobileApprove => "MobileApprove", - } - ) - } -} - // User information ready to be sent as part of diagnostic data. #[derive(Serialize)] pub struct UserDiagnostic { @@ -202,6 +159,15 @@ fn hash_password(password: &str) -> Result { .to_string()) } +impl From> for UserContext { + fn from(value: User) -> Self { + Self { + last_name: value.last_name, + first_name: value.first_name, + } + } +} + impl User { #[must_use] pub fn new>( @@ -1308,14 +1274,14 @@ impl Distribution> for Standard { #[cfg(test)] mod test { + use defguard_common::{ + config::{DefGuardConfig, SERVER_CONFIG}, + db::setup_pool, + }; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use super::*; - use crate::{ - SERVER_CONFIG, - config::DefGuardConfig, - db::{models::settings::initialize_current_settings, setup_pool}, - }; + use defguard_common::db::models::settings::initialize_current_settings; #[sqlx::test] async fn test_mfa_code(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/src/db/models/webauthn.rs b/crates/defguard_core/src/db/models/webauthn.rs index 64a2c794c9..9b4407d67f 100644 --- a/crates/defguard_core/src/db/models/webauthn.rs +++ b/crates/defguard_core/src/db/models/webauthn.rs @@ -2,8 +2,7 @@ use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query, query_as, query_scalar}; use webauthn_rs::prelude::Passkey; -use super::error::ModelError; -use crate::db::{Id, NoId}; +use defguard_common::db::{Id, NoId, models::ModelError}; #[derive(Model, Clone, Debug)] pub struct WebAuthn { diff --git a/crates/defguard_core/src/db/models/webhook.rs b/crates/defguard_core/src/db/models/webhook.rs index fb7a83dd5b..f45d9831d7 100644 --- a/crates/defguard_core/src/db/models/webhook.rs +++ b/crates/defguard_core/src/db/models/webhook.rs @@ -2,7 +2,7 @@ use model_derive::Model; use sqlx::{Error as SqlxError, FromRow, PgPool, query_as}; use super::UserInfo; -use crate::db::{Id, NoId}; +use defguard_common::db::{Id, NoId}; /// App events which triggers webhook action #[derive(Debug)] diff --git a/crates/defguard_core/src/db/models/wireguard.rs b/crates/defguard_core/src/db/models/wireguard.rs index 4c486ffa30..fef77250f3 100644 --- a/crates/defguard_core/src/db/models/wireguard.rs +++ b/crates/defguard_core/src/db/models/wireguard.rs @@ -7,6 +7,11 @@ use std::{ use base64::prelude::{BASE64_STANDARD, Engine}; use chrono::{NaiveDateTime, TimeDelta, Utc}; +use defguard_common::{ + auth::claims::{Claims, ClaimsType}, + csv::AsCsv, + db::{Id, NoId, models::ModelError}, +}; use ipnetwork::{IpNetwork, IpNetworkError, NetworkSize}; use model_derive::Model; use rand::rngs::OsRng; @@ -24,23 +29,18 @@ use super::{ device::{ Device, DeviceError, DeviceInfo, DeviceNetworkInfo, DeviceType, WireguardNetworkDevice, }, - error::ModelError, user::User, wireguard_peer_stats::WireguardPeerStats, }; use crate::{ - AsCsv, - auth::{Claims, ClaimsType}, - db::{Id, NoId}, enterprise::firewall::FirewallError, - grpc::{ - gateway::{Peer, send_multiple_wireguard_events, state::GatewayState}, - proto::{ - enterprise::firewall::FirewallConfig, proxy::LocationMfaMode as ProtoLocationMfaMode, - }, - }, + grpc::gateway::{send_multiple_wireguard_events, state::GatewayState}, wg_config::ImportedDevice, }; +use defguard_proto::{ + enterprise::firewall::FirewallConfig, gateway::Peer, + proxy::LocationMfaMode as ProtoLocationMfaMode, +}; pub const DEFAULT_KEEPALIVE_INTERVAL: i32 = 25; pub const DEFAULT_DISCONNECT_THRESHOLD: i32 = 300; @@ -1419,11 +1419,12 @@ mod test { use std::str::FromStr; use chrono::{SubsecRound, TimeDelta}; + use defguard_common::db::setup_pool; use matches::assert_matches; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use super::*; - use crate::db::{Group, setup_pool}; + use crate::db::Group; #[sqlx::test] async fn test_connected_at_reconnection(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/src/db/models/wireguard_peer_stats.rs b/crates/defguard_core/src/db/models/wireguard_peer_stats.rs index 7a2f7322e8..2323584c7d 100644 --- a/crates/defguard_core/src/db/models/wireguard_peer_stats.rs +++ b/crates/defguard_core/src/db/models/wireguard_peer_stats.rs @@ -6,7 +6,7 @@ use ipnetwork::IpNetwork; use model_derive::Model; use sqlx::{PgExecutor, PgPool, query, query_as, query_scalar}; -use crate::db::{Id, NoId}; +use defguard_common::db::{Id, NoId}; #[derive(Debug, Deserialize, Model, Serialize)] #[table(wireguard_peer_stats)] diff --git a/crates/defguard_core/src/db/models/yubikey.rs b/crates/defguard_core/src/db/models/yubikey.rs index 1535297078..0813319334 100644 --- a/crates/defguard_core/src/db/models/yubikey.rs +++ b/crates/defguard_core/src/db/models/yubikey.rs @@ -1,7 +1,7 @@ use model_derive::Model; use sqlx::{PgExecutor, query, query_as}; -use crate::db::{Id, NoId}; +use defguard_common::db::{Id, NoId}; #[derive(Deserialize, Model, Serialize)] pub struct YubiKey { diff --git a/crates/defguard_core/src/enterprise/activity_log_stream/http_stream.rs b/crates/defguard_core/src/enterprise/activity_log_stream/http_stream.rs index d835607f83..64593c021a 100644 --- a/crates/defguard_core/src/enterprise/activity_log_stream/http_stream.rs +++ b/crates/defguard_core/src/enterprise/activity_log_stream/http_stream.rs @@ -2,16 +2,14 @@ use std::sync::Arc; use base64::prelude::{BASE64_STANDARD, Engine}; use bytes::Bytes; +use defguard_common::secret::SecretStringWrapper; use reqwest::tls; use tokio::sync::broadcast::Receiver; use tokio_util::sync::CancellationToken; use tracing::{debug, error}; -use crate::{ - enterprise::db::models::activity_log_stream::{ - LogstashHttpActivityLogStream, VectorHttpActivityLogStream, - }, - secret::SecretStringWrapper, +use crate::enterprise::db::models::activity_log_stream::{ + LogstashHttpActivityLogStream, VectorHttpActivityLogStream, }; /// Spawns an asynchronous task that reads activity log events from the channel and sends them as NDJSON via HTTP. diff --git a/crates/defguard_core/src/enterprise/db/models/acl.rs b/crates/defguard_core/src/enterprise/db/models/acl.rs index 60e781b47c..faddd5c08f 100644 --- a/crates/defguard_core/src/enterprise/db/models/acl.rs +++ b/crates/defguard_core/src/enterprise/db/models/acl.rs @@ -6,6 +6,7 @@ use std::{ }; use chrono::NaiveDateTime; +use defguard_common::db::{Id, NoId}; use ipnetwork::{IpNetwork, IpNetworkError}; use model_derive::Model; use sqlx::{ @@ -17,10 +18,7 @@ use thiserror::Error; use crate::{ DeviceType, appstate::AppState, - db::{ - Device, GatewayEvent, Group, Id, NoId, User, WireguardNetwork, - models::wireguard::LocationMfaMode, - }, + db::{Device, GatewayEvent, Group, User, WireguardNetwork, models::wireguard::LocationMfaMode}, enterprise::{ firewall::FirewallError, handlers::acl::{ApiAclAlias, ApiAclRule, EditAclAlias, EditAclRule}, diff --git a/crates/defguard_core/src/enterprise/db/models/acl/tests.rs b/crates/defguard_core/src/enterprise/db/models/acl/tests.rs index ca828a4238..becdc26481 100644 --- a/crates/defguard_core/src/enterprise/db/models/acl/tests.rs +++ b/crates/defguard_core/src/enterprise/db/models/acl/tests.rs @@ -1,10 +1,11 @@ use std::ops::Bound; +use defguard_common::db::setup_pool; use rand::{Rng, thread_rng}; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use super::*; -use crate::{db::setup_pool, handlers::wireguard::parse_address_list}; +use crate::handlers::wireguard::parse_address_list; #[sqlx::test] async fn test_alias(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/src/enterprise/db/models/activity_log_stream.rs b/crates/defguard_core/src/enterprise/db/models/activity_log_stream.rs index 9bfb86676a..2d472bec2c 100644 --- a/crates/defguard_core/src/enterprise/db/models/activity_log_stream.rs +++ b/crates/defguard_core/src/enterprise/db/models/activity_log_stream.rs @@ -3,9 +3,9 @@ use serde::Serialize; use sqlx::{Error as SqlxError, FromRow, PgExecutor, Type, query_as}; use strum_macros::{Display, EnumString}; -use crate::{ +use crate::enterprise::activity_log_stream::error::ActivityLogStreamError; +use defguard_common::{ db::{Id, NoId}, - enterprise::activity_log_stream::error::ActivityLogStreamError, secret::SecretStringWrapper, }; diff --git a/crates/defguard_core/src/enterprise/db/models/api_tokens.rs b/crates/defguard_core/src/enterprise/db/models/api_tokens.rs index ef1ffebfd4..9e01c7248c 100644 --- a/crates/defguard_core/src/enterprise/db/models/api_tokens.rs +++ b/crates/defguard_core/src/enterprise/db/models/api_tokens.rs @@ -2,7 +2,7 @@ use chrono::NaiveDateTime; use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, query_as}; -use crate::db::{Id, NoId}; +use defguard_common::db::{Id, NoId}; #[derive(Clone, Debug, Deserialize, Model, Serialize)] #[table(api_token)] diff --git a/crates/defguard_core/src/enterprise/db/models/openid_provider.rs b/crates/defguard_core/src/enterprise/db/models/openid_provider.rs index 70fa182087..1d6a563268 100644 --- a/crates/defguard_core/src/enterprise/db/models/openid_provider.rs +++ b/crates/defguard_core/src/enterprise/db/models/openid_provider.rs @@ -3,7 +3,7 @@ use std::fmt; use model_derive::Model; use sqlx::{Error as SqlxError, PgExecutor, PgPool, Type, query, query_as}; -use crate::db::{Id, NoId}; +use defguard_common::db::{Id, NoId}; // The behavior when a user is deleted from the directory // Keep: Keep the user, despite being deleted from the external provider's directory diff --git a/crates/defguard_core/src/enterprise/db/models/snat.rs b/crates/defguard_core/src/enterprise/db/models/snat.rs index 127da55bf9..68b7b71c56 100644 --- a/crates/defguard_core/src/enterprise/db/models/snat.rs +++ b/crates/defguard_core/src/enterprise/db/models/snat.rs @@ -5,10 +5,8 @@ use serde::{Deserialize, Serialize}; use sqlx::{PgExecutor, query_as}; use utoipa::ToSchema; -use crate::{ - db::{Id, NoId}, - enterprise::snat::error::UserSnatBindingError, -}; +use crate::enterprise::snat::error::UserSnatBindingError; +use defguard_common::db::{Id, NoId}; #[derive(Clone, Debug, Deserialize, Model, Serialize, ToSchema)] #[table(user_snat_binding)] diff --git a/crates/defguard_core/src/enterprise/directory_sync/mod.rs b/crates/defguard_core/src/enterprise/directory_sync/mod.rs index 78e25f8a96..70ddd638ce 100644 --- a/crates/defguard_core/src/enterprise/directory_sync/mod.rs +++ b/crates/defguard_core/src/enterprise/directory_sync/mod.rs @@ -3,6 +3,7 @@ use std::{ time::Duration, }; +use defguard_common::db::Id; use paste::paste; use reqwest::header::AUTHORIZATION; use sqlx::{PgPool, error::Error as SqlxError}; @@ -16,7 +17,7 @@ use super::{ ldap::utils::ldap_update_users_state, }; use crate::{ - db::{GatewayEvent, Group, Id, User}, + db::{GatewayEvent, Group, User}, enterprise::{ db::models::openid_provider::DirectorySyncUserBehavior, ldap::utils::{ldap_add_users_to_groups, ldap_delete_users, ldap_remove_users_from_groups}, diff --git a/crates/defguard_core/src/enterprise/directory_sync/tests.rs b/crates/defguard_core/src/enterprise/directory_sync/tests.rs index 54df5c6a2c..d6ae4f4beb 100644 --- a/crates/defguard_core/src/enterprise/directory_sync/tests.rs +++ b/crates/defguard_core/src/enterprise/directory_sync/tests.rs @@ -2,6 +2,13 @@ mod test { use std::str::FromStr; + use defguard_common::{ + config::{DefGuardConfig, SERVER_CONFIG}, + db::{ + models::{Settings, settings::initialize_current_settings}, + setup_pool, + }, + }; use ipnetwork::IpNetwork; use secrecy::ExposeSecret; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; @@ -9,15 +16,9 @@ mod test { use super::super::*; use crate::{ - SERVER_CONFIG, - config::DefGuardConfig, db::{ - Device, Session, SessionState, Settings, WireguardNetwork, - models::{ - device::DeviceType, settings::initialize_current_settings, - wireguard::LocationMfaMode, - }, - setup_pool, + Device, Session, SessionState, WireguardNetwork, + models::{device::DeviceType, wireguard::LocationMfaMode}, }, enterprise::db::models::openid_provider::DirectorySyncTarget, }; diff --git a/crates/defguard_core/src/enterprise/firewall/mod.rs b/crates/defguard_core/src/enterprise/firewall/mod.rs index 4611be8acf..a87f9110ac 100644 --- a/crates/defguard_core/src/enterprise/firewall/mod.rs +++ b/crates/defguard_core/src/enterprise/firewall/mod.rs @@ -3,6 +3,7 @@ use std::{ ops::RangeInclusive, }; +use defguard_common::db::{Id, models::ModelError}; use ipnetwork::IpNetwork; use sqlx::{Error as SqlxError, PgConnection, query_as, query_scalar}; @@ -14,16 +15,16 @@ use super::{ utils::merge_ranges, }; use crate::{ - db::{Device, Id, User, WireguardNetwork, models::error::ModelError}, + db::{Device, User, WireguardNetwork}, enterprise::{ db::models::{acl::AliasKind, snat::UserSnatBinding}, is_enterprise_enabled, }, - grpc::proto::enterprise::firewall::{ - FirewallConfig, FirewallPolicy, FirewallRule, IpAddress, IpRange, IpVersion, Port, - PortRange as PortRangeProto, SnatBinding as SnatBindingProto, ip_address::Address, - port::Port as PortInner, - }, +}; +use defguard_proto::enterprise::firewall::{ + FirewallConfig, FirewallPolicy, FirewallRule, IpAddress, IpRange, IpVersion, Port, + PortRange as PortRangeProto, SnatBinding as SnatBindingProto, ip_address::Address, + port::Port as PortInner, }; #[derive(Debug, thiserror::Error)] diff --git a/crates/defguard_core/src/enterprise/firewall/tests.rs b/crates/defguard_core/src/enterprise/firewall/tests.rs index d286e4a1c2..0785fb85fc 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests.rs @@ -1,6 +1,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use chrono::{DateTime, NaiveDateTime}; +use defguard_common::db::{Id, NoId, setup_pool}; use ipnetwork::{IpNetwork, Ipv6Network}; use rand::{Rng, thread_rng}; use sqlx::{ @@ -15,9 +16,8 @@ use super::{ }; use crate::{ db::{ - Device, Group, Id, NoId, User, WireguardNetwork, + Device, Group, User, WireguardNetwork, models::device::{DeviceType, WireguardNetworkDevice}, - setup_pool, }, enterprise::{ db::models::acl::{ @@ -26,10 +26,10 @@ use crate::{ }, firewall::{get_source_addrs, get_source_network_devices}, }, - grpc::proto::enterprise::firewall::{ - FirewallPolicy, IpAddress, IpRange, IpVersion, Port, PortRange as PortRangeProto, Protocol, - ip_address::Address, port::Port as PortInner, - }, +}; +use defguard_proto::enterprise::firewall::{ + FirewallPolicy, IpAddress, IpRange, IpVersion, Port, PortRange as PortRangeProto, Protocol, + ip_address::Address, port::Port as PortInner, }; impl Default for AclRuleDestinationRange { diff --git a/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs b/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs index 069d1fd86d..f48e012554 100644 --- a/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs +++ b/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs @@ -10,10 +10,10 @@ use crate::{ events::{BidiRequestContext, BidiStreamEvent, BidiStreamEventType, DesktopClientMfaEvent}, grpc::{ client_mfa::{ClientLoginSession, ClientMfaServer}, - proto::proxy::{ClientMfaOidcAuthenticateRequest, DeviceInfo, MfaMethod}, utils::parse_client_info, }, }; +use defguard_proto::proxy::{ClientMfaOidcAuthenticateRequest, DeviceInfo, MfaMethod}; impl ClientMfaServer { #[instrument(skip_all)] diff --git a/crates/defguard_core/src/enterprise/grpc/polling.rs b/crates/defguard_core/src/enterprise/grpc/polling.rs index b7c7eacef7..782ca70fab 100644 --- a/crates/defguard_core/src/enterprise/grpc/polling.rs +++ b/crates/defguard_core/src/enterprise/grpc/polling.rs @@ -1,14 +1,13 @@ +use defguard_common::db::Id; use sqlx::PgPool; use tonic::Status; use crate::{ - db::{Device, Id, User, models::polling_token::PollingToken}, + db::{Device, User, models::polling_token::PollingToken}, enterprise::is_enterprise_enabled, - grpc::{ - proto::proxy::{InstanceInfoRequest, InstanceInfoResponse}, - utils::build_device_config_response, - }, + grpc::utils::build_device_config_response, }; +use defguard_proto::proxy::{InstanceInfoRequest, InstanceInfoResponse}; pub struct PollingServer { pool: PgPool, diff --git a/crates/defguard_core/src/enterprise/handlers/acl.rs b/crates/defguard_core/src/enterprise/handlers/acl.rs index d2ce367206..953550e1cb 100644 --- a/crates/defguard_core/src/enterprise/handlers/acl.rs +++ b/crates/defguard_core/src/enterprise/handlers/acl.rs @@ -4,13 +4,13 @@ use axum::{ http::StatusCode, }; use chrono::NaiveDateTime; +use defguard_common::db::Id; use serde_json::{Value, json}; use super::LicenseInfo; use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, - db::Id, enterprise::db::models::acl::{ AclAlias, AclAliasInfo, AclRule, AclRuleInfo, AliasKind, AliasState, Protocol, RuleState, }, diff --git a/crates/defguard_core/src/enterprise/handlers/activity_log_stream.rs b/crates/defguard_core/src/enterprise/handlers/activity_log_stream.rs index feca06d5b1..f125fddf5e 100644 --- a/crates/defguard_core/src/enterprise/handlers/activity_log_stream.rs +++ b/crates/defguard_core/src/enterprise/handlers/activity_log_stream.rs @@ -9,13 +9,13 @@ use super::LicenseInfo; use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, - db::{Id, NoId}, enterprise::db::models::activity_log_stream::{ ActivityLogStream, ActivityLogStreamConfig, ActivityLogStreamType, }, events::{ApiEvent, ApiEventType, ApiRequestContext}, handlers::{ApiResponse, ApiResult}, }; +use defguard_common::db::{Id, NoId}; pub async fn get_activity_log_stream( _admin: AdminRole, diff --git a/crates/defguard_core/src/enterprise/handlers/api_tokens.rs b/crates/defguard_core/src/enterprise/handlers/api_tokens.rs index d76a3488ea..12132fbc7e 100644 --- a/crates/defguard_core/src/enterprise/handlers/api_tokens.rs +++ b/crates/defguard_core/src/enterprise/handlers/api_tokens.rs @@ -15,8 +15,8 @@ use crate::{ error::WebError, events::{ApiEvent, ApiEventType, ApiRequestContext}, handlers::{ApiResponse, ApiResult, user_for_admin_or_self}, - random::gen_alphanumeric, }; +use defguard_common::random::gen_alphanumeric; const API_TOKEN_LENGTH: usize = 32; diff --git a/crates/defguard_core/src/enterprise/handlers/openid_login.rs b/crates/defguard_core/src/enterprise/handlers/openid_login.rs index 0eadebbe0c..2f3743b196 100644 --- a/crates/defguard_core/src/enterprise/handlers/openid_login.rs +++ b/crates/defguard_core/src/enterprise/handlers/openid_login.rs @@ -9,6 +9,13 @@ use axum_extra::{ headers::UserAgent, }; use base64::{Engine, prelude::BASE64_STANDARD}; +use defguard_common::{ + config::server_config, + db::{ + Id, + models::{Settings, settings::OpenidUsernameHandling}, + }, +}; use openidconnect::{ AuthorizationCode, ClientId, ClientSecret, CsrfToken, EndpointMaybeSet, EndpointNotSet, EndpointSet, IssuerUrl, Nonce, OAuth2TokenResponse, RedirectUrl, Scope, @@ -29,7 +36,7 @@ pub(crate) const SELECT_ACCOUNT_SUPPORTED_PROVIDERS: &[&str] = &["Google"]; use super::LicenseInfo; use crate::{ appstate::AppState, - db::{Id, Settings, User, models::settings::OpenidUsernameHandling}, + db::User, enterprise::{ db::models::openid_provider::OpenIdProvider, directory_sync::sync_user_groups_if_configured, ldap::utils::ldap_update_user_state, @@ -40,7 +47,6 @@ use crate::{ ApiResponse, AuthResponse, SESSION_COOKIE_NAME, SIGN_IN_COOKIE_NAME, auth::create_session, user::check_username, }, - server_config, }; /// Prune the given username from illegal characters in accordance with the following rules: diff --git a/crates/defguard_core/src/enterprise/handlers/openid_providers.rs b/crates/defguard_core/src/enterprise/handlers/openid_providers.rs index 51d300116f..f5bbc3403b 100644 --- a/crates/defguard_core/src/enterprise/handlers/openid_providers.rs +++ b/crates/defguard_core/src/enterprise/handlers/openid_providers.rs @@ -3,6 +3,10 @@ use axum::{ extract::{Path, State}, http::StatusCode, }; +use defguard_common::db::models::{ + Settings, + settings::{OpenidUsernameHandling, update_current_settings}, +}; use rsa::{RsaPrivateKey, pkcs8::DecodePrivateKey}; use serde_json::json; @@ -10,13 +14,7 @@ use super::LicenseInfo; use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, - db::{ - Settings, WireguardNetwork, - models::{ - settings::{OpenidUsernameHandling, update_current_settings}, - wireguard::LocationMfaMode, - }, - }, + db::{WireguardNetwork, models::wireguard::LocationMfaMode}, enterprise::{ db::models::openid_provider::OpenIdProvider, directory_sync::test_directory_sync_connection, }, diff --git a/crates/defguard_core/src/enterprise/ldap/client.rs b/crates/defguard_core/src/enterprise/ldap/client.rs index 6e029ac291..36a5f1b0d9 100644 --- a/crates/defguard_core/src/enterprise/ldap/client.rs +++ b/crates/defguard_core/src/enterprise/ldap/client.rs @@ -10,10 +10,8 @@ use ldap3::{ }; use super::error::LdapError; -use crate::{ - db::{Settings, User}, - enterprise::ldap::model::extract_rdn_value, -}; +use crate::{db::User, enterprise::ldap::model::extract_rdn_value}; +use defguard_common::db::models::Settings; impl super::LDAPConnection { pub(crate) async fn create() -> Result { diff --git a/crates/defguard_core/src/enterprise/ldap/hash.rs b/crates/defguard_core/src/enterprise/ldap/hash.rs index a5b47dbb8e..ddb6d4c8d6 100644 --- a/crates/defguard_core/src/enterprise/ldap/hash.rs +++ b/crates/defguard_core/src/enterprise/ldap/hash.rs @@ -1,4 +1,5 @@ use base64::Engine; +use defguard_common::hex::to_lower_hex; use md4::Md4; use rand::{RngCore, rngs::OsRng}; use sha1::{ @@ -6,8 +7,6 @@ use sha1::{ digest::generic_array::{GenericArray, sequence::Concat}, }; -use crate::hex::to_lower_hex; - /// Calculate salted SHA1 hash from given password in SSHA password storage scheme. #[must_use] pub fn salted_sha1_hash(password: &str) -> String { diff --git a/crates/defguard_core/src/enterprise/ldap/mod.rs b/crates/defguard_core/src/enterprise/ldap/mod.rs index c4ab28da63..17152ee73d 100644 --- a/crates/defguard_core/src/enterprise/ldap/mod.rs +++ b/crates/defguard_core/src/enterprise/ldap/mod.rs @@ -1,16 +1,23 @@ use std::{collections::HashSet, future::Future}; +use defguard_common::db::{ + Id, + models::{ + Settings, + settings::{LdapSyncStatus, update_current_settings}, + }, +}; #[cfg(not(test))] use ldap3::Ldap; use ldap3::{Mod, SearchEntry, ldap_escape}; use model::UserObjectClass; use rand::Rng; use sqlx::PgPool; -use sync::{SyncStatus, get_ldap_sync_status, is_ldap_desynced, set_ldap_sync_status}; +use sync::{get_ldap_sync_status, is_ldap_desynced, set_ldap_sync_status}; use self::error::LdapError; use crate::{ - db::{self, Id, Settings, User, models::settings::update_current_settings}, + db::{self, User}, enterprise::{is_enterprise_enabled, ldap::model::extract_dn_path, limits::update_counts}, }; @@ -36,8 +43,8 @@ pub(crate) async fn do_ldap_sync(pool: &PgPool) -> Result<(), LdapError> { // doesn't matter for the sync status if we can't pull changes. if !settings.ldap_enabled { debug!("LDAP is disabled, not performing LDAP sync"); - if get_ldap_sync_status() == SyncStatus::InSync { - set_ldap_sync_status(SyncStatus::OutOfSync, pool).await?; + if get_ldap_sync_status() == LdapSyncStatus::InSync { + set_ldap_sync_status(LdapSyncStatus::OutOfSync, pool).await?; } return Ok(()); } @@ -68,16 +75,16 @@ pub(crate) async fn do_ldap_sync(pool: &PgPool) -> Result<(), LdapError> { let mut ldap_connection = match LDAPConnection::create().await { Ok(connection) => connection, Err(err) => { - set_ldap_sync_status(SyncStatus::OutOfSync, pool).await?; + set_ldap_sync_status(LdapSyncStatus::OutOfSync, pool).await?; return Err(err); } }; if let Err(err) = ldap_connection.sync(pool, is_ldap_desynced()).await { - set_ldap_sync_status(SyncStatus::OutOfSync, pool).await?; + set_ldap_sync_status(LdapSyncStatus::OutOfSync, pool).await?; return Err(err); } - set_ldap_sync_status(SyncStatus::InSync, pool).await?; + set_ldap_sync_status(LdapSyncStatus::InSync, pool).await?; let _ = update_counts(pool).await; @@ -95,17 +102,17 @@ where let settings = Settings::get_current_settings(); if !is_enterprise_enabled() { info!("Enterprise features are disabled, not performing LDAP operation"); - set_ldap_sync_status(SyncStatus::OutOfSync, pool).await?; + set_ldap_sync_status(LdapSyncStatus::OutOfSync, pool).await?; return Err(LdapError::EnterpriseDisabled("LDAP".to_string())); } if !settings.ldap_enabled { debug!("LDAP is disabled, not performing LDAP operation"); - set_ldap_sync_status(SyncStatus::OutOfSync, pool).await?; + set_ldap_sync_status(LdapSyncStatus::OutOfSync, pool).await?; return Err(LdapError::MissingSettings("LDAP is disabled".into())); } - if settings.ldap_sync_enabled && get_ldap_sync_status() == SyncStatus::OutOfSync { + if settings.ldap_sync_enabled && get_ldap_sync_status() == LdapSyncStatus::OutOfSync { warn!("LDAP is considered to be desynced, not performing LDAP operation"); return Err(LdapError::Desynced); } @@ -114,7 +121,7 @@ where Ok(result) => Ok(result), Err(e) => { warn!("Encountered an error while performing LDAP operation: {e:?}"); - if let Err(status_err) = set_ldap_sync_status(SyncStatus::OutOfSync, pool).await { + if let Err(status_err) = set_ldap_sync_status(LdapSyncStatus::OutOfSync, pool).await { warn!("Failed to update LDAP sync status: {status_err:?}"); } diff --git a/crates/defguard_core/src/enterprise/ldap/model.rs b/crates/defguard_core/src/enterprise/ldap/model.rs index bfb3a20561..79a295732d 100644 --- a/crates/defguard_core/src/enterprise/ldap/model.rs +++ b/crates/defguard_core/src/enterprise/ldap/model.rs @@ -1,14 +1,11 @@ use std::collections::HashSet; +use defguard_common::db::{Id, models::Settings}; use ldap3::{Mod, SearchEntry}; use sqlx::{Error as SqlxError, PgExecutor}; use super::{LDAPConfig, error::LdapError}; -use crate::{ - db::{Id, Settings, User}, - handlers::user::check_username, - hashset, -}; +use crate::{db::User, handlers::user::check_username, hashset}; pub(crate) enum UserObjectClass { SambaSamAccount, diff --git a/crates/defguard_core/src/enterprise/ldap/sync.rs b/crates/defguard_core/src/enterprise/ldap/sync.rs index d7a795b6f5..f1dbeb27e1 100644 --- a/crates/defguard_core/src/enterprise/ldap/sync.rs +++ b/crates/defguard_core/src/enterprise/ldap/sync.rs @@ -54,11 +54,18 @@ //! use std::collections::{HashMap, HashSet}; -use sqlx::{PgConnection, PgPool, Type}; +use defguard_common::db::{ + Id, + models::{ + Settings, + settings::{LdapSyncStatus, update_current_settings}, + }, +}; +use sqlx::{PgConnection, PgPool}; use super::{LDAPConfig, error::LdapError}; use crate::{ - db::{Group, Id, Settings, User, models::settings::update_current_settings}, + db::{Group, User}, hashset, }; @@ -85,28 +92,13 @@ pub enum Authority { Defguard, } -#[derive(Clone, Debug, Copy, Eq, PartialEq, Deserialize, Serialize, Default, Type)] -#[sqlx(type_name = "ldap_sync_status", rename_all = "lowercase")] -pub enum SyncStatus { - InSync, - #[default] - OutOfSync, -} - -impl SyncStatus { - #[must_use] - pub fn is_out_of_sync(&self) -> bool { - matches!(self, SyncStatus::OutOfSync) - } -} - #[must_use] -pub fn get_ldap_sync_status() -> SyncStatus { +pub fn get_ldap_sync_status() -> LdapSyncStatus { let settings = Settings::get_current_settings(); settings.ldap_sync_status } -pub async fn set_ldap_sync_status(status: SyncStatus, pool: &PgPool) -> Result<(), LdapError> { +pub async fn set_ldap_sync_status(status: LdapSyncStatus, pool: &PgPool) -> Result<(), LdapError> { debug!("Setting LDAP sync status to {status:?}"); let mut settings = Settings::get_current_settings(); settings.ldap_sync_status = status; diff --git a/crates/defguard_core/src/enterprise/ldap/tests.rs b/crates/defguard_core/src/enterprise/ldap/tests.rs index 8d9d47fe4b..580573c416 100644 --- a/crates/defguard_core/src/enterprise/ldap/tests.rs +++ b/crates/defguard_core/src/enterprise/ldap/tests.rs @@ -1,15 +1,12 @@ use std::collections::HashMap; +use defguard_common::db::{models::settings::initialize_current_settings, setup_pool}; use ldap3::SearchEntry; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use super::*; use crate::{ - db::{ - Group, User, - models::settings::{Settings, initialize_current_settings, update_current_settings}, - setup_pool, - }, + db::{Group, User}, enterprise::ldap::{ model::extract_rdn_value, sync::{ diff --git a/crates/defguard_core/src/enterprise/ldap/utils.rs b/crates/defguard_core/src/enterprise/ldap/utils.rs index d952e8e22f..4ddc7bc8e6 100644 --- a/crates/defguard_core/src/enterprise/ldap/utils.rs +++ b/crates/defguard_core/src/enterprise/ldap/utils.rs @@ -3,11 +3,12 @@ use std::collections::{HashMap, HashSet}; +use defguard_common::db::Id; use sqlx::PgPool; use super::{LDAPConnection, error::LdapError}; use crate::{ - db::{Group, Id, User}, + db::{Group, User}, enterprise::ldap::with_ldap_status, }; diff --git a/crates/defguard_core/src/enterprise/license.rs b/crates/defguard_core/src/enterprise/license.rs index 420bdac777..ea27ea3aba 100644 --- a/crates/defguard_core/src/enterprise/license.rs +++ b/crates/defguard_core/src/enterprise/license.rs @@ -14,12 +14,12 @@ use thiserror::Error; use tokio::time::sleep; use super::limits::Counts; -use crate::{ +use crate::grpc::proto::enterprise::license::{LicenseKey, LicenseLimits, LicenseMetadata}; +use defguard_common::{ VERSION, - db::{Settings, models::settings::update_current_settings}, + config::server_config, + db::models::{Settings, settings::update_current_settings}, global_value, - grpc::proto::enterprise::license::{LicenseKey, LicenseLimits, LicenseMetadata}, - server_config, }; const LICENSE_SERVER_URL: &str = "https://pkgs.defguard.net/api/license/renew"; diff --git a/crates/defguard_core/src/enterprise/limits.rs b/crates/defguard_core/src/enterprise/limits.rs index 51a006a682..6e7d0f66b7 100644 --- a/crates/defguard_core/src/enterprise/limits.rs +++ b/crates/defguard_core/src/enterprise/limits.rs @@ -1,9 +1,10 @@ +use defguard_common::global_value; use sqlx::{PgPool, error::Error as SqlxError, query}; use super::license::License; #[cfg(test)] use super::license::get_cached_license; -use crate::{global_value, grpc::proto::enterprise::license::LicenseLimits}; +use crate::grpc::proto::enterprise::license::LicenseLimits; // Limits for free users pub const DEFAULT_USERS_LIMIT: u32 = 5; diff --git a/crates/defguard_core/src/enterprise/snat/handlers.rs b/crates/defguard_core/src/enterprise/snat/handlers.rs index 3b9e71adbf..3db0d81760 100644 --- a/crates/defguard_core/src/enterprise/snat/handlers.rs +++ b/crates/defguard_core/src/enterprise/snat/handlers.rs @@ -4,6 +4,7 @@ use axum::{ Json, extract::{Path, State}, }; +use defguard_common::db::Id; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -12,7 +13,7 @@ use utoipa::ToSchema; use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, - db::{GatewayEvent, Id, User, WireguardNetwork}, + db::{GatewayEvent, User, WireguardNetwork}, enterprise::{ db::models::snat::UserSnatBinding, handlers::LicenseInfo, snat::error::UserSnatBindingError, }, diff --git a/crates/defguard_core/src/error.rs b/crates/defguard_core/src/error.rs index bcd9e7f250..e58a2f8876 100644 --- a/crates/defguard_core/src/error.rs +++ b/crates/defguard_core/src/error.rs @@ -1,4 +1,6 @@ use axum::http::StatusCode; +use defguard_common::db::models::{ModelError, settings::SettingsValidationError}; +use defguard_mail::templates::TemplateError; use sqlx::error::Error as SqlxError; use thiserror::Error; use tokio::sync::mpsc::error::SendError; @@ -6,17 +8,13 @@ use utoipa::ToSchema; use crate::{ auth::failed_login::FailedLoginError, - db::models::{ - device::DeviceError, enrollment::TokenError, error::ModelError, - settings::SettingsValidationError, wireguard::WireguardNetworkError, - }, + db::models::{device::DeviceError, enrollment::TokenError, wireguard::WireguardNetworkError}, enterprise::{ activity_log_stream::error::ActivityLogStreamError, db::models::acl::AclError, firewall::FirewallError, ldap::error::LdapError, license::LicenseError, }, events::ApiEvent, grpc::gateway::map::GatewayMapError, - templates::TemplateError, }; /// Represents kinds of error that occurred diff --git a/crates/defguard_core/src/events.rs b/crates/defguard_core/src/events.rs index 59c3d9ffcf..68bc260a72 100644 --- a/crates/defguard_core/src/events.rs +++ b/crates/defguard_core/src/events.rs @@ -1,19 +1,22 @@ use std::net::IpAddr; use chrono::{NaiveDateTime, Utc}; -use serde::Serialize; +use defguard_common::db::{ + Id, + models::{AuthenticationKey, MFAMethod, Settings}, +}; use crate::{ db::{ - Device, Group, Id, MFAMethod, Settings, User, WebAuthn, WebHook, WireguardNetwork, - models::{authentication_key::AuthenticationKey, oauth2client::OAuth2Client}, + Device, Group, User, WebAuthn, WebHook, WireguardNetwork, + models::oauth2client::OAuth2Client, }, enterprise::db::models::{ activity_log_stream::ActivityLogStream, api_tokens::ApiToken, openid_provider::OpenIdProvider, snat::UserSnatBinding, }, - grpc::proto::proxy::MfaMethod, }; +use defguard_proto::proxy::MfaMethod; /// Shared context that needs to be added to every API event /// @@ -383,23 +386,6 @@ pub enum PasswordResetEvent { pub type ClientMFAMethod = MfaMethod; -impl Serialize for ClientMFAMethod { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match *self { - MfaMethod::Totp => serializer.serialize_unit_variant("MfaMethod", 0, "Totp"), - MfaMethod::Email => serializer.serialize_unit_variant("MfaMethod", 1, "Email"), - MfaMethod::Oidc => serializer.serialize_unit_variant("MfaMethod", 2, "Oidc"), - MfaMethod::Biometric => serializer.serialize_unit_variant("MfaMethod", 3, "Biometric"), - MfaMethod::MobileApprove => { - serializer.serialize_unit_variant("MfaMethod", 4, "MobileApprove") - } - } - } -} - #[derive(Debug)] pub enum DesktopClientMfaEvent { Connected { diff --git a/crates/defguard_core/src/grpc/auth.rs b/crates/defguard_core/src/grpc/auth.rs index 99ebd75608..c8099389f6 100644 --- a/crates/defguard_core/src/grpc/auth.rs +++ b/crates/defguard_core/src/grpc/auth.rs @@ -1,20 +1,17 @@ use std::sync::{Arc, Mutex}; +use defguard_common::auth::claims::{Claims, ClaimsType}; +use defguard_proto::auth::{AuthenticateRequest, AuthenticateResponse, auth_service_server}; use jsonwebtoken::errors::Error as JWTError; use sqlx::PgPool; use tonic::{Request, Response, Status}; use crate::{ - auth::{ - Claims, ClaimsType, - failed_login::{FailedLoginMap, check_failed_logins, log_failed_login_attempt}, - }, + auth::failed_login::{FailedLoginMap, check_failed_logins, log_failed_login_attempt}, db::User, server_config, }; -tonic::include_proto!("auth"); - pub struct AuthServer { pool: PgPool, failed_logins: Arc>, diff --git a/crates/defguard_core/src/grpc/client_mfa.rs b/crates/defguard_core/src/grpc/client_mfa.rs index f7ac11e548..0f911e853a 100644 --- a/crates/defguard_core/src/grpc/client_mfa.rs +++ b/crates/defguard_core/src/grpc/client_mfa.rs @@ -1,6 +1,14 @@ use std::collections::HashMap; use chrono::Utc; +use defguard_common::{ + auth::claims::{Claims, ClaimsType}, + db::{ + Id, + models::{BiometricAuth, BiometricChallenge}, + }, +}; +use defguard_mail::Mail; use sqlx::PgPool; use thiserror::Error; use tokio::sync::{ @@ -9,28 +17,23 @@ use tokio::sync::{ }; use tonic::{Code, Status}; -use super::proto::proxy::{ - self, ClientMfaFinishRequest, ClientMfaFinishResponse, ClientMfaStartRequest, - ClientMfaStartResponse, MfaMethod, -}; use crate::{ - auth::{Claims, ClaimsType}, db::{ - Device, GatewayEvent, Id, User, UserInfo, WireguardNetwork, + Device, GatewayEvent, User, UserInfo, WireguardNetwork, models::{ - biometric_auth::{BiometricAuth, BiometricChallenge}, device::{DeviceInfo, DeviceNetworkInfo, WireguardNetworkDevice}, wireguard::LocationMfaMode, }, }, enterprise::{db::models::openid_provider::OpenIdProvider, is_enterprise_enabled}, events::{BidiRequestContext, BidiStreamEvent, BidiStreamEventType, DesktopClientMfaEvent}, - grpc::{ - proto::proxy::{ClientMfaTokenValidationRequest, ClientMfaTokenValidationResponse}, - utils::parse_client_info, - }, + grpc::utils::parse_client_info, handlers::mail::send_email_mfa_code_email, - mail::Mail, +}; +use defguard_proto::proxy::{ + self, ClientMfaFinishRequest, ClientMfaFinishResponse, ClientMfaStartRequest, + ClientMfaStartResponse, ClientMfaTokenValidationRequest, ClientMfaTokenValidationResponse, + MfaMethod, }; const CLIENT_SESSION_TIMEOUT: u64 = 60 * 5; // 10 minutes diff --git a/crates/defguard_core/src/grpc/enrollment.rs b/crates/defguard_core/src/grpc/enrollment.rs index 3947b1dc66..f677017868 100644 --- a/crates/defguard_core/src/grpc/enrollment.rs +++ b/crates/defguard_core/src/grpc/enrollment.rs @@ -1,5 +1,16 @@ use std::collections::HashSet; +use defguard_common::{ + csv::AsCsv, + db::{ + Id, + models::{BiometricAuth, MFAMethod, Settings}, + }, +}; +use defguard_mail::{ + Mail, + templates::{self, TemplateLocation}, +}; use sqlx::{PgPool, Transaction, query_scalar}; use tokio::sync::{ broadcast::Sender, @@ -7,20 +18,11 @@ use tokio::sync::{ }; use tonic::Status; -use super::{ - InstanceInfo, - proto::proxy::{ - ActivateUserRequest, AdminInfo, Device as ProtoDevice, DeviceConfig as ProtoDeviceConfig, - DeviceConfigResponse, EnrollmentStartRequest, EnrollmentStartResponse, ExistingDevice, - InitialUserInfo, NewDevice, - }, -}; +use super::InstanceInfo; use crate::{ - AsCsv, db::{ - Device, GatewayEvent, Id, MFAMethod, Settings, User, WireguardNetwork, + Device, GatewayEvent, User, WireguardNetwork, models::{ - biometric_auth::BiometricAuth, device::{DeviceConfig, DeviceInfo, DeviceType}, enrollment::{ENROLLMENT_TOKEN_TYPE, Token, TokenError}, polling_token::PollingToken, @@ -33,14 +35,7 @@ use crate::{ limits::update_counts, }, events::{BidiRequestContext, BidiStreamEvent, BidiStreamEventType, EnrollmentEvent}, - grpc::{ - proto::proxy::{ - CodeMfaSetupFinishRequest, CodeMfaSetupFinishResponse, CodeMfaSetupStartRequest, - CodeMfaSetupStartResponse, LocationMfaMode as ProtoLocationMfaMode, MfaMethod, - RegisterMobileAuthRequest, - }, - utils::{build_device_config_response, new_polling_token, parse_client_info}, - }, + grpc::utils::{build_device_config_response, new_polling_token, parse_client_info}, handlers::{ mail::{ send_email_mfa_activation_email, send_mfa_configured_email, send_new_device_added_email, @@ -48,10 +43,14 @@ use crate::{ user::check_password_strength, }, headers::get_device_info, - is_valid_phone_number, - mail::Mail, - server_config, - templates::{self, TemplateLocation}, + is_valid_phone_number, server_config, +}; +use defguard_proto::proxy::{ + ActivateUserRequest, AdminInfo, CodeMfaSetupFinishRequest, CodeMfaSetupFinishResponse, + CodeMfaSetupStartRequest, CodeMfaSetupStartResponse, Device as ProtoDevice, + DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse, EnrollmentStartRequest, + EnrollmentStartResponse, ExistingDevice, InitialUserInfo, + LocationMfaMode as ProtoLocationMfaMode, MfaMethod, NewDevice, RegisterMobileAuthRequest, }; pub(super) struct EnrollmentServer { @@ -131,7 +130,7 @@ impl EnrollmentServer { pub async fn start_enrollment( &self, request: EnrollmentStartRequest, - info: Option, + info: Option, ) -> Result { debug!("Starting enrollment session, request: {request:?}"); // fetch enrollment token @@ -231,7 +230,7 @@ impl EnrollmentServer { user.username, user.id ); let (username, user_id) = (user.username.clone(), user.id); - let user_info = InitialUserInfo::from_user(&self.pool, user) + let user_info = initial_info_from_user(&self.pool, user) .await .map_err(|err| { error!( @@ -264,14 +263,14 @@ impl EnrollmentServer { .fetch_one(&self.pool) .await .map_err(|_| Status::internal("Failed to read data".to_string()))?; - let enrollment_settings = super::proto::proxy::EnrollmentSettings { + let enrollment_settings = defguard_proto::proxy::EnrollmentSettings { vpn_setup_optional, smtp_configured, only_client_activation: enterprise_settings.only_client_activation, admin_device_management: enterprise_settings.admin_device_management, mfa_required: instance_has_internal_mfa, }; - let response = super::proto::proxy::EnrollmentStartResponse { + let response = defguard_proto::proxy::EnrollmentStartResponse { admin: admin_info, user: Some(user_info), deadline_timestamp: session_deadline.and_utc().timestamp(), @@ -361,7 +360,7 @@ impl EnrollmentServer { pub async fn activate_user( &self, request: ActivateUserRequest, - req_device_info: Option, + req_device_info: Option, ) -> Result<(), Status> { debug!("Activating user account"); let enrollment = self.validate_session(request.token.as_ref()).await?; @@ -484,7 +483,7 @@ impl EnrollmentServer { pub async fn create_device( &self, request: NewDevice, - req_device_info: Option, + req_device_info: Option, ) -> Result { debug!("Adding new user device"); let enrollment_token = self.validate_session(request.token.as_ref()).await?; @@ -1038,24 +1037,25 @@ impl From> for AdminInfo { } } -impl InitialUserInfo { - async fn from_user(pool: &PgPool, user: User) -> Result { - let enrolled = user.is_enrolled(); - let devices = user.user_devices(pool).await?; - let device_names = devices.into_iter().map(|dev| dev.device.name).collect(); - let is_admin = user.is_admin(pool).await?; - Ok(Self { - first_name: user.first_name, - last_name: user.last_name, - login: user.username, - email: user.email, - phone_number: user.phone, - is_active: user.is_active, - device_names, - enrolled, - is_admin, - }) - } +async fn initial_info_from_user( + pool: &PgPool, + user: User, +) -> Result { + let enrolled = user.is_enrolled(); + let devices = user.user_devices(pool).await?; + let device_names = devices.into_iter().map(|dev| dev.device.name).collect(); + let is_admin = user.is_admin(pool).await?; + Ok(InitialUserInfo { + first_name: user.first_name, + last_name: user.last_name, + login: user.username, + email: user.email, + phone_number: user.phone, + is_active: user.is_active, + device_names, + enrolled, + is_admin, + }) } impl From for ProtoDeviceConfig { @@ -1143,8 +1143,8 @@ impl Token { to: admin.email.clone(), subject: "[defguard] User enrollment completed".into(), content: templates::enrollment_admin_notification( - user, - admin, + &user.clone().into(), + &admin.clone().into(), ip_address, device_info, )?, diff --git a/crates/defguard_core/src/grpc/gateway/client_state.rs b/crates/defguard_core/src/grpc/gateway/client_state.rs index 76b5439306..1bc49a404c 100644 --- a/crates/defguard_core/src/grpc/gateway/client_state.rs +++ b/crates/defguard_core/src/grpc/gateway/client_state.rs @@ -1,11 +1,12 @@ use std::{collections::HashMap, net::SocketAddr}; use chrono::{NaiveDateTime, TimeDelta, Utc}; +use defguard_common::db::Id; use thiserror::Error; use tonic::{Code, Status}; use crate::{ - db::{Device, Id, User, WireguardNetwork, models::wireguard_peer_stats::WireguardPeerStats}, + db::{Device, User, WireguardNetwork, models::wireguard_peer_stats::WireguardPeerStats}, events::GrpcRequestContext, }; diff --git a/crates/defguard_core/src/grpc/gateway/map.rs b/crates/defguard_core/src/grpc/gateway/map.rs index cef0016cdd..42e665784d 100644 --- a/crates/defguard_core/src/grpc/gateway/map.rs +++ b/crates/defguard_core/src/grpc/gateway/map.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use chrono::Utc; +use defguard_common::db::Id; use defguard_version::tracing::VersionInfo; use semver::Version; use sqlx::PgPool; @@ -9,7 +10,7 @@ use tokio::sync::mpsc::UnboundedSender; use uuid::Uuid; use super::state::GatewayState; -use crate::{db::Id, mail::Mail}; +use defguard_mail::Mail; /// Helper struct used to handle gateway state. Gateways are grouped by network. type GatewayHostname = String; diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 39f6fd283d..ca3d668283 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -7,6 +7,15 @@ use std::{ use chrono::{DateTime, TimeDelta, Utc}; use client_state::ClientMap; +use defguard_common::db::{Id, NoId}; +use defguard_mail::Mail; +use defguard_proto::{ + enterprise::firewall::FirewallConfig, + gateway::{ + Configuration, ConfigurationRequest, Peer, PeerStats, StatsUpdate, Update, + gateway_service_server, stats_update, update, + }, +}; use defguard_version::version_info_from_metadata; use semver::Version; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query}; @@ -23,18 +32,12 @@ use tokio_stream::Stream; use tonic::{Code, Request, Response, Status, metadata::MetadataMap}; use self::map::GatewayMap; -use super::proto::enterprise::firewall::FirewallConfig; -pub use crate::grpc::proto::gateway::{ - Configuration, ConfigurationRequest, Peer, PeerStats, StatsUpdate, Update, - gateway_service_server, stats_update, update, -}; use crate::{ db::{ - Device, GatewayEvent, Id, NoId, User, + Device, GatewayEvent, User, models::{wireguard::WireguardNetwork, wireguard_peer_stats::WireguardPeerStats}, }, events::{GrpcEvent, GrpcRequestContext}, - mail::Mail, }; pub mod client_state; diff --git a/crates/defguard_core/src/grpc/gateway/state.rs b/crates/defguard_core/src/grpc/gateway/state.rs index b44fd21c13..7219c30d16 100644 --- a/crates/defguard_core/src/grpc/gateway/state.rs +++ b/crates/defguard_core/src/grpc/gateway/state.rs @@ -1,6 +1,8 @@ use std::time::Duration; use chrono::NaiveDateTime; +use defguard_common::db::{Id, models::Settings}; +use defguard_mail::Mail; use defguard_version::{DefguardComponent, tracing::VersionInfo}; use semver::Version; use serde::Serialize; @@ -11,10 +13,8 @@ use utoipa::ToSchema; use uuid::Uuid; use crate::{ - db::{Id, Settings}, grpc::MIN_GATEWAY_VERSION, handlers::mail::{send_gateway_disconnected_email, send_gateway_reconnected_email}, - mail::Mail, }; #[derive(Clone, Debug, Serialize, ToSchema)] diff --git a/crates/defguard_core/src/grpc/interceptor.rs b/crates/defguard_core/src/grpc/interceptor.rs index 51e8a865d9..9267e12d38 100644 --- a/crates/defguard_core/src/grpc/interceptor.rs +++ b/crates/defguard_core/src/grpc/interceptor.rs @@ -1,9 +1,7 @@ +use defguard_common::auth::claims::{Claims, ClaimsType}; use tonic::{Status, service::Interceptor}; -use crate::{ - auth::{Claims, ClaimsType}, - grpc::{AUTHORIZATION_HEADER, HOSTNAME_HEADER}, -}; +use crate::grpc::{AUTHORIZATION_HEADER, HOSTNAME_HEADER}; /// Auth interceptor used by gRPC services. Verifies JWT token sent /// in gRPC metadata under "authorization" key. diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index cf32efd637..0882c47fa5 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -3,14 +3,18 @@ use std::{ fs::read_to_string, time::{Duration, Instant}, }; -#[cfg(any(feature = "wireguard", feature = "worker"))] use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex, RwLock}, }; use axum::http::Uri; -#[cfg(feature = "wireguard")] +use defguard_common::{ + VERSION, + auth::claims::ClaimsType, + db::{Id, models::Settings}, +}; +use defguard_mail::Mail; use defguard_version::server::DefguardVersionLayer; use defguard_version::{ ComponentInfo, DefguardComponent, Version, client::ClientVersionInterceptor, @@ -19,7 +23,6 @@ use defguard_version::{ use openidconnect::{AuthorizationCode, Nonce, Scope, core::CoreAuthenticationFlow}; use reqwest::Url; use serde::Serialize; -#[cfg(feature = "worker")] use sqlx::PgPool; use tokio::{ sync::{ @@ -30,34 +33,25 @@ use tokio::{ }; use tokio_stream::wrappers::UnboundedReceiverStream; use tonic::{ - Code, Status, Streaming, + Code, Streaming, transport::{ Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig, server::Router, }, }; use tower::ServiceBuilder; -#[cfg(feature = "wireguard")] -use self::gateway::{GatewayServer, gateway_service_server::GatewayServiceServer}; +use self::gateway::GatewayServer; use self::{ - auth::{AuthServer, auth_service_server::AuthServiceServer}, - client_mfa::ClientMfaServer, - enrollment::EnrollmentServer, + auth::AuthServer, client_mfa::ClientMfaServer, enrollment::EnrollmentServer, password_reset::PasswordResetServer, - proto::proxy::core_response, -}; -#[cfg(feature = "worker")] -use self::{ - interceptor::JwtInterceptor, proto::worker::worker_service_server::WorkerServiceServer, - worker::WorkerServer, }; -#[cfg(feature = "wireguard")] +use self::{interceptor::JwtInterceptor, worker::WorkerServer}; +use crate::db::GatewayEvent; pub use crate::version::MIN_GATEWAY_VERSION; use crate::{ - VERSION, auth::failed_login::FailedLoginMap, db::{ - AppEvent, Id, Settings, + AppEvent, models::enrollment::{ENROLLMENT_TOKEN_TYPE, Token}, }, enterprise::{ @@ -72,53 +66,37 @@ use crate::{ }, events::{BidiStreamEvent, GrpcEvent}, grpc::gateway::{client_state::ClientMap, map::GatewayMap}, - mail::Mail, server_config, version::{IncompatibleComponents, IncompatibleProxyData, is_proxy_version_supported}, }; -#[cfg(feature = "worker")] -use crate::{auth::ClaimsType, db::GatewayEvent}; static VERSION_ZERO: Version = Version::new(0, 0, 0); mod auth; pub(crate) mod client_mfa; pub mod enrollment; -#[cfg(feature = "wireguard")] pub mod gateway; -#[cfg(any(feature = "wireguard", feature = "worker"))] mod interceptor; pub mod password_reset; pub(crate) mod utils; -#[cfg(feature = "worker")] pub mod worker; pub mod proto { - pub mod proxy { - tonic::include_proto!("defguard.proxy"); - } - pub mod gateway { - tonic::include_proto!("gateway"); - } - pub mod auth { - tonic::include_proto!("auth"); - } - pub mod worker { - tonic::include_proto!("worker"); - } pub mod enterprise { pub mod license { tonic::include_proto!("enterprise.license"); } - pub mod firewall { - tonic::include_proto!("enterprise.firewall"); - } } } -use proto::proxy::{ - AuthCallbackResponse, AuthInfoResponse, CoreError, CoreRequest, CoreResponse, core_request, - proxy_client::ProxyClient, +use defguard_proto::{ + auth::auth_service_server::AuthServiceServer, + gateway::gateway_service_server::GatewayServiceServer, + proxy::{ + AuthCallbackResponse, AuthInfoResponse, CoreError, CoreRequest, CoreResponse, core_request, + core_response, proxy_client::ProxyClient, + }, + worker::worker_service_server::WorkerServiceServer, }; // gRPC header for passing auth token from clients @@ -129,15 +107,6 @@ pub static HOSTNAME_HEADER: &str = "hostname"; const TEN_SECS: Duration = Duration::from_secs(10); -impl From for CoreError { - fn from(status: Status) -> Self { - Self { - status_code: status.code().into(), - message: status.message().into(), - } - } -} - struct ProxyMessageLoopContext<'a> { pool: PgPool, tx: UnboundedSender, @@ -733,7 +702,6 @@ pub async fn build_grpc_service_router( ) -> Result { let auth_service = AuthServiceServer::new(AuthServer::new(pool.clone(), failed_logins)); - #[cfg(feature = "worker")] let worker_service = WorkerServiceServer::with_interceptor( WorkerServer::new(pool.clone(), worker_state), JwtInterceptor::new(ClaimsType::YubiBridge), @@ -750,7 +718,6 @@ pub async fn build_grpc_service_router( .add_service(health_service) .add_service(auth_service); - #[cfg(feature = "wireguard")] let router = { use crate::version::GatewayVersionInterceptor; @@ -777,13 +744,11 @@ pub async fn build_grpc_service_router( ) }; - #[cfg(feature = "worker")] let router = router.add_service(worker_service); Ok(router) } -#[cfg(feature = "worker")] pub struct Job { id: u32, first_name: String, @@ -792,7 +757,6 @@ pub struct Job { username: String, } -#[cfg(feature = "worker")] #[derive(Serialize)] pub struct JobResponse { pub success: bool, @@ -802,14 +766,12 @@ pub struct JobResponse { pub username: String, } -#[cfg(feature = "worker")] pub struct WorkerInfo { last_seen: Instant, ip: IpAddr, jobs: Vec, } -#[cfg(feature = "worker")] pub struct WorkerState { current_job_id: u32, workers: HashMap, @@ -817,7 +779,6 @@ pub struct WorkerState { webhook_tx: UnboundedSender, } -#[cfg(feature = "worker")] #[derive(Deserialize, Serialize)] pub struct WorkerDetail { id: String, @@ -862,7 +823,7 @@ impl InstanceInfo { } } -impl From for proto::proxy::InstanceInfo { +impl From for defguard_proto::proxy::InstanceInfo { fn from(instance: InstanceInfo) -> Self { Self { name: instance.name, diff --git a/crates/defguard_core/src/grpc/password_reset.rs b/crates/defguard_core/src/grpc/password_reset.rs index 69e2a9b555..9049fb449e 100644 --- a/crates/defguard_core/src/grpc/password_reset.rs +++ b/crates/defguard_core/src/grpc/password_reset.rs @@ -1,11 +1,8 @@ +use defguard_mail::Mail; use sqlx::PgPool; use tokio::sync::mpsc::{UnboundedSender, error::SendError}; use tonic::Status; -use super::proto::proxy::{ - DeviceInfo, PasswordResetInitializeRequest, PasswordResetRequest, PasswordResetStartRequest, - PasswordResetStartResponse, -}; use crate::{ db::{ User, @@ -19,9 +16,12 @@ use crate::{ user::check_password_strength, }, headers::get_device_info, - mail::Mail, server_config, }; +use defguard_proto::proxy::{ + DeviceInfo, PasswordResetInitializeRequest, PasswordResetRequest, PasswordResetStartRequest, + PasswordResetStartResponse, +}; pub(super) struct PasswordResetServer { pool: PgPool, diff --git a/crates/defguard_core/src/grpc/utils.rs b/crates/defguard_core/src/grpc/utils.rs index 0e5772d574..757b9d9490 100644 --- a/crates/defguard_core/src/grpc/utils.rs +++ b/crates/defguard_core/src/grpc/utils.rs @@ -1,16 +1,16 @@ use std::{net::IpAddr, str::FromStr}; +use defguard_common::{ + csv::AsCsv, + db::{Id, models::Settings}, +}; use sqlx::PgPool; use tonic::Status; -use super::{ - InstanceInfo, - proto::proxy::{DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse, DeviceInfo}, -}; +use super::InstanceInfo; use crate::{ - AsCsv, db::{ - Device, Id, Settings, User, + Device, User, models::{ device::{DeviceType, WireguardNetworkDevice}, polling_token::PollingToken, @@ -20,7 +20,10 @@ use crate::{ enterprise::db::models::{ enterprise_settings::EnterpriseSettings, openid_provider::OpenIdProvider, }, - grpc::proto::proxy::LocationMfaMode as ProtoLocationMfaMode, +}; +use defguard_proto::proxy::{ + DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse, DeviceInfo, + LocationMfaMode as ProtoLocationMfaMode, }; // Create a new token for configuration polling. diff --git a/crates/defguard_core/src/grpc/worker.rs b/crates/defguard_core/src/grpc/worker.rs index 2b973fbcaf..c0acfa12ec 100644 --- a/crates/defguard_core/src/grpc/worker.rs +++ b/crates/defguard_core/src/grpc/worker.rs @@ -5,19 +5,15 @@ use std::{ time::Instant, }; +use defguard_common::db::models::{AuthenticationKey, AuthenticationKeyType}; use sqlx::{PgPool, query}; use tokio::sync::mpsc::UnboundedSender; use tonic::{Request, Response, Status}; use super::{Job, JobResponse, WorkerDetail, WorkerInfo, WorkerState}; -pub use crate::grpc::proto::worker::JobStatus; -use crate::{ - db::{ - AppEvent, HWKeyUserData, User, YubiKey, - models::authentication_key::{AuthenticationKey, AuthenticationKeyType}, - }, - grpc::proto::worker::{GetJobResponse, Worker, worker_service_server}, -}; +use crate::db::{AppEvent, HWKeyUserData, User, YubiKey}; +pub use defguard_proto::worker::JobStatus; +use defguard_proto::worker::{GetJobResponse, Worker, worker_service_server}; impl WorkerInfo { /// Create new `Worker` instance. diff --git a/crates/defguard_core/src/handlers/activity_log.rs b/crates/defguard_core/src/handlers/activity_log.rs index c14a68fa88..5485d0d8c0 100644 --- a/crates/defguard_core/src/handlers/activity_log.rs +++ b/crates/defguard_core/src/handlers/activity_log.rs @@ -3,6 +3,7 @@ use std::fmt::{self, Display, Formatter}; use axum::extract::State; use axum_extra::extract::Query; use chrono::{DateTime, NaiveDateTime, Utc}; +use defguard_common::db::Id; use ipnetwork::IpNetwork; use sqlx::{FromRow, Postgres, QueryBuilder, Type}; @@ -10,11 +11,7 @@ use super::{ DEFAULT_API_PAGE_SIZE, pagination::{PaginatedApiResponse, PaginatedApiResult, PaginationMeta, PaginationParams}, }; -use crate::{ - appstate::AppState, - auth::SessionInfo, - db::{Id, models::activity_log::ActivityLogModule}, -}; +use crate::{appstate::AppState, auth::SessionInfo, db::models::activity_log::ActivityLogModule}; #[derive(Debug, Deserialize, Default)] pub struct FilterParams { diff --git a/crates/defguard_core/src/handlers/app_info.rs b/crates/defguard_core/src/handlers/app_info.rs index bc67e8c17f..e53592f9c8 100644 --- a/crates/defguard_core/src/handlers/app_info.rs +++ b/crates/defguard_core/src/handlers/app_info.rs @@ -3,10 +3,9 @@ use serde_json::json; use super::{ApiResponse, ApiResult}; use crate::{ - VERSION, appstate::AppState, auth::SessionInfo, - db::{Settings, WireguardNetwork}, + db::WireguardNetwork, enterprise::{ db::models::openid_provider::OpenIdProvider, is_enterprise_enabled, is_enterprise_free, @@ -14,6 +13,7 @@ use crate::{ limits::{LimitsExceeded, get_counts}, }, }; +use defguard_common::{VERSION, db::models::Settings}; #[derive(Serialize)] struct LicenseInfo { diff --git a/crates/defguard_core/src/handlers/auth.rs b/crates/defguard_core/src/handlers/auth.rs index 752297662e..a68f642877 100644 --- a/crates/defguard_core/src/handlers/auth.rs +++ b/crates/defguard_core/src/handlers/auth.rs @@ -13,6 +13,11 @@ use axum_extra::{ }, headers::UserAgent, }; +use defguard_common::db::{ + Id, + models::{MFAMethod, Settings}, +}; +use defguard_mail::Mail; use serde_json::json; use sqlx::{PgPool, types::Uuid}; use time::Duration; @@ -31,7 +36,7 @@ use crate::{ SessionInfo, failed_login::{check_failed_logins, log_failed_login_attempt}, }, - db::{Id, MFAInfo, MFAMethod, Session, SessionState, Settings, User, UserInfo, WebAuthn}, + db::{MFAInfo, Session, SessionState, User, UserInfo, WebAuthn}, enterprise::ldap::utils::login_through_ldap, error::WebError, events::{ApiEvent, ApiEventType, ApiRequestContext}, @@ -43,7 +48,6 @@ use crate::{ user_for_admin_or_self, }, headers::{USER_AGENT_PARSER, check_new_device_login, get_user_agent_device}, - mail::Mail, server_config, }; @@ -87,7 +91,7 @@ pub(crate) async fn create_session( check_new_device_login( pool, mail_tx, - &session, + &session.clone().into(), user, ip_address.to_string(), login_event_type, @@ -112,7 +116,7 @@ pub(crate) async fn create_session( check_new_device_login( pool, mail_tx, - &session, + &session.clone().into(), user, ip_address.to_string(), login_event_type, @@ -471,7 +475,7 @@ pub async fn webauthn_finish( .await?; if user.mfa_method == MFAMethod::None { send_mfa_configured_email( - Some(&session.session), + Some(&session.session.into()), &user, &MFAMethod::Webauthn, &appstate.mail_tx, @@ -640,7 +644,7 @@ pub async fn totp_enable( user.enable_totp(&appstate.pool).await?; if user.mfa_method == MFAMethod::None { send_mfa_configured_email( - Some(&session.session), + Some(&session.session.into()), &user, &MFAMethod::OneTimePassword, &appstate.mail_tx, @@ -789,7 +793,7 @@ pub async fn email_mfa_init(session: SessionInfo, State(appstate): State, - session: &Session, + session: &SessionContext, created: NaiveDateTime, ) -> Result<(), TemplateError> { debug!("User {user_email} new device login mail to {SUPPORT_EMAIL_ADDRESS}"); @@ -319,7 +322,7 @@ pub async fn send_new_device_ocid_login_email( user_email: &str, oauth2client_name: String, mail_tx: &UnboundedSender, - session: &Session, + session: &SessionContext, ) -> Result<(), TemplateError> { debug!("User {user_email} new device OCID login mail to {SUPPORT_EMAIL_ADDRESS}"); @@ -348,7 +351,7 @@ pub async fn send_new_device_ocid_login_email( } pub fn send_mfa_configured_email( - session: Option<&Session>, + session: Option<&SessionContext>, user: &User, mfa_method: &MFAMethod, mail_tx: &UnboundedSender, @@ -382,7 +385,7 @@ pub fn send_mfa_configured_email( pub fn send_email_mfa_activation_email( user: &User, mail_tx: &UnboundedSender, - session: Option<&Session>, + session: Option<&SessionContext>, ) -> Result<(), TemplateError> { debug!("Sending email MFA activation mail to {}", user.email); @@ -395,7 +398,7 @@ pub fn send_email_mfa_activation_email( let mail = Mail { to: user.email.clone(), subject: EMAIL_MFA_ACTIVATION_EMAIL_SUBJECT.into(), - content: templates::email_mfa_activation_mail(user, &code, session)?, + content: templates::email_mfa_activation_mail(&user.clone().into(), &code, session)?, attachments: Vec::new(), result_tx: None, }; @@ -417,7 +420,7 @@ pub fn send_email_mfa_activation_email( pub fn send_email_mfa_code_email( user: &User, mail_tx: &UnboundedSender, - session: Option<&Session>, + session: Option<&SessionContext>, ) -> Result<(), TemplateError> { debug!("Sending email MFA code mail to {}", user.email); @@ -430,7 +433,7 @@ pub fn send_email_mfa_code_email( let mail = Mail { to: user.email.clone(), subject: EMAIL_MFA_CODE_EMAIL_SUBJECT.into(), - content: templates::email_mfa_code_mail(user, &code, session)?, + content: templates::email_mfa_code_mail(&user.clone().into(), &code, session)?, attachments: Vec::new(), result_tx: None, }; diff --git a/crates/defguard_core/src/handlers/mod.rs b/crates/defguard_core/src/handlers/mod.rs index 58999f837d..cbe8263013 100644 --- a/crates/defguard_core/src/handlers/mod.rs +++ b/crates/defguard_core/src/handlers/mod.rs @@ -6,17 +6,17 @@ use axum::{ }; use axum_client_ip::InsecureClientIp; use axum_extra::{TypedHeader, headers::UserAgent}; +use defguard_common::db::{Id, NoId}; use serde_json::{Value, json}; use sqlx::PgPool; use utoipa::ToSchema; use webauthn_rs::prelude::RegisterPublicKeyCredential; -#[cfg(feature = "wireguard")] use crate::db::Device; use crate::{ appstate::AppState, auth::SessionInfo, - db::{Id, NoId, User, UserInfo, WebHook}, + db::{User, UserInfo, WebHook}, enterprise::{db::models::acl::AclError, license::LicenseError}, error::WebError, events::ApiRequestContext, @@ -29,9 +29,7 @@ pub(crate) mod forward_auth; pub(crate) mod group; pub(crate) mod mail; pub mod network_devices; -#[cfg(feature = "openid")] pub(crate) mod openid_clients; -#[cfg(feature = "openid")] pub mod openid_flow; pub(crate) mod pagination; pub(crate) mod settings; @@ -40,9 +38,7 @@ pub(crate) mod support; pub(crate) mod updates; pub(crate) mod user; pub(crate) mod webhooks; -#[cfg(feature = "wireguard")] pub mod wireguard; -#[cfg(feature = "worker")] pub mod worker; pub(crate) mod yubikey; @@ -439,7 +435,6 @@ pub async fn user_for_admin_or_self( /// Try to fetch [`Device'] if the device.id is of the currently logged in user, or /// the logged in user is an admin. -#[cfg(feature = "wireguard")] pub async fn device_for_admin_or_self<'e, E: sqlx::PgExecutor<'e>>( executor: E, session: &SessionInfo, diff --git a/crates/defguard_core/src/handlers/network_devices.rs b/crates/defguard_core/src/handlers/network_devices.rs index 6f85448f06..b0b1863fd7 100644 --- a/crates/defguard_core/src/handlers/network_devices.rs +++ b/crates/defguard_core/src/handlers/network_devices.rs @@ -8,17 +8,18 @@ use axum::{ http::StatusCode, }; use chrono::NaiveDateTime; +use defguard_common::{csv::AsCsv, db::Id}; +use defguard_mail::templates::TemplateLocation; use ipnetwork::IpNetwork; use serde_json::json; use sqlx::PgConnection; use super::{ApiResponse, ApiResult, WebError}; use crate::{ - AsCsv, appstate::AppState, auth::{AdminRole, SessionInfo}, db::{ - Device, GatewayEvent, Id, User, WireguardNetwork, + Device, GatewayEvent, User, WireguardNetwork, models::{ device::{DeviceConfig, DeviceInfo, DeviceType, WireguardNetworkDevice}, wireguard::NetworkAddressError, @@ -28,7 +29,6 @@ use crate::{ events::{ApiEvent, ApiEventType, ApiRequestContext}, handlers::mail::send_new_device_added_email, server_config, - templates::TemplateLocation, }; #[derive(Serialize)] diff --git a/crates/defguard_core/src/handlers/openid_flow.rs b/crates/defguard_core/src/handlers/openid_flow.rs index 823066ede9..cf907c74c6 100644 --- a/crates/defguard_core/src/handlers/openid_flow.rs +++ b/crates/defguard_core/src/handlers/openid_flow.rs @@ -15,6 +15,7 @@ use axum::{ use axum_extra::extract::cookie::{Cookie, CookieJar, PrivateCookieJar, SameSite}; use base64::{Engine, prelude::BASE64_STANDARD}; use chrono::Utc; +use defguard_common::db::{Id, NoId, models::AuthCode}; use openidconnect::{ AccessToken, AdditionalClaims, Audience, AuthUrl, AuthorizationCode, EmptyAdditionalProviderMetadata, EmptyExtraTokenFields, EndUserEmail, EndUserFamilyName, @@ -43,8 +44,8 @@ use crate::{ appstate::AppState, auth::{AccessUserInfo, SessionInfo, UserClaims}, db::{ - Id, NoId, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User, - models::{auth_code::AuthCode, oauth2client::OAuth2Client}, + OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User, + models::oauth2client::OAuth2Client, }, error::WebError, handlers::{SIGN_IN_COOKIE_NAME, mail::send_new_device_ocid_login_email}, @@ -575,7 +576,7 @@ pub async fn secure_authorization( &session_info.user.email, oauth2client.name.to_string(), &appstate.mail_tx, - &session_info.session, + &session_info.session.into(), ) .await?; } diff --git a/crates/defguard_core/src/handlers/settings.rs b/crates/defguard_core/src/handlers/settings.rs index ee7485bca9..4e72b70e38 100644 --- a/crates/defguard_core/src/handlers/settings.rs +++ b/crates/defguard_core/src/handlers/settings.rs @@ -2,6 +2,10 @@ use axum::{ extract::{Json, Path, State}, http::StatusCode, }; +use defguard_common::db::models::{ + Settings, SettingsEssentials, + settings::{LdapSyncStatus, SettingsPatch, update_current_settings}, +}; use serde_json::json; use struct_patch::Patch; @@ -9,14 +13,7 @@ use super::{ApiResponse, ApiResult}; use crate::{ AppState, auth::{AdminRole, SessionInfo}, - db::{ - Settings, - models::settings::{SettingsEssentials, SettingsPatch, update_current_settings}, - }, - enterprise::{ - ldap::{LDAPConnection, sync::SyncStatus}, - license::update_cached_license, - }, + enterprise::{ldap::LDAPConnection, license::update_cached_license}, error::WebError, events::{ApiEvent, ApiEventType, ApiRequestContext}, }; @@ -147,19 +144,19 @@ pub async fn patch_settings( if let Some(ldap_enabled) = data.ldap_enabled { if !ldap_enabled { - settings.ldap_sync_status = SyncStatus::OutOfSync; + settings.ldap_sync_status = LdapSyncStatus::OutOfSync; } } if let Some(ldap_authority) = data.ldap_is_authoritative { if settings.ldap_is_authoritative != ldap_authority { - settings.ldap_sync_status = SyncStatus::OutOfSync; + settings.ldap_sync_status = LdapSyncStatus::OutOfSync; } } if let Some(ldap_sync_groups) = &data.ldap_sync_groups { if &settings.ldap_sync_groups != ldap_sync_groups { - settings.ldap_sync_status = SyncStatus::OutOfSync; + settings.ldap_sync_status = LdapSyncStatus::OutOfSync; } } diff --git a/crates/defguard_core/src/handlers/ssh_authorized_keys.rs b/crates/defguard_core/src/handlers/ssh_authorized_keys.rs index 91d925453a..8b6be6d37e 100644 --- a/crates/defguard_core/src/handlers/ssh_authorized_keys.rs +++ b/crates/defguard_core/src/handlers/ssh_authorized_keys.rs @@ -3,6 +3,10 @@ use axum::{ extract::{Path, Query, State}, http::StatusCode, }; +use defguard_common::db::{ + Id, + models::{AuthenticationKey, AuthenticationKeyType}, +}; use serde_json::json; use sqlx::{Error as SqlxError, PgExecutor, PgPool, query}; use ssh_key::PublicKey; @@ -11,10 +15,7 @@ use super::{ApiResponse, ApiResult, user_for_admin_or_self}; use crate::{ appstate::AppState, auth::SessionInfo, - db::{ - Group, Id, User, - models::authentication_key::{AuthenticationKey, AuthenticationKeyType}, - }, + db::{Group, User}, error::WebError, events::{ApiEvent, ApiEventType, ApiRequestContext}, }; diff --git a/crates/defguard_core/src/handlers/user.rs b/crates/defguard_core/src/handlers/user.rs index 785bb46d74..3b0f3316b1 100644 --- a/crates/defguard_core/src/handlers/user.rs +++ b/crates/defguard_core/src/handlers/user.rs @@ -4,6 +4,7 @@ use axum::{ extract::{Json, Path, State}, http::StatusCode, }; +use defguard_mail::{Mail, templates}; use serde_json::json; use super::{ @@ -31,9 +32,7 @@ use crate::{ }, error::WebError, events::{ApiEvent, ApiEventType, ApiRequestContext}, - is_valid_phone_number, - mail::Mail, - server_config, templates, + is_valid_phone_number, server_config, }; /// Verify the given username diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index 1508bc7cdc..466cc7cb62 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -11,6 +11,8 @@ use axum::{ http::StatusCode, }; use chrono::{DateTime, NaiveDateTime, TimeDelta, Utc}; +use defguard_common::{csv::AsCsv, db::Id}; +use defguard_mail::templates::TemplateLocation; use ipnetwork::IpNetwork; use serde_json::{Value, json}; use sqlx::PgPool; @@ -19,11 +21,10 @@ use uuid::Uuid; use super::{ApiResponse, ApiResult, WebError, device_for_admin_or_self, user_for_admin_or_self}; use crate::{ - AsCsv, appstate::AppState, auth::{AdminRole, SessionInfo}, db::{ - AddDevice, Device, GatewayEvent, Id, WireguardNetwork, + AddDevice, Device, GatewayEvent, WireguardNetwork, models::{ device::{ DeviceConfig, DeviceInfo, DeviceNetworkInfo, DeviceType, ModifyDevice, @@ -45,7 +46,6 @@ use crate::{ grpc::gateway::map::GatewayMap, handlers::mail::send_new_device_added_email, server_config, - templates::TemplateLocation, wg_config::{ImportedDevice, parse_wireguard_config}, }; diff --git a/crates/defguard_core/src/handlers/worker.rs b/crates/defguard_core/src/handlers/worker.rs index 07f1388e1a..cab88e8989 100644 --- a/crates/defguard_core/src/handlers/worker.rs +++ b/crates/defguard_core/src/handlers/worker.rs @@ -4,12 +4,13 @@ use axum::{ extract::{Extension, Json, Path, State}, http::StatusCode, }; +use defguard_common::auth::claims::{Claims, ClaimsType}; use serde_json::json; use super::{ApiResponse, ApiResult}; use crate::{ appstate::AppState, - auth::{AdminRole, Claims, ClaimsType, SessionInfo}, + auth::{AdminRole, SessionInfo}, db::User, error::WebError, grpc::WorkerState, diff --git a/crates/defguard_core/src/headers.rs b/crates/defguard_core/src/headers.rs index 95c090a9b8..cb2fbbbd4c 100644 --- a/crates/defguard_core/src/headers.rs +++ b/crates/defguard_core/src/headers.rs @@ -1,16 +1,16 @@ use std::{borrow::Borrow, sync::LazyLock}; use axum::http::{HeaderName, HeaderValue}; +use defguard_common::db::{Id, models::DeviceLoginEvent}; +use defguard_mail::{ + Mail, + templates::{SessionContext, TemplateError}, +}; use sqlx::PgPool; use tokio::sync::mpsc::UnboundedSender; use uaparser::{Client, Parser, UserAgentParser}; -use crate::{ - db::{Id, Session, User, models::device_login::DeviceLoginEvent}, - handlers::mail::send_new_device_login_email, - mail::Mail, - templates::TemplateError, -}; +use crate::{db::User, handlers::mail::send_new_device_login_email}; pub(crate) const CONTENT_SECURITY_POLICY_HEADER_NAME: HeaderName = HeaderName::from_static("content-security-policy"); @@ -91,7 +91,7 @@ fn get_user_agent_device_login_data( pub(crate) async fn check_new_device_login( pool: &PgPool, mail_tx: &UnboundedSender, - session: &Session, + session: &SessionContext, user: &User, ip_address: String, event_type: String, diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index f6abd4ca90..5e200f2725 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -3,7 +3,7 @@ #![allow(clippy::result_large_err)] use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::{Arc, LazyLock, Mutex, OnceLock, RwLock}, + sync::{Arc, LazyLock, Mutex, RwLock}, }; use crate::version::IncompatibleComponents; @@ -15,6 +15,13 @@ use axum::{ serve, }; use db::models::{device::DeviceType, wireguard::LocationMfaMode}; +use defguard_common::{ + VERSION, + auth::claims::{Claims, ClaimsType}, + config::{DefGuardConfig, InitVpnLocationArgs, server_config}, + db::init_db, +}; +use defguard_mail::Mail; use defguard_version::server::DefguardVersionLayer; use defguard_web_ui::{index, svg, web_asset}; use enterprise::{ @@ -82,18 +89,15 @@ use utoipa::{ }; use utoipa_swagger_ui::SwaggerUi; -#[cfg(feature = "wireguard")] use self::handlers::wireguard::{ add_device, add_user_devices, create_network, create_network_token, delete_device, delete_network, devices_stats, download_config, gateway_status, get_device, import_network, list_devices, list_networks, list_user_devices, modify_device, modify_network, network_details, network_stats, remove_gateway, }; -#[cfg(feature = "worker")] use self::handlers::worker::{ create_job, create_worker_token, job_status, list_workers, remove_worker, }; -#[cfg(feature = "openid")] use self::handlers::{ openid_clients::{ add_openid_client, change_openid_client, change_openid_client_state, delete_openid_client, @@ -105,10 +109,8 @@ use self::handlers::{ }; use self::{ appstate::AppState, - auth::{Claims, ClaimsType}, - config::{DefGuardConfig, InitVpnLocationArgs}, db::{ - AppEvent, Device, GatewayEvent, User, WireguardNetwork, init_db, + AppEvent, Device, GatewayEvent, User, WireguardNetwork, models::wireguard::{DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL}, }, handlers::{ @@ -141,9 +143,7 @@ use self::{ add_webhook, change_enabled, change_webhook, delete_webhook, get_webhook, list_webhooks, }, }, - mail::Mail, }; -#[cfg(any(feature = "openid", feature = "worker"))] use self::{ auth::failed_login::FailedLoginMap, db::models::oauth2client::OAuth2Client, @@ -153,21 +153,14 @@ use self::{ pub mod appstate; pub mod auth; -pub mod config; pub mod db; pub mod enterprise; mod error; pub mod events; -pub mod globals; pub mod grpc; pub mod handlers; pub mod headers; -pub mod hex; -pub mod mail; -pub(crate) mod random; -pub mod secret; pub mod support; -pub mod templates; pub mod updates; pub mod utility_thread; pub mod version; @@ -181,19 +174,6 @@ extern crate tracing; #[macro_use] extern crate serde; -// helper for easier migration handling with a custom `migration` folder location -// reference: https://docs.rs/sqlx/latest/sqlx/attr.test.html#automatic-migrations-requires-migrate-feature -pub static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("../../migrations"); - -pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "+", env!("VERGEN_GIT_SHA")); -pub static SERVER_CONFIG: OnceLock = OnceLock::new(); - -pub(crate) fn server_config() -> &'static DefGuardConfig { - SERVER_CONFIG - .get() - .expect("Server configuration not set yet") -} - static PHONE_NUMBER_REGEX: LazyLock = LazyLock::new(|| { Regex::new(r"^(\+?\d{1,3}\s?)?(\(\d{1,3}\)|\d{1,3})[-\s]?\d{1,4}[-\s]?\d{1,4}?$") .expect("Failed to parse phone number regex") @@ -543,7 +523,6 @@ pub fn build_webapp( ), ); - #[cfg(feature = "openid")] let webapp = webapp .nest( "/api/v1/oauth", @@ -587,7 +566,6 @@ pub fn build_webapp( .route("/alias/apply", put(apply_acl_aliases)), ); - #[cfg(feature = "wireguard")] let webapp = webapp.nest( "/api/v1", Router::new() @@ -661,7 +639,6 @@ pub fn build_webapp( .layer(Extension(gateway_state)), ); - #[cfg(feature = "worker")] let webapp = webapp.nest( "/api/v1/worker", Router::new() @@ -735,11 +712,12 @@ pub async fn run_web_server( incompatible_components, ); info!("Started web services"); + let server_config = server_config(); let addr = SocketAddr::new( - server_config() + server_config .http_bind_address .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), - server_config().http_port, + server_config.http_port, ); let listener = TcpListener::bind(&addr).await?; serve( @@ -836,7 +814,6 @@ pub async fn init_dev_env(config: &DefGuardConfig) { .expect("Could not assign IP to device"); } - #[cfg(feature = "openid")] for app_id in 1..=3 { OAuth2Client::new( vec![format!("https://app-{app_id}.com")], @@ -953,24 +930,6 @@ pub async fn init_vpn_location( Ok(token) } -pub trait AsCsv { - fn as_csv(&self) -> String; -} - -impl AsCsv for I -where - I: ?Sized + std::iter::IntoIterator, - for<'a> &'a I: IntoIterator, - T: ToString, -{ - fn as_csv(&self) -> String { - self.into_iter() - .map(ToString::to_string) - .collect::>() - .join(",") - } -} - pub(crate) fn is_valid_phone_number(number: &str) -> bool { PHONE_NUMBER_REGEX.is_match(number) } diff --git a/crates/defguard_core/src/support.rs b/crates/defguard_core/src/support.rs index fd2ccfa037..753daa5c5e 100644 --- a/crates/defguard_core/src/support.rs +++ b/crates/defguard_core/src/support.rs @@ -5,10 +5,13 @@ use serde_json::{Value, json, value::to_value}; use sqlx::PgPool; use crate::{ - VERSION, - db::{Id, Settings, User, WireguardNetwork, models::device::WireguardNetworkDevice}, + db::{User, WireguardNetwork, models::device::WireguardNetworkDevice}, server_config, }; +use defguard_common::{ + VERSION, + db::{Id, models::Settings}, +}; /// Unwraps the result returning a JSON representation of value or error fn unwrap_json(result: Result) -> Value { diff --git a/crates/defguard_core/src/updates.rs b/crates/defguard_core/src/updates.rs index 2cbfb96d71..e286c8ea8c 100644 --- a/crates/defguard_core/src/updates.rs +++ b/crates/defguard_core/src/updates.rs @@ -1,13 +1,11 @@ use std::{env, time::Duration}; use chrono::NaiveDate; +use defguard_common::{CARGO_VERSION, global_value}; use semver::Version; -use crate::global_value; - const PRODUCT_NAME: &str = "Defguard"; const UPDATES_URL: &str = "https://pkgs.defguard.net/api/update/check"; -const VERSION: &str = env!("CARGO_PKG_VERSION"); const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); #[derive(Deserialize, Debug, Serialize)] @@ -26,7 +24,7 @@ global_value!(NEW_UPDATE, Option, None, set_update, get_update); async fn fetch_update() -> Result { let body = serde_json::json!({ "product": PRODUCT_NAME, - "client_version": VERSION, + "client_version": CARGO_VERSION, "operating_system": env::consts::OS, }); let response = reqwest::Client::new() @@ -41,7 +39,7 @@ async fn fetch_update() -> Result { pub(crate) async fn do_new_version_check() -> Result<(), anyhow::Error> { debug!("Checking for new version of Defguard."); let update = fetch_update().await?; - let current_version = Version::parse(VERSION)?; + let current_version = Version::parse(CARGO_VERSION)?; let new_version = Version::parse(&update.version)?; if new_version > current_version { if update.critical { diff --git a/crates/defguard_core/src/utility_thread.rs b/crates/defguard_core/src/utility_thread.rs index 0e608d1275..6533ff71c5 100644 --- a/crates/defguard_core/src/utility_thread.rs +++ b/crates/defguard_core/src/utility_thread.rs @@ -1,5 +1,6 @@ use std::{collections::HashSet, time::Duration}; +use defguard_common::db::Id; use sqlx::{PgPool, query_as}; use tokio::{ sync::broadcast::Sender, @@ -8,7 +9,7 @@ use tokio::{ use tracing::Instrument; use crate::{ - db::{GatewayEvent, Id, WireguardNetwork}, + db::{GatewayEvent, WireguardNetwork}, enterprise::{ db::models::acl::{AclRule, RuleState}, directory_sync::{do_directory_sync, get_directory_sync_interval}, diff --git a/crates/defguard_core/src/wg_config.rs b/crates/defguard_core/src/wg_config.rs index 4dcdc47ff4..2a42ea68dd 100644 --- a/crates/defguard_core/src/wg_config.rs +++ b/crates/defguard_core/src/wg_config.rs @@ -171,7 +171,7 @@ pub(crate) fn parse_wireguard_config( #[cfg(test)] mod test { use super::*; - use crate::db::NoId; + use defguard_common::db::NoId; #[test] fn test_parse_config() { diff --git a/crates/defguard_core/src/wireguard_peer_disconnect.rs b/crates/defguard_core/src/wireguard_peer_disconnect.rs index 06be7734d9..537db19a30 100644 --- a/crates/defguard_core/src/wireguard_peer_disconnect.rs +++ b/crates/defguard_core/src/wireguard_peer_disconnect.rs @@ -11,6 +11,7 @@ use std::{ }; use chrono::NaiveDateTime; +use defguard_common::db::{Id, models::ModelError}; use sqlx::{Error as SqlxError, PgPool, query_as}; use thiserror::Error; use tokio::{ @@ -23,10 +24,9 @@ use tokio::{ use crate::{ db::{ - Device, GatewayEvent, Id, WireguardNetwork, + Device, GatewayEvent, WireguardNetwork, models::{ device::{DeviceInfo, DeviceNetworkInfo, DeviceType, WireguardNetworkDevice}, - error::ModelError, wireguard::{LocationMfaMode, WireguardNetworkError}, }, }, diff --git a/crates/defguard_core/tests/integration/api/acl.rs b/crates/defguard_core/tests/integration/api/acl.rs index 9bf2ffad6f..237b573fdd 100644 --- a/crates/defguard_core/tests/integration/api/acl.rs +++ b/crates/defguard_core/tests/integration/api/acl.rs @@ -1,10 +1,11 @@ -use defguard_core::{ +use defguard_common::{ config::DefGuardConfig, + db::{Id, models::settings::initialize_current_settings}, +}; +use defguard_core::{ db::{ - Device, Group, Id, User, WireguardNetwork, - models::{ - device::DeviceType, settings::initialize_current_settings, wireguard::LocationMfaMode, - }, + Device, Group, User, WireguardNetwork, + models::{device::DeviceType, wireguard::LocationMfaMode}, }, enterprise::{ db::models::acl::{AclAlias, AclRule, AliasKind, AliasState, RuleState}, diff --git a/crates/defguard_core/tests/integration/api/auth.rs b/crates/defguard_core/tests/integration/api/auth.rs index e47b7e2285..7cacd2630d 100644 --- a/crates/defguard_core/tests/integration/api/auth.rs +++ b/crates/defguard_core/tests/integration/api/auth.rs @@ -2,11 +2,10 @@ use std::time::SystemTime; use chrono::DateTime; use claims::{assert_err, assert_ok}; +use defguard_common::db::models::{MFAMethod, Settings, settings::update_current_settings}; use defguard_core::{ auth::{TOTP_CODE_DIGITS, TOTP_CODE_VALIDITY_PERIOD}, - db::{ - MFAInfo, MFAMethod, Settings, User, UserDetails, models::settings::update_current_settings, - }, + db::{MFAInfo, User, UserDetails}, handlers::{Auth, AuthCode, AuthResponse, AuthTotp}, }; use reqwest::{StatusCode, header::USER_AGENT}; diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index ea5b3393ab..884299aad7 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -2,22 +2,22 @@ pub(crate) mod client; use std::sync::{Arc, Mutex}; -pub use defguard_core::db::setup_pool; -use defguard_core::{ +pub use defguard_common::db::setup_pool; +use defguard_common::{ VERSION, + config::DefGuardConfig, + db::{Id, NoId, models::settings::initialize_current_settings}, +}; +use defguard_core::{ auth::failed_login::FailedLoginMap, build_webapp, - config::DefGuardConfig, - db::{ - AppEvent, GatewayEvent, Id, NoId, User, UserDetails, - models::settings::initialize_current_settings, - }, + db::{AppEvent, GatewayEvent, User, UserDetails}, enterprise::license::{License, set_cached_license}, events::ApiEvent, grpc::{WorkerState, gateway::map::GatewayMap}, handlers::Auth, - mail::Mail, }; +use defguard_mail::Mail; use reqwest::{StatusCode, header::HeaderName}; use semver::Version; use serde::de::DeserializeOwned; diff --git a/crates/defguard_core/tests/integration/api/forward_auth.rs b/crates/defguard_core/tests/integration/api/forward_auth.rs index 83b97c1ba9..7525383ca0 100644 --- a/crates/defguard_core/tests/integration/api/forward_auth.rs +++ b/crates/defguard_core/tests/integration/api/forward_auth.rs @@ -1,4 +1,5 @@ -use defguard_core::{SERVER_CONFIG, handlers::Auth}; +use defguard_common::config::SERVER_CONFIG; +use defguard_core::handlers::Auth; use reqwest::StatusCode; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; diff --git a/crates/defguard_core/tests/integration/api/oauth.rs b/crates/defguard_core/tests/integration/api/oauth.rs index 06d86dbe3b..4e7329734f 100644 --- a/crates/defguard_core/tests/integration/api/oauth.rs +++ b/crates/defguard_core/tests/integration/api/oauth.rs @@ -1,8 +1,9 @@ use std::borrow::Cow; +use defguard_common::db::Id; use defguard_core::{ db::{ - Id, OAuth2AuthorizedApp, + OAuth2AuthorizedApp, models::{ NewOpenIDClient, oauth2client::{OAuth2Client, OAuth2ClientSafe}, diff --git a/crates/defguard_core/tests/integration/api/openid.rs b/crates/defguard_core/tests/integration/api/openid.rs index 1a3882d4c2..d4052b6e58 100644 --- a/crates/defguard_core/tests/integration/api/openid.rs +++ b/crates/defguard_core/tests/integration/api/openid.rs @@ -2,9 +2,10 @@ use std::str::FromStr; use axum::http::header::ToStrError; use claims::assert_err; +use defguard_common::db::Id; use defguard_core::{ db::{ - Id, User, + User, models::{NewOpenIDClient, oauth2client::OAuth2Client}, }, handlers::Auth, diff --git a/crates/defguard_core/tests/integration/api/openid_login.rs b/crates/defguard_core/tests/integration/api/openid_login.rs index 461f8fa840..b9912eadd2 100644 --- a/crates/defguard_core/tests/integration/api/openid_login.rs +++ b/crates/defguard_core/tests/integration/api/openid_login.rs @@ -1,6 +1,6 @@ use chrono::{Duration, Utc}; +use defguard_common::db::models::settings::OpenidUsernameHandling; use defguard_core::{ - db::models::settings::OpenidUsernameHandling, enterprise::{ db::models::openid_provider::{DirectorySyncTarget, DirectorySyncUserBehavior}, handlers::openid_providers::AddProviderData, diff --git a/crates/defguard_core/tests/integration/api/settings.rs b/crates/defguard_core/tests/integration/api/settings.rs index a832b60eb8..d0859e60d3 100644 --- a/crates/defguard_core/tests/integration/api/settings.rs +++ b/crates/defguard_core/tests/integration/api/settings.rs @@ -1,7 +1,5 @@ -use defguard_core::{ - db::models::settings::{Settings, SettingsPatch}, - handlers::Auth, -}; +use defguard_common::db::models::{Settings, settings::SettingsPatch}; +use defguard_core::handlers::Auth; use reqwest::StatusCode; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; diff --git a/crates/defguard_core/tests/integration/api/snat.rs b/crates/defguard_core/tests/integration/api/snat.rs index a396041d4c..01655d9b56 100644 --- a/crates/defguard_core/tests/integration/api/snat.rs +++ b/crates/defguard_core/tests/integration/api/snat.rs @@ -1,7 +1,7 @@ use std::net::IpAddr; +use defguard_common::db::Id; use defguard_core::{ - db::Id, enterprise::{ db::models::snat::UserSnatBinding, license::{get_cached_license, set_cached_license}, diff --git a/crates/defguard_core/tests/integration/api/user.rs b/crates/defguard_core/tests/integration/api/user.rs index 197eea3afc..d7605a5348 100644 --- a/crates/defguard_core/tests/integration/api/user.rs +++ b/crates/defguard_core/tests/integration/api/user.rs @@ -1,6 +1,7 @@ +use defguard_common::db::Id; use defguard_core::{ db::{ - AddDevice, Id, UserInfo, + AddDevice, UserInfo, models::{NewOpenIDClient, oauth2client::OAuth2Client}, }, handlers::{AddUserData, Auth, PasswordChange, PasswordChangeSelf, Username}, diff --git a/crates/defguard_core/tests/integration/api/webhook.rs b/crates/defguard_core/tests/integration/api/webhook.rs index 326592f3e0..4d420395ac 100644 --- a/crates/defguard_core/tests/integration/api/webhook.rs +++ b/crates/defguard_core/tests/integration/api/webhook.rs @@ -1,7 +1,5 @@ -use defguard_core::{ - db::{Id, NoId, WebHook}, - handlers::Auth, -}; +use defguard_common::db::{Id, NoId}; +use defguard_core::{db::WebHook, handlers::Auth}; use reqwest::StatusCode; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; diff --git a/crates/defguard_core/tests/integration/api/wireguard.rs b/crates/defguard_core/tests/integration/api/wireguard.rs index becf937611..79625944c0 100644 --- a/crates/defguard_core/tests/integration/api/wireguard.rs +++ b/crates/defguard_core/tests/integration/api/wireguard.rs @@ -1,11 +1,11 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use defguard_common::db::{Id, models::settings::OpenidUsernameHandling}; use defguard_core::{ db::{ - Device, GatewayEvent, Id, WireguardNetwork, + Device, GatewayEvent, WireguardNetwork, models::{ device::WireguardNetworkDevice, - settings::OpenidUsernameHandling, wireguard::{ DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL, LocationMfaMode, }, diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs index 51ecb04207..3a2f9e15a1 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs @@ -1,9 +1,9 @@ use std::net::IpAddr; use claims::assert_err; +use defguard_common::{csv::AsCsv, db::Id}; use defguard_core::{ - AsCsv, - db::{Device, GatewayEvent, Group, Id, User, WireguardNetwork, models::device::DeviceType}, + db::{Device, GatewayEvent, Group, User, WireguardNetwork, models::device::DeviceType}, handlers::{Auth, wireguard::ImportedNetworkData}, }; use matches::assert_matches; diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs index ae99878995..f16ed592bd 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs @@ -1,7 +1,8 @@ use std::{net::IpAddr, str::FromStr}; +use defguard_common::db::Id; use defguard_core::{ - db::{Device, GatewayEvent, Id, WireguardNetwork}, + db::{Device, GatewayEvent, WireguardNetwork}, handlers::{Auth, network_devices::AddNetworkDevice}, }; use ipnetwork::IpNetwork; diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs b/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs index 8a02b5c994..5a03e8169b 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs @@ -1,15 +1,13 @@ use chrono::{Datelike, Duration, NaiveDate, SubsecRound, Timelike, Utc}; +use defguard_common::db::{Id, NoId}; use defguard_core::{ - db::{ - Id, NoId, - models::{ - device::Device, - wireguard::{ - WireguardDeviceStatsRow, WireguardDeviceTransferRow, WireguardNetworkStats, - WireguardUserStatsRow, - }, - wireguard_peer_stats::WireguardPeerStats, + db::models::{ + device::Device, + wireguard::{ + WireguardDeviceStatsRow, WireguardDeviceTransferRow, WireguardNetworkStats, + WireguardUserStatsRow, }, + wireguard_peer_stats::WireguardPeerStats, }, handlers::Auth, }; diff --git a/crates/defguard_core/tests/integration/common.rs b/crates/defguard_core/tests/integration/common.rs index d89859fc9c..82d0950925 100644 --- a/crates/defguard_core/tests/integration/common.rs +++ b/crates/defguard_core/tests/integration/common.rs @@ -1,6 +1,7 @@ use std::str::FromStr; -use defguard_core::{SERVER_CONFIG, config::DefGuardConfig, db::User}; +use defguard_common::config::{DefGuardConfig, SERVER_CONFIG}; +use defguard_core::db::User; use reqwest::Url; use secrecy::ExposeSecret; use sqlx::PgPool; diff --git a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs index 5622c399fb..6440f5e8f9 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs @@ -1,11 +1,9 @@ use std::time::Duration; -use defguard_core::grpc::{ - AUTHORIZATION_HEADER, HOSTNAME_HEADER, - proto::gateway::{ - Configuration, ConfigurationRequest, StatsUpdate, Update, - gateway_service_client::GatewayServiceClient, - }, +use defguard_core::grpc::{AUTHORIZATION_HEADER, HOSTNAME_HEADER}; +use defguard_proto::gateway::{ + Configuration, ConfigurationRequest, StatsUpdate, Update, + gateway_service_client::GatewayServiceClient, }; use defguard_version::{Version, client::ClientVersionInterceptor}; use tokio::{ diff --git a/crates/defguard_core/tests/integration/grpc/common/mod.rs b/crates/defguard_core/tests/integration/grpc/common/mod.rs index ae5bb98997..96609dbfa7 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mod.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mod.rs @@ -1,17 +1,18 @@ use std::sync::{Arc, Mutex}; use axum::http::Uri; +use defguard_common::db::models::settings::initialize_current_settings; use defguard_core::{ auth::failed_login::FailedLoginMap, - db::{AppEvent, GatewayEvent, models::settings::initialize_current_settings}, + db::{AppEvent, GatewayEvent}, enterprise::license::{License, set_cached_license}, events::GrpcEvent, grpc::{ WorkerState, build_grpc_service_router, gateway::{client_state::ClientMap, map::GatewayMap}, }, - mail::Mail, }; +use defguard_mail::Mail; use hyper_util::rt::TokioIo; use sqlx::PgPool; use tokio::{ diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index d4aba18ee5..8be4c88c31 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -5,25 +5,22 @@ use std::{ use chrono::{Days, Utc}; use claims::{assert_err_eq, assert_matches}; +use defguard_common::db::{Id, NoId, setup_pool}; use defguard_core::{ db::{ - Device, Id, NoId, User, WireguardNetwork, + Device, User, WireguardNetwork, models::{ device::DeviceType, wireguard::LocationMfaMode, wireguard_peer_stats::WireguardPeerStats, }, - setup_pool, }, enterprise::{license::set_cached_license, limits::update_counts}, events::GrpcEvent, - grpc::{ - MIN_GATEWAY_VERSION, - gateway::{Configuration, Update, update}, - proto::{ - enterprise::firewall::FirewallPolicy, - gateway::{PeerStats, StatsUpdate, stats_update::Payload}, - }, - }, + grpc::MIN_GATEWAY_VERSION, +}; +use defguard_proto::{ + enterprise::firewall::FirewallPolicy, + gateway::{Configuration, PeerStats, StatsUpdate, Update, stats_update::Payload, update}, }; use semver::Version; use sqlx::{ diff --git a/crates/defguard_event_logger/Cargo.toml b/crates/defguard_event_logger/Cargo.toml index 871ba70866..4f26e19fb5 100644 --- a/crates/defguard_event_logger/Cargo.toml +++ b/crates/defguard_event_logger/Cargo.toml @@ -9,13 +9,14 @@ rust-version.workspace = true [dependencies] # internal crates -defguard_core = { workspace = true } +defguard_common.workspace = true +defguard_core.workspace = true # external dependencies -bytes = { workspace = true } -chrono = { workspace = true } -serde_json = { workspace = true } -sqlx = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } +bytes.workspace = true +chrono.workspace = true +serde_json.workspace = true +sqlx.workspace = true +thiserror.workspace = true +tokio.workspace = true +tracing.workspace = true diff --git a/crates/defguard_event_logger/src/lib.rs b/crates/defguard_event_logger/src/lib.rs index 3d04dce4eb..29c6123957 100644 --- a/crates/defguard_event_logger/src/lib.rs +++ b/crates/defguard_event_logger/src/lib.rs @@ -1,24 +1,22 @@ use bytes::Bytes; -use defguard_core::db::{ - NoId, - models::activity_log::{ - ActivityLogEvent, ActivityLogModule, EventType, - metadata::{ - ActivityLogStreamMetadata, ActivityLogStreamModifiedMetadata, ApiTokenMetadata, - ApiTokenRenamedMetadata, AuthenticationKeyMetadata, AuthenticationKeyRenamedMetadata, - ClientConfigurationTokenMetadata, DeviceMetadata, DeviceModifiedMetadata, - EnrollmentDeviceAddedMetadata, EnrollmentTokenMetadata, GroupAssignedMetadata, - GroupMembersModifiedMetadata, GroupMetadata, GroupModifiedMetadata, - GroupsBulkAssignedMetadata, LoginFailedMetadata, MfaLoginFailedMetadata, - MfaLoginMetadata, MfaSecurityKeyMetadata, NetworkDeviceMetadata, - NetworkDeviceModifiedMetadata, OpenIdAppMetadata, OpenIdAppModifiedMetadata, - OpenIdAppStateChangedMetadata, OpenIdProviderMetadata, PasswordChangedByAdminMetadata, - PasswordResetMetadata, SettingsUpdateMetadata, UserGroupsModifiedMetadata, - UserMetadata, UserMfaDisabledMetadata, UserModifiedMetadata, UserSnatBindingMetadata, - UserSnatBindingModifiedMetadata, VpnClientMetadata, VpnClientMfaFailedMetadata, - VpnClientMfaMetadata, VpnLocationMetadata, VpnLocationModifiedMetadata, - WebHookMetadata, WebHookModifiedMetadata, WebHookStateChangedMetadata, - }, +use defguard_common::db::NoId; +use defguard_core::db::models::activity_log::{ + ActivityLogEvent, ActivityLogModule, EventType, + metadata::{ + ActivityLogStreamMetadata, ActivityLogStreamModifiedMetadata, ApiTokenMetadata, + ApiTokenRenamedMetadata, AuthenticationKeyMetadata, AuthenticationKeyRenamedMetadata, + ClientConfigurationTokenMetadata, DeviceMetadata, DeviceModifiedMetadata, + EnrollmentDeviceAddedMetadata, EnrollmentTokenMetadata, GroupAssignedMetadata, + GroupMembersModifiedMetadata, GroupMetadata, GroupModifiedMetadata, + GroupsBulkAssignedMetadata, LoginFailedMetadata, MfaLoginFailedMetadata, MfaLoginMetadata, + MfaSecurityKeyMetadata, NetworkDeviceMetadata, NetworkDeviceModifiedMetadata, + OpenIdAppMetadata, OpenIdAppModifiedMetadata, OpenIdAppStateChangedMetadata, + OpenIdProviderMetadata, PasswordChangedByAdminMetadata, PasswordResetMetadata, + SettingsUpdateMetadata, UserGroupsModifiedMetadata, UserMetadata, UserMfaDisabledMetadata, + UserModifiedMetadata, UserSnatBindingMetadata, UserSnatBindingModifiedMetadata, + VpnClientMetadata, VpnClientMfaFailedMetadata, VpnClientMfaMetadata, VpnLocationMetadata, + VpnLocationModifiedMetadata, WebHookMetadata, WebHookModifiedMetadata, + WebHookStateChangedMetadata, }, }; use description::{ diff --git a/crates/defguard_event_logger/src/message.rs b/crates/defguard_event_logger/src/message.rs index 26b4145689..0dc0eebcda 100644 --- a/crates/defguard_event_logger/src/message.rs +++ b/crates/defguard_event_logger/src/message.rs @@ -1,10 +1,14 @@ use std::net::IpAddr; use chrono::NaiveDateTime; +use defguard_common::db::{ + Id, + models::{AuthenticationKey, MFAMethod, Settings}, +}; use defguard_core::{ db::{ - Device, Group, Id, MFAMethod, Settings, User, WebAuthn, WebHook, WireguardNetwork, - models::{authentication_key::AuthenticationKey, oauth2client::OAuth2Client}, + Device, Group, User, WebAuthn, WebHook, WireguardNetwork, + models::oauth2client::OAuth2Client, }, enterprise::db::models::{ activity_log_stream::ActivityLogStream, api_tokens::ApiToken, diff --git a/crates/defguard_event_router/Cargo.toml b/crates/defguard_event_router/Cargo.toml index 9641e5bb95..c4c9b3c150 100644 --- a/crates/defguard_event_router/Cargo.toml +++ b/crates/defguard_event_router/Cargo.toml @@ -11,6 +11,7 @@ rust-version.workspace = true # internal crates defguard_core = { workspace = true } defguard_event_logger = { workspace = true } +defguard_mail = { workspace = true } # external dependencies thiserror = { workspace = true } diff --git a/crates/defguard_event_router/src/lib.rs b/crates/defguard_event_router/src/lib.rs index 335ca17838..b9633f9f07 100644 --- a/crates/defguard_event_router/src/lib.rs +++ b/crates/defguard_event_router/src/lib.rs @@ -22,9 +22,9 @@ use std::sync::Arc; use defguard_core::{ db::GatewayEvent, events::{ApiEvent, BidiStreamEvent, GrpcEvent, InternalEvent}, - mail::Mail, }; use defguard_event_logger::message::{EventContext, EventLoggerMessage, LoggerEvent}; +use defguard_mail::Mail; use error::EventRouterError; use events::Event; use tokio::sync::{ diff --git a/crates/defguard_mail/Cargo.toml b/crates/defguard_mail/Cargo.toml new file mode 100644 index 0000000000..854671cb2e --- /dev/null +++ b/crates/defguard_mail/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "defguard_mail" +version = "0.0.0" +edition.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +defguard_common.workspace = true + +chrono.workspace = true +lettre.workspace = true +pulldown-cmark.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +sqlx.workspace = true +tera.workspace = true +thiserror.workspace = true +tokio.workspace = true +tracing.workspace = true + +[dev-dependencies] +claims.workspace = true diff --git a/crates/defguard_core/src/mail.rs b/crates/defguard_mail/src/lib.rs similarity index 98% rename from crates/defguard_core/src/mail.rs rename to crates/defguard_mail/src/lib.rs index 13ed6c904c..081127548f 100644 --- a/crates/defguard_core/src/mail.rs +++ b/crates/defguard_mail/src/lib.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use defguard_common::db::models::{Settings, settings::SmtpEncryption}; use lettre::{ Address, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, address::AddressError, @@ -8,8 +9,9 @@ use lettre::{ }; use thiserror::Error; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tracing::{debug, error, info, instrument, warn}; -use crate::db::{Settings, models::settings::SmtpEncryption}; +pub mod templates; const SMTP_TIMEOUT_SECONDS: u64 = 15; diff --git a/crates/defguard_core/src/templates.rs b/crates/defguard_mail/src/templates.rs similarity index 93% rename from crates/defguard_core/src/templates.rs rename to crates/defguard_mail/src/templates.rs index 0c945ffe47..5fe6b6d6ca 100644 --- a/crates/defguard_core/src/templates.rs +++ b/crates/defguard_mail/src/templates.rs @@ -1,16 +1,13 @@ use std::collections::HashMap; use chrono::{Datelike, NaiveDateTime, Utc}; +use defguard_common::{VERSION, config::server_config, db::models::user::MFAMethod}; use reqwest::Url; +use serde::Serialize; use serde_json::Value; use tera::{Context, Function, Tera}; use thiserror::Error; - -use crate::{ - VERSION, - db::{Id, MFAMethod, Session, User}, - server_config, -}; +use tracing::debug; static MAIL_BASE: &str = include_str!("../templates/base.tera"); static MAIL_MACROS: &str = include_str!("../templates/macros.tera"); @@ -56,7 +53,7 @@ impl Function for NoOp { /// Return a safe instance of Tera, as Tera is vulnerable to `get_env()` function exploit. /// See: https://github.com/Keats/tera/issues/677 -pub(crate) fn safe_tera() -> Tera { +pub fn safe_tera() -> Tera { let mut tera = Tera::default(); let noop = NoOp("get_env"); tera.register_function(noop.0, noop); @@ -64,9 +61,19 @@ pub(crate) fn safe_tera() -> Tera { tera } +pub struct SessionContext { + pub ip_address: String, + pub device_info: Option, +} + +pub struct UserContext { + pub last_name: String, + pub first_name: String, +} + fn get_base_tera( external_context: Option, - session: Option<&Session>, + session: Option<&SessionContext>, ip_address: Option<&str>, device_info: Option<&str>, ) -> Result<(Tera, Context), TemplateError> { @@ -99,7 +106,7 @@ fn get_base_tera( } // sends test message when requested during SMTP configuration process -pub fn test_mail(session: Option<&Session>) -> Result { +pub fn test_mail(session: Option<&SessionContext>) -> Result { let (mut tera, context) = get_base_tera(None, session, None, None)?; tera.add_raw_template("mail_test", MAIL_TEST)?; Ok(tera.render("mail_test", &context)?) @@ -169,9 +176,9 @@ pub fn enrollment_welcome_mail( } // notification sent to admin after user completes enrollment -pub fn enrollment_admin_notification( - user: &User, - admin: &User, +pub fn enrollment_admin_notification( + user: &UserContext, + admin: &UserContext, ip_address: &str, device_info: Option<&str>, ) -> Result { @@ -221,7 +228,7 @@ pub fn new_device_added_mail( } pub fn mfa_configured_mail( - session: Option<&Session>, + session: Option<&SessionContext>, method: &MFAMethod, ) -> Result { let (mut tera, mut context) = get_base_tera(None, session, None, None)?; @@ -233,7 +240,7 @@ pub fn mfa_configured_mail( } pub fn new_device_login_mail( - session: &Session, + session: &SessionContext, created: NaiveDateTime, ) -> Result { let (mut tera, mut context) = get_base_tera(None, Some(session), None, None)?; @@ -248,7 +255,7 @@ pub fn new_device_login_mail( } pub fn new_device_ocid_login_mail( - session: &Session, + session: &SessionContext, oauth2client_name: &str, ) -> Result { let (mut tera, mut context) = get_base_tera(None, Some(session), None, None)?; @@ -290,9 +297,9 @@ pub fn gateway_reconnected_mail( } pub fn email_mfa_activation_mail( - user: &User, + user: &UserContext, code: &str, - session: Option<&Session>, + session: Option<&SessionContext>, ) -> Result { let (mut tera, mut context) = get_base_tera(None, session, None, None)?; let timeout = server_config().mfa_code_timeout; @@ -306,9 +313,9 @@ pub fn email_mfa_activation_mail( } pub fn email_mfa_code_mail( - user: &User, + user: &UserContext, code: &str, - session: Option<&Session>, + session: Option<&SessionContext>, ) -> Result { let (mut tera, mut context) = get_base_tera(None, session, None, None)?; let timeout = server_config().mfa_code_timeout; @@ -359,9 +366,9 @@ pub fn email_password_reset_success_mail( #[cfg(test)] mod test { use claims::assert_ok; + use defguard_common::config::{DefGuardConfig, SERVER_CONFIG}; use super::*; - use crate::{SERVER_CONFIG, config::DefGuardConfig}; fn get_welcome_context() -> Context { let mut context = Context::new(); @@ -401,7 +408,7 @@ mod test { #[test] fn test_enrollment_start_mail() { - let _ = SERVER_CONFIG.set(DefGuardConfig::default()); + let _ = SERVER_CONFIG.set(DefGuardConfig::new_test_config()); assert_ok!(enrollment_start_mail( Context::new(), Url::parse("http://localhost:8080").unwrap(), @@ -457,14 +464,11 @@ mod test { #[test] fn test_enrollment_admin_notification() { - let test_user: User = User::new( - "test", - Some("1234"), - "test_last", - "test_first", - "test@example.com", - Some("99999".into()), - ); + let test_user = UserContext { + last_name: "test_last".into(), + first_name: "test_first".into(), + }; + assert_ok!(enrollment_admin_notification( &test_user, &test_user, diff --git a/crates/defguard_core/templates/base.tera b/crates/defguard_mail/templates/base.tera similarity index 100% rename from crates/defguard_core/templates/base.tera rename to crates/defguard_mail/templates/base.tera diff --git a/crates/defguard_core/templates/macros.tera b/crates/defguard_mail/templates/macros.tera similarity index 100% rename from crates/defguard_core/templates/macros.tera rename to crates/defguard_mail/templates/macros.tera diff --git a/crates/defguard_core/templates/mail_desktop_start.tera b/crates/defguard_mail/templates/mail_desktop_start.tera similarity index 100% rename from crates/defguard_core/templates/mail_desktop_start.tera rename to crates/defguard_mail/templates/mail_desktop_start.tera diff --git a/crates/defguard_core/templates/mail_email_mfa_activation.tera b/crates/defguard_mail/templates/mail_email_mfa_activation.tera similarity index 100% rename from crates/defguard_core/templates/mail_email_mfa_activation.tera rename to crates/defguard_mail/templates/mail_email_mfa_activation.tera diff --git a/crates/defguard_core/templates/mail_email_mfa_code.tera b/crates/defguard_mail/templates/mail_email_mfa_code.tera similarity index 100% rename from crates/defguard_core/templates/mail_email_mfa_code.tera rename to crates/defguard_mail/templates/mail_email_mfa_code.tera diff --git a/crates/defguard_core/templates/mail_enrollment_admin_notification.tera b/crates/defguard_mail/templates/mail_enrollment_admin_notification.tera similarity index 100% rename from crates/defguard_core/templates/mail_enrollment_admin_notification.tera rename to crates/defguard_mail/templates/mail_enrollment_admin_notification.tera diff --git a/crates/defguard_core/templates/mail_enrollment_start.tera b/crates/defguard_mail/templates/mail_enrollment_start.tera similarity index 100% rename from crates/defguard_core/templates/mail_enrollment_start.tera rename to crates/defguard_mail/templates/mail_enrollment_start.tera diff --git a/crates/defguard_core/templates/mail_enrollment_welcome.tera b/crates/defguard_mail/templates/mail_enrollment_welcome.tera similarity index 100% rename from crates/defguard_core/templates/mail_enrollment_welcome.tera rename to crates/defguard_mail/templates/mail_enrollment_welcome.tera diff --git a/crates/defguard_core/templates/mail_gateway_disconnected.tera b/crates/defguard_mail/templates/mail_gateway_disconnected.tera similarity index 100% rename from crates/defguard_core/templates/mail_gateway_disconnected.tera rename to crates/defguard_mail/templates/mail_gateway_disconnected.tera diff --git a/crates/defguard_core/templates/mail_gateway_reconnected.tera b/crates/defguard_mail/templates/mail_gateway_reconnected.tera similarity index 100% rename from crates/defguard_core/templates/mail_gateway_reconnected.tera rename to crates/defguard_mail/templates/mail_gateway_reconnected.tera diff --git a/crates/defguard_core/templates/mail_mfa_configured.tera b/crates/defguard_mail/templates/mail_mfa_configured.tera similarity index 100% rename from crates/defguard_core/templates/mail_mfa_configured.tera rename to crates/defguard_mail/templates/mail_mfa_configured.tera diff --git a/crates/defguard_core/templates/mail_new_device_added.tera b/crates/defguard_mail/templates/mail_new_device_added.tera similarity index 100% rename from crates/defguard_core/templates/mail_new_device_added.tera rename to crates/defguard_mail/templates/mail_new_device_added.tera diff --git a/crates/defguard_core/templates/mail_new_device_login.tera b/crates/defguard_mail/templates/mail_new_device_login.tera similarity index 100% rename from crates/defguard_core/templates/mail_new_device_login.tera rename to crates/defguard_mail/templates/mail_new_device_login.tera diff --git a/crates/defguard_core/templates/mail_new_device_ocid_login.tera b/crates/defguard_mail/templates/mail_new_device_ocid_login.tera similarity index 100% rename from crates/defguard_core/templates/mail_new_device_ocid_login.tera rename to crates/defguard_mail/templates/mail_new_device_ocid_login.tera diff --git a/crates/defguard_core/templates/mail_password_reset_start.tera b/crates/defguard_mail/templates/mail_password_reset_start.tera similarity index 100% rename from crates/defguard_core/templates/mail_password_reset_start.tera rename to crates/defguard_mail/templates/mail_password_reset_start.tera diff --git a/crates/defguard_core/templates/mail_password_reset_success.tera b/crates/defguard_mail/templates/mail_password_reset_success.tera similarity index 100% rename from crates/defguard_core/templates/mail_password_reset_success.tera rename to crates/defguard_mail/templates/mail_password_reset_success.tera diff --git a/crates/defguard_core/templates/mail_support_data.tera b/crates/defguard_mail/templates/mail_support_data.tera similarity index 100% rename from crates/defguard_core/templates/mail_support_data.tera rename to crates/defguard_mail/templates/mail_support_data.tera diff --git a/crates/defguard_core/templates/mail_test.tera b/crates/defguard_mail/templates/mail_test.tera similarity index 100% rename from crates/defguard_core/templates/mail_test.tera rename to crates/defguard_mail/templates/mail_test.tera diff --git a/crates/defguard_proto/Cargo.toml b/crates/defguard_proto/Cargo.toml new file mode 100644 index 0000000000..5a749a3eb1 --- /dev/null +++ b/crates/defguard_proto/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "defguard_proto" +version = "0.0.0" +edition.workspace = true +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +prost.workspace = true +serde.workspace = true +tonic.workspace = true +tonic-prost.workspace = true + +[build-dependencies] +tonic-prost-build.workspace = true diff --git a/crates/defguard_proto/build.rs b/crates/defguard_proto/build.rs new file mode 100644 index 0000000000..d8584959a6 --- /dev/null +++ b/crates/defguard_proto/build.rs @@ -0,0 +1,37 @@ +fn main() -> Result<(), Box> { + tonic_prost_build::configure() + // These types contain sensitive data. + .skip_debug([ + "ActivateUserRequest", + "AuthInfoResponse", + "AuthenticateRequest", + "AuthenticateResponse", + "ClientMfaFinishResponse", + "CodeMfaSetupStartResponse", + "CodeMfaSetupFinishResponse", + "CoreRequest", + "CoreResponse", + "DeviceConfigResponse", + "InstanceInfoResponse", + "NewDevice", + "PasswordResetRequest", + ]) + .protoc_arg("--experimental_allow_proto3_optional") + .compile_protos( + &[ + "../../proto/core/auth.proto", + "../../proto/core/proxy.proto", + "../../proto/worker/worker.proto", + "../../proto/wireguard/gateway.proto", + "../../proto/enterprise/firewall/firewall.proto", + ], + &[ + "../../proto/core", + "../../proto/worker", + "../../proto/wireguard", + "../../proto/enterprise/firewall", + ], + )?; + println!("cargo:rerun-if-changed=../../proto"); + Ok(()) +} diff --git a/crates/defguard_proto/src/lib.rs b/crates/defguard_proto/src/lib.rs new file mode 100644 index 0000000000..b7a38faaaf --- /dev/null +++ b/crates/defguard_proto/src/lib.rs @@ -0,0 +1,66 @@ +use std::fmt; + +pub mod proxy { + tonic::include_proto!("defguard.proxy"); +} +pub mod gateway { + tonic::include_proto!("gateway"); +} +pub mod auth { + tonic::include_proto!("auth"); +} +pub mod worker { + tonic::include_proto!("worker"); +} +pub mod enterprise { + pub mod firewall { + tonic::include_proto!("enterprise.firewall"); + } +} + +use proxy::{CoreError, MfaMethod}; +use serde::Serialize; +use tonic::Status; + +// Client MFA methods +impl fmt::Display for MfaMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + MfaMethod::Totp => "TOTP", + MfaMethod::Email => "Email", + MfaMethod::Oidc => "OIDC", + MfaMethod::Biometric => "Biometric", + MfaMethod::MobileApprove => "MobileApprove", + } + ) + } +} + +impl Serialize for MfaMethod { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match *self { + MfaMethod::Totp => serializer.serialize_unit_variant("MfaMethod", 0, "Totp"), + MfaMethod::Email => serializer.serialize_unit_variant("MfaMethod", 1, "Email"), + MfaMethod::Oidc => serializer.serialize_unit_variant("MfaMethod", 2, "Oidc"), + MfaMethod::Biometric => serializer.serialize_unit_variant("MfaMethod", 3, "Biometric"), + MfaMethod::MobileApprove => { + serializer.serialize_unit_variant("MfaMethod", 4, "MobileApprove") + } + } + } +} + +impl From for CoreError { + fn from(status: Status) -> Self { + Self { + status_code: status.code().into(), + message: status.message().into(), + } + } +} diff --git a/deny.toml b/deny.toml index 3e865a910a..d26808d382 100644 --- a/deny.toml +++ b/deny.toml @@ -111,9 +111,18 @@ exceptions = [ { allow = [ "AGPL-3.0-only", ], crate = "defguard" }, + { allow = [ + "AGPL-3.0-only", + ], crate = "defguard_common" }, { allow = [ "AGPL-3.0-only", ], crate = "defguard_core" }, + { allow = [ + "AGPL-3.0-only", + ], crate = "defguard_mail" }, + { allow = [ + "AGPL-3.0-only", + ], crate = "defguard_proto" }, { allow = [ "AGPL-3.0-only", ], crate = "defguard_web_ui" }, diff --git a/flake.lock b/flake.lock index abf59ab737..6b2f9bdad5 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1757347588, - "narHash": "sha256-tLdkkC6XnsY9EOZW9TlpesTclELy8W7lL2ClL+nma8o=", + "lastModified": 1758035966, + "narHash": "sha256-qqIJ3yxPiB0ZQTT9//nFGQYn8X/PBoJbofA7hRKZnmE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b599843bad24621dcaa5ab60dac98f9b0eb1cabe", + "rev": "8d4ddb19d03c65a36ad8d189d001dc32ffb0306b", "type": "github" }, "original": { @@ -48,11 +48,11 @@ ] }, "locked": { - "lastModified": 1757471515, - "narHash": "sha256-0+rSzNsYindDWjO9VVULKGjXlPsQV6IDjRU5G3SwI9U=", + "lastModified": 1758204348, + "narHash": "sha256-jkz/NihbcEwy1EHDv/6g0HEqkpyIWCnQ1siGrhHEtFM=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "aecf31120156fe47a7d1992aa814052910178fca", + "rev": "067b3536e55341f579385ce8593cdcc9d022972b", "type": "github" }, "original": {