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
34 changes: 34 additions & 0 deletions crates/defguard_core/src/enterprise/handlers/acl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use axum::{
use chrono::NaiveDateTime;
use defguard_common::db::Id;
use serde_json::{Value, json};
use sqlx::query_as;
use utoipa::ToSchema;

use super::LicenseInfo;
Expand Down Expand Up @@ -191,6 +192,12 @@ pub(crate) struct ApplyAclAliasesData {
aliases: Vec<Id>,
}

#[derive(Debug, Serialize, ToSchema, sqlx::FromRow)]
pub struct AclStateCount {
pub applied: i64,
pub pending: i64,
}

/// List all ACL rules.
#[utoipa::path(
get,
Expand Down Expand Up @@ -221,6 +228,33 @@ pub(crate) async fn list_acl_rules(
Ok(ApiResponse::json(api_rules, StatusCode::OK))
}

/// Count ACL rules by state.
#[utoipa::path(
get,
path = "/api/v1/acl/rule/count",
tag = "ACL",
responses(
(status = OK, description = "ACL rule state counts", body = AclStateCount),
),
)]
pub(crate) async fn count_acl_rules(
_admin: AdminRole,
State(appstate): State<AppState>,
) -> ApiResult {
let counts = query_as::<_, AclStateCount>(
"SELECT \
COUNT(*) FILTER (WHERE state = 'applied'::aclrule_state) AS applied, \
COUNT(*) FILTER ( \
WHERE state IN ('new'::aclrule_state, 'modified'::aclrule_state, 'deleted'::aclrule_state) \
) AS pending \
FROM aclrule",
)
.fetch_one(&appstate.pool)
.await?;

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

/// Get ACL rule.
#[utoipa::path(
get,
Expand Down
31 changes: 29 additions & 2 deletions crates/defguard_core/src/enterprise/handlers/acl/alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use axum::{
};
use defguard_common::db::{Id, NoId};
use serde_json::{Value, json};
use sqlx::{PgConnection, PgPool, query};
use sqlx::{PgConnection, PgPool, query, query_as};
use utoipa::ToSchema;

use super::LicenseInfo;
use super::{AclStateCount, LicenseInfo};
use crate::{
appstate::AppState,
auth::{AdminRole, SessionInfo},
Expand Down Expand Up @@ -206,6 +206,33 @@ pub(crate) async fn list_acl_aliases(
Ok(ApiResponse::json(api_aliases, StatusCode::OK))
}

/// Count ACL aliases by state.
#[utoipa::path(
get,
path = "/api/v1/acl/alias/count",
tag = "ACL",
responses(
(status = OK, description = "ACL alias state counts", body = AclStateCount),
),
)]
pub(crate) async fn count_acl_aliases(
_admin: AdminRole,
State(appstate): State<AppState>,
) -> ApiResult {
let counts = query_as::<_, AclStateCount>(
"SELECT \
COUNT(*) FILTER (WHERE state = 'applied'::aclalias_state) AS applied, \
COUNT(*) FILTER (WHERE state = 'modified'::aclalias_state) AS pending \
FROM aclalias \
WHERE kind = $1",
)
.bind(AliasKind::Component)
.fetch_one(&appstate.pool)
.await?;

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

/// Get ACL alias.
#[utoipa::path(
get,
Expand Down
31 changes: 29 additions & 2 deletions crates/defguard_core/src/enterprise/handlers/acl/destination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use axum::{
use defguard_common::db::{Id, NoId};
use reqwest::StatusCode;
use serde_json::{Value, json};
use sqlx::{PgConnection, PgPool, query};
use sqlx::{PgConnection, PgPool, query, query_as};
use utoipa::ToSchema;

use super::LicenseInfo;
use super::{AclStateCount, LicenseInfo};
use crate::{
appstate::AppState,
auth::{AdminRole, SessionInfo},
Expand Down Expand Up @@ -215,6 +215,33 @@ pub(crate) async fn list_acl_destinations(
Ok(ApiResponse::json(api_aliases, StatusCode::OK))
}

/// Count ACL destinations by state.
#[utoipa::path(
get,
path = "/api/v1/acl/destination/count",
tag = "ACL",
responses(
(status = OK, description = "ACL destination state counts", body = AclStateCount),
)
)]
pub(crate) async fn count_acl_destinations(
_admin: AdminRole,
State(appstate): State<AppState>,
) -> ApiResult {
let counts = query_as::<_, AclStateCount>(
"SELECT \
COUNT(*) FILTER (WHERE state = 'applied'::aclalias_state) AS applied, \
COUNT(*) FILTER (WHERE state = 'modified'::aclalias_state) AS pending \
FROM aclalias \
WHERE kind = $1",
)
.bind(AliasKind::Destination)
.fetch_one(&appstate.pool)
.await?;

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

/// Get ACL destination.
#[utoipa::path(
get,
Expand Down
14 changes: 9 additions & 5 deletions crates/defguard_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,14 @@ use crate::{
handlers::{
acl::{
alias::{
create_acl_alias, delete_acl_alias, get_acl_alias, list_acl_aliases,
update_acl_alias,
count_acl_aliases, create_acl_alias, delete_acl_alias, get_acl_alias,
list_acl_aliases, update_acl_alias,
},
apply_acl_aliases, apply_acl_rules, create_acl_rule, delete_acl_rule,
apply_acl_aliases, apply_acl_rules, count_acl_rules, create_acl_rule,
delete_acl_rule,
destination::{
create_acl_destination, delete_acl_destination, get_acl_destination,
list_acl_destinations, update_acl_destination,
count_acl_destinations, create_acl_destination, delete_acl_destination,
get_acl_destination, list_acl_destinations, update_acl_destination,
},
get_acl_rule, list_acl_rules, update_acl_rule,
},
Expand Down Expand Up @@ -444,6 +445,7 @@ pub fn build_webapp(
"/api/v1/acl",
Router::new()
.route("/rule", get(list_acl_rules).post(create_acl_rule))
.route("/rule/count", get(count_acl_rules))
.route("/rule/apply", put(apply_acl_rules))
.route(
"/rule/{id}",
Expand All @@ -452,6 +454,7 @@ pub fn build_webapp(
.delete(delete_acl_rule),
)
.route("/alias", get(list_acl_aliases).post(create_acl_alias))
.route("/alias/count", get(count_acl_aliases))
.route(
"/alias/{id}",
get(get_acl_alias)
Expand All @@ -463,6 +466,7 @@ pub fn build_webapp(
"/destination",
get(list_acl_destinations).post(create_acl_destination),
)
.route("/destination/count", get(count_acl_destinations))
.route(
"/destination/{id}",
get(get_acl_destination)
Expand Down
3 changes: 3 additions & 0 deletions crates/defguard_core/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,23 @@ use super::{
openid_providers::list_openid_providers,
// /acl/rule
acl::list_acl_rules,
acl::count_acl_rules,
acl::create_acl_rule,
acl::apply_acl_rules,
acl::get_acl_rule,
acl::update_acl_rule,
acl::delete_acl_rule,
// /acl/alias
acl::alias::list_acl_aliases,
acl::alias::count_acl_aliases,
acl::alias::create_acl_alias,
acl::alias::get_acl_alias,
acl::alias::update_acl_alias,
acl::alias::delete_acl_alias,
acl::apply_acl_aliases,
// /acl/destination
acl::destination::list_acl_destinations,
acl::destination::count_acl_destinations,
acl::destination::create_acl_destination,
acl::destination::get_acl_destination,
acl::destination::update_acl_destination,
Expand Down
98 changes: 98 additions & 0 deletions crates/defguard_core/tests/integration/api/acl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ fn make_alias() -> EditAclAlias {
}
}

fn make_destination() -> Value {
json!({
"name": "destination",
"addresses": "10.20.30.40, 10.0.0.1/24, 10.0.10.1-10.0.20.1",
"ports": "1, 2, 3, 10-20, 30-40",
"protocols": [6, 17],
"any_address": false,
"any_port": false,
"any_protocol": false
})
}

fn edit_rule_data_into_api_response(
data: &EditAclRule,
id: Id,
Expand Down Expand Up @@ -1266,3 +1278,89 @@ async fn test_multiple_aliases_application(_: PgPoolOptions, options: PgConnectO
let alias: ApiAclAlias = client.get("/api/v1/acl/alias/6").send().await.json().await;
assert_eq!(alias.state, AliasState::Applied);
}

#[sqlx::test]
async fn test_acl_count_endpoints(_: PgPoolOptions, options: PgConnectOptions) {
let pool = setup_pool(options).await;
let (mut client, _) = make_test_client(pool.clone()).await;
authenticate_admin(&mut client).await;

// rules: 1 applied, 1 pending (new)
let rule = make_rule();
let response = client.post("/api/v1/acl/rule").json(&rule).send().await;
assert_eq!(response.status(), StatusCode::CREATED);
set_rule_state(&pool, 1, RuleState::Applied, None).await;

let response = client.post("/api/v1/acl/rule").json(&rule).send().await;
assert_eq!(response.status(), StatusCode::CREATED);

// aliases: 2 applied, 1 pending (modified)
let alias = make_alias();
let response = client.post("/api/v1/acl/alias").json(&alias).send().await;
assert_eq!(response.status(), StatusCode::CREATED);
let response = client.post("/api/v1/acl/alias").json(&alias).send().await;
assert_eq!(response.status(), StatusCode::CREATED);
let mut alias_to_update: ApiAclAlias =
client.get("/api/v1/acl/alias/2").send().await.json().await;
alias_to_update.name = "updated alias".to_string();
let response = client
.put("/api/v1/acl/alias/2")
.json(&alias_to_update)
.send()
.await;
assert_eq!(response.status(), StatusCode::OK);

// destinations: 2 applied, 1 pending (modified)
let destination = make_destination();
let response = client
.post("/api/v1/acl/destination")
.json(&destination)
.send()
.await;
assert_eq!(response.status(), StatusCode::CREATED);
let destination_1: Value = response.json().await;
let destination_1_id = destination_1["id"].as_i64().unwrap();

let response = client
.post("/api/v1/acl/destination")
.json(&destination)
.send()
.await;
assert_eq!(response.status(), StatusCode::CREATED);

let mut destination_to_update = destination_1;
destination_to_update["name"] = json!("updated destination");
let response = client
.put(format!("/api/v1/acl/destination/{destination_1_id}"))
.json(&destination_to_update)
.send()
.await;
assert_eq!(response.status(), StatusCode::OK);

let counts: Value = client
.get("/api/v1/acl/rule/count")
.send()
.await
.json()
.await;
assert_eq!(counts["applied"], json!(1));
assert_eq!(counts["pending"], json!(1));

let counts: Value = client
.get("/api/v1/acl/alias/count")
.send()
.await
.json()
.await;
assert_eq!(counts["applied"], json!(2));
assert_eq!(counts["pending"], json!(1));

let counts: Value = client
.get("/api/v1/acl/destination/count")
.send()
.await
.json()
.await;
assert_eq!(counts["applied"], json!(2));
assert_eq!(counts["pending"], json!(1));
}
2 changes: 1 addition & 1 deletion tools/defguard_generator/src/vpn_session_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ async fn prepare_gateway(pool: &PgPool, location_id: Id) -> Result<Gateway<Id>>
match existing_gateways.into_iter().next() {
Some(gateway) => Ok(gateway),
None => {
let gateway = Gateway::new(location_id, "http://localhost:50055", "gateway")
let gateway = Gateway::new(location_id, "test", "localhost", 50055, 1)
.save(pool)
.await?;
Ok(gateway)
Expand Down
3 changes: 2 additions & 1 deletion web/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './shared/defguard-ui/scss/index.scss';
import 'react-loading-skeleton/dist/skeleton.css';
// keep this as last style import
import './shared/defguard-ui/scss/index.scss';
import { App } from './app/App.tsx';

// biome-ignore lint/style/noNonNullAssertion: always there
Expand Down
Loading
Loading