Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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.

1 change: 1 addition & 0 deletions nix/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ in
trunk
sqlx-cli
vtsls
trivy
];

shellHook = with pkgs; ''
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/proto
Submodule proto updated 1 files
+2 −0 v1/client/client.proto
2 changes: 2 additions & 0 deletions src-tauri/src/bin/defguard-client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ fn main() {
save_device_config,
all_instances,
connect,
connect_with_posture,
disconnect,
update_instance,
location_stats,
Expand All @@ -182,6 +183,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
46 changes: 44 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::connect_with_posture_check,
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 Down Expand Up @@ -490,6 +494,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 +548,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 +1527,42 @@ pub fn get_platform_header() -> String {
construct_platform_header()
}

/// Connect to a location that requires a posture check.
///
/// Collects device posture data, sends it to the proxy, and on success establishes
/// the WireGuard tunnel using the returned preshared key.
#[tauri::command(async)]
pub async fn connect_with_posture(
Comment thread
filipslezaklab marked this conversation as resolved.
Outdated
location_id: Id,
_connection_type: ConnectionType,
Comment thread
j-chmielewski marked this conversation as resolved.
Outdated
handle: AppHandle,
) -> Result<(), Error> {
debug!("Received a command to connect with posture check to location with ID {location_id}");
connect_with_posture_check(location_id, &handle).await
}

#[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;
103 changes: 103 additions & 0 deletions src-tauri/src/enterprise/posture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::time::Duration;

use reqwest::{Client, StatusCode};
use serde::Deserialize;
use tauri::AppHandle;

use crate::{
database::{
models::{instance::Instance, location::Location, wireguard_keys::WireguardKeys},
DB_POOL,
},
error::Error,
service::proto::defguard::enterprise::posture::v2::{
DevicePostureCheckRequest, DevicePostureCheckResponse, DevicePostureData,
},
tray::{configure_tray_icon, reload_tray_menu},
utils::{construct_platform_header, handle_connection_for_location},
CLIENT_PLATFORM_HEADER, CLIENT_VERSION_HEADER, PKG_VERSION,
};

const HTTP_TIMEOUT: Duration = Duration::from_secs(10);
const POSTURE_ENDPOINT: &str = "/api/v1/posture/connect";

/// Collects device posture data, sends it to the proxy, and on success establishes
/// the WireGuard tunnel using the returned preshared key.
pub async fn connect_with_posture_check(
location_id: crate::database::models::Id,
Comment thread
j-chmielewski marked this conversation as resolved.
Outdated
handle: &AppHandle,
) -> Result<(), Error> {
let location = Location::find_by_id(&*DB_POOL, location_id)
.await?
.ok_or(Error::NotFound)?;

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 = Client::new()
.post(proxy_url)
.json(&request)
.header(CLIENT_VERSION_HEADER, PKG_VERSION)
.header(CLIENT_PLATFORM_HEADER, construct_platform_header())
.timeout(HTTP_TIMEOUT)
.send()
.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()))?;
debug!("Posture check approved for location {location_id}, connecting...");
handle_connection_for_location(&location, Some(body.preshared_key), handle).await?;
Comment thread
j-chmielewski marked this conversation as resolved.
Outdated
reload_tray_menu(handle).await;
configure_tray_icon(handle).await?;
info!("Connected to location {location} after posture check");
Ok(())
}
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
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
37 changes: 29 additions & 8 deletions src-tauri/src/service/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,23 @@ use tonic::{
transport::Server,
Code, Response, Status,
};
use tracing::{debug, error, info, info_span, Instrument};
use tracing::{debug, error, info, info_span, warn, Instrument};

use super::{
config::Config,
proto::{
proto::defguard::client::v1::{
desktop_daemon_service_server::{DesktopDaemonService, DesktopDaemonServiceServer},
CreateInterfaceRequest, InterfaceData, ReadInterfaceDataRequest, RemoveInterfaceRequest,
CreateInterfaceRequest, DeleteServiceLocationsRequest, InterfaceData,
ReadInterfaceDataRequest, RemoveInterfaceRequest, SaveServiceLocationsRequest,
},
};
#[cfg(windows)]
use crate::enterprise::service_locations::ServiceLocationManager;
#[cfg(windows)]
use crate::service::named_pipe::{get_named_pipe_server_stream, PIPE_NAME};
use crate::{
enterprise::service_locations::ServiceLocationError,
service::proto::{DeleteServiceLocationsRequest, SaveServiceLocationsRequest},
VERSION,
};
#[cfg(not(windows))]
use crate::service::proto::defguard::enterprise::posture::v2::DevicePostureData;
use crate::{enterprise::service_locations::ServiceLocationError, VERSION};

#[cfg(unix)]
pub(super) const DAEMON_SOCKET_PATH: &str = "/var/run/defguard.socket";
Expand Down Expand Up @@ -241,6 +240,19 @@ impl DesktopDaemonService for DaemonService {
Ok(Response::new(()))
}

#[cfg(not(windows))]
async fn get_posture_data(
&self,
_request: tonic::Request<()>,
) -> Result<Response<DevicePostureData>, Status> {
warn!(
"Daemon service received a get_posture_data request. Daemon posture requests are only supported on windows systems. Unix systems perform client-side posture checks."
);
Err(Status::unimplemented(
"Service-side posture checks are only supported on Unix systems",
))
}

#[cfg(windows)]
async fn delete_service_locations(
&self,
Expand Down Expand Up @@ -497,6 +509,15 @@ impl DesktopDaemonService for DaemonService {
Box::pin(output_stream) as Self::ReadInterfaceDataStream
))
}

#[cfg(windows)]
async fn get_posture_data(
&self,
_request: tonic::Request<()>,
) -> Result<Response<DevicePostureData>, Status> {
debug!("Get posture data request received");
Ok(Response::new(DevicePostureData::new()))
}
}

#[cfg(unix)]
Expand Down
Loading
Loading