Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
12 changes: 6 additions & 6 deletions flake.lock

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

2 changes: 2 additions & 0 deletions nix/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ in
trunk
sqlx-cli
vtsls
trivy
just
];

shellHook = with pkgs; ''
Expand Down
1 change: 1 addition & 0 deletions src-tauri/permissions/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ commands.allow = [
"swap_to_old_ui",
"all_active_connections",
"disconnect_locations",
"get_posture_data",
]
2 changes: 1 addition & 1 deletion src-tauri/proto
Submodule proto updated 1 files
+2 −0 v1/client/client.proto
1 change: 1 addition & 0 deletions src-tauri/src/bin/defguard-client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ fn main() {
command_set_app_config,
get_provisioning_config,
get_platform_header,
get_posture_data,
set_location_mfa_method,
open_new_ui_window,
open_old_ui_window,
Expand Down
37 changes: 35 additions & 2 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@ use crate::{
},
DB_POOL,
},
enterprise::{periodic::config::poll_instance, provisioning::ProvisioningConfig},
enterprise::{
periodic::config::poll_instance, posture::authorize_posture_session,
provisioning::ProvisioningConfig,
},
error::Error,
events::EventKey,
log_watcher::{
global_log_watcher::{spawn_global_log_watcher_task, stop_global_log_watcher_task},
service_log_watcher::stop_log_watcher_task,
},
proto::defguard::client_types::DeviceConfigResponse,
service::proto::defguard::enterprise::posture::v2::DevicePostureData,
tray::{configure_tray_icon, reload_tray_menu},
utils::{
construct_platform_header, disconnect_interface, get_location_interface_details,
Expand All @@ -50,7 +54,7 @@ use crate::{
use crate::{
service::{
client::DAEMON_CLIENT,
proto::{
proto::defguard::client::v1::{
DeleteServiceLocationsRequest, RemoveInterfaceRequest, SaveServiceLocationsRequest,
},
},
Expand All @@ -72,6 +76,11 @@ pub async fn connect(
"Identified location with ID {location_id} as \"{}\", handling connection.",
location.name
);
let preshared_key = if location.posture_check_required && preshared_key.is_none() {
Some(authorize_posture_session(&location).await?)
} else {
preshared_key
};
handle_connection_for_location(&location, preshared_key, &handle).await?;
reload_tray_menu(&handle).await;
info!("Connected to location {location}");
Expand Down Expand Up @@ -490,6 +499,7 @@ pub struct LocationInfo {
pub pubkey: String,
pub network_id: Id,
pub location_mfa_mode: LocationMfaMode,
pub posture_check_required: bool,
pub mfa_method: Option<LocationMfaMethod>,
}

Expand Down Expand Up @@ -543,6 +553,7 @@ pub async fn all_locations(instance_id: Id) -> Result<Vec<LocationInfo>, Error>
pubkey: location.pubkey,
network_id: location.network_id,
location_mfa_mode: location.location_mfa_mode,
posture_check_required: location.posture_check_required,
mfa_method: location.mfa_method,
};
location_info.push(info);
Expand Down Expand Up @@ -1521,6 +1532,28 @@ pub fn get_platform_header() -> String {
construct_platform_header()
}

#[tauri::command(async)]
#[cfg(not(windows))]
pub async fn get_posture_data() -> Result<DevicePostureData, Error> {
debug!("Received a command to prepare posture report");
Ok(DevicePostureData::new())
}

#[tauri::command(async)]
#[cfg(windows)]
pub async fn get_posture_data() -> Result<DevicePostureData, Error> {
debug!("Received a command to prepare posture report");
DAEMON_CLIENT
.clone()
.get_posture_data(tonic::Request::new(()))
.await
.map(|response| response.into_inner())
.map_err(|err| {
error!("Failed to get posture data from the daemon: {err}");
Error::InternalError(format!("Failed to get posture data from the daemon: {err}"))
})
}

#[derive(Debug, Serialize)]
pub struct ActiveConnectionSummary {
pub id: Id,
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/enterprise/inspector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{env::consts::OS, error::Error, fmt};
use sysinfo::System;

use crate::{
proto::defguard::enterprise::posture::v2::{
service::proto::defguard::enterprise::posture::v2::{
bool_check, string_check, BoolCheck, DevicePostureData, StringCheck, UnavailableReason,
},
VERSION,
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/enterprise/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod inspector;
pub mod models;
pub mod periodic;
pub mod posture;
pub mod provisioning;
pub mod service_locations;
17 changes: 4 additions & 13 deletions src-tauri/src/enterprise/periodic/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
time::Duration,
};

use reqwest::{Client, StatusCode};
use reqwest::StatusCode;
use serde::Serialize;
use sqlx::{Sqlite, Transaction};
use tauri::{AppHandle, Emitter, Url};
Expand All @@ -24,13 +24,11 @@ use crate::{
proto::defguard::client_types::{
DeviceConfigResponse, InstanceInfoRequest, InstanceInfoResponse,
},
utils::construct_platform_header,
CLIENT_PLATFORM_HEADER, CLIENT_VERSION_HEADER, MIN_CORE_VERSION, MIN_PROXY_VERSION,
PKG_VERSION,
utils::post_with_headers,
MIN_CORE_VERSION, MIN_PROXY_VERSION,
};

const INTERVAL_SECONDS: Duration = Duration::from_secs(30);
const HTTP_REQ_TIMEOUT: Duration = Duration::from_secs(5);
static POLLING_ENDPOINT: &str = "/api/v1/poll";

/// Periodically retrieves and updates configuration for all [`Instance`]s.
Expand Down Expand Up @@ -138,14 +136,7 @@ pub async fn poll_instance(
instance.proxy_url
))
})?;
let response = Client::new()
.post(url)
.json(&request)
.header(CLIENT_VERSION_HEADER, PKG_VERSION)
.header(CLIENT_PLATFORM_HEADER, construct_platform_header())
.timeout(HTTP_REQ_TIMEOUT)
.send()
.await;
let response = post_with_headers(url, &request).await;
let response = response.map_err(|err| {
Error::InternalError(format!(
"HTTP request failed for instance {}({}), url: {}, {err}",
Expand Down
79 changes: 79 additions & 0 deletions src-tauri/src/enterprise/posture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use reqwest::StatusCode;
use serde::Deserialize;

use crate::{
database::{
models::{instance::Instance, location::Location, wireguard_keys::WireguardKeys, Id},
DB_POOL,
},
error::Error,
service::proto::defguard::enterprise::posture::v2::{
DevicePostureCheckRequest, DevicePostureCheckResponse, DevicePostureData,
},
utils::post_with_headers,
};

const POSTURE_ENDPOINT: &str = "/api/v1/posture/connect";

/// Collects device posture data, sends it to the proxy, and returns the runtime preshared key.
pub async fn authorize_posture_session(location: &Location<Id>) -> Result<String, Error> {
let instance = Instance::find_by_id(&*DB_POOL, location.instance_id)
.await?
.ok_or(Error::NotFound)?;

let keys = WireguardKeys::find_by_instance_id(&*DB_POOL, location.instance_id)
.await?
.ok_or_else(|| {
Error::ResourceNotFound(format!(
"WireGuard keys not found for instance {}",
location.instance_id
))
})?;

let posture_data = DevicePostureData::new();

let request = DevicePostureCheckRequest {
location_id: location.network_id,
pubkey: keys.pubkey,
device_posture_data: Some(posture_data),
};

let proxy_url = tauri::Url::parse(&instance.proxy_url)
Comment thread
j-chmielewski marked this conversation as resolved.
.map_err(|e| Error::InternalError(format!("Invalid proxy URL: {e}")))?
.join(POSTURE_ENDPOINT)
.map_err(|e| Error::InternalError(format!("Failed to build posture URL: {e}")))?;

debug!("Sending posture check request to {proxy_url}");
let response = post_with_headers(proxy_url, &request)
.await
.map_err(|e| Error::HttpError(e.to_string()))?;

match response.status() {
StatusCode::OK => {
let body: DevicePostureCheckResponse = response
.json()
.await
.map_err(|e| Error::HttpError(e.to_string()))?;
info!("Posture check approved for location {}", location.id);
Ok(body.preshared_key)
}
StatusCode::FORBIDDEN => {
#[derive(Deserialize)]
struct PostureRejection {
error: String,
}
let body: PostureRejection = response
.json()
.await
.map_err(|e| Error::HttpError(e.to_string()))?;
error!(
"Posture check rejected for location {}: {}",
location.id, body.error
);
Err(Error::PostureCheckFailed(body.error))
}
status => Err(Error::HttpError(format!(
"Unexpected proxy response: {status}"
))),
}
}
2 changes: 1 addition & 1 deletion src-tauri/src/enterprise/service_locations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
location::{Location, ServiceLocationMode},
Id,
},
service::proto::ServiceLocation,
service::proto::defguard::client::v1::ServiceLocation,
};

#[cfg(windows)]
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/enterprise/service_locations/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::{
},
service::{
daemon::setup_wgapi,
proto::{ServiceLocation, ServiceLocationMode},
proto::defguard::client::v1::{ServiceLocation, ServiceLocationMode},
},
};

Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ pub enum Error {
ConversionError(String),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("HTTP request error: {0}")]
HttpError(String),
#[error("Posture check failed: {0}")]
PostureCheckFailed(String),
}

// we must manually implement serde::Serialize
Expand Down
13 changes: 1 addition & 12 deletions src-tauri/src/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,13 @@ use crate::database::models::{
};

pub(crate) mod defguard {
#[allow(dead_code)]
pub(crate) mod enterprise {
pub(crate) mod posture {
pub(crate) mod v2 {
tonic::include_proto!("defguard.enterprise.posture.v2");
}
}
}
pub(crate) use crate::service::proto::defguard::client_types;

pub(crate) mod proxy {
pub(crate) mod v1 {
tonic::include_proto!("defguard.proxy.v1");
}
}

pub mod client_types {
tonic::include_proto!("defguard.client_types");
}
}

impl defguard::client_types::DeviceConfig {
Expand Down
3 changes: 2 additions & 1 deletion src-tauri/src/service/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ use tower::service_fn;
#[cfg(windows)]
use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY;

use crate::service::proto::defguard::client::v1::desktop_daemon_service_client::DesktopDaemonServiceClient;

#[cfg(unix)]
use super::daemon::DAEMON_SOCKET_PATH;
#[cfg(windows)]
use super::named_pipe::PIPE_NAME;
use super::proto::desktop_daemon_service_client::DesktopDaemonServiceClient;

pub(crate) static DAEMON_CLIENT: LazyLock<DesktopDaemonServiceClient<Channel>> =
LazyLock::new(|| {
Expand Down
Loading
Loading