diff --git a/.sqlx/query-35950188e70029c8314055621857ea0091c302e9c27de798a2f50901ffd747e0.json b/.sqlx/query-48223bdfca4f2bd5c7cd14594f5d81db67dfb1c0c2c01cf1fd9c8df421f366ef.json similarity index 57% rename from .sqlx/query-35950188e70029c8314055621857ea0091c302e9c27de798a2f50901ffd747e0.json rename to .sqlx/query-48223bdfca4f2bd5c7cd14594f5d81db67dfb1c0c2c01cf1fd9c8df421f366ef.json index 3fa364d320..2eb5251a9f 100644 --- a/.sqlx/query-35950188e70029c8314055621857ea0091c302e9c27de798a2f50901ffd747e0.json +++ b/.sqlx/query-48223bdfca4f2bd5c7cd14594f5d81db67dfb1c0c2c01cf1fd9c8df421f366ef.json @@ -1,16 +1,17 @@ { "db_name": "PostgreSQL", - "query": "UPDATE \"enterprisesettings\" SET \"admin_device_management\" = $2,\"only_client_activation\" = $3 WHERE id = $1", + "query": "UPDATE \"enterprisesettings\" SET \"admin_device_management\" = $2,\"disable_all_traffic\" = $3,\"only_client_activation\" = $4 WHERE id = $1", "describe": { "columns": [], "parameters": { "Left": [ "Int8", "Bool", + "Bool", "Bool" ] }, "nullable": [] }, - "hash": "35950188e70029c8314055621857ea0091c302e9c27de798a2f50901ffd747e0" + "hash": "48223bdfca4f2bd5c7cd14594f5d81db67dfb1c0c2c01cf1fd9c8df421f366ef" } diff --git a/.sqlx/query-6332fe33694ebd090eba2ef0777ea91e86978f827cbff94a6a28338a7b0bf6ee.json b/.sqlx/query-68fc762354c30e66a83ba47746a0d4707e9b8e9829965bf6e4b334d1ab41bb6b.json similarity index 59% rename from .sqlx/query-6332fe33694ebd090eba2ef0777ea91e86978f827cbff94a6a28338a7b0bf6ee.json rename to .sqlx/query-68fc762354c30e66a83ba47746a0d4707e9b8e9829965bf6e4b334d1ab41bb6b.json index b861d4934a..010db59ccd 100644 --- a/.sqlx/query-6332fe33694ebd090eba2ef0777ea91e86978f827cbff94a6a28338a7b0bf6ee.json +++ b/.sqlx/query-68fc762354c30e66a83ba47746a0d4707e9b8e9829965bf6e4b334d1ab41bb6b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"admin_device_management\",\"only_client_activation\" FROM \"enterprisesettings\" WHERE id = $1", + "query": "SELECT id \"id?\", \"admin_device_management\",\"disable_all_traffic\",\"only_client_activation\" FROM \"enterprisesettings\" WHERE id = $1", "describe": { "columns": [ { @@ -15,6 +15,11 @@ }, { "ordinal": 2, + "name": "disable_all_traffic", + "type_info": "Bool" + }, + { + "ordinal": 3, "name": "only_client_activation", "type_info": "Bool" } @@ -25,10 +30,11 @@ ] }, "nullable": [ + false, false, false, false ] }, - "hash": "6332fe33694ebd090eba2ef0777ea91e86978f827cbff94a6a28338a7b0bf6ee" + "hash": "68fc762354c30e66a83ba47746a0d4707e9b8e9829965bf6e4b334d1ab41bb6b" } diff --git a/.sqlx/query-bd0665f9401b5919b55e8b68ef238b514c12becf8e9dfbb8641b02e4deb8b5e5.json b/.sqlx/query-834d5b83aebddf1e23511025d5f98b8a69e1227e8bfbd2b1d0110c8def72f8aa.json similarity index 64% rename from .sqlx/query-bd0665f9401b5919b55e8b68ef238b514c12becf8e9dfbb8641b02e4deb8b5e5.json rename to .sqlx/query-834d5b83aebddf1e23511025d5f98b8a69e1227e8bfbd2b1d0110c8def72f8aa.json index b2dae11eb9..96a0c7b4e7 100644 --- a/.sqlx/query-bd0665f9401b5919b55e8b68ef238b514c12becf8e9dfbb8641b02e4deb8b5e5.json +++ b/.sqlx/query-834d5b83aebddf1e23511025d5f98b8a69e1227e8bfbd2b1d0110c8def72f8aa.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO \"enterprisesettings\" (\"admin_device_management\",\"only_client_activation\") VALUES ($1,$2) RETURNING id", + "query": "INSERT INTO \"enterprisesettings\" (\"admin_device_management\",\"disable_all_traffic\",\"only_client_activation\") VALUES ($1,$2,$3) RETURNING id", "describe": { "columns": [ { @@ -11,6 +11,7 @@ ], "parameters": { "Left": [ + "Bool", "Bool", "Bool" ] @@ -19,5 +20,5 @@ false ] }, - "hash": "bd0665f9401b5919b55e8b68ef238b514c12becf8e9dfbb8641b02e4deb8b5e5" + "hash": "834d5b83aebddf1e23511025d5f98b8a69e1227e8bfbd2b1d0110c8def72f8aa" } diff --git a/.sqlx/query-205507ac888563e614157c6977abb744104284cb6bc7070127797bd6df0ac7af.json b/.sqlx/query-e2483b9d167af9476ba49c63fa20a07aa53bfe7f783fcb9257312304094d5edc.json similarity index 59% rename from .sqlx/query-205507ac888563e614157c6977abb744104284cb6bc7070127797bd6df0ac7af.json rename to .sqlx/query-e2483b9d167af9476ba49c63fa20a07aa53bfe7f783fcb9257312304094d5edc.json index 749e67273b..beb848d755 100644 --- a/.sqlx/query-205507ac888563e614157c6977abb744104284cb6bc7070127797bd6df0ac7af.json +++ b/.sqlx/query-e2483b9d167af9476ba49c63fa20a07aa53bfe7f783fcb9257312304094d5edc.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"admin_device_management\",\"only_client_activation\" FROM \"enterprisesettings\"", + "query": "SELECT id \"id?\", \"admin_device_management\",\"disable_all_traffic\",\"only_client_activation\" FROM \"enterprisesettings\"", "describe": { "columns": [ { @@ -15,6 +15,11 @@ }, { "ordinal": 2, + "name": "disable_all_traffic", + "type_info": "Bool" + }, + { + "ordinal": 3, "name": "only_client_activation", "type_info": "Bool" } @@ -23,10 +28,11 @@ "Left": [] }, "nullable": [ + false, false, false, false ] }, - "hash": "205507ac888563e614157c6977abb744104284cb6bc7070127797bd6df0ac7af" + "hash": "e2483b9d167af9476ba49c63fa20a07aa53bfe7f783fcb9257312304094d5edc" } diff --git a/migrations/20240902103930_add_option_to_disable_all_traffic.down.sql b/migrations/20240902103930_add_option_to_disable_all_traffic.down.sql new file mode 100644 index 0000000000..d07635dbce --- /dev/null +++ b/migrations/20240902103930_add_option_to_disable_all_traffic.down.sql @@ -0,0 +1 @@ +ALTER TABLE enterprisesettings DROP COLUMN disable_all_traffic; diff --git a/migrations/20240902103930_add_option_to_disable_all_traffic.up.sql b/migrations/20240902103930_add_option_to_disable_all_traffic.up.sql new file mode 100644 index 0000000000..21de95022a --- /dev/null +++ b/migrations/20240902103930_add_option_to_disable_all_traffic.up.sql @@ -0,0 +1 @@ +ALTER TABLE enterprisesettings ADD COLUMN disable_all_traffic BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/proto b/proto index d069a0e530..de58067ab6 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit d069a0e5304281cfc8b09e949a8e7a9feb5fc115 +Subproject commit de58067ab652f6e5ddf84c7bbc6e4d2363914738 diff --git a/src/db/models/polling_token.rs b/src/db/models/polling_token.rs index 3235f6b05c..99f611909d 100644 --- a/src/db/models/polling_token.rs +++ b/src/db/models/polling_token.rs @@ -2,9 +2,8 @@ use chrono::{NaiveDateTime, Utc}; use model_derive::Model; use sqlx::{query_as, Error as SqlxError}; -use crate::random::gen_alphanumeric; - use super::DbPool; +use crate::random::gen_alphanumeric; // Token used for polling requests. #[derive(Clone, Debug, Model)] diff --git a/src/enterprise/db/models/enterprise_settings.rs b/src/enterprise/db/models/enterprise_settings.rs index cebf54e74c..6b1a4f33ca 100644 --- a/src/enterprise/db/models/enterprise_settings.rs +++ b/src/enterprise/db/models/enterprise_settings.rs @@ -11,6 +11,8 @@ pub struct EnterpriseSettings { pub id: Option, // If true, only admins can manage devices pub admin_device_management: bool, + // If true, the option to route all traffic through the vpn is disabled in the client + pub disable_all_traffic: bool, // If true, manual WireGuard setup is disabled pub only_client_activation: bool, } @@ -22,6 +24,7 @@ impl Default for EnterpriseSettings { Self { id: None, admin_device_management: false, + disable_all_traffic: false, only_client_activation: false, } } diff --git a/src/enterprise/grpc/polling.rs b/src/enterprise/grpc/polling.rs index f351a89be2..88fb28a07f 100644 --- a/src/enterprise/grpc/polling.rs +++ b/src/enterprise/grpc/polling.rs @@ -1,11 +1,13 @@ +use tonic::Status; + use crate::{ db::{models::polling_token::PollingToken, DbPool, Device, User}, enterprise::license::{get_cached_license, validate_license}, - grpc::utils::build_device_config_response, + grpc::{ + proto::{InstanceInfoRequest, InstanceInfoResponse}, + utils::{build_device_config_response, build_instance_config_response}, + }, }; -use tonic::Status; - -use crate::grpc::proto::{InstanceInfoRequest, InstanceInfoResponse}; pub struct PollingServer { pool: DbPool, @@ -81,8 +83,10 @@ impl PollingServer { // Build & return polling info let device_config = build_device_config_response(&self.pool, &device.wireguard_pubkey).await?; + let instance_config = build_instance_config_response(&self.pool).await?; Ok(InstanceInfoResponse { device_config: Some(device_config), + instance_config: Some(instance_config), }) } } diff --git a/src/enterprise/handlers/openid_login.rs b/src/enterprise/handlers/openid_login.rs index e02d57bda4..e180d9c267 100644 --- a/src/enterprise/handlers/openid_login.rs +++ b/src/enterprise/handlers/openid_login.rs @@ -1,32 +1,38 @@ -use axum::extract::State; -use axum::http::StatusCode; -use axum::Json; +use axum::{extract::State, http::StatusCode, Json}; use axum_client_ip::{InsecureClientIp, LeftmostXForwardedFor}; -use axum_extra::extract::cookie::{Cookie, SameSite}; -use axum_extra::extract::{CookieJar, PrivateCookieJar}; -use axum_extra::headers::UserAgent; -use axum_extra::TypedHeader; -use openidconnect::core::{ - CoreClient, CoreGenderClaim, CoreJsonWebKeyType, CoreJweContentEncryptionAlgorithm, - CoreJwsSigningAlgorithm, CoreResponseType, +use axum_extra::{ + extract::{ + cookie::{Cookie, SameSite}, + CookieJar, PrivateCookieJar, + }, + headers::UserAgent, + TypedHeader, }; use openidconnect::{ - core::CoreProviderMetadata, reqwest::async_http_client, ClientId, ClientSecret, IssuerUrl, - ProviderMetadata, RedirectUrl, + core::{ + CoreClient, CoreGenderClaim, CoreJsonWebKeyType, CoreJweContentEncryptionAlgorithm, + CoreJwsSigningAlgorithm, CoreProviderMetadata, CoreResponseType, + }, + reqwest::async_http_client, + AuthenticationFlow, ClientId, ClientSecret, CsrfToken, EmptyAdditionalClaims, IdToken, + IssuerUrl, Nonce, ProviderMetadata, RedirectUrl, Scope, }; -use openidconnect::{AuthenticationFlow, CsrfToken, EmptyAdditionalClaims, IdToken, Nonce, Scope}; use serde_json::json; use time::Duration; use super::LicenseInfo; -use crate::appstate::AppState; -use crate::db::{DbPool, MFAInfo, Session, SessionState, Settings, User, UserInfo}; -use crate::enterprise::db::models::openid_provider::OpenIdProvider; -use crate::error::WebError; -use crate::handlers::user::{check_username, prune_username}; -use crate::handlers::{ApiResponse, AuthResponse, SESSION_COOKIE_NAME, SIGN_IN_COOKIE_NAME}; -use crate::headers::{check_new_device_login, get_user_agent_device, parse_user_agent}; -use crate::server_config; +use crate::{ + appstate::AppState, + db::{DbPool, MFAInfo, Session, SessionState, Settings, User, UserInfo}, + enterprise::db::models::openid_provider::OpenIdProvider, + error::WebError, + handlers::{ + user::{check_username, prune_username}, + ApiResponse, AuthResponse, SESSION_COOKIE_NAME, SIGN_IN_COOKIE_NAME, + }, + headers::{check_new_device_login, get_user_agent_device, parse_user_agent}, + server_config, +}; type ProvMeta = ProviderMetadata< openidconnect::EmptyAdditionalProviderMetadata, diff --git a/src/grpc/enrollment.rs b/src/grpc/enrollment.rs index 6cf74f4c5e..21ac12f783 100644 --- a/src/grpc/enrollment.rs +++ b/src/grpc/enrollment.rs @@ -1,16 +1,18 @@ use std::sync::Arc; -use super::InstanceInfo; use ipnetwork::IpNetwork; use sqlx::Transaction; use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; use tonic::Status; use uaparser::UserAgentParser; -use super::proto::{ - ActivateUserRequest, AdminInfo, Device as ProtoDevice, DeviceConfig as ProtoDeviceConfig, - DeviceConfigResponse, EnrollmentStartRequest, EnrollmentStartResponse, ExistingDevice, - InitialUserInfo, NewDevice, +use super::{ + proto::{ + ActivateUserRequest, AdminInfo, Device as ProtoDevice, DeviceConfig as ProtoDeviceConfig, + DeviceConfigResponse, EnrollmentStartRequest, EnrollmentStartResponse, ExistingDevice, + InitialUserInfo, NewDevice, + }, + InstanceInfo, }; use crate::{ db::{ diff --git a/src/grpc/mod.rs b/src/grpc/mod.rs index 63c36e6c7b..ef63907146 100644 --- a/src/grpc/mod.rs +++ b/src/grpc/mod.rs @@ -9,7 +9,6 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::enterprise::grpc::polling::PollingServer; use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc}; use reqwest::Url; use serde::Serialize; @@ -46,6 +45,7 @@ use self::{ use crate::{ auth::failed_login::FailedLoginMap, db::{AppEvent, Settings}, + enterprise::grpc::polling::PollingServer, handlers::mail::send_gateway_disconnected_email, mail::Mail, server_config, diff --git a/src/grpc/utils.rs b/src/grpc/utils.rs index bb9e511eab..38bd8a1ed6 100644 --- a/src/grpc/utils.rs +++ b/src/grpc/utils.rs @@ -1,11 +1,19 @@ -use super::InstanceInfo; use ipnetwork::IpNetwork; use tonic::Status; -use super::proto::{DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse}; -use crate::db::{ - models::{device::WireguardNetworkDevice, wireguard::WireguardNetwork}, - DbPool, Device, Settings, User, +use super::{ + proto::{DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse, InstanceConfigResponse}, + InstanceInfo, +}; +use crate::{ + db::{ + models::{device::WireguardNetworkDevice, wireguard::WireguardNetwork}, + DbPool, Device, Settings, User, + }, + enterprise::{ + db::models::enterprise_settings::EnterpriseSettings, + license::{get_cached_license, validate_license}, + }, }; pub(crate) async fn build_device_config_response( @@ -93,3 +101,20 @@ pub(crate) async fn build_device_config_response( token: None, }) } + +pub(crate) async fn build_instance_config_response( + pool: &DbPool, +) -> Result { + debug!("Building instance config response"); + let enterprise = validate_license(get_cached_license().as_ref()).is_ok(); + let enterprise_settings = EnterpriseSettings::get(pool).await.map_err(|err| { + error!("Failed to get enterprise settings while building instance config response: {err}"); + Status::internal("unexpected error") + })?; + debug!("Instance config response built"); + + Ok(InstanceConfigResponse { + enterprise, + disable_all_traffic: enterprise_settings.disable_all_traffic, + }) +} diff --git a/src/handlers/app_info.rs b/src/handlers/app_info.rs index c0f60f693d..a8bc4294c9 100644 --- a/src/handlers/app_info.rs +++ b/src/handlers/app_info.rs @@ -2,11 +2,11 @@ use axum::{extract::State, http::StatusCode}; use serde_json::json; use super::{ApiResponse, ApiResult, VERSION}; -use crate::db::Settings; -use crate::enterprise::license::get_cached_license; use crate::{ - appstate::AppState, auth::SessionInfo, db::WireguardNetwork, - enterprise::license::validate_license, + appstate::AppState, + auth::SessionInfo, + db::{Settings, WireguardNetwork}, + enterprise::license::{get_cached_license, validate_license}, }; /// Additional information about core state. diff --git a/src/secret.rs b/src/secret.rs index ff062e1959..d0434a8f95 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -1,6 +1,4 @@ -use std::convert::Infallible; -use std::error::Error; -use std::str::FromStr; +use std::{convert::Infallible, error::Error, str::FromStr}; use secrecy::{ExposeSecret, Secret}; use serde::{Deserialize, Serialize}; diff --git a/tests/enterprise_settings.rs b/tests/enterprise_settings.rs index 6f064d2a32..88a3fc41c3 100644 --- a/tests/enterprise_settings.rs +++ b/tests/enterprise_settings.rs @@ -1,14 +1,16 @@ mod common; -use defguard::enterprise::{ - db::models::enterprise_settings::EnterpriseSettings, - license::{get_cached_license, set_cached_license}, +use defguard::{ + enterprise::{ + db::models::enterprise_settings::EnterpriseSettings, + license::{get_cached_license, set_cached_license}, + }, + handlers::Auth, }; use reqwest::StatusCode; +use serde_json::{json, Value}; use self::common::make_test_client; -use defguard::handlers::Auth; -use serde_json::{json, Value}; fn make_network() -> Value { json!({ @@ -41,6 +43,7 @@ async fn test_only_enterprise_can_modify() { let settings = EnterpriseSettings { id: None, admin_device_management: true, + disable_all_traffic: false, only_client_activation: false, }; @@ -85,6 +88,7 @@ async fn test_admin_devices_management_is_enforced() { let settings = EnterpriseSettings { id: None, admin_device_management: true, + disable_all_traffic: false, only_client_activation: false, }; let response = client @@ -162,6 +166,7 @@ async fn test_regular_user_device_management() { let settings = EnterpriseSettings { id: None, admin_device_management: false, + disable_all_traffic: false, only_client_activation: false, }; let response = client diff --git a/web/src/i18n/en/index.ts b/web/src/i18n/en/index.ts index 6217edfb73..c8c7fb13f6 100644 --- a/web/src/i18n/en/index.ts +++ b/web/src/i18n/en/index.ts @@ -1236,6 +1236,11 @@ const en: BaseTranslation = { helper: "When this option is enabled, only users in the Admin group can manage devices in user profile (it's disabled for all other users)", }, + disableAllTraffic: { + label: 'Disable the option to route all traffic through VPN', + helper: + 'When this option is enabled, users will not be able to route all traffic through the VPN using the defguard client.', + }, manualConfig: { label: 'Disable users ability to download manual WireGuard configuration', helper: diff --git a/web/src/i18n/i18n-types.ts b/web/src/i18n/i18n-types.ts index 1046d6e973..b5feb55d1a 100644 --- a/web/src/i18n/i18n-types.ts +++ b/web/src/i18n/i18n-types.ts @@ -2936,6 +2936,16 @@ type RootTranslation = { */ helper: string } + disableAllTraffic: { + /** + * D​i​s​a​b​l​e​ ​t​h​e​ ​o​p​t​i​o​n​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​V​P​N + */ + label: string + /** + * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​u​s​e​r​s​ ​w​i​l​l​ ​n​o​t​ ​b​e​ ​a​b​l​e​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​t​h​e​ ​V​P​N​ ​u​s​i​n​g​ ​t​h​e​ ​d​e​f​g​u​a​r​d​ ​c​l​i​e​n​t​. + */ + helper: string + } manualConfig: { /** * D​i​s​a​b​l​e​ ​u​s​e​r​s​ ​a​b​i​l​i​t​y​ ​t​o​ ​d​o​w​n​l​o​a​d​ ​m​a​n​u​a​l​ ​W​i​r​e​G​u​a​r​d​ ​c​o​n​f​i​g​u​r​a​t​i​o​n @@ -7176,6 +7186,16 @@ export type TranslationFunctions = { */ helper: () => LocalizedString } + disableAllTraffic: { + /** + * Disable the option to route all traffic through VPN + */ + label: () => LocalizedString + /** + * When this option is enabled, users will not be able to route all traffic through the VPN using the defguard client. + */ + helper: () => LocalizedString + } manualConfig: { /** * Disable users ability to download manual WireGuard configuration diff --git a/web/src/i18n/pl/index.ts b/web/src/i18n/pl/index.ts index 723d73afa7..8c73d9bcea 100644 --- a/web/src/i18n/pl/index.ts +++ b/web/src/i18n/pl/index.ts @@ -1223,6 +1223,11 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe helper: 'Kiedy ta opcja jest włączona, tylko użytkownicy w grupie "Admin" mogą zarządzać urządzeniami w profilu użytkownika', }, + disableAllTraffic: { + label: 'Zablokuj możliwość przekierowania całego ruchu przez VPN', + helper: + 'Kiedy ta opcja jest włączona, użytkownicy nie będą mogli przekierować całego ruchu przez VPN za pomocą klienta Defguard.', + }, manualConfig: { label: 'Wyłącz wyświetlanie konfiguracji WireGuard', helper: diff --git a/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx b/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx index 912fd3d80c..fac922a097 100644 --- a/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx +++ b/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx @@ -75,6 +75,19 @@ export const EnterpriseForm = () => { {parse(LL.settingsPage.enterprise.fields.manualConfig.helper())} +
+ + mutate({ disable_all_traffic: !settings.disable_all_traffic }) + } + /> + + {parse(LL.settingsPage.enterprise.fields.disableAllTraffic.helper())} + +
); diff --git a/web/src/shared/types.ts b/web/src/shared/types.ts index 06336c7aee..fd02238e08 100644 --- a/web/src/shared/types.ts +++ b/web/src/shared/types.ts @@ -871,6 +871,7 @@ export type SettingsLicense = { export type SettingsEnterprise = { admin_device_management: boolean; + disable_all_traffic: boolean; only_client_activation: boolean; };