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

Filter by extension

Filter by extension

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

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

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

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

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

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

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

6 changes: 3 additions & 3 deletions crates/defguard_core/src/db/models/activity_log/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
enterprise::db::models::{
activity_log_stream::{ActivityLogStream, ActivityLogStreamType},
api_tokens::ApiToken,
enterprise_settings::EnterpriseSettings,
enterprise_settings::EnterpriseSettingsInfo,
openid_provider::{DirectorySyncTarget, DirectorySyncUserBehavior, OpenIdProvider},
snat::UserSnatBinding,
},
Expand Down Expand Up @@ -357,8 +357,8 @@ pub struct SettingsUpdateMetadata {

#[derive(Serialize)]
pub struct EnterpriseSettingsUpdateMetadata {
pub before: EnterpriseSettings,
pub after: EnterpriseSettings,
pub before: EnterpriseSettingsInfo,
pub after: EnterpriseSettingsInfo,
}

#[derive(Serialize)]
Expand Down
111 changes: 110 additions & 1 deletion crates/defguard_core/src/enterprise/db/models/enterprise_settings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use defguard_common::db::models::Settings;
use defguard_common::db::{Id, models::Settings};
use sqlx::{PgExecutor, Type, query, query_as};
use struct_patch::Patch;

Expand All @@ -19,6 +19,33 @@ pub struct EnterpriseSettings {
pub display_password_reset: bool,
}

#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct GroupClientTrafficPolicies {
pub none: Vec<Id>,
pub disable_all_traffic: Vec<Id>,
pub force_all_traffic: Vec<Id>,
}

#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct EnterpriseSettingsInfo {
#[serde(flatten)]
pub settings: EnterpriseSettings,
pub group_client_traffic_policies: GroupClientTrafficPolicies,
}

impl EnterpriseSettingsInfo {
#[must_use]
pub fn new(
settings: EnterpriseSettings,
group_client_traffic_policies: GroupClientTrafficPolicies,
) -> Self {
Self {
settings,
group_client_traffic_policies,
}
}
}

// We want to be conscious of what the defaults are here
#[allow(clippy::derivable_impls)]
impl Default for EnterpriseSettings {
Expand Down Expand Up @@ -107,3 +134,85 @@ pub enum ClientTrafficPolicy {
/// Clients are forced to route all traffic through the VPN.
ForceAllTraffic,
}

/// Resolves group policies over the instance-level policy.
///
/// A configured group policy takes precedence over the instance policy. When a user belongs to
/// multiple groups, disabling all traffic takes precedence over forcing all traffic. An explicit
/// `None` group policy takes precedence over the instance policy when no restrictive group policy
/// is present.
#[must_use]
pub fn resolve_client_traffic_policy(
instance_policy: ClientTrafficPolicy,
group_policies: impl IntoIterator<Item = ClientTrafficPolicy>,
) -> ClientTrafficPolicy {
let mut has_force_all_traffic = false;
let mut has_none = false;

for policy in group_policies {
match policy {
ClientTrafficPolicy::DisableAllTraffic => {
return ClientTrafficPolicy::DisableAllTraffic;
}
ClientTrafficPolicy::ForceAllTraffic => has_force_all_traffic = true,
ClientTrafficPolicy::None => has_none = true,
}
}

if has_force_all_traffic {
ClientTrafficPolicy::ForceAllTraffic
} else if has_none {
ClientTrafficPolicy::None
} else {
instance_policy
}
}

#[cfg(test)]
mod tests {
use super::{ClientTrafficPolicy, resolve_client_traffic_policy};

#[test]
fn instance_policy_is_used_without_group_overrides() {
assert_eq!(
resolve_client_traffic_policy(ClientTrafficPolicy::ForceAllTraffic, []),
ClientTrafficPolicy::ForceAllTraffic
);
}

#[test]
fn group_policy_overrides_instance_policy() {
assert_eq!(
resolve_client_traffic_policy(
ClientTrafficPolicy::DisableAllTraffic,
[ClientTrafficPolicy::ForceAllTraffic]
),
ClientTrafficPolicy::ForceAllTraffic
);
}

#[test]
fn disable_all_traffic_wins_conflicting_group_policies() {
assert_eq!(
resolve_client_traffic_policy(
ClientTrafficPolicy::ForceAllTraffic,
[
ClientTrafficPolicy::ForceAllTraffic,
ClientTrafficPolicy::DisableAllTraffic,
]
),
ClientTrafficPolicy::DisableAllTraffic
);
}

#[test]
fn explicit_none_group_policy_overrides_instance_policy() {
assert_eq!(
resolve_client_traffic_policy(
ClientTrafficPolicy::ForceAllTraffic,
[ClientTrafficPolicy::None]
),
ClientTrafficPolicy::None
);
}
}
Loading
Loading