Skip to content
Merged
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.

3 changes: 1 addition & 2 deletions crates/defguard_core/src/enterprise/firewall/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::enterprise::license::{License, LicenseTier};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

use defguard_common::db::{
Expand Down Expand Up @@ -27,7 +26,7 @@ use crate::enterprise::{
AclRuleInfo, AclRuleNetwork, AclRuleUser, AliasKind, PortRange, RuleState,
},
firewall::try_get_location_firewall_config,
license::set_cached_license,
license::{License, LicenseTier, set_cached_license},
};

mod all_locations;
Expand Down
25 changes: 13 additions & 12 deletions crates/defguard_core/src/grpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,6 @@ use std::{
time::{Duration, Instant},
};

use crate::{
auth::failed_login::FailedLoginMap,
db::AppEvent,
enterprise::{
db::models::{
enterprise_settings::{ClientTrafficPolicy, EnterpriseSettings},
openid_provider::OpenIdProvider,
},
is_business_license_active, is_enterprise_license_active,
},
grpc::{auth::AuthServer, interceptor::JwtInterceptor, worker::WorkerServer},
};
use defguard_common::{
auth::claims::ClaimsType,
config::server_config,
Expand All @@ -35,6 +23,19 @@ use serde::Serialize;
use sqlx::PgPool;
use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender};

use crate::{
auth::failed_login::FailedLoginMap,
db::AppEvent,
enterprise::{
db::models::{
enterprise_settings::{ClientTrafficPolicy, EnterpriseSettings},
openid_provider::OpenIdProvider,
},
is_business_license_active, is_enterprise_license_active,
},
grpc::{auth::AuthServer, interceptor::JwtInterceptor, worker::WorkerServer},
};

mod auth;
pub mod client_version;
pub mod interceptor;
Expand Down
20 changes: 19 additions & 1 deletion crates/defguard_core/src/handlers/static_ips.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use axum::{
http::StatusCode,
};
use defguard_common::db::Id;
use defguard_static_ip::{LocationDevices, get_ips_for_user};
use defguard_static_ip::{DeviceLocationIp, LocationDevices, get_ips_for_device, get_ips_for_user};
use serde::Serialize;

use crate::{
Expand All @@ -20,6 +20,11 @@ pub struct LocationDevicesResponse {
pub locations: Vec<LocationDevices>,
}

#[derive(Serialize)]
pub struct DeviceLocationIpsResponse {
pub locations: Vec<DeviceLocationIp>,
}

pub async fn get_all_user_device_ips(
_admin_role: AdminRole,
_session: SessionInfo,
Expand All @@ -33,6 +38,19 @@ pub async fn get_all_user_device_ips(
))
}

pub async fn get_device_ips(
_admin_role: AdminRole,
_session: SessionInfo,
Path((username, device_id)): Path<(String, Id)>,
State(state): State<AppState>,
) -> ApiResult {
let locations = get_ips_for_device(&username, device_id, &state.pool).await?;
Ok(ApiResponse::json(
DeviceLocationIpsResponse { locations },
StatusCode::OK,
))
}

