diff --git a/.sqlx/query-0d0ed874821849ae07a9f49f17600b2a4cbedb33babd5b9fc908ec17d3f882e2.json b/.sqlx/query-770fcf951f69a40e2e9833486425dc105a0411bd634a080391e41f431f966c17.json similarity index 85% rename from .sqlx/query-0d0ed874821849ae07a9f49f17600b2a4cbedb33babd5b9fc908ec17d3f882e2.json rename to .sqlx/query-770fcf951f69a40e2e9833486425dc105a0411bd634a080391e41f431f966c17.json index c7a2ba03bc..418350329a 100644 --- a/.sqlx/query-0d0ed874821849ae07a9f49f17600b2a4cbedb33babd5b9fc908ec17d3f882e2.json +++ b/.sqlx/query-770fcf951f69a40e2e9833486425dc105a0411bd634a080391e41f431f966c17.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, name, base_url, client_id, client_secret, display_name, google_service_account_key, google_service_account_email, admin_email, directory_sync_enabled, \n directory_sync_interval, directory_sync_user_behavior \"directory_sync_user_behavior: DirectorySyncUserBehavior\", directory_sync_admin_behavior \"directory_sync_admin_behavior: DirectorySyncUserBehavior\", directory_sync_target \"directory_sync_target: DirectorySyncTarget\", okta_private_jwk, okta_dirsync_client_id, directory_sync_group_match FROM openidprovider WHERE name = $1", + "query": "SELECT id, name, base_url, client_id, client_secret, display_name, google_service_account_key, google_service_account_email, admin_email, directory_sync_enabled,\n directory_sync_interval, directory_sync_user_behavior \"directory_sync_user_behavior: DirectorySyncUserBehavior\", directory_sync_admin_behavior \"directory_sync_admin_behavior: DirectorySyncUserBehavior\", directory_sync_target \"directory_sync_target: DirectorySyncTarget\", okta_private_jwk, okta_dirsync_client_id, directory_sync_group_match FROM openidprovider WHERE name = $1", "describe": { "columns": [ { @@ -147,5 +147,5 @@ false ] }, - "hash": "0d0ed874821849ae07a9f49f17600b2a4cbedb33babd5b9fc908ec17d3f882e2" + "hash": "770fcf951f69a40e2e9833486425dc105a0411bd634a080391e41f431f966c17" } diff --git a/crates/defguard_core/src/db/models/audit_log/metadata.rs b/crates/defguard_core/src/db/models/audit_log/metadata.rs index 10cfe86686..141b7cc487 100644 --- a/crates/defguard_core/src/db/models/audit_log/metadata.rs +++ b/crates/defguard_core/src/db/models/audit_log/metadata.rs @@ -1,4 +1,19 @@ -use crate::db::{Device, Id, MFAMethod, WireguardNetwork}; +use chrono::NaiveDateTime; + +use crate::{ + db::{ + models::{ + authentication_key::{AuthenticationKey, AuthenticationKeyType}, + oauth2client::OAuth2Client, + }, + Device, Group, Id, MFAMethod, User, WebAuthn, WebHook, WireguardNetwork, + }, + enterprise::db::models::{ + api_tokens::ApiToken, + audit_stream::{AuditStream, AuditStreamType}, + openid_provider::{DirectorySyncTarget, DirectorySyncUserBehavior, OpenIdProvider}, + }, +}; #[derive(Serialize)] pub struct MfaLoginMetadata { @@ -6,75 +21,132 @@ pub struct MfaLoginMetadata { } #[derive(Serialize)] -pub struct DeviceAddedMetadata { - pub device_names: Vec, +pub struct UserNoSecrets { + pub id: Id, + pub username: String, + pub last_name: String, + pub first_name: String, + pub email: String, + pub phone: Option, + pub mfa_enabled: bool, + pub is_active: bool, + pub from_ldap: bool, + pub ldap_pass_randomized: bool, + pub ldap_rdn: Option, + pub openid_sub: Option, + pub totp_enabled: bool, + pub email_mfa_enabled: bool, + pub mfa_method: MFAMethod, } -#[derive(Serialize)] -pub struct DeviceRemovedMetadata { - pub device_names: Vec, +impl From> for UserNoSecrets { + fn from(value: User) -> Self { + Self { + id: value.id, + username: value.username, + last_name: value.last_name, + first_name: value.first_name, + email: value.email, + phone: value.phone, + mfa_enabled: value.mfa_enabled, + is_active: value.is_active, + from_ldap: value.from_ldap, + ldap_pass_randomized: value.ldap_pass_randomized, + ldap_rdn: value.ldap_rdn, + openid_sub: value.openid_sub, + totp_enabled: value.totp_enabled, + email_mfa_enabled: value.email_mfa_enabled, + mfa_method: value.mfa_method, + } + } } #[derive(Serialize)] -pub struct DeviceModifiedMetadata { - pub device_names: Vec, +pub struct DeviceMetadata { + pub owner: UserNoSecrets, + pub device: Device, } #[derive(Serialize)] -pub struct NetworkDeviceAddedMetadata { - pub device_id: Id, - pub device_name: String, - pub location_id: Id, - pub location: String, +pub struct DeviceModifiedMetadata { + pub owner: UserNoSecrets, + pub before: Device, + pub after: Device, } #[derive(Serialize)] -pub struct NetworkDeviceRemovedMetadata { - pub device_id: Id, - pub device_name: String, - pub location_id: Id, - pub location: String, +pub struct NetworkDeviceMetadata { + pub device: Device, + pub location: WireguardNetwork, } #[derive(Serialize)] pub struct NetworkDeviceModifiedMetadata { - pub device_id: Id, - pub device_name: String, - pub location_id: Id, - pub location: String, + pub location: WireguardNetwork, + pub before: Device, + pub after: Device, } #[derive(Serialize)] -pub struct UserAddedMetadata { - pub username: String, +pub struct UserMetadata { + pub user: UserNoSecrets, } #[derive(Serialize)] pub struct UserModifiedMetadata { - pub username: String, + pub before: UserNoSecrets, + pub after: UserNoSecrets, } #[derive(Serialize)] -pub struct UserRemovedMetadata { - pub username: String, +pub struct MfaSecurityKeyMetadata { + pub key: WebAuthnNoSecrets, } +// Avoid storing secrets in metadata #[derive(Serialize)] -pub struct MfaSecurityKeyRemovedMetadata { - pub key_id: Id, - pub key_name: String, +pub struct WebAuthnNoSecrets { + pub id: Id, + pub user_id: Id, + pub name: String, } -#[derive(Serialize)] -pub struct MfaSecurityKeyAddedMetadata { - pub key_id: Id, - pub key_name: String, +impl From> for WebAuthnNoSecrets { + fn from(value: WebAuthn) -> Self { + Self { + id: value.id, + user_id: value.user_id, + name: value.name, + } + } } #[derive(Serialize)] pub struct AuditStreamMetadata { + pub stream: AuditStreamNoSecrets, +} + +#[derive(Serialize)] +pub struct AuditStreamNoSecrets { pub id: Id, pub name: String, + pub stream_type: AuditStreamType, +} + +impl From> for AuditStreamNoSecrets { + fn from(value: AuditStream) -> Self { + Self { + id: value.id, + name: value.name, + stream_type: value.stream_type, + } + } +} + +#[derive(Serialize)] +pub struct AuditStreamModifiedMetadata { + pub before: AuditStreamNoSecrets, + pub after: AuditStreamNoSecrets, } #[derive(Serialize)] @@ -94,3 +166,224 @@ pub struct VpnClientMfaMetadata { pub struct EnrollmentDeviceAddedMetadata { pub device: Device, } + +#[derive(Serialize)] +pub struct EnrollmentTokenMetadata { + pub user: UserNoSecrets, +} + +#[derive(Serialize)] +pub struct VpnLocationMetadata { + pub location: WireguardNetwork, +} + +#[derive(Serialize)] +pub struct VpnLocationModifiedMetadata { + pub before: WireguardNetwork, + pub after: WireguardNetwork, +} + +#[derive(Serialize)] +pub struct ApiTokenMetadata { + pub owner: UserNoSecrets, + pub token: ApiTokenNoSecrets, +} + +#[derive(Serialize)] +pub struct ApiTokenNoSecrets { + id: Id, + pub user_id: Id, + pub created_at: NaiveDateTime, + pub name: String, +} + +impl From> for ApiTokenNoSecrets { + fn from(value: ApiToken) -> Self { + Self { + id: value.id, + user_id: value.user_id, + created_at: value.created_at, + name: value.name, + } + } +} + +#[derive(Serialize)] +pub struct ApiTokenRenamedMetadata { + pub owner: UserNoSecrets, + pub token: ApiTokenNoSecrets, + pub old_name: String, + pub new_name: String, +} + +#[derive(Serialize)] +pub struct OpenIdAppMetadata { + pub app: OAuth2ClientNoSecrets, +} + +#[derive(Serialize)] +pub struct OAuth2ClientNoSecrets { + pub id: Id, + pub client_id: String, // unique + pub redirect_uri: Vec, + pub scope: Vec, + pub name: String, + pub enabled: bool, +} + +impl From> for OAuth2ClientNoSecrets { + fn from(value: OAuth2Client) -> Self { + Self { + id: value.id, + client_id: value.client_id, + redirect_uri: value.redirect_uri, + scope: value.scope, + name: value.name, + enabled: value.enabled, + } + } +} + +#[derive(Serialize)] +pub struct OpenIdAppModifiedMetadata { + pub before: OAuth2ClientNoSecrets, + pub after: OAuth2ClientNoSecrets, +} + +#[derive(Serialize)] +pub struct OpenIdAppStateChangedMetadata { + pub app: OAuth2ClientNoSecrets, + pub enabled: bool, +} + +#[derive(Serialize)] +pub struct OpenIdProviderMetadata { + pub provider: OpenIdProviderNoSecrets, +} + +#[derive(Serialize)] +pub struct OpenIdProviderNoSecrets { + pub id: Id, + pub name: String, + pub base_url: String, + pub client_id: String, + pub display_name: Option, + pub google_service_account_email: Option, + pub admin_email: Option, + pub directory_sync_enabled: bool, + pub directory_sync_interval: i32, + pub directory_sync_user_behavior: DirectorySyncUserBehavior, + pub directory_sync_admin_behavior: DirectorySyncUserBehavior, + pub directory_sync_target: DirectorySyncTarget, + pub okta_dirsync_client_id: Option, + pub directory_sync_group_match: Vec, +} + +impl From> for OpenIdProviderNoSecrets { + fn from(value: OpenIdProvider) -> Self { + Self { + id: value.id, + name: value.name, + base_url: value.base_url, + client_id: value.client_id, + display_name: value.display_name, + google_service_account_email: value.google_service_account_email, + admin_email: value.admin_email, + directory_sync_enabled: value.directory_sync_enabled, + directory_sync_interval: value.directory_sync_interval, + directory_sync_user_behavior: value.directory_sync_user_behavior, + directory_sync_admin_behavior: value.directory_sync_admin_behavior, + directory_sync_target: value.directory_sync_target, + okta_dirsync_client_id: value.okta_dirsync_client_id, + directory_sync_group_match: value.directory_sync_group_match, + } + } +} + +#[derive(Serialize)] +pub struct GroupsBulkAssignedMetadata { + pub users: Vec, + pub groups: Vec>, +} + +#[derive(Serialize)] +pub struct GroupMetadata { + pub group: Group, +} + +#[derive(Serialize)] +pub struct GroupModifiedMetadata { + pub before: Group, + pub after: Group, +} + +#[derive(Serialize)] +pub struct GroupAssignedMetadata { + pub group: Group, + pub user: UserNoSecrets, +} + +#[derive(Serialize)] +pub struct WebHookMetadata { + pub webhook: WebHook, +} + +#[derive(Serialize)] +pub struct WebHookModifiedMetadata { + pub before: WebHook, + pub after: WebHook, +} + +#[derive(Serialize)] +pub struct WebHookStateChangedMetadata { + pub webhook: WebHook, + pub enabled: bool, +} + +#[derive(Serialize)] +pub struct AuthenticationKeyMetadata { + pub key: AuthenticationKeyNoSecrets, +} + +#[derive(Serialize)] +pub struct AuthenticationKeyNoSecrets { + pub id: Id, + pub yubikey_id: Option, + pub name: Option, + pub user_id: Id, + pub key_type: AuthenticationKeyType, +} + +impl From> for AuthenticationKeyNoSecrets { + fn from(value: AuthenticationKey) -> Self { + Self { + id: value.id, + yubikey_id: value.yubikey_id, + name: value.name, + user_id: value.user_id, + key_type: value.key_type, + } + } +} + +#[derive(Serialize)] +pub struct AuthenticationKeyRenamedMetadata { + pub key: AuthenticationKeyNoSecrets, + pub old_name: Option, + pub new_name: Option, +} + +#[derive(Serialize)] +pub struct PasswordChangedByAdminMetadata { + pub user: UserNoSecrets, +} + +#[derive(Serialize)] +pub struct PasswordResetMetadata { + pub user: UserNoSecrets, +} + +#[derive(Serialize)] +pub struct ClientConfigurationTokenMetadata { + pub user: UserNoSecrets, +} diff --git a/crates/defguard_core/src/db/models/audit_log/mod.rs b/crates/defguard_core/src/db/models/audit_log/mod.rs index 0e4d6f24a3..0cf327d92f 100644 --- a/crates/defguard_core/src/db/models/audit_log/mod.rs +++ b/crates/defguard_core/src/db/models/audit_log/mod.rs @@ -43,6 +43,9 @@ pub enum EventType { UserAdded, UserRemoved, UserModified, + PasswordChanged, + PasswordChangedByAdmin, + PasswordReset, // device management DeviceAdded, DeviceRemoved, @@ -50,6 +53,7 @@ pub enum EventType { NetworkDeviceAdded, NetworkDeviceRemoved, NetworkDeviceModified, + ClientConfigurationTokenAdded, // audit stream AuditStreamCreated, AuditStreamModified, @@ -58,6 +62,10 @@ pub enum EventType { OpenIdAppAdded, OpenIdAppRemoved, OpenIdAppModified, + OpenIdAppStateChanged, + // OpenID provider management + OpenIdProviderRemoved, + OpenIdProviderModified, // VPN location management VpnLocationAdded, VpnLocationRemoved, @@ -69,12 +77,37 @@ pub enum EventType { VpnClientDisconnectedMfa, VpnClientMfaFailed, // Enrollment events + EnrollmentTokenAdded, EnrollmentStarted, EnrollmentDeviceAdded, EnrollmentCompleted, PasswordResetRequested, PasswordResetStarted, PasswordResetCompleted, + // API token management, + ApiTokenAdded, + ApiTokenRemoved, + ApiTokenRenamed, + // Settings management + SettingsUpdated, + SettingsUpdatedPartial, + SettingsDefaultBrandingRestored, + // Groups management + GroupsBulkAssigned, + GroupAdded, + GroupModified, + GroupRemoved, + GroupMemberAdded, + GroupMemberRemoved, + // WebHook management + WebHookAdded, + WebHookModified, + WebHookRemoved, + WebHookStateChanged, + // Authentication key management + AuthenticationKeyAdded, + AuthenticationKeyRemoved, + AuthenticationKeyRenamed, } #[derive(Model, FromRow, Serialize)] diff --git a/crates/defguard_core/src/db/models/authentication_key.rs b/crates/defguard_core/src/db/models/authentication_key.rs index cf877ca700..260ab50feb 100644 --- a/crates/defguard_core/src/db/models/authentication_key.rs +++ b/crates/defguard_core/src/db/models/authentication_key.rs @@ -13,14 +13,14 @@ pub enum AuthenticationKeyType { #[derive(Deserialize, Model, Serialize)] #[table(authentication_key)] -pub(crate) struct AuthenticationKey { - id: I, - pub yubikey_id: Option, - pub name: Option, - pub user_id: Id, - pub key: String, +pub struct AuthenticationKey { + pub(crate) id: I, + pub(crate) yubikey_id: Option, + pub(crate) name: Option, + pub(crate) user_id: Id, + pub(crate) key: String, #[model(enum)] - key_type: AuthenticationKeyType, + pub(crate) key_type: AuthenticationKeyType, } impl AuthenticationKey { diff --git a/crates/defguard_core/src/db/models/group.rs b/crates/defguard_core/src/db/models/group.rs index 4e03336c2e..634929fcd8 100644 --- a/crates/defguard_core/src/db/models/group.rs +++ b/crates/defguard_core/src/db/models/group.rs @@ -19,7 +19,7 @@ impl fmt::Display for Permission { } } -#[derive(Clone, Debug, Model, ToSchema, FromRow, PartialEq)] +#[derive(Clone, Debug, Model, ToSchema, FromRow, PartialEq, Serialize)] pub struct Group { pub(crate) id: I, pub name: String, diff --git a/crates/defguard_core/src/db/models/oauth2client.rs b/crates/defguard_core/src/db/models/oauth2client.rs index 91d40f9a27..41c9601783 100644 --- a/crates/defguard_core/src/db/models/oauth2client.rs +++ b/crates/defguard_core/src/db/models/oauth2client.rs @@ -7,7 +7,7 @@ use crate::{ random::gen_alphanumeric, }; -#[derive(Deserialize, Model, Serialize)] +#[derive(Clone, Deserialize, Model, Serialize)] pub struct OAuth2Client { pub id: I, pub client_id: String, // unique diff --git a/crates/defguard_core/src/db/models/webauthn.rs b/crates/defguard_core/src/db/models/webauthn.rs index 29fbe4695b..47aad4dc15 100644 --- a/crates/defguard_core/src/db/models/webauthn.rs +++ b/crates/defguard_core/src/db/models/webauthn.rs @@ -5,11 +5,11 @@ use webauthn_rs::prelude::Passkey; use super::error::ModelError; use crate::db::{Id, NoId}; -#[derive(Model)] +#[derive(Model, Clone)] pub struct WebAuthn { - id: I, + pub(crate) id: I, pub(crate) user_id: Id, - name: String, + pub(crate) name: String, // serialize from/to [`Passkey`] pub passkey: Vec, } diff --git a/crates/defguard_core/src/db/models/webhook.rs b/crates/defguard_core/src/db/models/webhook.rs index c9eda286c0..5263c69806 100644 --- a/crates/defguard_core/src/db/models/webhook.rs +++ b/crates/defguard_core/src/db/models/webhook.rs @@ -46,7 +46,7 @@ impl AppEvent { } } -#[derive(Debug, Deserialize, FromRow, Model, Serialize)] +#[derive(Clone, Debug, Deserialize, FromRow, Model, Serialize)] pub struct WebHook { pub id: I, pub url: String, 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 8bb6d879b1..262c2361c3 100644 --- a/crates/defguard_core/src/enterprise/db/models/api_tokens.rs +++ b/crates/defguard_core/src/enterprise/db/models/api_tokens.rs @@ -4,10 +4,10 @@ use sqlx::{query_as, Error as SqlxError, PgExecutor}; use crate::db::{Id, NoId}; -#[derive(Deserialize, Model, Serialize)] +#[derive(Clone, Deserialize, Model, Serialize)] #[table(api_token)] pub struct ApiToken { - id: I, + pub id: I, pub user_id: Id, pub created_at: NaiveDateTime, pub name: String, diff --git a/crates/defguard_core/src/enterprise/db/models/audit_stream.rs b/crates/defguard_core/src/enterprise/db/models/audit_stream.rs index ef3730ba61..1fff1822b4 100644 --- a/crates/defguard_core/src/enterprise/db/models/audit_stream.rs +++ b/crates/defguard_core/src/enterprise/db/models/audit_stream.rs @@ -19,7 +19,7 @@ pub enum AuditStreamType { LogstashHttp, } -#[derive(Debug, Serialize, Model, FromRow)] +#[derive(Clone, Debug, Serialize, Model, FromRow)] #[table(audit_stream)] pub struct AuditStream { pub id: I, 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 4039727bb3..f3cb40c7e8 100644 --- a/crates/defguard_core/src/enterprise/db/models/openid_provider.rs +++ b/crates/defguard_core/src/enterprise/db/models/openid_provider.rs @@ -85,7 +85,7 @@ impl From for DirectorySyncTarget { } } -#[derive(Deserialize, Model, Serialize)] +#[derive(Clone, Deserialize, Model, Serialize)] pub struct OpenIdProvider { pub id: I, pub name: String, @@ -199,7 +199,7 @@ impl OpenIdProvider { query_as!( OpenIdProvider, "SELECT id, name, base_url, client_id, client_secret, display_name, \ - google_service_account_key, google_service_account_email, admin_email, directory_sync_enabled, + google_service_account_key, google_service_account_email, admin_email, directory_sync_enabled, directory_sync_interval, directory_sync_user_behavior \"directory_sync_user_behavior: DirectorySyncUserBehavior\", \ directory_sync_admin_behavior \"directory_sync_admin_behavior: DirectorySyncUserBehavior\", \ directory_sync_target \"directory_sync_target: DirectorySyncTarget\", \ diff --git a/crates/defguard_core/src/enterprise/handlers/api_tokens.rs b/crates/defguard_core/src/enterprise/handlers/api_tokens.rs index 2f11c46ad4..808fd114b8 100644 --- a/crates/defguard_core/src/enterprise/handlers/api_tokens.rs +++ b/crates/defguard_core/src/enterprise/handlers/api_tokens.rs @@ -10,8 +10,10 @@ use super::LicenseInfo; use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, + db::User, enterprise::db::models::api_tokens::{ApiToken, ApiTokenInfo}, error::WebError, + events::{ApiEvent, ApiEventType, ApiRequestContext}, handlers::{user_for_admin_or_self, ApiResponse, ApiResult}, random::gen_alphanumeric, }; @@ -28,6 +30,7 @@ pub async fn add_api_token( _admin: AdminRole, State(appstate): State, session: SessionInfo, + context: ApiRequestContext, Path(username): Path, Json(data): Json, ) -> ApiResult { @@ -53,7 +56,7 @@ pub async fn add_api_token( // all API tokens start with a `dg-` prefix let token_string = format!("dg-{}", gen_alphanumeric(API_TOKEN_LENGTH)); - ApiToken::new( + let token = ApiToken::new( user.id, Utc::now().naive_utc(), data.name.clone(), @@ -63,7 +66,12 @@ pub async fn add_api_token( .await?; info!("Added new API token {} for user {username}", data.name); - + if let Some(owner) = User::find_by_id(&appstate.pool, token.user_id).await? { + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::ApiTokenAdded { owner, token }), + })?; + } Ok(ApiResponse { json: json!({"token": token_string}), status: StatusCode::CREATED, @@ -96,6 +104,7 @@ pub async fn delete_api_token( _admin: AdminRole, State(appstate): State, session: SessionInfo, + context: ApiRequestContext, Path((username, token_id)): Path<(String, i64)>, ) -> ApiResult { debug!("Removing API token {token_id} for user {username}"); @@ -104,7 +113,20 @@ pub async fn delete_api_token( if !session.is_admin && user.id != token.user_id { return Err(WebError::Forbidden(String::new())); } - token.delete(&appstate.pool).await?; + token.clone().delete(&appstate.pool).await?; + if let Some(owner) = User::find_by_id(&appstate.pool, token.user_id).await? { + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::ApiTokenRemoved { + owner, + token: token.clone(), + }), + })?; + } + info!( + "User {} removed API token {}({token_id}) for user {username}", + user.username, token.name + ); } else { error!("API token with id {token_id} not found"); return Err(WebError::BadRequest("Key not found".into())); @@ -126,16 +148,35 @@ pub async fn rename_api_token( _admin: AdminRole, State(appstate): State, session: SessionInfo, + context: ApiRequestContext, Path((username, token_id)): Path<(String, i64)>, Json(data): Json, ) -> ApiResult { + debug!("Renaming API token {token_id} for user {username}"); let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(mut token) = ApiToken::find_by_id(&appstate.pool, token_id).await? { if !session.is_admin && user.id != token.user_id { return Err(WebError::Forbidden(String::new())); } + let old_name = token.name.clone(); token.name = data.name; + let new_name = token.name.clone(); token.save(&appstate.pool).await?; + if let Some(owner) = User::find_by_id(&appstate.pool, token.user_id).await? { + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::ApiTokenRenamed { + owner, + token: token.clone(), + old_name, + new_name, + }), + })?; + } + info!( + "User {} renamed API token {}({token_id}) for user {username}", + user.username, token.name + ); } else { error!("User {username} tried to rename non-existing API token with id {token_id}",); return Err(WebError::ObjectNotFound(String::new())); diff --git a/crates/defguard_core/src/enterprise/handlers/audit_stream.rs b/crates/defguard_core/src/enterprise/handlers/audit_stream.rs index 22bc663242..e920801ee6 100644 --- a/crates/defguard_core/src/enterprise/handlers/audit_stream.rs +++ b/crates/defguard_core/src/enterprise/handlers/audit_stream.rs @@ -60,12 +60,8 @@ pub async fn create_audit_stream( info!("User {session_username} created audit stream"); appstate.emit_event(ApiEvent { context, - event: ApiEventType::AuditStreamCreated { - stream_id: stream.id, - stream_name: stream.name, - }, + event: Box::new(ApiEventType::AuditStreamCreated { stream }), })?; - debug!("AuditStreamCreated api event sent"); Ok(ApiResponse { json: json!({}), status: StatusCode::CREATED, @@ -84,18 +80,23 @@ pub async fn modify_audit_stream( let session_username = &session.user.username; debug!("User {session_username} modifies audit stream "); if let Some(mut stream) = AuditStream::find_by_id(&appstate.pool, id).await? { + // store stream before modifications + let before = stream.clone(); //validate config let _ = AuditStreamConfig::from_serde_value(&data.stream_type, &data.stream_config)?; stream.name = data.name; stream.config = data.stream_config; stream.save(&appstate.pool).await?; - info!("User {session_username} modified audit stream"); + info!( + "User {session_username} modified audit stream {}", + stream.name + ); appstate.emit_event(ApiEvent { context, - event: ApiEventType::AuditStreamModified { - stream_id: stream.id, - stream_name: stream.name, - }, + event: Box::new(ApiEventType::AuditStreamModified { + before, + after: stream, + }), })?; debug!("AuditStreamModified api event sent"); return Ok(ApiResponse::default()); @@ -116,15 +117,10 @@ pub async fn delete_audit_stream( let session_username = &session.user.username; debug!("User {session_username} deleting Audit stream ({id})"); if let Some(stream) = AuditStream::find_by_id(&appstate.pool, id).await? { - let stream_id = stream.id; - let stream_name = stream.name.clone(); - stream.delete(&appstate.pool).await?; + stream.clone().delete(&appstate.pool).await?; appstate.emit_event(ApiEvent { context, - event: ApiEventType::AuditStreamRemoved { - stream_id, - stream_name, - }, + event: Box::new(ApiEventType::AuditStreamRemoved { stream }), })?; } else { return Err(crate::error::WebError::ObjectNotFound(format!( diff --git a/crates/defguard_core/src/enterprise/handlers/openid_providers.rs b/crates/defguard_core/src/enterprise/handlers/openid_providers.rs index 143a210e87..92e63f292b 100644 --- a/crates/defguard_core/src/enterprise/handlers/openid_providers.rs +++ b/crates/defguard_core/src/enterprise/handlers/openid_providers.rs @@ -17,6 +17,7 @@ use crate::{ enterprise::{ db::models::openid_provider::OpenIdProvider, directory_sync::test_directory_sync_connection, }, + events::{ApiEvent, ApiEventType, ApiRequestContext}, handlers::{ApiResponse, ApiResult}, }; @@ -51,9 +52,14 @@ pub async fn add_openid_provider( _license: LicenseInfo, _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Json(provider_data): Json, ) -> ApiResult { + debug!( + "User {} adding OpenID provider {}", + session.user.username, provider_data.name + ); let current_provider = OpenIdProvider::get_current(&appstate.pool).await?; // The key is sent from the frontend only when user explicitly changes it, as we never send it back. @@ -148,14 +154,16 @@ pub async fn add_openid_provider( ) .upsert(&appstate.pool) .await?; - debug!( - "User {} adding OpenID provider {}", - session.user.username, new_provider.name - ); info!( "User {} added OpenID client {}", session.user.username, new_provider.name ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::OpenIdProviderModified { + provider: new_provider, + }), + })?; Ok(ApiResponse { json: json!({}), @@ -197,6 +205,7 @@ pub async fn delete_openid_provider( _license: LicenseInfo, _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(provider_data): Path, ) -> ApiResult { @@ -206,55 +215,22 @@ pub async fn delete_openid_provider( ); let provider = OpenIdProvider::find_by_name(&appstate.pool, &provider_data.name).await?; if let Some(provider) = provider { - provider.delete(&appstate.pool).await?; + provider.clone().delete(&appstate.pool).await?; info!( "User {} deleted OpenID provider {}", - session.user.username, provider_data.name - ); - Ok(ApiResponse { - json: json!({}), - status: StatusCode::OK, - }) - } else { - warn!( - "User {} failed to delete OpenID provider {}. Such provider does not exist.", - session.user.username, provider_data.name - ); - Ok(ApiResponse { - json: json!({}), - status: StatusCode::NOT_FOUND, - }) - } -} - -pub async fn modify_openid_provider( - _license: LicenseInfo, - _admin: AdminRole, - session: SessionInfo, - State(appstate): State, - Json(provider_data): Json, -) -> ApiResult { - debug!( - "User {} modifying OpenID provider {}", - session.user.username, provider_data.name - ); - let provider = OpenIdProvider::find_by_name(&appstate.pool, &provider_data.name).await?; - if let Some(mut provider) = provider { - provider.base_url = provider_data.base_url; - provider.client_id = provider_data.client_id; - provider.client_secret = provider_data.client_secret; - provider.save(&appstate.pool).await?; - info!( - "User {} modified OpenID client {}", session.user.username, provider.name ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::OpenIdProviderRemoved { provider }), + })?; Ok(ApiResponse { json: json!({}), status: StatusCode::OK, }) } else { warn!( - "User {} failed to modify OpenID client {}. Such client does not exist.", + "User {} failed to delete OpenID provider {}. Such provider does not exist.", session.user.username, provider_data.name ); Ok(ApiResponse { diff --git a/crates/defguard_core/src/events.rs b/crates/defguard_core/src/events.rs index d73d95e78b..8d557f7f06 100644 --- a/crates/defguard_core/src/events.rs +++ b/crates/defguard_core/src/events.rs @@ -1,6 +1,14 @@ use std::net::IpAddr; -use crate::db::{Device, Id, MFAMethod, WireguardNetwork}; +use crate::{ + db::{ + models::{authentication_key::AuthenticationKey, oauth2client::OAuth2Client}, + Device, Group, Id, MFAMethod, User, WebAuthn, WebHook, WireguardNetwork, + }, + enterprise::db::models::{ + api_tokens::ApiToken, audit_stream::AuditStream, openid_provider::OpenIdProvider, + }, +}; use chrono::{NaiveDateTime, Utc}; /// Shared context that needs to be added to every API event @@ -63,9 +71,9 @@ impl GrpcRequestContext { } } -#[derive(Debug)] pub enum ApiEventType { UserLogin, + UserLogout, UserLoginFailed, UserMfaLogin { mfa_method: MFAMethod, @@ -74,81 +82,176 @@ pub enum ApiEventType { mfa_method: MFAMethod, }, RecoveryCodeUsed, - UserLogout, + PasswordChangedByAdmin { + user: User, + }, + PasswordChanged, + PasswordReset { + user: User, + }, MfaDisabled, MfaTotpDisabled, MfaTotpEnabled, MfaEmailDisabled, MfaEmailEnabled, MfaSecurityKeyAdded { - key_id: Id, - key_name: String, + key: WebAuthn, }, MfaSecurityKeyRemoved { - key_id: Id, - key_name: String, + key: WebAuthn, }, UserAdded { - username: String, + user: User, }, UserRemoved { - username: String, + user: User, }, UserModified { - username: String, + before: User, + after: User, }, UserDeviceAdded { - device_id: Id, - owner: String, - device_name: String, + owner: User, + device: Device, }, UserDeviceRemoved { - device_id: Id, - owner: String, - device_name: String, + owner: User, + device: Device, }, UserDeviceModified { - device_id: Id, - owner: String, - device_name: String, + owner: User, + before: Device, + after: Device, }, NetworkDeviceAdded { - device_id: Id, - device_name: String, - location_id: Id, - location: String, + device: Device, + location: WireguardNetwork, }, NetworkDeviceRemoved { - device_id: Id, - device_name: String, - location_id: Id, - location: String, + device: Device, + location: WireguardNetwork, }, NetworkDeviceModified { - device_id: Id, - device_name: String, - location_id: Id, - location: String, + before: Device, + after: Device, + location: WireguardNetwork, }, AuditStreamCreated { - stream_id: Id, - stream_name: String, + stream: AuditStream, }, AuditStreamModified { - stream_id: Id, - stream_name: String, + before: AuditStream, + after: AuditStream, }, AuditStreamRemoved { - stream_id: Id, - stream_name: String, + stream: AuditStream, + }, + VpnLocationAdded { + location: WireguardNetwork, + }, + VpnLocationRemoved { + location: WireguardNetwork, + }, + VpnLocationModified { + before: WireguardNetwork, + after: WireguardNetwork, + }, + ApiTokenAdded { + owner: User, + token: ApiToken, + }, + ApiTokenRemoved { + owner: User, + token: ApiToken, + }, + ApiTokenRenamed { + owner: User, + token: ApiToken, + old_name: String, + new_name: String, + }, + OpenIdAppAdded { + app: OAuth2Client, + }, + OpenIdAppRemoved { + app: OAuth2Client, + }, + OpenIdAppModified { + before: OAuth2Client, + after: OAuth2Client, + }, + OpenIdAppStateChanged { + app: OAuth2Client, + enabled: bool, + }, + OpenIdProviderModified { + provider: OpenIdProvider, + }, + OpenIdProviderRemoved { + provider: OpenIdProvider, + }, + SettingsUpdated, + SettingsUpdatedPartial, + SettingsDefaultBrandingRestored, + GroupsBulkAssigned { + users: Vec>, + groups: Vec>, + }, + GroupAdded { + group: Group, + }, + GroupModified { + before: Group, + after: Group, + }, + GroupRemoved { + group: Group, + }, + GroupMemberAdded { + group: Group, + user: User, + }, + GroupMemberRemoved { + group: Group, + user: User, + }, + WebHookAdded { + webhook: WebHook, + }, + WebHookModified { + before: WebHook, + after: WebHook, + }, + WebHookRemoved { + webhook: WebHook, + }, + WebHookStateChanged { + webhook: WebHook, + enabled: bool, + }, + AuthenticationKeyAdded { + key: AuthenticationKey, + }, + AuthenticationKeyRemoved { + key: AuthenticationKey, + }, + AuthenticationKeyRenamed { + key: AuthenticationKey, + old_name: Option, + new_name: Option, + }, + EnrollmentTokenAdded { + user: User, + }, + ClientConfigurationTokenAdded { + user: User, }, } /// Events from Web API -#[derive(Debug)] pub struct ApiEvent { pub context: ApiRequestContext, - pub event: ApiEventType, + pub event: Box, } /// Events from gRPC server @@ -203,12 +306,11 @@ pub struct BidiStreamEvent { /// Wrapper enum for different types of events emitted by the bidi stream. /// /// Each variant represents a separate gRPC service that's part of the bi-directional communications server. -#[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum BidiStreamEventType { - Enrollment(EnrollmentEvent), - PasswordReset(PasswordResetEvent), - DesktopClientMfa(DesktopClientMfaEvent), + Enrollment(Box), + PasswordReset(Box), + DesktopClientMfa(Box), } #[derive(Debug)] diff --git a/crates/defguard_core/src/grpc/desktop_client_mfa.rs b/crates/defguard_core/src/grpc/desktop_client_mfa.rs index f0909651c3..f4d2289031 100644 --- a/crates/defguard_core/src/grpc/desktop_client_mfa.rs +++ b/crates/defguard_core/src/grpc/desktop_client_mfa.rs @@ -28,7 +28,6 @@ use crate::{ const CLIENT_SESSION_TIMEOUT: u64 = 60 * 5; // 10 minutes #[derive(Debug, Error)] -#[allow(clippy::large_enum_variant)] pub enum ClientMfaServerError { #[error("gRPC event channel error: {0}")] BidiEventChannelError(#[from] SendError), @@ -251,13 +250,13 @@ impl ClientMfaServer { error!("Provided TOTP code is not valid"); self.emit_event(BidiStreamEvent { context, - event: BidiStreamEventType::DesktopClientMfa( + event: BidiStreamEventType::DesktopClientMfa(Box::new( DesktopClientMfaEvent::Failed { location: location.clone(), device: device.clone(), method: (*method).into(), }, - ), + )), })?; return Err(Status::unauthenticated("unauthorized")); } @@ -267,13 +266,13 @@ impl ClientMfaServer { error!("Provided email code is not valid"); self.emit_event(BidiStreamEvent { context, - event: BidiStreamEventType::DesktopClientMfa( + event: BidiStreamEventType::DesktopClientMfa(Box::new( DesktopClientMfaEvent::Failed { location: location.clone(), device: device.clone(), method: (*method).into(), }, - ), + )), })?; return Err(Status::unauthenticated("unauthorized")); } @@ -334,11 +333,13 @@ impl ClientMfaServer { ); self.emit_event(BidiStreamEvent { context, - event: BidiStreamEventType::DesktopClientMfa(DesktopClientMfaEvent::Connected { - location: location.clone(), - device: device.clone(), - method: (*method).into(), - }), + event: BidiStreamEventType::DesktopClientMfa(Box::new( + DesktopClientMfaEvent::Connected { + location: location.clone(), + device: device.clone(), + method: (*method).into(), + }, + )), })?; // remove login session from map diff --git a/crates/defguard_core/src/grpc/enrollment.rs b/crates/defguard_core/src/grpc/enrollment.rs index c57ccc3828..4b0205e14e 100644 --- a/crates/defguard_core/src/grpc/enrollment.rs +++ b/crates/defguard_core/src/grpc/enrollment.rs @@ -104,7 +104,7 @@ impl EnrollmentServer { ) -> Result<(), SendError> { let event = BidiStreamEvent { context, - event: BidiStreamEventType::Enrollment(event), + event: BidiStreamEventType::Enrollment(Box::new(event)), }; self.bidi_event_tx.send(event) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 2717838c9d..ee7d5c7cfe 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -54,8 +54,8 @@ pub fn send_multiple_wireguard_events(events: Vec, wg_tx: &Sender< } } -#[derive(Debug, Error)] #[allow(clippy::large_enum_variant)] +#[derive(Debug, Error)] pub enum GatewayServerError { #[error("Failed to acquire lock on VPN client state map")] ClientStateMutexError, diff --git a/crates/defguard_core/src/grpc/password_reset.rs b/crates/defguard_core/src/grpc/password_reset.rs index 987716c247..b84792782b 100644 --- a/crates/defguard_core/src/grpc/password_reset.rs +++ b/crates/defguard_core/src/grpc/password_reset.rs @@ -83,7 +83,7 @@ impl PasswordResetServer { ) -> Result<(), SendError> { let event = BidiStreamEvent { context, - event: BidiStreamEventType::PasswordReset(event), + event: BidiStreamEventType::PasswordReset(Box::new(event)), }; self.bidi_event_tx.send(event) diff --git a/crates/defguard_core/src/handlers/auth.rs b/crates/defguard_core/src/handlers/auth.rs index 413e4ffca0..37c51e0ed3 100644 --- a/crates/defguard_core/src/handlers/auth.rs +++ b/crates/defguard_core/src/handlers/auth.rs @@ -159,7 +159,7 @@ pub(crate) async fn authenticate( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserLoginFailed, + event: Box::new(ApiEventType::UserLoginFailed), })?; return Err(WebError::Authorization(err.to_string())); } @@ -174,7 +174,7 @@ pub(crate) async fn authenticate( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserLoginFailed, + event: Box::new(ApiEventType::UserLoginFailed), })?; return Err(WebError::Authorization(err.to_string())); } @@ -204,7 +204,7 @@ pub(crate) async fn authenticate( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserLoginFailed, + event: Box::new(ApiEventType::UserLoginFailed), })?; return Err(WebError::Authorization(err.to_string())); } @@ -218,7 +218,7 @@ pub(crate) async fn authenticate( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserLoginFailed, + event: Box::new(ApiEventType::UserLoginFailed), })?; return Err(WebError::Authorization(err.to_string())); } @@ -310,7 +310,7 @@ pub(crate) async fn authenticate( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserLogin, + event: Box::new(ApiEventType::UserLogin), })?; Ok(( @@ -355,7 +355,7 @@ pub async fn logout( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserLogout, + event: Box::new(ApiEventType::UserLogout), })?; Ok((cookies, ApiResponse::default())) @@ -397,7 +397,7 @@ pub async fn mfa_disable( user.disable_mfa(&appstate.pool).await?; appstate.emit_event(ApiEvent { context, - event: ApiEventType::MfaDisabled, + event: Box::new(ApiEventType::MfaDisabled), })?; info!("Disabled MFA for user {}", user.username); Ok(ApiResponse::default()) @@ -442,6 +442,7 @@ pub async fn webauthn_init( /// Finish WebAuthn registration pub async fn webauthn_finish( session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Json(webauth_reg): Json, ) -> ApiResult { @@ -485,8 +486,9 @@ pub async fn webauthn_finish( .await? .ok_or(WebError::WebauthnRegistration("User not found".into()))?; let recovery_codes = RecoveryCodes::new(user.get_recovery_codes(&appstate.pool).await?); - let webauthn = WebAuthn::new(session.session.user_id, webauth_reg.name, &passkey)?; - webauthn.save(&appstate.pool).await?; + let webauthn = WebAuthn::new(session.session.user_id, webauth_reg.name, &passkey)? + .save(&appstate.pool) + .await?; if user.mfa_method == MFAMethod::None { send_mfa_configured_email( Some(&session.session), @@ -499,6 +501,10 @@ pub async fn webauthn_finish( } info!("Finished Webauthn registration for user {}", user.username); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::MfaSecurityKeyAdded { key: webauthn }), + })?; Ok(ApiResponse { json: json!(recovery_codes), @@ -561,9 +567,9 @@ pub async fn webauthn_end( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserMfaLogin { + event: Box::new(ApiEventType::UserMfaLogin { mfa_method: MFAMethod::Webauthn, - }, + }), })?; if let Some(openid_cookie) = private_cookies.get(SIGN_IN_COOKIE_NAME) { @@ -608,9 +614,9 @@ pub async fn webauthn_end( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserMfaLoginFailed { + event: Box::new(ApiEventType::UserMfaLoginFailed { mfa_method: MFAMethod::Webauthn, - }, + }), })?; } } @@ -657,7 +663,7 @@ pub async fn totp_enable( info!("Enabled TOTP for user {}", user.username); appstate.emit_event(ApiEvent { context, - event: ApiEventType::MfaTotpEnabled, + event: Box::new(ApiEventType::MfaTotpEnabled), })?; Ok(ApiResponse { json: json!(recovery_codes), @@ -681,7 +687,7 @@ pub async fn totp_disable( info!("Disabled TOTP for user {}", user.username); appstate.emit_event(ApiEvent { context, - event: ApiEventType::MfaTotpDisabled, + event: Box::new(ApiEventType::MfaTotpDisabled), })?; Ok(ApiResponse::default()) } @@ -714,9 +720,9 @@ pub async fn totp_code( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserMfaLogin { + event: Box::new(ApiEventType::UserMfaLogin { mfa_method: MFAMethod::OneTimePassword, - }, + }), })?; if let Some(openid_cookie) = private_cookies.get(SIGN_IN_COOKIE_NAME) { debug!("Found openid session cookie."); @@ -755,9 +761,9 @@ pub async fn totp_code( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserMfaLoginFailed { + event: Box::new(ApiEventType::UserMfaLoginFailed { mfa_method: MFAMethod::OneTimePassword, - }, + }), })?; Err(WebError::Authorization("Invalid TOTP code".into())) } @@ -813,7 +819,7 @@ pub async fn email_mfa_enable( info!("Enabled email MFA for user {}", user.username); appstate.emit_event(ApiEvent { context, - event: ApiEventType::MfaEmailEnabled, + event: Box::new(ApiEventType::MfaEmailEnabled), })?; Ok(ApiResponse { json: json!(recovery_codes), @@ -837,7 +843,7 @@ pub async fn email_mfa_disable( info!("Disabled email MFA for user {}", user.username); appstate.emit_event(ApiEvent { context, - event: ApiEventType::MfaEmailDisabled, + event: Box::new(ApiEventType::MfaEmailDisabled), })?; Ok(ApiResponse::default()) } @@ -889,9 +895,9 @@ pub async fn email_mfa_code( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserMfaLogin { + event: Box::new(ApiEventType::UserMfaLogin { mfa_method: MFAMethod::Email, - }, + }), })?; if let Some(openid_cookie) = private_cookies.get(SIGN_IN_COOKIE_NAME) { debug!("Found openid session cookie."); @@ -930,9 +936,9 @@ pub async fn email_mfa_code( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::UserMfaLoginFailed { + event: Box::new(ApiEventType::UserMfaLoginFailed { mfa_method: MFAMethod::Email, - }, + }), })?; Err(WebError::Authorization("Invalid email MFA code".into())) } @@ -972,7 +978,7 @@ pub async fn recovery_code( insecure_ip, user_agent.to_string(), ), - event: ApiEventType::RecoveryCodeUsed, + event: Box::new(ApiEventType::RecoveryCodeUsed), })?; if let Some(openid_cookie) = private_cookies.get(SIGN_IN_COOKIE_NAME) { debug!("Found OpenID session cookie."); diff --git a/crates/defguard_core/src/handlers/group.rs b/crates/defguard_core/src/handlers/group.rs index d17b528e95..45be113728 100644 --- a/crates/defguard_core/src/handlers/group.rs +++ b/crates/defguard_core/src/handlers/group.rs @@ -19,6 +19,7 @@ use crate::{ ldap_update_users_state, }, error::WebError, + events::{ApiEvent, ApiEventType, ApiRequestContext}, hashset, }; @@ -66,6 +67,7 @@ pub(crate) struct BulkAssignToGroupsRequest { pub(crate) async fn bulk_assign_to_groups( _role: AdminRole, State(appstate): State, + context: ApiRequestContext, Json(data): Json, ) -> Result { debug!("Assigning groups to users."); @@ -123,6 +125,10 @@ pub(crate) async fn bulk_assign_to_groups( ldap_update_users_state(users_to_maybe_update, &appstate.pool).await; info!("Assigned {} groups to {} users.", groups.len(), users.len()); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::GroupsBulkAssigned { users, groups }), + })?; Ok(ApiResponse { json: json!({}), @@ -307,6 +313,7 @@ pub(crate) async fn get_group( pub(crate) async fn create_group( _role: AdminRole, State(appstate): State, + context: ApiRequestContext, Json(group_info): Json, ) -> Result { debug!("Creating group {}", group_info.name); @@ -350,6 +357,10 @@ pub(crate) async fn create_group( } info!("Created group {}", group_info.name); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::GroupAdded { group }), + })?; Ok(ApiResponse { json: json!(group_info), @@ -382,6 +393,7 @@ pub(crate) async fn create_group( pub(crate) async fn modify_group( _role: AdminRole, State(appstate): State, + context: ApiRequestContext, Path(name): Path, Json(group_info): Json, ) -> Result { @@ -391,6 +403,8 @@ pub(crate) async fn modify_group( error!(msg); return Err(WebError::ObjectNotFound(msg)); }; + // store group before modifications + let before = group.clone(); let mut add_to_ldap_groups: HashMap<&User, HashSet<&str>> = HashMap::new(); let mut remove_from_ldap_groups: HashMap<&User, HashSet<&str>> = HashMap::new(); @@ -477,6 +491,13 @@ pub(crate) async fn modify_group( ldap_update_users_state(affected_users, &appstate.pool).await; info!("Modified group {}", group.name); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::GroupModified { + before, + after: group, + }), + })?; Ok(ApiResponse::default()) } @@ -507,6 +528,7 @@ pub(crate) async fn modify_group( pub(crate) async fn delete_group( _session: SessionInfo, State(appstate): State, + context: ApiRequestContext, Path(name): Path, ) -> Result { debug!("Deleting group {name}"); @@ -524,7 +546,7 @@ pub(crate) async fn delete_group( }); } } - group.delete(&appstate.pool).await?; + group.clone().delete(&appstate.pool).await?; ldap_delete_group(&name, &appstate.pool).await; // sync allowed devices for all locations @@ -532,6 +554,10 @@ pub(crate) async fn delete_group( WireguardNetwork::sync_all_networks(&mut conn, &appstate.wireguard_tx).await?; info!("Deleted group {name}"); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::GroupRemoved { group }), + })?; Ok(ApiResponse::default()) } else { let msg = format!("Failed to find group {name}"); @@ -568,6 +594,7 @@ pub(crate) async fn delete_group( pub(crate) async fn add_group_member( _role: AdminRole, State(appstate): State, + context: ApiRequestContext, Path(name): Path, Json(data): Json, ) -> Result { @@ -580,6 +607,10 @@ pub(crate) async fn add_group_member( let mut conn = appstate.pool.acquire().await?; WireguardNetwork::sync_all_networks(&mut conn, &appstate.wireguard_tx).await?; info!("Added user: {} to group: {}", user.username, group.name); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::GroupMemberAdded { group, user }), + })?; Ok(ApiResponse::default()) } else { error!("User not found {}", data.username); @@ -623,6 +654,7 @@ pub(crate) async fn add_group_member( pub(crate) async fn remove_group_member( _role: AdminRole, State(appstate): State, + context: ApiRequestContext, Path((name, username)): Path<(String, String)>, ) -> Result { if let Some(group) = Group::find_by_name(&appstate.pool, &name).await? { @@ -638,6 +670,10 @@ pub(crate) async fn remove_group_member( let mut conn = appstate.pool.acquire().await?; WireguardNetwork::sync_all_networks(&mut conn, &appstate.wireguard_tx).await?; info!("Removed user: {} from group: {}", user.username, group.name); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::GroupMemberRemoved { group, user }), + })?; Ok(ApiResponse { json: json!({}), status: StatusCode::OK, diff --git a/crates/defguard_core/src/handlers/network_devices.rs b/crates/defguard_core/src/handlers/network_devices.rs index 3405e97491..cdd3ccb42c 100644 --- a/crates/defguard_core/src/handlers/network_devices.rs +++ b/crates/defguard_core/src/handlers/network_devices.rs @@ -631,27 +631,25 @@ pub(crate) async fn add_network_device( session.session.device_info.clone().as_deref(), )?; + let result = AddNetworkDeviceResult { + config, + device: NetworkDeviceInfo::from_device(device.clone(), &mut transaction).await?, + }; + + transaction.commit().await?; + info!( "User {} added a new network device {device_name}.", user.username ); appstate.emit_event(ApiEvent { context, - event: ApiEventType::NetworkDeviceAdded { - device_id: device.id, - device_name: device.name.clone(), - location_id: network.id, - location: network.name, - }, + event: Box::new(ApiEventType::NetworkDeviceAdded { + device, + location: network, + }), })?; - let result = AddNetworkDeviceResult { - config, - device: NetworkDeviceInfo::from_device(device, &mut transaction).await?, - }; - - transaction.commit().await?; - Ok(ApiResponse { json: json!(result), status: StatusCode::CREATED, @@ -681,6 +679,8 @@ pub async fn modify_network_device( error!("Failed to update device {device_id}, device not found"); WebError::ObjectNotFound(format!("Device {device_id} not found")) })?; + // store device before modifications + let before = device.clone(); let device_network = device .find_network_device_networks(&mut *transaction) .await? @@ -733,19 +733,18 @@ pub async fn modify_network_device( device_network.name ); } + let network_device_info = + NetworkDeviceInfo::from_device(device.clone(), &mut transaction).await?; + transaction.commit().await?; - let network_device_info = NetworkDeviceInfo::from_device(device, &mut transaction).await?; appstate.emit_event(ApiEvent { context, - event: ApiEventType::NetworkDeviceModified { - device_id: network_device_info.id, - device_name: network_device_info.name.clone(), - location_id: device_network.id, - location: device_network.name, - }, + event: Box::new(ApiEventType::NetworkDeviceModified { + before, + after: device, + location: device_network, + }), })?; - transaction.commit().await?; - Ok(ApiResponse { json: json!(network_device_info), status: StatusCode::OK, diff --git a/crates/defguard_core/src/handlers/openid_clients.rs b/crates/defguard_core/src/handlers/openid_clients.rs index b9d2132ea5..7d71e359b5 100644 --- a/crates/defguard_core/src/handlers/openid_clients.rs +++ b/crates/defguard_core/src/handlers/openid_clients.rs @@ -12,23 +12,31 @@ use crate::{ oauth2client::{OAuth2Client, OAuth2ClientSafe}, NewOpenIDClient, }, + events::{ApiEvent, ApiEventType, ApiRequestContext}, }; pub async fn add_openid_client( _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Json(data): Json, ) -> ApiResult { - let client = OAuth2Client::from_new(data).save(&appstate.pool).await?; debug!( "User {} adding OpenID client {}", - session.user.username, client.name + session.user.username, data.name ); + let client = OAuth2Client::from_new(data).save(&appstate.pool).await?; info!( "User {} added OpenID client {}", session.user.username, client.name ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::OpenIdAppAdded { + app: client.clone(), + }), + })?; Ok(ApiResponse { json: json!(client), status: StatusCode::CREATED, @@ -36,9 +44,9 @@ pub async fn add_openid_client( } pub async fn list_openid_clients(_admin: AdminRole, State(appstate): State) -> ApiResult { - let openid_clients = OAuth2Client::all(&appstate.pool).await?; + let clients = OAuth2Client::all(&appstate.pool).await?; Ok(ApiResponse { - json: json!(openid_clients), + json: json!(clients), status: StatusCode::OK, }) } @@ -49,15 +57,15 @@ pub async fn get_openid_client( session: SessionInfo, ) -> ApiResult { match OAuth2Client::find_by_client_id(&appstate.pool, &client_id).await? { - Some(openid_client) => { + Some(client) => { if session.is_admin { Ok(ApiResponse { - json: json!(openid_client), + json: json!(client), status: StatusCode::OK, }) } else { Ok(ApiResponse { - json: json!(OAuth2ClientSafe::from(openid_client)), + json: json!(OAuth2ClientSafe::from(client)), status: StatusCode::OK, }) } @@ -72,6 +80,7 @@ pub async fn get_openid_client( pub async fn change_openid_client( _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(client_id): Path, Json(data): Json, @@ -81,16 +90,25 @@ pub async fn change_openid_client( session.user.username ); let status = match OAuth2Client::find_by_client_id(&appstate.pool, &client_id).await? { - Some(mut openid_client) => { - openid_client.name = data.name; - openid_client.redirect_uri = data.redirect_uri; - openid_client.enabled = data.enabled; - openid_client.scope = data.scope; - openid_client.save(&appstate.pool).await?; + Some(mut client) => { + // store client before mods + let before = client.clone(); + client.name = data.name; + client.redirect_uri = data.redirect_uri; + client.enabled = data.enabled; + client.scope = data.scope; + client.save(&appstate.pool).await?; info!( "User {} updated OpenID client {client_id} ({})", - session.user.username, openid_client.name + session.user.username, client.name ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::OpenIdAppModified { + before, + after: client, + }), + })?; StatusCode::OK } None => StatusCode::NOT_FOUND, @@ -104,6 +122,7 @@ pub async fn change_openid_client( pub async fn change_openid_client_state( _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(client_id): Path, Json(data): Json, @@ -113,13 +132,20 @@ pub async fn change_openid_client_state( session.user.username ); let status = match OAuth2Client::find_by_client_id(&appstate.pool, &client_id).await? { - Some(mut openid_client) => { - openid_client.enabled = data.enabled; - openid_client.save(&appstate.pool).await?; + Some(mut client) => { + client.enabled = data.enabled; + client.save(&appstate.pool).await?; info!( "User {} updated OpenID client {client_id} ({}) enabled state to {}", - session.user.username, openid_client.name, openid_client.enabled, + session.user.username, client.name, client.enabled, ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::OpenIdAppStateChanged { + enabled: client.enabled, + app: client, + }), + })?; StatusCode::OK } None => StatusCode::NOT_FOUND, @@ -133,6 +159,7 @@ pub async fn change_openid_client_state( pub async fn delete_openid_client( _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(client_id): Path, ) -> ApiResult { @@ -141,12 +168,16 @@ pub async fn delete_openid_client( session.user.username ); let status = match OAuth2Client::find_by_client_id(&appstate.pool, &client_id).await? { - Some(openid_client) => { - openid_client.delete(&appstate.pool).await?; + Some(client) => { + client.clone().delete(&appstate.pool).await?; info!( "User {} deleted OpenID client {client_id}", session.user.username ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::OpenIdAppRemoved { app: client }), + })?; StatusCode::OK } None => StatusCode::NOT_FOUND, diff --git a/crates/defguard_core/src/handlers/settings.rs b/crates/defguard_core/src/handlers/settings.rs index fad4ca348e..9bfb1caf24 100644 --- a/crates/defguard_core/src/handlers/settings.rs +++ b/crates/defguard_core/src/handlers/settings.rs @@ -17,6 +17,7 @@ use crate::{ license::update_cached_license, }, error::WebError, + events::{ApiEvent, ApiEventType, ApiRequestContext}, AppState, }; @@ -47,6 +48,7 @@ pub async fn get_settings(_admin: AdminRole, State(appstate): State) - pub async fn update_settings( _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Json(data): Json, ) -> ApiResult { @@ -57,6 +59,10 @@ pub async fn update_settings( update_current_settings(&appstate.pool, data).await?; info!("User {} updated settings", session.user.username); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::SettingsUpdated), + })?; Ok(ApiResponse::default()) } @@ -84,6 +90,7 @@ pub async fn set_default_branding( State(appstate): State, Path(_id): Path, // TODO: check with front-end and remove. session: SessionInfo, + context: ApiRequestContext, ) -> ApiResult { debug!( "User {} restoring default branding settings", @@ -100,6 +107,10 @@ pub async fn set_default_branding( "User {} restored default branding settings", session.user.username ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::SettingsDefaultBrandingRestored), + })?; Ok(ApiResponse { json: json!(settings), status: StatusCode::OK, @@ -113,6 +124,7 @@ pub async fn patch_settings( _admin: AdminRole, State(appstate): State, session: SessionInfo, + context: ApiRequestContext, Json(data): Json, ) -> ApiResult { debug!( @@ -150,6 +162,10 @@ pub async fn patch_settings( update_current_settings(&appstate.pool, settings).await?; info!("Admin {} patched settings.", session.user.username); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::SettingsUpdatedPartial), + })?; Ok(ApiResponse::default()) } diff --git a/crates/defguard_core/src/handlers/ssh_authorized_keys.rs b/crates/defguard_core/src/handlers/ssh_authorized_keys.rs index ef8075965e..7916dd667d 100644 --- a/crates/defguard_core/src/handlers/ssh_authorized_keys.rs +++ b/crates/defguard_core/src/handlers/ssh_authorized_keys.rs @@ -16,6 +16,7 @@ use crate::{ Group, Id, User, }, error::WebError, + events::{ApiEvent, ApiEventType, ApiRequestContext}, }; #[derive(Deserialize, Serialize)] @@ -156,6 +157,7 @@ pub struct AddAuthenticationKeyData { pub async fn add_authentication_key( State(appstate): State, session: SessionInfo, + context: ApiRequestContext, Path(username): Path, Json(data): Json, ) -> ApiResult { @@ -195,7 +197,7 @@ pub async fn add_authentication_key( return Err(WebError::BadRequest("Key already exists.".into())); } - AuthenticationKey::new( + let key = AuthenticationKey::new( user.id, trimmed_key.to_string(), Some(data.name.clone()), @@ -209,6 +211,10 @@ pub async fn add_authentication_key( "Added new key \"{}\" of type {:?} for user {username}", data.name, data.key_type ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::AuthenticationKeyAdded { key }), + })?; Ok(ApiResponse { json: json!({}), @@ -234,6 +240,7 @@ pub async fn fetch_authentication_keys( pub async fn delete_authentication_key( State(appstate): State, session: SessionInfo, + context: ApiRequestContext, Path((username, key_id)): Path<(String, i64)>, ) -> ApiResult { let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; @@ -241,7 +248,15 @@ pub async fn delete_authentication_key( if !session.is_admin && user.id != key.user_id { return Err(WebError::Forbidden(String::new())); } - key.delete(&appstate.pool).await?; + + info!( + "Removed key \"{:?}\"({}) of type {:?} for user {username}", + key.name, key.id, key.key_type + ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::AuthenticationKeyRemoved { key }), + })?; } else { error!("Key with id {} not found", key_id); return Err(WebError::BadRequest("Key not found".into())); @@ -261,6 +276,7 @@ pub struct RenameRequest { pub async fn rename_authentication_key( State(appstate): State, session: SessionInfo, + context: ApiRequestContext, Path((username, key_id)): Path<(String, i64)>, Json(data): Json, ) -> ApiResult { @@ -280,8 +296,21 @@ pub async fn rename_authentication_key( ); return Err(WebError::Forbidden(String::new())); } + let old_name = key.name.clone(); key.name = Some(data.name); key.save(&appstate.pool).await?; + info!( + "User {} renamed key {:?}({}) of user with id {}", + user.username, key.name, key.id, key.user_id + ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::AuthenticationKeyRenamed { + old_name, + new_name: key.name.clone(), + key, + }), + })?; } else { error!( "User {} tried to rename non-existing key with id {}", diff --git a/crates/defguard_core/src/handlers/user.rs b/crates/defguard_core/src/handlers/user.rs index 86fdd5bdc2..7fc510f566 100644 --- a/crates/defguard_core/src/handlers/user.rs +++ b/crates/defguard_core/src/handlers/user.rs @@ -342,9 +342,7 @@ pub async fn add_user( } appstate.emit_event(ApiEvent { context, - event: ApiEventType::UserAdded { - username: user.username, - }, + event: Box::new(ApiEventType::UserAdded { user }), })?; Ok(ApiResponse { json: json!(&user_info), @@ -383,12 +381,13 @@ pub async fn add_user( pub async fn start_enrollment( _role: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(username): Path, Json(data): Json, ) -> ApiResult { debug!( - "User {} has started a new enrollment request.", + "User {} creating enrollment token for user {username}.", session.user.username ); @@ -435,7 +434,7 @@ pub async fn start_enrollment( debug!("Transaction committed."); info!( - "The enrollment process for {} has ended with success.", + "User {} created enrollment token for user {username}.", session.user.username ); debug!( @@ -443,6 +442,10 @@ pub async fn start_enrollment( enrollment_token, config.enrollment_url.to_string() ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::EnrollmentTokenAdded { user }), + })?; Ok(ApiResponse { json: json!({"enrollment_token": enrollment_token, "enrollment_url": config.enrollment_url.to_string()}), @@ -479,6 +482,7 @@ pub async fn start_enrollment( )] pub async fn start_remote_desktop_configuration( session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(username): Path, Json(data): Json, @@ -539,6 +543,10 @@ pub async fn start_remote_desktop_configuration( desktop_configuration_token, config.enrollment_url.to_string() ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::ClientConfigurationTokenAdded { user }), + })?; Ok(ApiResponse { json: json!({"enrollment_token": desktop_configuration_token, "enrollment_url": config.enrollment_url.to_string()}), @@ -630,6 +638,8 @@ pub async fn modify_user( ) -> ApiResult { debug!("User {} updating user {username}", session.user.username); let mut user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; + // store user before mods + let before = user.clone(); let old_username = user.username.clone(); if let Err(err) = check_username(&user_info.username) { debug!("Username {} rejected: {err}", user_info.username); @@ -737,9 +747,10 @@ pub async fn modify_user( info!("User {} updated user {username}", session.user.username); appstate.emit_event(ApiEvent { context, - event: ApiEventType::UserModified { - username: user.username, - }, + event: Box::new(ApiEventType::UserModified { + before, + after: user, + }), })?; Ok(ApiResponse::default()) } @@ -796,7 +807,8 @@ pub async fn delete_user( } else { None }; - user.delete_and_cleanup(&mut transaction, &appstate.wireguard_tx) + user.clone() + .delete_and_cleanup(&mut transaction, &appstate.wireguard_tx) .await?; appstate.trigger_action(AppEvent::UserDeleted(username.clone())); @@ -809,7 +821,7 @@ pub async fn delete_user( info!("User {} deleted user {}", session.user.username, &username); appstate.emit_event(ApiEvent { context, - event: ApiEventType::UserRemoved { username }, + event: Box::new(ApiEventType::UserRemoved { user }), })?; Ok(ApiResponse::default()) } else { @@ -843,6 +855,7 @@ pub async fn delete_user( )] pub async fn change_self_password( session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Json(data): Json, ) -> ApiResult { @@ -869,6 +882,10 @@ pub async fn change_self_password( ldap_change_password(&mut user, &data.new_password, &appstate.pool).await; info!("User {} changed his password.", &user.username); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::PasswordChanged), + })?; Ok(ApiResponse { json: json!({}), @@ -907,6 +924,7 @@ pub async fn change_self_password( pub async fn change_password( _role: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(username): Path, Json(data): Json, @@ -949,6 +967,10 @@ pub async fn change_password( "Admin {} changed password for user {username}", session.user.username ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::PasswordChangedByAdmin { user }), + })?; Ok(ApiResponse::default()) } else { debug!("Can't change password for user {username}, user not found"); @@ -989,6 +1011,7 @@ pub async fn change_password( pub async fn reset_password( _role: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(username): Path, ) -> ApiResult { @@ -1058,6 +1081,10 @@ pub async fn reset_password( "Admin {} reset password for user {username}", session.user.username ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::PasswordReset { user }), + })?; Ok(ApiResponse::default()) } else { debug!("Can't reset password for user {username}, user not found"); @@ -1095,6 +1122,7 @@ pub async fn reset_password( )] pub async fn delete_security_key( session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path((username, id)): Path<(String, i64)>, ) -> ApiResult { @@ -1105,12 +1133,16 @@ pub async fn delete_security_key( let mut user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(webauthn) = WebAuthn::find_by_id(&appstate.pool, id).await? { if webauthn.user_id == user.id { - webauthn.delete(&appstate.pool).await?; + webauthn.clone().delete(&appstate.pool).await?; user.verify_mfa_state(&appstate.pool).await?; info!( "User {} deleted security key {id} for user {username}", session.user.username, ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::MfaSecurityKeyRemoved { key: webauthn }), + })?; Ok(ApiResponse::default()) } else { error!( diff --git a/crates/defguard_core/src/handlers/webhooks.rs b/crates/defguard_core/src/handlers/webhooks.rs index cc6513e36c..d30374f399 100644 --- a/crates/defguard_core/src/handlers/webhooks.rs +++ b/crates/defguard_core/src/handlers/webhooks.rs @@ -9,11 +9,13 @@ use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, db::WebHook, + events::{ApiEvent, ApiEventType, ApiRequestContext}, }; pub async fn add_webhook( _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Json(webhookdata): Json, ) -> ApiResult { @@ -21,10 +23,16 @@ pub async fn add_webhook( debug!("User {} adding webhook {url}", session.user.username); let webhook: WebHook = webhookdata.into(); let status = match webhook.save(&appstate.pool).await { - Ok(_) => StatusCode::CREATED, + Ok(webhook) => { + info!("User {} added webhook {url}", session.user.username); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::WebHookAdded { webhook }), + })?; + StatusCode::CREATED + } Err(_) => StatusCode::BAD_REQUEST, }; - info!("User {} added webhook {url}", session.user.username); Ok(ApiResponse { json: json!({}), @@ -62,6 +70,7 @@ pub async fn get_webhook( pub async fn change_webhook( _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(id): Path, Json(data): Json, @@ -69,6 +78,8 @@ pub async fn change_webhook( debug!("User {} updating webhook {id}", session.user.username); let status = match WebHook::find_by_id(&appstate.pool, id).await? { Some(mut webhook) => { + // store webhook before modifications + let before = webhook.clone(); webhook.url = data.url; webhook.description = data.description; webhook.token = data.token; @@ -78,11 +89,18 @@ pub async fn change_webhook( webhook.on_user_modified = data.on_user_modified; webhook.on_hwkey_provision = data.on_hwkey_provision; webhook.save(&appstate.pool).await?; + info!("User {} updated webhook {id}", session.user.username); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::WebHookModified { + before, + after: webhook, + }), + })?; StatusCode::OK } None => StatusCode::NOT_FOUND, }; - info!("User {} updated webhook {id}", session.user.username); Ok(ApiResponse { json: json!({}), @@ -93,18 +111,23 @@ pub async fn change_webhook( pub async fn delete_webhook( _admin: AdminRole, State(appstate): State, - Path(id): Path, session: SessionInfo, + context: ApiRequestContext, + Path(id): Path, ) -> ApiResult { debug!("User {} deleting webhook {id}", session.user.username); let status = match WebHook::find_by_id(&appstate.pool, id).await? { Some(webhook) => { - webhook.delete(&appstate.pool).await?; + webhook.clone().delete(&appstate.pool).await?; + info!("User {} deleted webhook {id}", session.user.username); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::WebHookRemoved { webhook }), + })?; StatusCode::OK } None => StatusCode::NOT_FOUND, }; - info!("User {} deleted webhook {id}", session.user.username); Ok(ApiResponse { json: json!({}), status, @@ -119,6 +142,7 @@ pub struct ChangeStateData { pub async fn change_enabled( _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Path(id): Path, Json(data): Json, @@ -131,14 +155,21 @@ pub async fn change_enabled( Some(mut webhook) => { webhook.enabled = data.enabled; webhook.save(&appstate.pool).await?; + info!( + "User {} changed webhook {id} enabled state to {}", + session.user.username, data.enabled + ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::WebHookStateChanged { + enabled: webhook.enabled, + webhook, + }), + })?; StatusCode::OK } None => StatusCode::NOT_FOUND, }; - info!( - "User {} changed webhook {id} enabled state to {}", - session.user.username, data.enabled - ); Ok(ApiResponse { json: json!({}), status, diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index 761008376a..95876b4cf0 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -129,6 +129,7 @@ pub(crate) async fn create_network( _role: AdminRole, State(appstate): State, session: SessionInfo, + context: ApiRequestContext, Json(data): Json, ) -> ApiResult { let network_name = data.name.clone(); @@ -170,6 +171,13 @@ pub(crate) async fn create_network( "User {} created WireGuard network {network_name}", session.user.username ); + + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::VpnLocationAdded { + location: network.clone(), + }), + })?; update_counts(&appstate.pool).await?; Ok(ApiResponse { @@ -205,6 +213,7 @@ pub(crate) async fn modify_network( Path(network_id): Path, State(appstate): State, session: SessionInfo, + context: ApiRequestContext, Json(data): Json, ) -> ApiResult { debug!( @@ -212,6 +221,8 @@ pub(crate) async fn modify_network( session.user.username ); let mut network = find_network(network_id, &appstate.pool).await?; + // store network before mods + let before = network.clone(); network.allowed_ips = data.parse_allowed_ips(); network.name = data.name; @@ -250,6 +261,13 @@ pub(crate) async fn modify_network( "User {} updated WireGuard network {network_id}", session.user.username, ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::VpnLocationModified { + before, + after: network.clone(), + }), + })?; Ok(ApiResponse { json: json!(network), status: StatusCode::OK, @@ -276,6 +294,7 @@ pub(crate) async fn delete_network( Path(network_id): Path, State(appstate): State, session: SessionInfo, + context: ApiRequestContext, ) -> ApiResult { debug!( "User {} deleting WireGuard network {network_id}", @@ -290,13 +309,17 @@ pub(crate) async fn delete_network( for device in network_devices { device.delete(&mut *transaction).await?; } - network.delete(&mut *transaction).await?; + network.clone().delete(&mut *transaction).await?; transaction.commit().await?; appstate.send_wireguard_event(GatewayEvent::NetworkDeleted(network_id, network_name)); info!( "User {} deleted WireGuard network {network_id}", session.user.username, ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::VpnLocationRemoved { location: network }), + })?; update_counts(&appstate.pool).await?; Ok(ApiResponse::default()) @@ -467,6 +490,7 @@ pub(crate) async fn remove_gateway( pub(crate) async fn import_network( _role: AdminRole, State(appstate): State, + context: ApiRequestContext, Json(data): Json, ) -> ApiResult { debug!("Importing network from config file"); @@ -507,7 +531,12 @@ pub(crate) async fn import_network( transaction.commit().await?; info!("Imported network {network} with {} devices", devices.len()); - + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::VpnLocationAdded { + location: network.clone(), + }), + })?; update_counts(&appstate.pool).await?; Ok(ApiResponse { @@ -762,21 +791,20 @@ pub(crate) async fn add_device( "User {} added device {device_name} for user {username}", session.user.username ); - // clone name to be used later - let device_name = device.name.clone(); - let device_id = device.id; - let result = AddDeviceResult { configs, device }; + let result = AddDeviceResult { + configs, + device: device.clone(), + }; update_counts(&appstate.pool).await?; appstate.emit_event(ApiEvent { context, - event: ApiEventType::UserDeviceAdded { - device_id, - owner: username, - device_name, - }, + event: Box::new(ApiEventType::UserDeviceAdded { + device, + owner: user, + }), })?; Ok(ApiResponse { @@ -831,6 +859,8 @@ pub(crate) async fn modify_device( ) -> ApiResult { debug!("User {} updating device {device_id}", session.user.username); let mut device = device_for_admin_or_self(&appstate.pool, &session, device_id).await?; + // store device before mods + let before = device.clone(); let networks = WireguardNetwork::all(&appstate.pool).await?; if networks.is_empty() { @@ -856,7 +886,6 @@ pub(crate) async fn modify_device( device.update_from(data); // clone to use later - let device_name = device.name.clone(); device.save(&appstate.pool).await?; @@ -882,14 +911,14 @@ pub(crate) async fn modify_device( info!("User {} updated device {device_id}", session.user.username); - let owner = device.get_owner(&appstate.pool).await?.username; + let owner = device.get_owner(&appstate.pool).await?; appstate.emit_event(ApiEvent { context, - event: ApiEventType::UserDeviceModified { + event: Box::new(ApiEventType::UserDeviceModified { owner, - device_id: device.id, - device_name, - }, + before, + after: device.clone(), + }), })?; Ok(ApiResponse { @@ -984,12 +1013,8 @@ pub(crate) async fn delete_device( // prepare device info let device_info = DeviceInfo::from_device(&mut *transaction, device.clone()).await?; - // clone to use later - let device_name = device.name.clone(); - let device_type = device.device_type.clone(); - // delete device before firewall config is generated - device.delete(&mut *transaction).await?; + device.clone().delete(&mut *transaction).await?; update_counts(&mut *transaction).await?; @@ -1017,20 +1042,12 @@ pub(crate) async fn delete_device( appstate.send_multiple_wireguard_events(events); // Emit event specific to the device type. - match device_type { + match device.device_type { DeviceType::User => { - let owner = device_info - .device - .get_owner(&mut *transaction) - .await? - .username; + let owner = device_info.device.get_owner(&mut *transaction).await?; appstate.emit_event(ApiEvent { context, - event: ApiEventType::UserDeviceRemoved { - device_name, - owner, - device_id, - }, + event: Box::new(ApiEventType::UserDeviceRemoved { device, owner }), })? } DeviceType::Network => { @@ -1041,18 +1058,19 @@ pub(crate) async fn delete_device( if let Some(location) = location { appstate.emit_event(ApiEvent { context, - event: ApiEventType::NetworkDeviceRemoved { - device_id, - device_name, - location_id: location.id, - location: location.name, - }, + event: Box::new(ApiEventType::NetworkDeviceRemoved { device, location }), })?; } else { - error!("Network device {device_name}({device_id}) is assigned to non-existent location {}", network_info.network_id); + error!( + "Network device {}({}) is assigned to non-existent location {}", + device.name, device.id, network_info.network_id + ); } } else { - error!("Network device {device_name}({device_id}) has no network assigned"); + error!( + "Network device {}({}) has no network assigned", + device.name, device.id + ); } } }; diff --git a/crates/defguard_event_logger/src/lib.rs b/crates/defguard_event_logger/src/lib.rs index 26b4d7ff38..500e975c06 100644 --- a/crates/defguard_event_logger/src/lib.rs +++ b/crates/defguard_event_logger/src/lib.rs @@ -10,11 +10,18 @@ use tracing::{debug, error, info, trace}; use defguard_core::db::{ models::audit_log::{ metadata::{ - AuditStreamMetadata, DeviceAddedMetadata, DeviceModifiedMetadata, - DeviceRemovedMetadata, EnrollmentDeviceAddedMetadata, MfaLoginMetadata, - MfaSecurityKeyAddedMetadata, MfaSecurityKeyRemovedMetadata, NetworkDeviceAddedMetadata, - NetworkDeviceModifiedMetadata, NetworkDeviceRemovedMetadata, UserAddedMetadata, - UserModifiedMetadata, UserRemovedMetadata, VpnClientMetadata, VpnClientMfaMetadata, + ApiTokenMetadata, ApiTokenRenamedMetadata, AuditStreamMetadata, + AuditStreamModifiedMetadata, AuthenticationKeyMetadata, + AuthenticationKeyRenamedMetadata, ClientConfigurationTokenMetadata, DeviceMetadata, + DeviceModifiedMetadata, EnrollmentDeviceAddedMetadata, EnrollmentTokenMetadata, + GroupAssignedMetadata, GroupMetadata, GroupModifiedMetadata, + GroupsBulkAssignedMetadata, MfaLoginMetadata, MfaSecurityKeyMetadata, + NetworkDeviceMetadata, NetworkDeviceModifiedMetadata, OpenIdAppMetadata, + OpenIdAppModifiedMetadata, OpenIdAppStateChangedMetadata, OpenIdProviderMetadata, + PasswordChangedByAdminMetadata, PasswordResetMetadata, UserMetadata, + UserModifiedMetadata, VpnClientMetadata, VpnClientMfaMetadata, VpnLocationMetadata, + VpnLocationModifiedMetadata, WebHookMetadata, WebHookModifiedMetadata, + WebHookStateChangedMetadata, }, AuditEvent, AuditModule, EventType, }, @@ -67,7 +74,7 @@ pub async fn run_event_logger( LoggerEvent::Defguard(event) => { let module = AuditModule::Defguard; - let (event_type, metadata) = match event { + let (event_type, metadata) = match *event { DefguardEvent::UserLogin => (EventType::UserLogin, None), DefguardEvent::UserLoginFailed => (EventType::UserLoginFailed, None), DefguardEvent::UserMfaLogin { mfa_method } => ( @@ -79,229 +86,312 @@ pub async fn run_event_logger( serde_json::to_value(MfaLoginMetadata { mfa_method }).ok(), ), DefguardEvent::UserLogout => (EventType::UserLogout, None), - DefguardEvent::UserDeviceAdded { - device_id: _, - device_name, - owner: _, - } => ( + DefguardEvent::UserDeviceAdded { owner, device } => ( EventType::DeviceAdded, - serde_json::to_value(DeviceAddedMetadata { - device_names: vec![device_name], + serde_json::to_value(DeviceMetadata { + owner: owner.into(), + device, }) .ok(), ), - DefguardEvent::UserDeviceRemoved { - device_id: _, - device_name, - owner: _, - } => ( + DefguardEvent::UserDeviceRemoved { owner, device } => ( EventType::DeviceRemoved, - serde_json::to_value(DeviceRemovedMetadata { - device_names: vec![device_name], + serde_json::to_value(DeviceMetadata { + owner: owner.into(), + device, }) .ok(), ), DefguardEvent::UserDeviceModified { - device_id: _, - device_name, - owner: _, + owner, + before, + after, } => ( EventType::DeviceModified, serde_json::to_value(DeviceModifiedMetadata { - device_names: vec![device_name], + owner: owner.into(), + before, + after, }) .ok(), ), DefguardEvent::RecoveryCodeUsed => (EventType::RecoveryCodeUsed, None), - DefguardEvent::PasswordChanged => todo!(), + DefguardEvent::PasswordChanged => (EventType::PasswordChanged, None), + DefguardEvent::PasswordChangedByAdmin { user } => ( + EventType::PasswordChangedByAdmin, + serde_json::to_value(PasswordChangedByAdminMetadata { + user: user.into(), + }) + .ok(), + ), DefguardEvent::MfaDisabled => (EventType::MfaDisabled, None), DefguardEvent::MfaTotpEnabled => (EventType::MfaTotpEnabled, None), DefguardEvent::MfaTotpDisabled => (EventType::MfaTotpDisabled, None), DefguardEvent::MfaEmailEnabled => (EventType::MfaEmailEnabled, None), DefguardEvent::MfaEmailDisabled => (EventType::MfaEmailDisabled, None), - DefguardEvent::MfaSecurityKeyAdded { key_id, key_name } => ( + DefguardEvent::MfaSecurityKeyAdded { key } => ( EventType::MfaSecurityKeyAdded, - serde_json::to_value(MfaSecurityKeyAddedMetadata { - key_id, - key_name, + serde_json::to_value(MfaSecurityKeyMetadata { key: key.into() }) + .ok(), + ), + DefguardEvent::MfaSecurityKeyRemoved { key } => ( + EventType::MfaSecurityKeyRemoved, + serde_json::to_value(MfaSecurityKeyMetadata { key: key.into() }) + .ok(), + ), + DefguardEvent::AuthenticationKeyAdded { key } => ( + EventType::AuthenticationKeyAdded, + serde_json::to_value(AuthenticationKeyMetadata { key: key.into() }) + .ok(), + ), + DefguardEvent::AuthenticationKeyRemoved { key } => ( + EventType::AuthenticationKeyRemoved, + serde_json::to_value(AuthenticationKeyMetadata { key: key.into() }) + .ok(), + ), + DefguardEvent::AuthenticationKeyRenamed { + key, + old_name, + new_name, + } => ( + EventType::AuthenticationKeyRenamed, + serde_json::to_value(AuthenticationKeyRenamedMetadata { + key: key.into(), + old_name, + new_name, }) .ok(), ), - DefguardEvent::MfaSecurityKeyRemoved { key_id, key_name } => ( - EventType::MfaSecurityKeyRemoved, - serde_json::to_value(MfaSecurityKeyRemovedMetadata { - key_id, - key_name, + DefguardEvent::ApiTokenAdded { owner, token } => ( + EventType::ApiTokenAdded, + serde_json::to_value(ApiTokenMetadata { + owner: owner.into(), + token: token.into(), + }) + .ok(), + ), + DefguardEvent::ApiTokenRemoved { owner, token } => ( + EventType::ApiTokenRemoved, + serde_json::to_value(ApiTokenMetadata { + owner: owner.into(), + token: token.into(), }) .ok(), ), - DefguardEvent::AuthenticationKeyAdded { - key_id: _, - key_name: _, - key_type: _, - } => todo!(), - DefguardEvent::AuthenticationKeyRemoved { - key_id: _, - key_name: _, - key_type: _, - } => todo!(), - DefguardEvent::AuthenticationKeyRenamed { - key_id: _, - key_name: _, - key_type: _, - } => todo!(), - DefguardEvent::ApiTokenAdded { - token_id: _, - token_name: _, - } => todo!(), - DefguardEvent::ApiTokenRemoved { - token_id: _, - token_name: _, - } => todo!(), DefguardEvent::ApiTokenRenamed { - token_id: _, - token_name: _, - } => todo!(), - DefguardEvent::UserAdded { username } => ( + owner, + token, + old_name, + new_name, + } => ( + EventType::ApiTokenRenamed, + serde_json::to_value(ApiTokenRenamedMetadata { + owner: owner.into(), + token: token.into(), + old_name, + new_name, + }) + .ok(), + ), + DefguardEvent::UserAdded { user } => ( EventType::UserAdded, - serde_json::to_value(UserAddedMetadata { username }).ok(), + serde_json::to_value(UserMetadata { user: user.into() }).ok(), ), - DefguardEvent::UserRemoved { username } => ( + DefguardEvent::UserRemoved { user } => ( EventType::UserRemoved, - serde_json::to_value(UserRemovedMetadata { username }).ok(), + serde_json::to_value(UserMetadata { user: user.into() }).ok(), ), - DefguardEvent::UserModified { username } => ( + DefguardEvent::UserModified { before, after } => ( EventType::UserModified, - serde_json::to_value(UserModifiedMetadata { username }).ok(), - ), - DefguardEvent::UserDisabled { username: _ } => todo!(), - DefguardEvent::NetworkDeviceAdded { - device_id, - device_name, - location_id, - location, - } => ( - EventType::NetworkDeviceAdded, - serde_json::to_value(NetworkDeviceAddedMetadata { - device_id, - device_name, - location_id, - location, + serde_json::to_value(UserModifiedMetadata { + before: before.into(), + after: after.into(), }) .ok(), ), - DefguardEvent::NetworkDeviceRemoved { - device_id, - device_name, - location_id, - location, - } => ( + DefguardEvent::NetworkDeviceAdded { device, location } => ( + EventType::NetworkDeviceAdded, + serde_json::to_value(NetworkDeviceMetadata { device, location }) + .ok(), + ), + DefguardEvent::NetworkDeviceRemoved { device, location } => ( EventType::NetworkDeviceRemoved, - serde_json::to_value(NetworkDeviceRemovedMetadata { - device_id, - device_name, - location_id, - location, - }) - .ok(), + serde_json::to_value(NetworkDeviceMetadata { device, location }) + .ok(), ), DefguardEvent::NetworkDeviceModified { - device_id, - device_name, - location_id, location, + before, + after, } => ( EventType::NetworkDeviceModified, serde_json::to_value(NetworkDeviceModifiedMetadata { - device_id, - device_name, - location_id, + before, + after, location, }) .ok(), ), - DefguardEvent::VpnLocationAdded { - location_id: _, - location_name: _, - } => todo!(), - DefguardEvent::VpnLocationRemoved { - location_id: _, - location_name: _, - } => todo!(), - DefguardEvent::VpnLocationModified { - location_id: _, - location_name: _, - } => todo!(), - DefguardEvent::OpenIdAppAdded { - app_id: _, - app_name: _, - } => todo!(), - DefguardEvent::OpenIdAppRemoved { - app_id: _, - app_name: _, - } => todo!(), - DefguardEvent::OpenIdAppModified { - app_id: _, - app_name: _, - } => todo!(), - DefguardEvent::OpenIdAppDisabled { - app_id: _, - app_name: _, - } => todo!(), - DefguardEvent::OpenIdProviderAdded { - provider_id: _, - provider_name: _, - } => todo!(), - DefguardEvent::OpenIdProviderRemoved { - provider_id: _, - provider_name: _, - } => todo!(), - DefguardEvent::SettingsUpdated => todo!(), - DefguardEvent::SettingsUpdatedPartial => todo!(), - DefguardEvent::SettingsDefaultBrandingRestored => todo!(), - DefguardEvent::AuditStreamCreated { - stream_id, - stream_name, - } => ( + DefguardEvent::VpnLocationAdded { location } => ( + EventType::VpnLocationAdded, + serde_json::to_value(VpnLocationMetadata { location }).ok(), + ), + DefguardEvent::VpnLocationRemoved { location } => ( + EventType::VpnLocationRemoved, + serde_json::to_value(VpnLocationMetadata { location }).ok(), + ), + DefguardEvent::VpnLocationModified { before, after } => ( + EventType::VpnLocationModified, + serde_json::to_value(VpnLocationModifiedMetadata { before, after }) + .ok(), + ), + DefguardEvent::OpenIdAppAdded { app } => ( + EventType::OpenIdAppAdded, + serde_json::to_value(OpenIdAppMetadata { app: app.into() }).ok(), + ), + DefguardEvent::OpenIdAppRemoved { app } => ( + EventType::OpenIdAppRemoved, + serde_json::to_value(OpenIdAppMetadata { app: app.into() }).ok(), + ), + DefguardEvent::OpenIdAppModified { before, after } => ( + EventType::OpenIdAppModified, + serde_json::to_value(OpenIdAppModifiedMetadata { + before: before.into(), + after: after.into(), + }) + .ok(), + ), + DefguardEvent::OpenIdAppStateChanged { app, enabled } => ( + EventType::OpenIdAppStateChanged, + serde_json::to_value(OpenIdAppStateChangedMetadata { + app: app.into(), + enabled, + }) + .ok(), + ), + DefguardEvent::OpenIdProviderModified { provider } => ( + EventType::OpenIdProviderModified, + serde_json::to_value(OpenIdProviderMetadata { + provider: provider.into(), + }) + .ok(), + ), + DefguardEvent::OpenIdProviderRemoved { provider } => ( + EventType::OpenIdProviderRemoved, + serde_json::to_value(OpenIdProviderMetadata { + provider: provider.into(), + }) + .ok(), + ), + DefguardEvent::SettingsUpdated => (EventType::SettingsUpdated, None), + DefguardEvent::SettingsUpdatedPartial => { + (EventType::SettingsUpdatedPartial, None) + } + DefguardEvent::SettingsDefaultBrandingRestored => { + (EventType::SettingsDefaultBrandingRestored, None) + } + DefguardEvent::AuditStreamCreated { stream } => ( EventType::AuditStreamCreated, serde_json::to_value(AuditStreamMetadata { - id: stream_id, - name: stream_name, + stream: stream.into(), }) .ok(), ), - DefguardEvent::AuditStreamRemoved { - stream_id, - stream_name, - } => ( + DefguardEvent::AuditStreamRemoved { stream } => ( EventType::AuditStreamRemoved, serde_json::to_value(AuditStreamMetadata { - id: stream_id, - name: stream_name, + stream: stream.into(), }) .ok(), ), - DefguardEvent::AuditStreamModified { - stream_id, - stream_name, - } => ( + DefguardEvent::AuditStreamModified { before, after } => ( EventType::AuditStreamModified, - serde_json::to_value(AuditStreamMetadata { - id: stream_id, - name: stream_name, + serde_json::to_value(AuditStreamModifiedMetadata { + before: before.into(), + after: after.into(), }) .ok(), ), + DefguardEvent::GroupsBulkAssigned { users, groups } => ( + EventType::GroupsBulkAssigned, + serde_json::to_value(GroupsBulkAssignedMetadata { + users: users.into_iter().map(Into::into).collect(), + groups, + }) + .ok(), + ), + DefguardEvent::GroupAdded { group } => ( + EventType::GroupAdded, + serde_json::to_value(GroupMetadata { group }).ok(), + ), + DefguardEvent::GroupModified { before, after } => ( + EventType::GroupModified, + serde_json::to_value(GroupModifiedMetadata { before, after }).ok(), + ), + DefguardEvent::GroupRemoved { group } => ( + EventType::GroupRemoved, + serde_json::to_value(GroupMetadata { group }).ok(), + ), + DefguardEvent::GroupMemberAdded { group, user } => ( + EventType::GroupMemberAdded, + serde_json::to_value(GroupAssignedMetadata { + group, + user: user.into(), + }) + .ok(), + ), + DefguardEvent::GroupMemberRemoved { group, user } => ( + EventType::GroupMemberRemoved, + serde_json::to_value(GroupAssignedMetadata { + group, + user: user.into(), + }) + .ok(), + ), + DefguardEvent::WebHookAdded { webhook } => ( + EventType::WebHookAdded, + serde_json::to_value(WebHookMetadata { webhook }).ok(), + ), + DefguardEvent::WebHookModified { before, after } => ( + EventType::WebHookModified, + serde_json::to_value(WebHookModifiedMetadata { before, after }) + .ok(), + ), + DefguardEvent::WebHookRemoved { webhook } => ( + EventType::WebHookRemoved, + serde_json::to_value(WebHookMetadata { webhook }).ok(), + ), + DefguardEvent::WebHookStateChanged { webhook, enabled } => ( + EventType::WebHookStateChanged, + serde_json::to_value(WebHookStateChangedMetadata { + webhook, + enabled, + }) + .ok(), + ), + DefguardEvent::PasswordReset { user } => ( + EventType::PasswordReset, + serde_json::to_value(PasswordResetMetadata { user: user.into() }) + .ok(), + ), + DefguardEvent::ClientConfigurationTokenAdded { user } => ( + EventType::ClientConfigurationTokenAdded, + serde_json::to_value(ClientConfigurationTokenMetadata { + user: user.into(), + }) + .ok(), + ), + DefguardEvent::EnrollmentTokenAdded { user } => ( + EventType::EnrollmentTokenAdded, + serde_json::to_value(EnrollmentTokenMetadata { user: user.into() }) + .ok(), + ), }; (module, event_type, metadata) } - LoggerEvent::Client(_event) => { - let _module = AuditModule::Client; - unimplemented!() - } LoggerEvent::Vpn(event) => { let module = AuditModule::Vpn; - let (event_type, metadata) = match event { + let (event_type, metadata) = match *event { VpnEvent::MfaFailed { location, device, @@ -345,7 +435,7 @@ pub async fn run_event_logger( } LoggerEvent::Enrollment(event) => { let module = AuditModule::Enrollment; - let (event_type, metadata) = match event { + let (event_type, metadata) = match *event { EnrollmentEvent::EnrollmentStarted => { (EventType::EnrollmentStarted, None) } @@ -365,6 +455,11 @@ pub async fn run_event_logger( EnrollmentEvent::PasswordResetCompleted => { (EventType::PasswordResetCompleted, None) } + EnrollmentEvent::TokenAdded { user } => ( + EventType::EnrollmentTokenAdded, + serde_json::to_value(EnrollmentTokenMetadata { user: user.into() }) + .ok(), + ), }; (module, event_type, metadata) } diff --git a/crates/defguard_event_logger/src/message.rs b/crates/defguard_event_logger/src/message.rs index 06d2b178a0..476e5c0ae9 100644 --- a/crates/defguard_event_logger/src/message.rs +++ b/crates/defguard_event_logger/src/message.rs @@ -3,7 +3,11 @@ use std::net::IpAddr; use defguard_core::{ db::{ - models::authentication_key::AuthenticationKeyType, Device, Id, MFAMethod, WireguardNetwork, + models::{authentication_key::AuthenticationKey, oauth2client::OAuth2Client}, + Device, Group, Id, MFAMethod, User, WebAuthn, WebHook, WireguardNetwork, + }, + enterprise::db::models::{ + api_tokens::ApiToken, audit_stream::AuditStream, openid_provider::OpenIdProvider, }, events::{ApiRequestContext, BidiRequestContext, GrpcRequestContext, InternalEventContext}, }; @@ -21,13 +25,10 @@ impl EventLoggerMessage { } /// Possible audit event types split by module -// TODO: remove lint override below once all events are updated to pass whole objects -#[allow(clippy::large_enum_variant)] pub enum LoggerEvent { - Defguard(DefguardEvent), - Client(ClientEvent), - Vpn(VpnEvent), - Enrollment(EnrollmentEvent), + Defguard(Box), + Vpn(Box), + Enrollment(Box), } /// Shared context that's included in all events @@ -89,8 +90,8 @@ impl From for EventContext { /// Represents audit events related to actions performed in Web UI pub enum DefguardEvent { - // authentication UserLogin, + UserLogout, UserLoginFailed, UserMfaLogin { mfa_method: MFAMethod, @@ -98,161 +99,171 @@ pub enum DefguardEvent { UserMfaLoginFailed { mfa_method: MFAMethod, }, - UserLogout, RecoveryCodeUsed, + PasswordChangedByAdmin { + user: User, + }, PasswordChanged, - // user MFA management + PasswordReset { + user: User, + }, MfaDisabled, - MfaTotpEnabled, MfaTotpDisabled, - MfaEmailEnabled, + MfaTotpEnabled, MfaEmailDisabled, + MfaEmailEnabled, MfaSecurityKeyAdded { - key_id: Id, - key_name: String, + key: WebAuthn, }, MfaSecurityKeyRemoved { - key_id: Id, - key_name: String, - }, - // authentication key management - AuthenticationKeyAdded { - key_id: Id, - key_name: String, - key_type: AuthenticationKeyType, - }, - AuthenticationKeyRemoved { - key_id: Id, - key_name: String, - key_type: AuthenticationKeyType, - }, - AuthenticationKeyRenamed { - key_id: Id, - key_name: String, - key_type: AuthenticationKeyType, - }, - // API token management - ApiTokenAdded { - token_id: Id, - token_name: String, - }, - ApiTokenRemoved { - token_id: Id, - token_name: String, - }, - ApiTokenRenamed { - token_id: Id, - token_name: String, + key: WebAuthn, }, - // user management UserAdded { - username: String, + user: User, }, UserRemoved { - username: String, + user: User, }, UserModified { - username: String, - }, - UserDisabled { - username: String, + before: User, + after: User, }, - // device management UserDeviceAdded { - device_id: Id, - device_name: String, - owner: String, + owner: User, + device: Device, }, UserDeviceRemoved { - device_id: Id, - device_name: String, - owner: String, + owner: User, + device: Device, }, UserDeviceModified { - device_id: Id, - device_name: String, - owner: String, + owner: User, + before: Device, + after: Device, }, NetworkDeviceAdded { - device_id: Id, - device_name: String, - location_id: Id, - location: String, + device: Device, + location: WireguardNetwork, }, NetworkDeviceRemoved { - device_id: Id, - device_name: String, - location_id: Id, - location: String, + device: Device, + location: WireguardNetwork, }, NetworkDeviceModified { - device_id: Id, - device_name: String, - location_id: Id, - location: String, + before: Device, + after: Device, + location: WireguardNetwork, + }, + AuditStreamCreated { + stream: AuditStream, + }, + AuditStreamModified { + before: AuditStream, + after: AuditStream, + }, + AuditStreamRemoved { + stream: AuditStream, }, - // VPN location management VpnLocationAdded { - location_id: Id, - location_name: String, + location: WireguardNetwork, }, VpnLocationRemoved { - location_id: Id, - location_name: String, + location: WireguardNetwork, }, VpnLocationModified { - location_id: Id, - location_name: String, + before: WireguardNetwork, + after: WireguardNetwork, + }, + ApiTokenAdded { + owner: User, + token: ApiToken, + }, + ApiTokenRemoved { + owner: User, + token: ApiToken, + }, + ApiTokenRenamed { + owner: User, + token: ApiToken, + old_name: String, + new_name: String, }, - // OpenID app management OpenIdAppAdded { - app_id: Id, - app_name: String, + app: OAuth2Client, }, OpenIdAppRemoved { - app_id: Id, - app_name: String, + app: OAuth2Client, }, OpenIdAppModified { - app_id: Id, - app_name: String, + before: OAuth2Client, + after: OAuth2Client, }, - OpenIdAppDisabled { - app_id: Id, - app_name: String, + OpenIdAppStateChanged { + app: OAuth2Client, + enabled: bool, }, - // OpenID provider management - OpenIdProviderAdded { - provider_id: Id, - provider_name: String, + OpenIdProviderModified { + provider: OpenIdProvider, }, OpenIdProviderRemoved { - provider_id: Id, - provider_name: String, + provider: OpenIdProvider, }, - // settings management SettingsUpdated, SettingsUpdatedPartial, SettingsDefaultBrandingRestored, - // audit stream management - AuditStreamCreated { - stream_id: Id, - stream_name: String, + GroupsBulkAssigned { + users: Vec>, + groups: Vec>, }, - AuditStreamModified { - stream_id: Id, - stream_name: String, + GroupAdded { + group: Group, }, - AuditStreamRemoved { - stream_id: Id, - stream_name: String, + GroupModified { + before: Group, + after: Group, + }, + GroupRemoved { + group: Group, + }, + GroupMemberAdded { + group: Group, + user: User, + }, + GroupMemberRemoved { + group: Group, + user: User, + }, + WebHookAdded { + webhook: WebHook, + }, + WebHookModified { + before: WebHook, + after: WebHook, + }, + WebHookRemoved { + webhook: WebHook, + }, + WebHookStateChanged { + webhook: WebHook, + enabled: bool, + }, + AuthenticationKeyAdded { + key: AuthenticationKey, + }, + AuthenticationKeyRemoved { + key: AuthenticationKey, + }, + AuthenticationKeyRenamed { + key: AuthenticationKey, + old_name: Option, + new_name: Option, + }, + EnrollmentTokenAdded { + user: User, + }, + ClientConfigurationTokenAdded { + user: User, }, -} - -/// Represents audit events related to client applications -pub enum ClientEvent { - DesktopClientActivated { device_id: Id, device_name: String }, - DesktopClientUpdated { device_id: Id, device_name: String }, } /// Represents audit events related to VPN @@ -289,4 +300,5 @@ pub enum EnrollmentEvent { PasswordResetRequested, PasswordResetStarted, PasswordResetCompleted, + TokenAdded { user: User }, } diff --git a/crates/defguard_event_router/src/events.rs b/crates/defguard_event_router/src/events.rs index 02c0fb10b8..ca02c1a570 100644 --- a/crates/defguard_event_router/src/events.rs +++ b/crates/defguard_event_router/src/events.rs @@ -4,9 +4,6 @@ use defguard_core::events::{ApiEvent, BidiStreamEvent, GrpcEvent, InternalEvent} /// /// System components can send events to the event router through their own event channels. /// The enum itself is organized based on event source to make splitting logic into smaller chunks easier. -// TODO: remove lint override below once all events are updated to pass whole objects -#[allow(clippy::large_enum_variant)] -#[derive(Debug)] pub enum Event { Api(ApiEvent), Grpc(GrpcEvent), diff --git a/crates/defguard_event_router/src/handlers/api.rs b/crates/defguard_event_router/src/handlers/api.rs index 4c4c9dd4e9..2d3ef26379 100644 --- a/crates/defguard_event_router/src/handlers/api.rs +++ b/crates/defguard_event_router/src/handlers/api.rs @@ -1,139 +1,237 @@ use defguard_core::events::{ApiEvent, ApiEventType}; -use defguard_event_logger::message::{DefguardEvent, LoggerEvent}; +use defguard_event_logger::message::{DefguardEvent, EnrollmentEvent, LoggerEvent}; use tracing::debug; use crate::{error::EventRouterError, EventRouter}; impl EventRouter { pub(crate) fn handle_api_event(&self, event: ApiEvent) -> Result<(), EventRouterError> { - debug!("Processing API event: {event:?}"); - let logger_event = match event.event { - ApiEventType::UserLogin => LoggerEvent::Defguard(DefguardEvent::UserLogin), - ApiEventType::UserLoginFailed => LoggerEvent::Defguard(DefguardEvent::UserLoginFailed), + debug!("Processing API event"); + let logger_event = match *event.event { + ApiEventType::UserLogin => LoggerEvent::Defguard(Box::new(DefguardEvent::UserLogin)), + ApiEventType::UserLoginFailed => { + LoggerEvent::Defguard(Box::new(DefguardEvent::UserLoginFailed)) + } ApiEventType::UserMfaLogin { mfa_method } => { - LoggerEvent::Defguard(DefguardEvent::UserMfaLogin { mfa_method }) + LoggerEvent::Defguard(Box::new(DefguardEvent::UserMfaLogin { mfa_method })) } ApiEventType::UserMfaLoginFailed { mfa_method } => { - LoggerEvent::Defguard(DefguardEvent::UserMfaLoginFailed { mfa_method }) + LoggerEvent::Defguard(Box::new(DefguardEvent::UserMfaLoginFailed { mfa_method })) } ApiEventType::RecoveryCodeUsed => { - LoggerEvent::Defguard(DefguardEvent::RecoveryCodeUsed) + LoggerEvent::Defguard(Box::new(DefguardEvent::RecoveryCodeUsed)) + } + ApiEventType::UserLogout => LoggerEvent::Defguard(Box::new(DefguardEvent::UserLogout)), + ApiEventType::UserAdded { user } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::UserAdded { user })) + } + ApiEventType::UserRemoved { user } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::UserRemoved { user })) + } + ApiEventType::UserModified { before, after } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::UserModified { before, after })) } - ApiEventType::UserLogout => LoggerEvent::Defguard(DefguardEvent::UserLogout), - ApiEventType::UserAdded { username } => { - LoggerEvent::Defguard(DefguardEvent::UserAdded { username }) + ApiEventType::MfaDisabled => { + LoggerEvent::Defguard(Box::new(DefguardEvent::MfaDisabled)) } - ApiEventType::UserRemoved { username } => { - LoggerEvent::Defguard(DefguardEvent::UserRemoved { username }) + ApiEventType::MfaTotpDisabled => { + LoggerEvent::Defguard(Box::new(DefguardEvent::MfaTotpDisabled)) } - ApiEventType::UserModified { username } => { - LoggerEvent::Defguard(DefguardEvent::UserModified { username }) + ApiEventType::MfaTotpEnabled => { + LoggerEvent::Defguard(Box::new(DefguardEvent::MfaTotpEnabled)) } - ApiEventType::MfaDisabled => LoggerEvent::Defguard(DefguardEvent::MfaDisabled), - ApiEventType::MfaTotpDisabled => LoggerEvent::Defguard(DefguardEvent::MfaTotpDisabled), - ApiEventType::MfaTotpEnabled => LoggerEvent::Defguard(DefguardEvent::MfaTotpEnabled), ApiEventType::MfaEmailDisabled => { - LoggerEvent::Defguard(DefguardEvent::MfaEmailDisabled) + LoggerEvent::Defguard(Box::new(DefguardEvent::MfaEmailDisabled)) } - ApiEventType::MfaEmailEnabled => LoggerEvent::Defguard(DefguardEvent::MfaEmailEnabled), - ApiEventType::MfaSecurityKeyAdded { key_id, key_name } => { - LoggerEvent::Defguard(DefguardEvent::MfaSecurityKeyAdded { key_id, key_name }) + ApiEventType::MfaEmailEnabled => { + LoggerEvent::Defguard(Box::new(DefguardEvent::MfaEmailEnabled)) } - ApiEventType::MfaSecurityKeyRemoved { key_id, key_name } => { - LoggerEvent::Defguard(DefguardEvent::MfaSecurityKeyRemoved { key_id, key_name }) + ApiEventType::MfaSecurityKeyAdded { key } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::MfaSecurityKeyAdded { key })) + } + ApiEventType::MfaSecurityKeyRemoved { key } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::MfaSecurityKeyRemoved { key })) + } + ApiEventType::UserDeviceAdded { owner, device } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::UserDeviceAdded { device, owner })) + } + ApiEventType::UserDeviceRemoved { owner, device } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::UserDeviceRemoved { device, owner })) } - ApiEventType::UserDeviceAdded { - owner, - device_id, - device_name, - } => LoggerEvent::Defguard(DefguardEvent::UserDeviceAdded { - device_name, - device_id, - owner, - }), - ApiEventType::UserDeviceRemoved { - owner, - device_id, - device_name, - } => LoggerEvent::Defguard(DefguardEvent::UserDeviceRemoved { - device_name, - device_id, - owner, - }), ApiEventType::UserDeviceModified { owner, - device_id, - device_name, - } => LoggerEvent::Defguard(DefguardEvent::UserDeviceModified { - device_name, - device_id, + before, + after, + } => LoggerEvent::Defguard(Box::new(DefguardEvent::UserDeviceModified { owner, - }), - ApiEventType::NetworkDeviceAdded { - device_id, - device_name, - location_id, - location, - } => LoggerEvent::Defguard(DefguardEvent::NetworkDeviceAdded { - device_id, - device_name, - location_id, - location, - }), + before, + after, + })), + ApiEventType::NetworkDeviceAdded { device, location } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::NetworkDeviceAdded { + device, + location, + })) + } ApiEventType::NetworkDeviceModified { - device_id, - device_name, - location_id, - location, - } => LoggerEvent::Defguard(DefguardEvent::NetworkDeviceModified { - device_id, - device_name, - location_id, + before, + after, location, - }), - ApiEventType::NetworkDeviceRemoved { - device_id, - device_name, - location_id, + } => LoggerEvent::Defguard(Box::new(DefguardEvent::NetworkDeviceModified { + before, + after, location, - } => LoggerEvent::Defguard(DefguardEvent::NetworkDeviceRemoved { - device_id, - device_name, - location_id, - location, - }), - ApiEventType::AuditStreamCreated { - stream_id, - stream_name, - } => { + })), + ApiEventType::NetworkDeviceRemoved { device, location } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::NetworkDeviceRemoved { + device, + location, + })) + } + ApiEventType::AuditStreamCreated { stream } => { // Notify stream manager about configuration changes self.audit_stream_reload_notify.notify_waiters(); - LoggerEvent::Defguard(DefguardEvent::AuditStreamCreated { - stream_id, - stream_name, - }) - } - ApiEventType::AuditStreamModified { - stream_id, - stream_name, - } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::AuditStreamCreated { stream })) + } + ApiEventType::AuditStreamModified { before, after } => { // Notify stream manager about configuration changes self.audit_stream_reload_notify.notify_waiters(); - LoggerEvent::Defguard(DefguardEvent::AuditStreamModified { - stream_id, - stream_name, - }) - } - ApiEventType::AuditStreamRemoved { - stream_id, - stream_name, - } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::AuditStreamModified { + before, + after, + })) + } + ApiEventType::AuditStreamRemoved { stream } => { // Notify stream manager about configuration changes self.audit_stream_reload_notify.notify_waiters(); - LoggerEvent::Defguard(DefguardEvent::AuditStreamRemoved { - stream_id, - stream_name, - }) + LoggerEvent::Defguard(Box::new(DefguardEvent::AuditStreamRemoved { stream })) + } + ApiEventType::VpnLocationAdded { location } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::VpnLocationAdded { location })) + } + ApiEventType::VpnLocationRemoved { location } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::VpnLocationRemoved { location })) + } + ApiEventType::VpnLocationModified { before, after } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::VpnLocationModified { + before, + after, + })) + } + ApiEventType::ApiTokenAdded { owner, token } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::ApiTokenAdded { owner, token })) + } + ApiEventType::ApiTokenRemoved { owner, token } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::ApiTokenRemoved { owner, token })) + } + ApiEventType::ApiTokenRenamed { + owner, + token, + old_name, + new_name, + } => LoggerEvent::Defguard(Box::new(DefguardEvent::ApiTokenRenamed { + owner, + token, + old_name, + new_name, + })), + ApiEventType::OpenIdAppAdded { app } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::OpenIdAppAdded { app })) + } + ApiEventType::OpenIdAppRemoved { app } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::OpenIdAppRemoved { app })) + } + ApiEventType::OpenIdAppModified { before, after } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::OpenIdAppModified { before, after })) + } + ApiEventType::OpenIdAppStateChanged { app, enabled } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::OpenIdAppStateChanged { + app, + enabled, + })) + } + ApiEventType::OpenIdProviderRemoved { provider } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::OpenIdProviderRemoved { provider })) + } + ApiEventType::OpenIdProviderModified { provider } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::OpenIdProviderModified { provider })) + } + ApiEventType::SettingsUpdated => { + LoggerEvent::Defguard(Box::new(DefguardEvent::SettingsUpdated)) + } + ApiEventType::SettingsUpdatedPartial => { + LoggerEvent::Defguard(Box::new(DefguardEvent::SettingsUpdatedPartial)) + } + ApiEventType::SettingsDefaultBrandingRestored => { + LoggerEvent::Defguard(Box::new(DefguardEvent::SettingsDefaultBrandingRestored)) + } + ApiEventType::GroupsBulkAssigned { users, groups } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::GroupsBulkAssigned { + users, + groups, + })) + } + ApiEventType::GroupAdded { group } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::GroupAdded { group })) + } + ApiEventType::GroupModified { before, after } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::GroupModified { before, after })) + } + ApiEventType::GroupRemoved { group } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::GroupRemoved { group })) + } + ApiEventType::GroupMemberAdded { group, user } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::GroupMemberAdded { group, user })) + } + ApiEventType::GroupMemberRemoved { group, user } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::GroupMemberRemoved { group, user })) + } + ApiEventType::WebHookAdded { webhook } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::WebHookAdded { webhook })) + } + ApiEventType::WebHookModified { before, after } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::WebHookModified { before, after })) + } + ApiEventType::WebHookRemoved { webhook } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::WebHookRemoved { webhook })) + } + ApiEventType::WebHookStateChanged { webhook, enabled } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::WebHookStateChanged { + webhook, + enabled, + })) + } + ApiEventType::AuthenticationKeyAdded { key } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::AuthenticationKeyAdded { key })) + } + ApiEventType::AuthenticationKeyRemoved { key } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::AuthenticationKeyRemoved { key })) + } + ApiEventType::AuthenticationKeyRenamed { + key, + old_name, + new_name, + } => LoggerEvent::Defguard(Box::new(DefguardEvent::AuthenticationKeyRenamed { + key, + old_name, + new_name, + })), + ApiEventType::EnrollmentTokenAdded { user } => { + LoggerEvent::Enrollment(Box::new(EnrollmentEvent::TokenAdded { user })) + } + ApiEventType::PasswordChanged => { + LoggerEvent::Defguard(Box::new(DefguardEvent::PasswordChanged)) + } + ApiEventType::PasswordChangedByAdmin { user } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::PasswordChangedByAdmin { user })) + } + ApiEventType::PasswordReset { user } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::PasswordReset { user })) + } + ApiEventType::ClientConfigurationTokenAdded { user } => { + LoggerEvent::Defguard(Box::new(DefguardEvent::ClientConfigurationTokenAdded { + user, + })) } }; self.log_event(event.context.into(), logger_event) diff --git a/crates/defguard_event_router/src/handlers/bidi.rs b/crates/defguard_event_router/src/handlers/bidi.rs index b6a3681dc7..5c00c1895b 100644 --- a/crates/defguard_event_router/src/handlers/bidi.rs +++ b/crates/defguard_event_router/src/handlers/bidi.rs @@ -12,49 +12,51 @@ impl EventRouter { let BidiStreamEvent { context, event } = event; let logger_event = match event { - BidiStreamEventType::Enrollment(event) => match event { + BidiStreamEventType::Enrollment(event) => match *event { events::EnrollmentEvent::EnrollmentStarted => { - LoggerEvent::Enrollment(EnrollmentEvent::EnrollmentStarted) + LoggerEvent::Enrollment(Box::new(EnrollmentEvent::EnrollmentStarted)) } events::EnrollmentEvent::EnrollmentCompleted => { - LoggerEvent::Enrollment(EnrollmentEvent::EnrollmentCompleted) + LoggerEvent::Enrollment(Box::new(EnrollmentEvent::EnrollmentCompleted)) } events::EnrollmentEvent::EnrollmentDeviceAdded { device } => { - LoggerEvent::Enrollment(EnrollmentEvent::EnrollmentDeviceAdded { device }) + LoggerEvent::Enrollment(Box::new(EnrollmentEvent::EnrollmentDeviceAdded { + device, + })) } }, - BidiStreamEventType::PasswordReset(event) => match event { + BidiStreamEventType::PasswordReset(event) => match *event { PasswordResetEvent::PasswordResetRequested => { - LoggerEvent::Enrollment(EnrollmentEvent::PasswordResetRequested) + LoggerEvent::Enrollment(Box::new(EnrollmentEvent::PasswordResetRequested)) } PasswordResetEvent::PasswordResetStarted => { - LoggerEvent::Enrollment(EnrollmentEvent::PasswordResetStarted) + LoggerEvent::Enrollment(Box::new(EnrollmentEvent::PasswordResetStarted)) } PasswordResetEvent::PasswordResetCompleted => { - LoggerEvent::Enrollment(EnrollmentEvent::PasswordResetCompleted) + LoggerEvent::Enrollment(Box::new(EnrollmentEvent::PasswordResetCompleted)) } }, - BidiStreamEventType::DesktopClientMfa(event) => match event { + BidiStreamEventType::DesktopClientMfa(event) => match *event { DesktopClientMfaEvent::Connected { location, device, method, - } => LoggerEvent::Vpn(VpnEvent::ConnectedToMfaLocation { + } => LoggerEvent::Vpn(Box::new(VpnEvent::ConnectedToMfaLocation { location, device, method, - }), + })), DesktopClientMfaEvent::Failed { location, device, method, - } => LoggerEvent::Vpn(VpnEvent::MfaFailed { + } => LoggerEvent::Vpn(Box::new(VpnEvent::MfaFailed { location, device, method, - }), + })), }, }; diff --git a/crates/defguard_event_router/src/handlers/grpc.rs b/crates/defguard_event_router/src/handlers/grpc.rs index 282de5c990..2f4b62f93e 100644 --- a/crates/defguard_event_router/src/handlers/grpc.rs +++ b/crates/defguard_event_router/src/handlers/grpc.rs @@ -18,7 +18,7 @@ impl EventRouter { } => { self.log_event( context.into(), - LoggerEvent::Vpn(VpnEvent::ConnectedToLocation { location, device }), + LoggerEvent::Vpn(Box::new(VpnEvent::ConnectedToLocation { location, device })), )?; } GrpcEvent::ClientDisconnected { @@ -28,7 +28,10 @@ impl EventRouter { } => { self.log_event( context.into(), - LoggerEvent::Vpn(VpnEvent::DisconnectedFromLocation { location, device }), + LoggerEvent::Vpn(Box::new(VpnEvent::DisconnectedFromLocation { + location, + device, + })), )?; } } diff --git a/crates/defguard_event_router/src/handlers/internal.rs b/crates/defguard_event_router/src/handlers/internal.rs index d07cfb4baf..2dee7b1951 100644 --- a/crates/defguard_event_router/src/handlers/internal.rs +++ b/crates/defguard_event_router/src/handlers/internal.rs @@ -16,7 +16,10 @@ impl EventRouter { let device = context.device.clone(); self.log_event( context.into(), - LoggerEvent::Vpn(VpnEvent::DisconnectedFromMfaLocation { device, location }), + LoggerEvent::Vpn(Box::new(VpnEvent::DisconnectedFromMfaLocation { + device, + location, + })), ) } } diff --git a/crates/defguard_event_router/src/lib.rs b/crates/defguard_event_router/src/lib.rs index bd4f4e0130..c2c1b73de1 100644 --- a/crates/defguard_event_router/src/lib.rs +++ b/crates/defguard_event_router/src/lib.rs @@ -148,7 +148,7 @@ impl EventRouter { }, }; - debug!("Received event: {event:?}"); + debug!("Received event"); // Route the event to the appropriate handler match event { diff --git a/web/src/i18n/en/index.ts b/web/src/i18n/en/index.ts index c2df2e3aff..9de72cd3f3 100644 --- a/web/src/i18n/en/index.ts +++ b/web/src/i18n/en/index.ts @@ -1363,7 +1363,7 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do modulesVisibility: { header: 'Modules Visibility', helper: `

