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
5 changes: 5 additions & 0 deletions crates/defguard_core/src/db/models/activity_log/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,3 +571,8 @@ pub struct ProxyModifiedMetadata {
pub before: Proxy<Id>,
pub after: Proxy<Id>,
}

#[derive(Serialize)]
pub struct ProxyDeletedMetadata {
pub proxy: Proxy<Id>,
}
3 changes: 3 additions & 0 deletions crates/defguard_core/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ pub enum ApiEventType {
before: Proxy<Id>,
after: Proxy<Id>,
},
ProxyDeleted {
proxy: Proxy<Id>,
},
}

/// Events from Web API
Expand Down
77 changes: 63 additions & 14 deletions crates/defguard_core/src/handlers/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use axum::{
Json,
extract::{Path, State},
};
use defguard_common::db::models::proxy::Proxy;
use defguard_common::{db::models::proxy::Proxy, types::proxy::ProxyControlMessage};
use reqwest::StatusCode;
use serde_json::{Value, json};
use serde_json::Value;
use utoipa::ToSchema;

use crate::{
Expand Down Expand Up @@ -46,14 +46,8 @@ pub(crate) async fn proxy_details(
);
let proxy = Proxy::find_by_id(&appstate.pool, proxy_id).await?;
let response = match proxy {
Some(proxy) => ApiResponse {
json: json!(proxy),
status: StatusCode::OK,
},
None => ApiResponse {
json: Value::Null,
status: StatusCode::NOT_FOUND,
},
Some(proxy) => ApiResponse::json(proxy, StatusCode::OK),
None => ApiResponse::json(Value::Null, StatusCode::NOT_FOUND),
};
info!(
"User {} displayed details for proxy {proxy_id}",
Expand Down Expand Up @@ -92,10 +86,7 @@ pub(crate) async fn update_proxy(

let Some(mut proxy) = proxy else {
warn!("Proxy {proxy_id} not found");
return Ok(ApiResponse {
json: Value::Null,
status: StatusCode::NOT_FOUND,
});
return Ok(ApiResponse::json(Value::Null, StatusCode::NOT_FOUND));
};
let before = proxy.clone();

Expand All @@ -114,3 +105,61 @@ pub(crate) async fn update_proxy(

Ok(ApiResponse::json(proxy, StatusCode::OK))
}

#[utoipa::path(
delete,
path = "/api/v1/proxy/{proxy_id}",
request_body = Proxy,
responses(
(status = 200, description = "Successfully deleted edge.", body = ApiResponse),
(status = 401, description = "Unauthorized to delete edge.", body = ApiResponse, example = json!({"msg": "Session is required"})),
(status = 403, description = "You don't have permission delete an edge.", body = ApiResponse, example = json!({"msg": "access denied"})),
(status = 404, description = "Edge not found", body = ApiResponse, example = json!({"msg": "proxy not found"})),
(status = 500, description = "Unable to delete edge.", body = ApiResponse, example = json!({"msg": "Internal server error"}))
),
security(
("cookie" = []),
("api_token" = [])
)
)]
pub(crate) async fn delete_proxy(
_role: AdminRole,
Path(proxy_id): Path<i64>,
State(appstate): State<AppState>,
session: SessionInfo,
context: ApiRequestContext,
) -> ApiResult {
debug!("User {} deleteing proxy {proxy_id}", session.user.username);
let proxy = Proxy::find_by_id(&appstate.pool, proxy_id).await?;

let Some(proxy) = proxy else {
warn!("Proxy {proxy_id} not found");
return Ok(ApiResponse::json(Value::Null, StatusCode::NOT_FOUND));
};

// Disconnect the proxy
if let Err(err) = appstate
.proxy_control_tx
.send(ProxyControlMessage::ShutdownConnection(proxy.id))
.await
{
error!(
"Error shutting down proxy {}, it may be disconnected: {err:?}",
proxy.id
);
}

// TODO
// 1. Add proxy cert to CRL
// 2. Remove cert files on deleted proxy
proxy.clone().delete(&appstate.pool).await?;

info!("User {} deleted proxy {proxy_id}", session.user.username);

appstate.emit_event(ApiEvent {
context,
event: Box::new(ApiEventType::ProxyDeleted { proxy }),
})?;

Ok(ApiResponse::default())
}
7 changes: 5 additions & 2 deletions crates/defguard_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ use crate::{
authorization, discovery_keys, openid_configuration, secure_authorization, token,
userinfo,
},
proxy::{proxy_details, update_proxy},
proxy::{delete_proxy, proxy_details, update_proxy},
settings::{
get_settings, get_settings_essentials, patch_settings, set_default_branding,
test_ldap_settings, update_settings,
Expand Down Expand Up @@ -359,7 +359,10 @@ pub fn build_webapp(
// Certificate authority
.route("/ca", post(create_ca))
// Proxy routes
.route("/proxy/{proxy_id}", get(proxy_details).put(update_proxy))
.route(
"/proxy/{proxy_id}",
get(proxy_details).put(update_proxy).delete(delete_proxy),
)
// Proxy setup with SSE
.route("/proxy/setup/stream", get(setup_proxy_tls_stream)),
);
Expand Down
64 changes: 63 additions & 1 deletion crates/defguard_core/tests/integration/api/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,36 @@ use sqlx::postgres::{PgConnectOptions, PgPoolOptions};
use super::common::{make_test_client, setup_pool};

#[sqlx::test]
async fn test_update_proxy(_: PgPoolOptions, options: PgConnectOptions) {
async fn test_proxy_details(_: PgPoolOptions, options: PgConnectOptions) {
let pool = setup_pool(options).await;

let (client, _) = make_test_client(pool.clone()).await;

// Authorize as an administrator.
let auth = Auth::new("admin", "pass123");
let response = client.post("/api/v1/auth").json(&auth).send().await;
assert_eq!(response.status(), StatusCode::OK);

// Create new proxy.
let proxy = Proxy::new("test", "localhost", 50051, "public.net")
.save(&pool)
.await
.unwrap();

// Get proxy via API
let response = client
.get(format!("/api/v1/proxy/{}", proxy.id))
.send()
.await;
assert_eq!(response.status(), StatusCode::OK);

// Verify proxy is correct
let proxy_from_api: Proxy<Id> = response.json().await;
assert_eq!(proxy, proxy_from_api);
}

#[sqlx::test]
async fn test_proxy_update(_: PgPoolOptions, options: PgConnectOptions) {
let pool = setup_pool(options).await;

let (client, _) = make_test_client(pool.clone()).await;
Expand Down Expand Up @@ -53,3 +82,36 @@ async fn test_update_proxy(_: PgPoolOptions, options: PgConnectOptions) {
let proxy_updated: Proxy<Id> = response.json().await;
assert_eq!(proxy_before_mods, proxy_updated);
}

#[sqlx::test]
async fn test_delete_proxy(_: PgPoolOptions, options: PgConnectOptions) {
let pool = setup_pool(options).await;

let (client, _) = make_test_client(pool.clone()).await;

// Authorize as an administrator.
let auth = Auth::new("admin", "pass123");
let response = client.post("/api/v1/auth").json(&auth).send().await;
assert_eq!(response.status(), StatusCode::OK);

// Create new proxy.
let proxy = Proxy::new("test", "localhost", 50051, "public.net")
.save(&pool)
.await
.unwrap();

// Delete proxy via API
let response = client
.delete(format!("/api/v1/proxy/{}", proxy.id))
.send()
.await;
assert_eq!(response.status(), StatusCode::OK);

// Verify proxy is deleted
let response = client
.get(format!("/api/v1/proxy/{}", proxy.id))
.send()
.await;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
assert_eq!(Proxy::all(&pool).await.unwrap().len(), 0);
}
1 change: 1 addition & 0 deletions crates/defguard_event_logger/src/description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ pub fn get_defguard_event_description(event: &DefguardEvent) -> Option<String> {
DefguardEvent::ProxyModified { before: _, after } => {
Some(format!("Modified proxy {after}"))
}
DefguardEvent::ProxyDeleted { proxy } => Some(format!("Deleted proxy {proxy}")),
}
}

Expand Down
15 changes: 10 additions & 5 deletions crates/defguard_event_logger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ use defguard_core::db::models::activity_log::{
MfaSecurityKeyMetadata, NetworkDeviceMetadata, NetworkDeviceModifiedMetadata,
OpenIdAppMetadata, OpenIdAppModifiedMetadata, OpenIdAppStateChangedMetadata,
OpenIdProviderMetadata, PasswordChangedByAdminMetadata, PasswordResetMetadata,
ProxyModifiedMetadata, SettingsUpdateMetadata, UserGroupsModifiedMetadata, UserMetadata,
UserMfaDisabledMetadata, UserModifiedMetadata, UserSnatBindingMetadata,
UserSnatBindingModifiedMetadata, VpnClientMetadata, VpnClientMfaFailedMetadata,
VpnClientMfaMetadata, VpnLocationMetadata, VpnLocationModifiedMetadata, WebHookMetadata,
WebHookModifiedMetadata, WebHookStateChangedMetadata,
ProxyDeletedMetadata, ProxyModifiedMetadata, SettingsUpdateMetadata,
UserGroupsModifiedMetadata, UserMetadata, UserMfaDisabledMetadata, UserModifiedMetadata,
UserSnatBindingMetadata, UserSnatBindingModifiedMetadata, VpnClientMetadata,
VpnClientMfaFailedMetadata, VpnClientMfaMetadata, VpnLocationMetadata,
VpnLocationModifiedMetadata, WebHookMetadata, WebHookModifiedMetadata,
WebHookStateChangedMetadata,
},
};
use description::{
Expand Down Expand Up @@ -472,6 +473,10 @@ pub async fn run_event_logger(
EventType::ProxyModified,
serde_json::to_value(ProxyModifiedMetadata { before, after }).ok(),
),
DefguardEvent::ProxyDeleted { proxy } => (
EventType::ProxyModified,
serde_json::to_value(ProxyDeletedMetadata { proxy }).ok(),
),
};
(module, event_type, description, metadata)
}
Expand Down
3 changes: 3 additions & 0 deletions crates/defguard_event_logger/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ pub enum DefguardEvent {
before: Proxy<Id>,
after: Proxy<Id>,
},
ProxyDeleted {
proxy: Proxy<Id>,
},
}