#[derive(Deserialize)]
pub struct StaticIpAssignment {
pub device_id: i64,
Expand Down
8 changes: 7 additions & 1 deletion crates/defguard_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ use crate::{
test_ldap_settings, update_settings,
},
ssh_authorized_keys::get_authorized_keys,
static_ips::{assign_static_ips, get_all_user_device_ips, validate_ip_assignment},
static_ips::{
assign_static_ips, get_all_user_device_ips, get_device_ips, validate_ip_assignment,
},
support::{configuration, logs},
updates::outdated_components,
user::{
Expand Down Expand Up @@ -476,6 +478,10 @@ pub fn build_webapp(
"/device/user/{username}/ip",
get(get_all_user_device_ips).post(assign_static_ips),
)
.route(
"/device/user/{username}/ip/{device_id}",
get(get_device_ips),
)
.route(
"/device/user/{username}/ip/validate",
post(validate_ip_assignment),
Expand Down
11 changes: 5 additions & 6 deletions crates/defguard_gateway_manager/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ use defguard_common::{
},
messages::peer_stats_update::PeerStatsUpdate,
};
use defguard_core::{
enterprise::firewall::try_get_location_firewall_config, grpc::GatewayEvent,
handlers::mail::send_gateway_disconnected_email,
location_management::allowed_peers::get_location_allowed_peers,
};
#[cfg(not(test))]
use defguard_grpc_tls::{certs as tls_certs, connector::HttpsSchemeConnector};
use defguard_proto::{
Expand All @@ -45,12 +50,6 @@ use tokio::{
use tokio_stream::wrappers::UnboundedReceiverStream;
use tonic::{Code, Status, transport::Endpoint};

use defguard_core::{
enterprise::firewall::try_get_location_firewall_config, grpc::GatewayEvent,
handlers::mail::send_gateway_disconnected_email,
location_management::allowed_peers::get_location_allowed_peers,
};

use crate::{Client, TEN_SECS, error::GatewayError};

/// One instance per connected Gateway.
Expand Down
2 changes: 1 addition & 1 deletion crates/defguard_grpc_tls/src/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ where
C: tower_service::Service<Uri, Error = BoxError> + Clone + Send + 'static,
C::Future: Send,
{
type Response = C::Response;
type Error = BoxError;
type Future = std::pin::Pin<
Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>> + Send>,
>;
type Response = C::Response;

fn poll_ready(
&mut self,
Expand Down
71 changes: 71 additions & 0 deletions crates/defguard_static_ip/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ pub struct DeviceIps {
pub wireguard_ips: Vec<SplitIp>,
}

/// Flattened location entry used by the single-device IP endpoint.
/// Each entry represents one location the device belongs to,
/// without wrapping IPs in an inner device array.
#[derive(Serialize)]
pub struct DeviceLocationIp {
pub location_id: i64,
pub location_name: String,
pub wireguard_ips: Vec<SplitIp>,
}

#[derive(FromRow)]
struct DeviceIpRow {
location_id: i64,
Expand Down Expand Up @@ -110,6 +120,67 @@ pub async fn get_ips_for_user(
Ok(locations)
}

pub async fn get_ips_for_device(
username: &str,
device_id: Id,
pool: &PgPool,
) -> Result<Vec<DeviceLocationIp>, StaticIpError> {
debug!("Fetching static IPs for device {device_id} of user {username}");
let rows = sqlx::query_as!(
DeviceIpRow,
"SELECT \
wn.id AS location_id, \
wn.name AS location_name, \
d.id AS device_id, \
d.name AS device_name, \
wnd.wireguard_ips AS \"wireguard_ips: Vec<IpAddr>\" \
FROM wireguard_network wn \
JOIN wireguard_network_device wnd ON wnd.wireguard_network_id = wn.id \
JOIN device d ON d.id = wnd.device_id \
JOIN \"user\" u ON d.user_id = u.id \
WHERE u.username = $1 AND d.id = $2 \
ORDER BY wn.name",
username,
device_id
)
.fetch_all(pool)
.await?;

debug!(
"Found {} location(s) for device {device_id} of user {username}",
rows.len()
);
let mut locations: Vec<DeviceLocationIp> = Vec::new();

for row in rows {
let network = WireguardNetwork::find_by_id(pool, row.location_id)
.await?
.ok_or(StaticIpError::NetworkNotFound(row.location_id))?;

let wireguard_ips: Vec<SplitIp> = row
.wireguard_ips
.iter()
.filter_map(|ip| {
network
.get_containing_network(*ip)
.map(|net| split_ip(ip, &net))
})
.collect();

locations.push(DeviceLocationIp {
location_id: row.location_id,
location_name: row.location_name,
wireguard_ips,
});
}

debug!(
"Returning IP data for {} location(s) for device {device_id}",
locations.len()
);
Ok(locations)
}

pub async fn assign_static_ips(
device_id: Id,
ips: Vec<IpAddr>,
Expand Down
9 changes: 8 additions & 1 deletion web/messages/en/modal.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,12 @@
"modal_assign_user_ip_success": "{firstName} {lastName}'s IP addresses were successfully updated.",
"modal_assign_user_ip_error": "Failed to update IP addresses",
"modal_assign_user_ip_validation_error": "Invalid or already taken",
"modal_assign_user_ip_no_locations": "No locations available. Add a location first."
"modal_assign_user_ip_no_locations": "No locations available. Add a location first.",
"modal_assign_user_ip_no_devices": "This user has no devices.",
"modal_assign_user_device_ip_title": "Device IP settings",
"modal_assign_user_device_ip_title_fallback": "Device IP settings",
"modal_assign_user_device_ip_card_title": "{deviceName} IP settings",
"modal_assign_user_device_ip_assignment_description": "You can change the IP address for this device separately in each location/network one-by-one.",
"modal_assign_user_device_ip_success": "{deviceName}'s IP addresses were successfully updated.",
"modal_assign_user_device_ip_error": "Failed to update IP addresses"
}
1 change: 1 addition & 0 deletions web/messages/en/profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"profile_devices_col_location_connected": "Last connected",
"profile_devices_col_never_connected": "Never connected",
"profile_devices_menu_show_config": "Show configuration",
"profile_devices_menu_ip_settings": "Device IP settings",
"profile_auth_keys_no_data_title": "You don't have any added keys.",
"profile_auth_keys_no_data_subtitle": "To add one, click the button below",
"profile_auth_keys_no_data_cta": "Add new key",
Expand Down
1 change: 1 addition & 0 deletions web/src/pages/UsersOverviewPage/UsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ export const UsersTable = () => {
openModal(ModalName.AssignUserIP, {
user: rowData,
locationData: response.data,
hasDevices: rowData.devices.length > 0,
});
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,14 @@ export const AssignUserIPModal = () => {
);
};

const ModalContent = ({ user, locationData }: ModalData) => {
const ModalContent = ({ user, locationData, hasDevices }: ModalData) => {
return (
<AssignmentForm
username={user.username}
firstName={user.first_name}
lastName={user.last_name}
locationData={locationData}
hasDevices={hasDevices}
/>
);
};
Expand All @@ -107,13 +108,15 @@ type AssignmentFormProps = {
firstName: string;
lastName: string;
locationData: LocationDevicesResponse;
hasDevices: boolean;
};

const AssignmentForm = ({
username,
firstName,
lastName,
locationData,
hasDevices,
}: AssignmentFormProps) => {
const [openLocations, setOpenLocations] = useState<Set<number>>(() => new Set());

Expand Down Expand Up @@ -216,7 +219,11 @@ const AssignmentForm = ({

<div className="devices-list">
{locationData.locations.length === 0 && (
<p className="no-locations">{m.modal_assign_user_ip_no_locations()}</p>
<p className="no-locations">
{hasDevices
? m.modal_assign_user_ip_no_locations()
: m.modal_assign_user_ip_no_devices()}
</p>
)}
{locationData.locations.map((location: LocationDevices, locIdx) => (
<IpAssignmentCard
Expand Down
Loading
Loading