- If your not using some modules you can disable their visibility. + Hide unused modules.

Read more in documentation. @@ -2575,12 +2575,44 @@ This alias is currently in use by the following rule(s) and cannot be deleted. T vpn_client_connected_mfa: 'VPN client connected to MFA location', vpn_client_disconnected_mfa: 'VPN client disconnected from MFA location', vpn_client_mfa_failed: 'VPN client failed MFA authentication', + enrollment_token_added: 'Enrollment token added', enrollment_started: 'Enrollment started', enrollment_device_added: 'Device added', enrollment_completed: 'Enrollment completed', password_reset_requested: 'Password reset requested', password_reset_started: 'Password reset started', password_reset_completed: 'Password reset completed', + vpn_location_added: 'VPN location added', + vpn_location_removed: 'VPN location removed', + vpn_location_modified: 'VPN location modified', + api_token_added: 'API token added', + api_token_removed: 'API token removed', + api_token_renamed: 'API token renamed', + open_id_app_added: 'OpenID app added', + open_id_app_removed: 'OpenID app removed', + open_id_app_modified: 'OpenID app modified', + open_id_app_state_changed: 'OpenID app state changed', + open_id_provider_removed: 'OpenID provider removed', + open_id_provider_modified: 'OpenID provider modified', + settings_updated: 'Settings updated', + settings_updated_partial: 'Settings partially updated', + settings_default_branding_restored: 'Default branding restored', + groups_bulk_assigned: 'Groups bulk assigned', + group_added: 'Group added', + group_modified: 'Group modified', + group_removed: 'Group removed', + group_member_added: 'Group member added', + group_member_removed: 'Group member removed', + web_hook_added: 'Webhook added', + web_hook_modified: 'Webhook modified', + web_hook_removed: 'Webhook removed', + authentication_key_added: 'Authentication key added', + authentication_key_removed: 'Authentication key removed', + authentication_key_renamed: 'Authentication key renamed', + password_changed: 'Password changed', + password_changed_by_admin: 'Password changed by admin', + password_reset: 'Password reset', + client_configuration_token_added: 'Client configuration token added', }, auditModule: { defguard: 'Defguard', diff --git a/web/src/i18n/i18n-types.ts b/web/src/i18n/i18n-types.ts index 8aadfa84ac..fc1b5afde4 100644 --- a/web/src/i18n/i18n-types.ts +++ b/web/src/i18n/i18n-types.ts @@ -3329,7 +3329,7 @@ type RootTranslation = { header: string /** * <​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​I​f​ ​y​o​u​r​ ​n​o​t​ ​u​s​i​n​g​ ​s​o​m​e​ ​m​o​d​u​l​e​s​ ​y​o​u​ ​c​a​n​ ​d​i​s​a​b​l​e​ ​t​h​e​i​r​ ​v​i​s​i​b​i​l​i​t​y​.​ + ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​H​i​d​e​ ​u​n​u​s​e​d​ ​m​o​d​u​l​e​s​.​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​>​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​a​ ​h​r​e​f​=​"​{​d​o​c​u​m​e​n​t​a​t​i​o​n​L​i​n​k​}​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​R​e​a​d​ ​m​o​r​e​ ​i​n​ ​d​o​c​u​m​e​n​t​a​t​i​o​n​.​ @@ -6214,6 +6214,10 @@ type RootTranslation = { * V​P​N​ ​c​l​i​e​n​t​ ​f​a​i​l​e​d​ ​M​F​A​ ​a​u​t​h​e​n​t​i​c​a​t​i​o​n */ vpn_client_mfa_failed: string + /** + * E​n​r​o​l​l​m​e​n​t​ ​t​o​k​e​n​ ​a​d​d​e​d + */ + enrollment_token_added: string /** * E​n​r​o​l​l​m​e​n​t​ ​s​t​a​r​t​e​d */ @@ -6238,6 +6242,130 @@ type RootTranslation = { * P​a​s​s​w​o​r​d​ ​r​e​s​e​t​ ​c​o​m​p​l​e​t​e​d */ password_reset_completed: string + /** + * V​P​N​ ​l​o​c​a​t​i​o​n​ ​a​d​d​e​d + */ + vpn_location_added: string + /** + * V​P​N​ ​l​o​c​a​t​i​o​n​ ​r​e​m​o​v​e​d + */ + vpn_location_removed: string + /** + * V​P​N​ ​l​o​c​a​t​i​o​n​ ​m​o​d​i​f​i​e​d + */ + vpn_location_modified: string + /** + * A​P​I​ ​t​o​k​e​n​ ​a​d​d​e​d + */ + api_token_added: string + /** + * A​P​I​ ​t​o​k​e​n​ ​r​e​m​o​v​e​d + */ + api_token_removed: string + /** + * A​P​I​ ​t​o​k​e​n​ ​r​e​n​a​m​e​d + */ + api_token_renamed: string + /** + * O​p​e​n​I​D​ ​a​p​p​ ​a​d​d​e​d + */ + open_id_app_added: string + /** + * O​p​e​n​I​D​ ​a​p​p​ ​r​e​m​o​v​e​d + */ + open_id_app_removed: string + /** + * O​p​e​n​I​D​ ​a​p​p​ ​m​o​d​i​f​i​e​d + */ + open_id_app_modified: string + /** + * O​p​e​n​I​D​ ​a​p​p​ ​s​t​a​t​e​ ​c​h​a​n​g​e​d + */ + open_id_app_state_changed: string + /** + * O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​ ​r​e​m​o​v​e​d + */ + open_id_provider_removed: string + /** + * O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​ ​m​o​d​i​f​i​e​d + */ + open_id_provider_modified: string + /** + * S​e​t​t​i​n​g​s​ ​u​p​d​a​t​e​d + */ + settings_updated: string + /** + * S​e​t​t​i​n​g​s​ ​p​a​r​t​i​a​l​l​y​ ​u​p​d​a​t​e​d + */ + settings_updated_partial: string + /** + * D​e​f​a​u​l​t​ ​b​r​a​n​d​i​n​g​ ​r​e​s​t​o​r​e​d + */ + settings_default_branding_restored: string + /** + * G​r​o​u​p​s​ ​b​u​l​k​ ​a​s​s​i​g​n​e​d + */ + groups_bulk_assigned: string + /** + * G​r​o​u​p​ ​a​d​d​e​d + */ + group_added: string + /** + * G​r​o​u​p​ ​m​o​d​i​f​i​e​d + */ + group_modified: string + /** + * G​r​o​u​p​ ​r​e​m​o​v​e​d + */ + group_removed: string + /** + * G​r​o​u​p​ ​m​e​m​b​e​r​ ​a​d​d​e​d + */ + group_member_added: string + /** + * G​r​o​u​p​ ​m​e​m​b​e​r​ ​r​e​m​o​v​e​d + */ + group_member_removed: string + /** + * W​e​b​h​o​o​k​ ​a​d​d​e​d + */ + web_hook_added: string + /** + * W​e​b​h​o​o​k​ ​m​o​d​i​f​i​e​d + */ + web_hook_modified: string + /** + * W​e​b​h​o​o​k​ ​r​e​m​o​v​e​d + */ + web_hook_removed: string + /** + * A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​k​e​y​ ​a​d​d​e​d + */ + authentication_key_added: string + /** + * A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​k​e​y​ ​r​e​m​o​v​e​d + */ + authentication_key_removed: string + /** + * A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​k​e​y​ ​r​e​n​a​m​e​d + */ + authentication_key_renamed: string + /** + * P​a​s​s​w​o​r​d​ ​c​h​a​n​g​e​d + */ + password_changed: string + /** + * P​a​s​s​w​o​r​d​ ​c​h​a​n​g​e​d​ ​b​y​ ​a​d​m​i​n + */ + password_changed_by_admin: string + /** + * P​a​s​s​w​o​r​d​ ​r​e​s​e​t + */ + password_reset: string + /** + * C​l​i​e​n​t​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​t​o​k​e​n​ ​a​d​d​e​d + */ + client_configuration_token_added: string } auditModule: { /** @@ -9542,7 +9670,7 @@ export type TranslationFunctions = { header: () => LocalizedString /** *

- If your not using some modules you can disable their visibility. + Hide unused modules.

Read more in documentation. @@ -12399,6 +12527,10 @@ export type TranslationFunctions = { * VPN client failed MFA authentication */ vpn_client_mfa_failed: () => LocalizedString + /** + * Enrollment token added + */ + enrollment_token_added: () => LocalizedString /** * Enrollment started */ @@ -12423,6 +12555,130 @@ export type TranslationFunctions = { * Password reset completed */ password_reset_completed: () => LocalizedString + /** + * VPN location added + */ + vpn_location_added: () => LocalizedString + /** + * VPN location removed + */ + vpn_location_removed: () => LocalizedString + /** + * VPN location modified + */ + vpn_location_modified: () => LocalizedString + /** + * API token added + */ + api_token_added: () => LocalizedString + /** + * API token removed + */ + api_token_removed: () => LocalizedString + /** + * API token renamed + */ + api_token_renamed: () => LocalizedString + /** + * OpenID app added + */ + open_id_app_added: () => LocalizedString + /** + * OpenID app removed + */ + open_id_app_removed: () => LocalizedString + /** + * OpenID app modified + */ + open_id_app_modified: () => LocalizedString + /** + * OpenID app state changed + */ + open_id_app_state_changed: () => LocalizedString + /** + * OpenID provider removed + */ + open_id_provider_removed: () => LocalizedString + /** + * OpenID provider modified + */ + open_id_provider_modified: () => LocalizedString + /** + * Settings updated + */ + settings_updated: () => LocalizedString + /** + * Settings partially updated + */ + settings_updated_partial: () => LocalizedString + /** + * Default branding restored + */ + settings_default_branding_restored: () => LocalizedString + /** + * Groups bulk assigned + */ + groups_bulk_assigned: () => LocalizedString + /** + * Group added + */ + group_added: () => LocalizedString + /** + * Group modified + */ + group_modified: () => LocalizedString + /** + * Group removed + */ + group_removed: () => LocalizedString + /** + * Group member added + */ + group_member_added: () => LocalizedString + /** + * Group member removed + */ + group_member_removed: () => LocalizedString + /** + * Webhook added + */ + web_hook_added: () => LocalizedString + /** + * Webhook modified + */ + web_hook_modified: () => LocalizedString + /** + * Webhook removed + */ + web_hook_removed: () => LocalizedString + /** + * Authentication key added + */ + authentication_key_added: () => LocalizedString + /** + * Authentication key removed + */ + authentication_key_removed: () => LocalizedString + /** + * Authentication key renamed + */ + authentication_key_renamed: () => LocalizedString + /** + * Password changed + */ + password_changed: () => LocalizedString + /** + * Password changed by admin + */ + password_changed_by_admin: () => LocalizedString + /** + * Password reset + */ + password_reset: () => LocalizedString + /** + * Client configuration token added + */ + client_configuration_token_added: () => LocalizedString } auditModule: { /** diff --git a/web/src/pages/activity/types.ts b/web/src/pages/activity/types.ts index 2128737656..741f412422 100644 --- a/web/src/pages/activity/types.ts +++ b/web/src/pages/activity/types.ts @@ -38,12 +38,44 @@ export type AuditEventType = | 'vpn_client_connected_mfa' | 'vpn_client_disconnected_mfa' | 'vpn_client_mfa_failed' + | 'enrollment_token_added' | 'enrollment_started' | 'enrollment_device_added' | 'enrollment_completed' | 'password_reset_requested' | 'password_reset_started' - | 'password_reset_completed'; + | 'password_reset_completed' + | 'vpn_location_added' + | 'vpn_location_removed' + | 'vpn_location_modified' + | 'api_token_added' + | 'api_token_removed' + | 'api_token_renamed' + | 'open_id_app_added' + | 'open_id_app_removed' + | 'open_id_app_modified' + | 'open_id_app_state_changed' + | 'open_id_provider_removed' + | 'open_id_provider_modified' + | 'settings_updated' + | 'settings_updated_partial' + | 'settings_default_branding_restored' + | 'groups_bulk_assigned' + | 'group_added' + | 'group_modified' + | 'group_removed' + | 'group_member_added' + | 'group_member_removed' + | 'web_hook_added' + | 'web_hook_modified' + | 'web_hook_removed' + | 'authentication_key_added' + | 'authentication_key_removed' + | 'authentication_key_renamed' + | 'password_changed' + | 'password_changed_by_admin' + | 'password_reset' + | 'client_configuration_token_added'; export const auditEventTypeValues: AuditEventType[] = [ 'user_login', @@ -76,10 +108,42 @@ export const auditEventTypeValues: AuditEventType[] = [ 'vpn_client_connected_mfa', 'vpn_client_disconnected_mfa', 'vpn_client_mfa_failed', + 'enrollment_token_added', 'enrollment_started', 'enrollment_device_added', 'enrollment_completed', 'password_reset_requested', 'password_reset_started', 'password_reset_completed', + 'vpn_location_added', + 'vpn_location_removed', + 'vpn_location_modified', + 'api_token_added', + 'api_token_removed', + 'api_token_renamed', + 'open_id_app_added', + 'open_id_app_removed', + 'open_id_app_modified', + 'open_id_app_state_changed', + 'open_id_provider_removed', + 'open_id_provider_modified', + 'settings_updated', + 'settings_updated_partial', + 'settings_default_branding_restored', + 'groups_bulk_assigned', + 'group_added', + 'group_modified', + 'group_removed', + 'group_member_added', + 'group_member_removed', + 'web_hook_added', + 'web_hook_modified', + 'web_hook_removed', + 'authentication_key_added', + 'authentication_key_removed', + 'authentication_key_renamed', + 'password_changed', + 'password_changed_by_admin', + 'password_reset', + 'client_configuration_token_added', ];