From dae02661ec23e1fe1e617351f8ec79ea959edf21 Mon Sep 17 00:00:00 2001 From: Reimar Stier Date: Thu, 21 Dec 2023 10:34:40 +0100 Subject: [PATCH] CARL -> Remove access control rules if they already exist --- .../src/setup/tasks/create_service.rs | 12 +- .../request_capability_for_user.rs | 9 +- .../peers/configurator/tabs/devices/mod.rs | 4 +- .../src/client/implementation.rs | 195 +++++++++++------- .../src/netbird/access_control.rs | 16 -- .../opendut-vpn-netbird/src/netbird/error.rs | 19 ++ .../src/netbird/group/mod.rs | 20 +- .../opendut-vpn-netbird/src/netbird/mod.rs | 18 +- .../src/netbird/rules/mod.rs | 125 +++++++++++ opendut-vpn/opendut-vpn-netbird/src/routes.rs | 7 +- 10 files changed, 309 insertions(+), 116 deletions(-) delete mode 100644 opendut-vpn/opendut-vpn-netbird/src/netbird/access_control.rs create mode 100644 opendut-vpn/opendut-vpn-netbird/src/netbird/rules/mod.rs diff --git a/opendut-edgar/src/setup/tasks/create_service.rs b/opendut-edgar/src/setup/tasks/create_service.rs index 5e1bf67f3..f6d92bc45 100644 --- a/opendut-edgar/src/setup/tasks/create_service.rs +++ b/opendut-edgar/src/setup/tasks/create_service.rs @@ -1,4 +1,4 @@ -use std::fs; +use std::{env, fs}; use std::path::PathBuf; use std::process::Command; @@ -14,7 +14,7 @@ pub fn systemd_file_path() -> PathBuf { PathBuf::from(format!("/etc/systemd/system/{SYSTEMD_SERVICE_FILE_NAME}")) } -fn systemd_file_content() -> String { +fn systemd_file_content(service_user: &str) -> String { let executable = executable_install_path().unwrap(); let executable = executable.display(); @@ -30,8 +30,8 @@ StartLimitBurst=0 ExecStart={executable} service Restart=always RestartSec=30s -User={USER_NAME} -Group={USER_NAME} +User={service_user} +Group={service_user} [Install] WantedBy=multi-user.target @@ -59,7 +59,9 @@ impl Task for CreateServiceFile { let out_path = systemd_file_path(); fs::create_dir_all(out_path.parent().unwrap())?; - fs::write(&out_path, systemd_file_content()) + let service_user_name = env::var("OPENDUT_EDGAR_SERVICE_USER").unwrap_or(USER_NAME.to_string()); + log::info!("Using service user '{}'", service_user_name); + fs::write(&out_path, systemd_file_content(&service_user_name)) .context(format!("Error while writing service file to '{}'", out_path.display()))?; let _ = Command::new("systemctl") diff --git a/opendut-edgar/src/setup/tasks/linux_network_capability/request_capability_for_user.rs b/opendut-edgar/src/setup/tasks/linux_network_capability/request_capability_for_user.rs index 55998685b..efef7ed65 100644 --- a/opendut-edgar/src/setup/tasks/linux_network_capability/request_capability_for_user.rs +++ b/opendut-edgar/src/setup/tasks/linux_network_capability/request_capability_for_user.rs @@ -1,4 +1,4 @@ -use std::fs; +use std::{env, fs}; use std::path::PathBuf; use std::process::Command; @@ -21,6 +21,13 @@ impl Task for RequestCapabilityForUser { } fn check_fulfilled(&self) -> Result { let capability_file = capability_file(); + let is_root = env::var("OPENDUT_EDGAR_SERVICE_USER") + .map(|user| "root" == user) + .unwrap_or(false); + + if is_root { + return Ok(TaskFulfilled::Unchecked) + } if capability_file.exists() { let file_content = fs::read_to_string(&capability_file) diff --git a/opendut-lea/src/peers/configurator/tabs/devices/mod.rs b/opendut-lea/src/peers/configurator/tabs/devices/mod.rs index f811e7105..e4f0c9b46 100644 --- a/opendut-lea/src/peers/configurator/tabs/devices/mod.rs +++ b/opendut-lea/src/peers/configurator/tabs/devices/mod.rs @@ -1,6 +1,6 @@ -use leptos::{component, create_memo, create_rw_signal, create_slice, IntoView, RwSignal, SignalGetUntracked, SignalUpdate, SignalWith, SignalWithUntracked, view}; +use leptos::{component, create_memo, create_rw_signal, create_slice, IntoView, RwSignal, SignalUpdate, SignalWith, SignalWithUntracked, view}; -use opendut_types::topology::{Device, DeviceId}; +use opendut_types::topology::DeviceId; use crate::components::UserInputValue; use crate::peers::configurator::tabs::devices::device_panel::DevicePanel; diff --git a/opendut-vpn/opendut-vpn-netbird/src/client/implementation.rs b/opendut-vpn/opendut-vpn-netbird/src/client/implementation.rs index 0362340cc..c68e098ec 100644 --- a/opendut-vpn/opendut-vpn-netbird/src/client/implementation.rs +++ b/opendut-vpn/opendut-vpn-netbird/src/client/implementation.rs @@ -1,6 +1,6 @@ use anyhow::anyhow; use async_trait::async_trait; -use reqwest::{Body, header, Method, Request, Url}; +use reqwest::{Body, header, Method, Request, Response, Url}; use reqwest::header::{HeaderMap, HeaderValue}; use serde::Serialize; @@ -11,8 +11,9 @@ use opendut_vpn::{CreateClusterError, CreatePeerError, DeleteClusterError, Delet use crate::{netbird, NetbirdToken, routes}; use crate::client::request_handler::{DefaultRequestHandler, RequestHandler}; -use crate::netbird::{access_control, error}; -use crate::netbird::error::{CreateSetupKeyError, RequestError, GetGroupError}; +use crate::netbird::{error, group, rules}; +use crate::netbird::error::{CreateSetupKeyError, GetGroupError, GetRulesError, RequestError}; +use crate::netbird::rules::{RuleName}; pub struct Client { base_url: Url, @@ -21,9 +22,7 @@ pub struct Client { #[async_trait] impl VpnManagementClient for Client { - async fn create_cluster(&self, cluster_id: ClusterId, peers: &Vec) -> Result<(), CreateClusterError> { - match self.delete_cluster(cluster_id).await { Ok(_) => log::debug!("Deleted a previous cluster with ID <{cluster_id}> before creating the new cluster."), Err(cause) => match cause { @@ -38,7 +37,7 @@ impl VpnManagementClient for Client { let mut netbird_peers = vec![]; for peer_id in peers { let group = self.get_netbird_group(&(*peer_id).into()).await - .map_err(|error| CreateClusterError::PeerResolutionFailure { peer_id: *peer_id, cluster_id, error: error.into()})?; + .map_err(|error| CreateClusterError::PeerResolutionFailure { peer_id: *peer_id, cluster_id, error: error.into() })?; let peer = group.peers.into_iter().next() .ok_or(CreateClusterError::PeerResolutionFailure { peer_id: *peer_id, cluster_id, error: anyhow!("Self-Group does not contain expected peer!").into() })?; netbird_peers.push(peer.id); @@ -49,39 +48,76 @@ impl VpnManagementClient for Client { let group = self.create_netbird_group(cluster_id.into(), netbird_peers).await .map_err(|error| CreateClusterError::CreationFailure { cluster_id, error: error.into() })?; - self.create_netbird_self_access_control_rule(group, cluster_id).await + + self.create_netbird_self_access_control_rule(group, cluster_id.into()).await .map_err(|error| CreateClusterError::AccessControlRuleCreationFailure { cluster_id, error: error.into() })?; Ok(()) } + async fn delete_cluster(&self, cluster_id: ClusterId) -> Result<(), DeleteClusterError> { - let groups = self.get_all_netbird_groups(&netbird::GroupName::from(cluster_id)).await - .map_err(|cause| DeleteClusterError::DeletionFailure { cluster_id, error: anyhow!("Failed to request the list of groups to determine which should be deleted:\n {cause}").into() })?; + // delete cluster rule (access control) + let rule_name = RuleName::Cluster(cluster_id); + match self.get_netbird_rule(&rule_name).await { + Ok(rule) => { + match self.delete_netbird_rule(&rule.id).await { + Ok(_) => log::debug!("Deleted NetBird rule with name '{}' and NetBird Rule ID '{}'.", rule.name, rule.id.0), + Err(cause) => return match cause { + RequestError::IllegalStatus(error) => { + if let Some(http::StatusCode::NOT_FOUND) = error.status() { + Err(DeleteClusterError::NotFound { cluster_id, message: format!("Received '404 Not Found' when deleting rule for cluster <{cluster_id}> with NetBird rule ID <{netbird_rule}>.", netbird_rule = rule.id.0) }) + } else { + Err(DeleteClusterError::DeletionFailure { cluster_id, error: error.into() }) + } + } + RequestError::IllegalRequest(error, code) => { + Err(DeleteClusterError::DeletionFailure { + cluster_id, + error: anyhow!("Received status code '{code}' when deleting cluster <{cluster_id}> with NetBird rule ID <{netbird_rule}>:\n {error}", code=code, cluster_id=cluster_id, netbird_rule=rule.id.0, error=error).into(), + }) + } + other => Err(DeleteClusterError::DeletionFailure { cluster_id, error: other.into() }), + } + } + } + Err(GetRulesError::RuleNotFound { .. }) => { + // No rule found, so no need to delete it. + } + Err(cause) => { + return Err(DeleteClusterError::DeletionFailure { cluster_id, error: anyhow!("Failed to get cluster rule '{rule_name}' to be deleted.\n {cause}").into() }); + } + }; - if groups.is_empty() { - return Err(DeleteClusterError::NotFound { cluster_id, message: format!("No group matching the cluster <{cluster_id}> found.") }) - } else { - for group in groups { + let group_name = netbird::GroupName::from(cluster_id); + match self.get_netbird_group(&group_name).await { + Ok(group) => { match self.delete_netbird_group(&group.id).await { - Ok(_) => log::debug!("Deleted NetBird group with name '{}' and NetBird Group ID '{}'.", group.name, group.id.0), - Err(cause) => return match cause { + Ok(_) => { + log::debug!("Deleted NetBird group with name '{}' and NetBird Group ID '{}'.", group.name, group.id.0) ; + Ok(()) + }, + Err(cause) => match cause { RequestError::IllegalStatus(error) => { if let Some(http::StatusCode::NOT_FOUND) = error.status() { - Err(DeleteClusterError::NotFound { cluster_id, message: format!("Received '404 Not Found' when deleting cluster <{cluster_id}> with NetBird group ID <{netbird_group}>.", netbird_group=group.id.0) }) + Err(DeleteClusterError::NotFound { cluster_id, message: format!("Received '404 Not Found' when deleting group for cluster <{cluster_id}> with NetBird group ID <{netbird_group}>.", netbird_group = group.id.0) }) } else { Err(DeleteClusterError::DeletionFailure { cluster_id, error: error.into() }) //TODO logging of this doesn't show the HTTP body, making e.g. 400 Bad Request errors difficult to debug } - }, + } other => Err(DeleteClusterError::DeletionFailure { cluster_id, error: other.into() }), } } + + } + Err(GetGroupError::GroupNotFound { .. }) => { + // No group found, so no need to delete it. + Ok(()) + } + Err(cause) => { + Err(DeleteClusterError::DeletionFailure { cluster_id, error: anyhow!("Failed to get cluster group '{group_name}' to be deleted.\n {cause}").into() }) } } - - //TODO delete access control rule in NetBird - - Ok(()) } async fn create_peer(&self, peer_id: PeerId) -> Result<(), CreatePeerError> { @@ -92,7 +128,6 @@ impl VpnManagementClient for Client { } async fn delete_peer(&self, peer_id: PeerId) -> Result<(), DeletePeerError> { - let self_group = self.get_netbird_group(&netbird::GroupName::from(peer_id)).await .map_err(|error| DeletePeerError::ResolutionFailure { peer_id, error: error.into() })?; @@ -110,7 +145,6 @@ impl VpnManagementClient for Client { } async fn get_or_create_configuration(&self, peer_id: PeerId) -> Result { - let setup_keys = self.list_netbird_setup_keys().await .map_err(|error| GetOrCreateConfigurationError::QueryConfigurationsFailure { error: error.into() })?; @@ -133,7 +167,6 @@ impl VpnManagementClient for Client { } impl Client { - pub fn create(base_url: Url, token: NetbirdToken, https_only: HttpsOnly) -> Result { let headers = { let mut headers = HeaderMap::new(); @@ -160,7 +193,6 @@ impl Client { } async fn create_netbird_group(&self, name: netbird::GroupName, peers: Vec) -> Result { - let url = routes::groups(self.base_url.clone()); let body = { @@ -187,20 +219,23 @@ impl Client { Ok(result) } - async fn get_all_netbird_groups(&self, group_name: &netbird::GroupName) -> Result, GetGroupError> { - let url = routes::groups(self.base_url.clone()); + async fn get_netbird_rule(&self, rule_name: &RuleName) -> Result { + let url = routes::rules(self.base_url.clone()); let request = Request::new(Method::GET, url); - let response = self.requester.handle(request).await - .map_err(|cause| GetGroupError::RequestFailure { group_name: group_name.to_owned(), cause })?; - - let result = response.json::>().await - .map_err(|cause| GetGroupError::RequestFailure { group_name: group_name.to_owned(), cause: RequestError::JsonDeserialization(cause) })?; + .map_err(|cause| GetRulesError::RequestFailure { rule_name: rule_name.to_owned(), cause })?; + let result = response.json::>().await + .map_err(|cause| GetRulesError::RequestFailure { rule_name: rule_name.to_owned(), cause: RequestError::JsonDeserialization(cause) })?; - let result = result.into_iter() - .filter(|group| group.name == *group_name) + let rules = result.into_iter() + .filter(|rule| rule.name == *rule_name) .collect::>(); - Ok(result) + + if rules.len() > 1 { + Err(GetRulesError::MultipleRulesFound { rule_name: rule_name.to_owned() }) + } else { + rules.into_iter().next().ok_or(GetRulesError::RuleNotFound { rule_name: rule_name.to_owned() }) + } } async fn get_netbird_group(&self, group_name: &netbird::GroupName) -> Result { //TODO remove? Introduce error for multiple? Rename to 'find' and 'filter'? @@ -213,44 +248,69 @@ impl Client { let result = response.json::>().await .map_err(|cause| GetGroupError::RequestFailure { group_name: group_name.to_owned(), cause: RequestError::JsonDeserialization(cause) })?; - result.into_iter() - .find(|group| group.name == *group_name) - .ok_or(GetGroupError::GroupNotFound { group_name: group_name.to_owned() }) - } + let groups = result.into_iter() + .filter(|group| group.name == *group_name) + .collect::>(); - async fn delete_netbird_group(&self, group_id: &netbird::GroupId) -> Result<(), RequestError> { + if groups.len() > 1 { + Err(GetGroupError::MultipleGroupsFound { group_name: group_name.to_owned() }) + } else { + groups.into_iter().next().ok_or(GetGroupError::GroupNotFound { group_name: group_name.to_owned() }) + } + } + async fn delete_netbird_group(&self, group_id: &group::GroupId) -> Result<(), RequestError> { let url = routes::group(Clone::clone(&self.base_url), &group_id); let request = Request::new(Method::DELETE, url); let response = self.requester.handle(request).await?; - response.error_for_status() - .map_err(RequestError::IllegalStatus)?; + Self::parse_response_status(response, format!("NetBird group with ID <{:?}>", group_id)).await + } - Ok(()) + async fn parse_response_status(response: Response, error_text: String) -> Result<(), RequestError> { + match response.error_for_status_ref() { + Ok(_) => Ok(()), + Err(status) => { + let body = response.text().await.unwrap_or(String::from("")); + let status_code = status.status().expect("Error should be generated from a response"); + log::error!("Received status code '{code}' when deleting {error_text}:\n {body}", code=status_code, error_text=error_text, body=body); + Err(RequestError::IllegalRequest(status_code, body)) + } + } } - async fn create_netbird_self_access_control_rule(&self, group: netbird::Group, cluster_id: ClusterId) -> Result<(), RequestError> { + async fn delete_netbird_rule(&self, rule_id: &rules::RuleId) -> Result<(), RequestError> { + let url = routes::rule(Clone::clone(&self.base_url), &rule_id); + + let request = Request::new(Method::DELETE, url); + + let response = self.requester.handle(request).await?; + + Self::parse_response_status(response, format!("NetBird rule with ID <{:?}>", rule_id)).await + } + + async fn create_netbird_self_access_control_rule(&self, group: netbird::Group, rule_name: RuleName) -> Result<(), RequestError> { let url = routes::rules(self.base_url.clone()); let body = { #[derive(Serialize)] struct CreateAccessControlRule { - name: String, + name: RuleName, description: String, disabled: bool, - flow: access_control::RuleFlow, - sources: Vec, - destinations: Vec, + flow: rules::RuleFlow, + sources: Vec, + destinations: Vec, } + let description = rule_name.description(); CreateAccessControlRule { - name: access_control::rule_name(cluster_id), - description: access_control::rule_description(cluster_id), + name: rule_name, + description, disabled: false, - flow: access_control::RuleFlow::Bidirect, + flow: rules::RuleFlow::Bidirect, sources: vec![group.id.clone()], destinations: vec![group.id], } @@ -266,7 +326,6 @@ impl Client { } async fn delete_netbird_peer(&self, peer_id: &netbird::PeerId) -> Result<(), RequestError> { - let url = routes::peer(Clone::clone(&self.base_url), peer_id); let request = Request::new(Method::DELETE, url); @@ -280,7 +339,6 @@ impl Client { } async fn create_netbird_setup_key(&self, peer_id: PeerId) -> Result { - let peer_group_name = netbird::GroupName::from(peer_id); let peer_group = self.get_netbird_group(&peer_group_name).await .map_err(|cause| CreateSetupKeyError::PeerGroupNotFound { peer_id, cause })?; @@ -292,7 +350,8 @@ impl Client { struct CreateSetupKey { name: String, r#type: netbird::setup_key::Type, - expires_in: u64, //seconds + expires_in: u64, + //seconds revoked: bool, auto_groups: Vec, usage_limit: u64, @@ -300,8 +359,8 @@ impl Client { CreateSetupKey { name: netbird::setup_key::name_format(peer_id), - r#type: netbird::setup_key::Type::OneOff, - expires_in: 24*60*60, //TODO make configurable + r#type: netbird::setup_key::Type::Reusable, + expires_in: 24 * 60 * 60, //TODO make configurable revoked: false, auto_groups: vec![ peer_group.id.0 @@ -348,6 +407,7 @@ fn post_json_request(url: Url, body: impl Serialize) -> Result HeaderValue { HeaderValue::from_str(mime::APPLICATION_JSON.as_ref()) .expect("MIME type for application/json should always be convertable to a header value.") @@ -365,6 +425,7 @@ mod tests { use uuid::uuid; use opendut_types::cluster::ClusterId; + use crate::netbird::group; use super::*; @@ -385,7 +446,6 @@ mod tests { #[tokio::test] async fn delete_a_peer() -> anyhow::Result<()> { - fn peer_id() -> PeerId { PeerId::from(uuid!("f3fb5772-a259-400e-9e61-b5a69dc46c2a")) } let requester = MockRequester { @@ -422,16 +482,14 @@ mod tests { Ok(Response::from(http::Response::builder() .body("") .unwrap())) - } - else if path.starts_with("/api/peers/") { + } else if path.starts_with("/api/peers/") { assert_that!(request.method(), eq(&http::Method::DELETE)); assert_that!(request.url().path(), eq("/api/peers/ah8cca16lmn67acg5s11")); assert_that!(request.body(), none()); Ok(Response::from(http::Response::builder() .body("") .unwrap())) - } - else { + } else { panic!("Unexpected url path: {path}"); } } @@ -453,7 +511,6 @@ mod tests { #[tokio::test] async fn create_group() -> anyhow::Result<()> { - fn cluster_id() -> ClusterId { ClusterId::from(uuid!("999f8513-d7ab-43fe-9bf0-091abaff2a97")) } fn name() -> netbird::GroupName { netbird::GroupName::Cluster(cluster_id()) } fn netbird_peer() -> netbird::PeerId { netbird::PeerId(String::from("chacbco6lnnbn6cg5s90")) } @@ -526,7 +583,6 @@ mod tests { #[tokio::test] async fn find_group() -> anyhow::Result<()> { - fn peer_id() -> PeerId { PeerId::from(uuid!("53b49ffb-9962-487a-b522-981ebe6aac59")) } fn name() -> netbird::GroupName { netbird::GroupName::Peer(peer_id()) } @@ -576,10 +632,9 @@ mod tests { #[tokio::test] async fn create_a_setup_key() -> anyhow::Result<()> { - fn peer_id() -> PeerId { PeerId::from(uuid!("b7dd1960-9ab5-4f3a-851d-6b68a90099eb")) } fn name() -> String { netbird::setup_key::name_format(peer_id()) } - fn netbird_group_id() -> netbird::GroupId { netbird::GroupId::from("ch8i4ug6lnn4g9hqv7m0") } + fn netbird_group_id() -> group::GroupId { group::GroupId::from("ch8i4ug6lnn4g9hqv7m0") } let requester = MockRequester { on_request: |request| { @@ -682,14 +737,10 @@ mod tests { #[tokio::test] async fn create_access_control_rule() -> anyhow::Result<()> { - fn cluster_id() -> ClusterId { ClusterId::from(uuid!("999f8513-d7ab-43fe-9bf0-091abaff2a97")) } - fn name() -> String { access_control::rule_name(cluster_id()) } - fn description() -> String { - access_control::rule_description(cluster_id()) - } - fn netbird_group_id() -> netbird::GroupId { - netbird::GroupId::from("ch8i4ug6lnn4g9hqv7m0") + + fn netbird_group_id() -> group::GroupId { + group::GroupId::from("ch8i4ug6lnn4g9hqv7m0") } let requester = MockRequester { diff --git a/opendut-vpn/opendut-vpn-netbird/src/netbird/access_control.rs b/opendut-vpn/opendut-vpn-netbird/src/netbird/access_control.rs deleted file mode 100644 index 59e83904c..000000000 --- a/opendut-vpn/opendut-vpn-netbird/src/netbird/access_control.rs +++ /dev/null @@ -1,16 +0,0 @@ -use serde::Serialize; -use opendut_types::cluster::ClusterId; - -#[derive(Serialize)] -#[serde(rename_all="kebab-case")] -pub(crate) enum RuleFlow { - Bidirect, -} - -pub fn rule_name(cluster_id: ClusterId) -> String { - format!("opendut-cluster-rule-{cluster_id}") -} - -pub fn rule_description(cluster_id: ClusterId) -> String { - format!("Rule for the openDuT cluster <{cluster_id}>.") -} diff --git a/opendut-vpn/opendut-vpn-netbird/src/netbird/error.rs b/opendut-vpn/opendut-vpn-netbird/src/netbird/error.rs index fdad26285..3a2816d48 100644 --- a/opendut-vpn/opendut-vpn-netbird/src/netbird/error.rs +++ b/opendut-vpn/opendut-vpn-netbird/src/netbird/error.rs @@ -1,13 +1,17 @@ use reqwest::header::InvalidHeaderValue; use std::fmt::Debug; +use http::StatusCode; use opendut_types::peer::PeerId; use crate::netbird::group::GroupName; +use crate::netbird::rules::RuleName; #[derive(thiserror::Error, Debug)] pub enum GetGroupError { #[error("A group with name '{group_name}' does not exist!")] GroupNotFound { group_name: GroupName }, + #[error("Multiple groups with name '{group_name}' exist!")] + MultipleGroupsFound { group_name: GroupName }, #[error("Could not request group '{group_name}':\n {cause}")] RequestFailure { group_name: GroupName, @@ -15,6 +19,19 @@ pub enum GetGroupError { } } +#[derive(thiserror::Error, Debug)] +pub enum GetRulesError { + #[error("A rule with name '{rule_name}' does not exist!")] + RuleNotFound { rule_name: RuleName }, + #[error("Multiple rules with name '{rule_name}' exist!")] + MultipleRulesFound { rule_name: RuleName }, + #[error("Could not request rule '{rule_name}:\n {cause}")] + RequestFailure { + rule_name: RuleName, + cause: RequestError + } +} + #[derive(thiserror::Error, Debug)] pub enum CreateSetupKeyError { #[error("Auto-assign group for peer <{peer_id}> not found for setup-key creation:\n {cause}!")] @@ -32,6 +49,8 @@ pub enum RequestError { Request(reqwest::Error), //TODO can rename to Transport? #[error("Received status code indicating an error: {0}")] IllegalStatus(reqwest::Error), + #[error("Received status code '{0}' indicating an error: {1}")] + IllegalRequest(StatusCode, String), #[error("JSON deserialization error: {0}")] JsonDeserialization(reqwest::Error), #[error("JSON serialization error: {0}")] diff --git a/opendut-vpn/opendut-vpn-netbird/src/netbird/group/mod.rs b/opendut-vpn/opendut-vpn-netbird/src/netbird/group/mod.rs index f3499c95f..f6929b35e 100644 --- a/opendut-vpn/opendut-vpn-netbird/src/netbird/group/mod.rs +++ b/opendut-vpn/opendut-vpn-netbird/src/netbird/group/mod.rs @@ -1,4 +1,4 @@ -use serde::Deserialize; +use serde::{Deserialize, Serialize}; pub use group_name::GroupName; use crate::netbird; @@ -7,7 +7,7 @@ mod group_name; #[derive(Debug, Deserialize)] pub struct Group { - pub id: netbird::GroupId, + pub id: GroupId, pub name: GroupName, pub peers_count: usize, #[serde(deserialize_with = "opendut_util::serde::deserialize_null_default")] @@ -20,3 +20,19 @@ pub struct GroupPeerInfo { pub id: netbird::PeerId, pub name: String, } + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct GroupId(pub String); + +impl From<&str> for GroupId { + fn from(value: &str) -> Self { + GroupId(value.to_owned()) + } +} + +impl From for GroupId { + fn from(value: String) -> Self { + GroupId(value) + } +} diff --git a/opendut-vpn/opendut-vpn-netbird/src/netbird/mod.rs b/opendut-vpn/opendut-vpn-netbird/src/netbird/mod.rs index 042756697..3750c6e01 100644 --- a/opendut-vpn/opendut-vpn-netbird/src/netbird/mod.rs +++ b/opendut-vpn/opendut-vpn-netbird/src/netbird/mod.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; -pub(crate) mod access_control; pub(crate) mod token; pub(crate) mod group; pub(crate) use group::{Group, GroupName}; @@ -8,23 +7,8 @@ pub(crate) use group::{Group, GroupName}; pub(crate) mod setup_key; pub(crate) use setup_key::SetupKey; pub mod error; +pub(crate) mod rules; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct PeerId(pub String); - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct GroupId(pub String); - -impl From<&str> for GroupId { - fn from(value: &str) -> Self { - GroupId(value.to_owned()) - } -} - -impl From for GroupId { - fn from(value: String) -> Self { - GroupId(value) - } -} diff --git a/opendut-vpn/opendut-vpn-netbird/src/netbird/rules/mod.rs b/opendut-vpn/opendut-vpn-netbird/src/netbird/rules/mod.rs new file mode 100644 index 000000000..57ba09110 --- /dev/null +++ b/opendut-vpn/opendut-vpn-netbird/src/netbird/rules/mod.rs @@ -0,0 +1,125 @@ +use std::error::Error; +use std::fmt::{Display, Formatter}; +use serde::{Deserialize, Serialize}; +use opendut_types::cluster::ClusterId; + +use crate::netbird::group::GroupId; + +#[derive(thiserror::Error, Debug)] +#[error("Cannot create RuleName from '{value}':\n {cause}")] +pub struct InvalidRuleNameError { + value: String, + cause: Box, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub enum RuleName { + Cluster(ClusterId), + Other(String), +} + +impl RuleName { + const CLUSTER_RULE_PREFIX: &'static str = "opendut-cluster-rule-"; + + pub fn description(&self) -> String { + match self { + RuleName::Cluster(cluster_id) => format!("Rule for the openDuT cluster <{cluster_id}>."), + RuleName::Other(name) => name.to_owned(), + } + } +} + +impl From for RuleName { + fn from(cluster_id: ClusterId) -> Self { + RuleName::Cluster(cluster_id) + } +} + +impl TryFrom<&str> for RuleName { + type Error = InvalidRuleNameError; + + fn try_from(value: &str) -> Result { + if let Some(uuid) = value.strip_prefix(RuleName::CLUSTER_RULE_PREFIX) { + ClusterId::try_from(uuid) + .map(|id| Self::Cluster(id)) + .map_err(|cause| InvalidRuleNameError { value: value.to_owned(), cause: cause.into() }) + } + else { + Ok(Self::Other(value.to_owned())) + } + } +} + +impl TryFrom for RuleName { + + type Error = InvalidRuleNameError; + + fn try_from(value: String) -> Result { + RuleName::try_from(value.as_str()) + } +} + +impl From<&RuleName> for String { + fn from(value: &RuleName) -> Self { + match value { + RuleName::Cluster(id) => format!("{}{}", RuleName::CLUSTER_RULE_PREFIX, id), + RuleName::Other(name) => name.to_owned(), + } + } +} + +impl From for String { + fn from(value: RuleName) -> Self { + String::from(&value) + } +} + +impl Display for RuleName { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", String::from(self)) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct RuleId(pub String); + +impl From<&str> for RuleId { + fn from(value: &str) -> Self { + RuleId(value.to_owned()) + } +} + +impl From for RuleId { + fn from(value: String) -> Self { + RuleId(value) + } +} + + +#[derive(Debug, Deserialize)] +pub struct Rule { + pub id: RuleId, + pub name: RuleName, + pub description: String, + pub disabled: bool, + pub flow: RuleFlow, + + pub sources: Vec, + pub destinations: Vec, +} + + +#[derive(Debug, Deserialize)] +pub struct GroupInfo { + pub id: GroupId, + pub name: String, + pub peers_count: usize, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all="kebab-case")] +pub(crate) enum RuleFlow { + Bidirect, +} diff --git a/opendut-vpn/opendut-vpn-netbird/src/routes.rs b/opendut-vpn/opendut-vpn-netbird/src/routes.rs index c18d4ee18..87dae34d1 100644 --- a/opendut-vpn/opendut-vpn-netbird/src/routes.rs +++ b/opendut-vpn/opendut-vpn-netbird/src/routes.rs @@ -1,5 +1,6 @@ use reqwest::Url; use crate::netbird; +use crate::netbird::group; pub fn setup_keys(base_url: Url) -> Url { join(base_url, "setup-keys") @@ -9,7 +10,7 @@ pub fn groups(base_url: Url) -> Url { join(base_url, "groups") } -pub fn group(base_url: Url, group_id: &netbird::GroupId) -> Url { +pub fn group(base_url: Url, group_id: &group::GroupId) -> Url { join(groups(base_url), &group_id.0) } @@ -25,6 +26,10 @@ pub fn rules(base_url: Url) -> Url { join(base_url, "rules") } +pub fn rule(base_url: Url, rule_id: &netbird::rules::RuleId) -> Url { + join(rules(base_url), &rule_id.0) +} + fn join(mut base_url: Url, path: &str) -> Url { base_url.path_segments_mut() .map(|mut path_segments| {