/// Represents activity log events related to client applications
Expand Down
4 changes: 4 additions & 0 deletions crates/defguard_event_router/src/handlers/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,10 @@ impl EventRouter {
LoggerEvent::Defguard(Box::new(DefguardEvent::ProxyModified { before, after })),
None,
),
ApiEventType::ProxyDeleted { proxy } => (
LoggerEvent::Defguard(Box::new(DefguardEvent::ProxyDeleted { proxy })),
None,
),
};
self.log_event(
EventContext::from_api_context(event.context, location),
Expand Down
4 changes: 3 additions & 1 deletion web/messages/en/edge.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
"edge_edit_public_address": "Public domain",
"edge_edit_delete": "Delete",
"edge_edit_success": "Edge component updated",
"edge_edit_failed": "Failed to update edge component"
"edge_edit_failed": "Failed to update edge component",
"edge_delete_success": "Edge component deleted",
"edge_delete_failed": "Failed to delete edge component"
}
4 changes: 4 additions & 0 deletions web/src/pages/EditEdgePage/EditEdgePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ const EditEdgeForm = ({ edge }: { edge: Edge }) => {
to: '/edge',
replace: true,
});
Snackbar.success(m.edge_delete_success());
},
onError: () => {
Snackbar.error(m.edge_delete_failed());
},
});

Expand Down
Loading