Can create network filter rules

This commit is contained in:
Pierre HUBERT 2024-01-02 13:14:11 +01:00
parent 388a1ed478
commit 81f60ce766
8 changed files with 102 additions and 192 deletions

View File

@ -1882,6 +1882,16 @@ dependencies = [
"bytemuck", "bytemuck",
] ]
[[package]]
name = "quick-xml"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"memchr",
"serde",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.33" version = "1.0.33"
@ -2662,6 +2672,7 @@ dependencies = [
"log", "log",
"mime_guess", "mime_guess",
"num", "num",
"quick-xml",
"rand", "rand",
"reqwest", "reqwest",
"rust-embed", "rust-embed",

View File

@ -23,6 +23,7 @@ actix-http = "3.4.0"
serde = { version = "1.0.193", features = ["derive"] } serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108" serde_json = "1.0.108"
serde-xml-rs = "0.6.0" serde-xml-rs = "0.6.0"
quick-xml = { version = "0.31.0", features = ["serialize", "overlapped-lists"] }
futures-util = "0.3.28" futures-util = "0.3.28"
anyhow = "1.0.75" anyhow = "1.0.75"
actix-multipart = "0.6.1" actix-multipart = "0.6.1"

View File

@ -5,6 +5,7 @@ use crate::libvirt_lib_structures::nwfilter::*;
use crate::libvirt_lib_structures::*; use crate::libvirt_lib_structures::*;
use crate::libvirt_rest_structures::hypervisor::*; use crate::libvirt_rest_structures::hypervisor::*;
use crate::libvirt_rest_structures::net::*; use crate::libvirt_rest_structures::net::*;
use crate::libvirt_rest_structures::nw_filter::NetworkFilter;
use crate::libvirt_rest_structures::vm::*; use crate::libvirt_rest_structures::vm::*;
use actix::{Actor, Context, Handler, Message}; use actix::{Actor, Context, Handler, Message};
use image::ImageOutputFormat; use image::ImageOutputFormat;
@ -591,3 +592,29 @@ impl Handler<GetNWFilterXMLReq> for LibVirtActor {
NetworkFilterXML::parse_xml(xml) NetworkFilterXML::parse_xml(xml)
} }
} }
#[derive(Message)]
#[rtype(result = "anyhow::Result<XMLUuid>")]
pub struct DefineNWFilterReq(pub NetworkFilter, pub NetworkFilterXML);
impl Handler<DefineNWFilterReq> for LibVirtActor {
type Result = anyhow::Result<XMLUuid>;
fn handle(&mut self, mut msg: DefineNWFilterReq, _ctx: &mut Self::Context) -> Self::Result {
let xml = msg.1.into_xml()?;
log::debug!("Define network filter:\n{}", xml);
let filter = NWFilter::define_xml(&self.m, &xml)?;
let uuid = XMLUuid::parse_from_str(&filter.get_uuid_string()?)?;
// Save a copy of the source definition
msg.0.uuid = Some(uuid);
let json = serde_json::to_string(&msg.0)?;
std::fs::write(
AppConfig::get().net_filter_definition_path(&msg.0.name),
json,
)?;
Ok(uuid)
}
}

View File

@ -1,4 +1,5 @@
use crate::libvirt_lib_structures::XMLUuid; use crate::libvirt_lib_structures::XMLUuid;
use crate::libvirt_rest_structures::nw_filter::NetworkFilterName;
use clap::Parser; use clap::Parser;
use std::net::IpAddr; use std::net::IpAddr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -252,6 +253,11 @@ impl AppConfig {
pub fn net_definition_path(&self, name: &str) -> PathBuf { pub fn net_definition_path(&self, name: &str) -> PathBuf {
self.definitions_path().join(format!("net-{name}.json")) self.definitions_path().join(format!("net-{name}.json"))
} }
pub fn net_filter_definition_path(&self, name: &NetworkFilterName) -> PathBuf {
self.definitions_path()
.join(format!("nwfilter-{}.json", name.0))
}
} }
#[derive(Debug, Clone, serde::Serialize)] #[derive(Debug, Clone, serde::Serialize)]

View File

@ -25,9 +25,6 @@ pub async fn create(client: LibVirtReq, req: web::Json<NetworkFilter>) -> HttpRe
.json("Builtin network filter rules shall not be modified!")); .json("Builtin network filter rules shall not be modified!"));
} }
// TODO : remove
return Ok(HttpResponse::Ok().json(network));
let uid = match client.update_network_filter(req.0, network).await { let uid = match client.update_network_filter(req.0, network).await {
Ok(u) => u, Ok(u) => u,
Err(e) => { Err(e) => {

View File

@ -190,10 +190,11 @@ impl LibVirtClient {
/// Update the information about a single domain /// Update the information about a single domain
pub async fn update_network_filter( pub async fn update_network_filter(
&self, &self,
_vm_def: NetworkFilter, nwf_def: NetworkFilter,
xml: NetworkFilterXML, xml: NetworkFilterXML,
) -> anyhow::Result<XMLUuid> { ) -> anyhow::Result<XMLUuid> {
println!("nwfilter xml to update: {:#?}", xml); self.0
todo!() .send(libvirt_actor::DefineNWFilterReq(nwf_def, xml))
.await?
} }
} }

View File

@ -5,7 +5,7 @@ use std::net::{Ipv4Addr, Ipv6Addr};
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(rename = "filterref")] #[serde(rename = "filterref")]
pub struct NetworkFilterRefXML { pub struct NetworkFilterRefXML {
#[serde(rename(serialize = "@filter"))] #[serde(rename = "@filter")]
pub filter: String, pub filter: String,
} }
@ -16,225 +16,114 @@ pub struct NetworkFilterRuleProtocolAll {}
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(rename = "mac")] #[serde(rename = "mac")]
pub struct NetworkFilterRuleProtocolMac { pub struct NetworkFilterRuleProtocolMac {
#[serde( #[serde(rename = "@srcmacaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcmacaddr"),
skip_serializing_if = "Option::is_none"
)]
pub srcmacaddr: Option<String>, pub srcmacaddr: Option<String>,
#[serde( #[serde(rename = "@scmacmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcmacmask"),
skip_serializing_if = "Option::is_none"
)]
pub srcmacmask: Option<String>, pub srcmacmask: Option<String>,
#[serde( #[serde(rename = "@dstmacaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstmacaddr"),
skip_serializing_if = "Option::is_none"
)]
pub dstmacaddr: Option<String>, pub dstmacaddr: Option<String>,
#[serde( #[serde(rename = "@dstmacmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstmacmask"),
skip_serializing_if = "Option::is_none"
)]
pub dstmacmask: Option<String>, pub dstmacmask: Option<String>,
#[serde( #[serde(rename = "@comment", skip_serializing_if = "Option::is_none")]
rename(serialize = "@comment"),
skip_serializing_if = "Option::is_none"
)]
pub comment: Option<String>, pub comment: Option<String>,
} }
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(rename = "arp")] #[serde(rename = "arp")]
pub struct NetworkFilterRuleProtocolArpXML { pub struct NetworkFilterRuleProtocolArpXML {
#[serde( #[serde(rename = "@srcmacaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcmacaddr"),
skip_serializing_if = "Option::is_none"
)]
pub srcmacaddr: Option<String>, pub srcmacaddr: Option<String>,
#[serde( #[serde(rename = "@srcmacmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcmacmask"),
skip_serializing_if = "Option::is_none"
)]
pub srcmacmask: Option<String>, pub srcmacmask: Option<String>,
#[serde( #[serde(rename = "@dstmacaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstmacaddr"),
skip_serializing_if = "Option::is_none"
)]
pub dstmacaddr: Option<String>, pub dstmacaddr: Option<String>,
#[serde( #[serde(rename = "@dstmacmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstmacmask"),
skip_serializing_if = "Option::is_none"
)]
pub dstmacmask: Option<String>, pub dstmacmask: Option<String>,
#[serde( #[serde(rename = "@arpsrcipaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@arpsrcipaddr"),
skip_serializing_if = "Option::is_none"
)]
pub arpsrcipaddr: Option<String>, pub arpsrcipaddr: Option<String>,
#[serde( #[serde(rename = "@arpsrcipmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@arpsrcipmask"),
skip_serializing_if = "Option::is_none"
)]
pub arpsrcipmask: Option<u8>, pub arpsrcipmask: Option<u8>,
#[serde( #[serde(rename = "@arpdstipaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@arpdstipaddr"),
skip_serializing_if = "Option::is_none"
)]
pub arpdstipaddr: Option<String>, pub arpdstipaddr: Option<String>,
#[serde( #[serde(rename = "@arpdstipmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@arpdstipmask"),
skip_serializing_if = "Option::is_none"
)]
pub arpdstipmask: Option<u8>, pub arpdstipmask: Option<u8>,
#[serde( #[serde(rename = "@comment", skip_serializing_if = "Option::is_none")]
rename(serialize = "@comment"),
skip_serializing_if = "Option::is_none"
)]
pub comment: Option<String>, pub comment: Option<String>,
} }
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(rename = "ipvx")] #[serde(rename = "ipvx")]
pub struct NetworkFilterRuleProtocolIpvx { pub struct NetworkFilterRuleProtocolIpvx {
#[serde( #[serde(rename = "@srcmacaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcmacaddr"),
skip_serializing_if = "Option::is_none"
)]
pub srcmacaddr: Option<String>, pub srcmacaddr: Option<String>,
#[serde( #[serde(rename = "@srcmacmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcmacmask"),
skip_serializing_if = "Option::is_none"
)]
pub srcmacmask: Option<String>, pub srcmacmask: Option<String>,
#[serde( #[serde(rename = "@dstmacaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstmacaddr"),
skip_serializing_if = "Option::is_none"
)]
pub dstmacaddr: Option<String>, pub dstmacaddr: Option<String>,
#[serde( #[serde(rename = "@dstmacmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstmacmask"),
skip_serializing_if = "Option::is_none"
)]
pub dstmacmask: Option<String>, pub dstmacmask: Option<String>,
#[serde( #[serde(rename = "@srcipaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcipaddr"),
skip_serializing_if = "Option::is_none"
)]
pub srcipaddr: Option<String>, pub srcipaddr: Option<String>,
#[serde( #[serde(rename = "@srcipmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcipmask"),
skip_serializing_if = "Option::is_none"
)]
pub srcipmask: Option<u8>, pub srcipmask: Option<u8>,
#[serde( #[serde(rename = "@dstipaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstipaddr"),
skip_serializing_if = "Option::is_none"
)]
pub dstipaddr: Option<String>, pub dstipaddr: Option<String>,
#[serde( #[serde(rename = "@dstipmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstipmask"),
skip_serializing_if = "Option::is_none"
)]
pub dstipmask: Option<u8>, pub dstipmask: Option<u8>,
#[serde( #[serde(rename = "@comment", skip_serializing_if = "Option::is_none")]
rename(serialize = "@comment"),
skip_serializing_if = "Option::is_none"
)]
pub comment: Option<String>, pub comment: Option<String>,
} }
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(rename = "layer4")] #[serde(rename = "layer4")]
pub struct NetworkFilterRuleProtocolLayer4<IPv> { pub struct NetworkFilterRuleProtocolLayer4<IPv> {
#[serde( #[serde(rename = "@srcmacaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcmacaddr"),
skip_serializing_if = "Option::is_none"
)]
pub srcmacaddr: Option<String>, pub srcmacaddr: Option<String>,
#[serde( #[serde(rename = "@srcipaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcipaddr"),
skip_serializing_if = "Option::is_none"
)]
pub srcipaddr: Option<IPv>, pub srcipaddr: Option<IPv>,
#[serde( #[serde(rename = "@srcipmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcipmask"),
skip_serializing_if = "Option::is_none"
)]
pub srcipmask: Option<u8>, pub srcipmask: Option<u8>,
#[serde( #[serde(rename = "@dstipaddr", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstipaddr"),
skip_serializing_if = "Option::is_none"
)]
pub dstipaddr: Option<IPv>, pub dstipaddr: Option<IPv>,
#[serde( #[serde(rename = "@dstipmask", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstipmask"),
skip_serializing_if = "Option::is_none"
)]
pub dstipmask: Option<u8>, pub dstipmask: Option<u8>,
/// Start of range of source IP address /// Start of range of source IP address
#[serde( #[serde(rename = "@srcipfrom", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcipfrom"),
skip_serializing_if = "Option::is_none"
)]
pub srcipfrom: Option<IPv>, pub srcipfrom: Option<IPv>,
/// End of range of source IP address /// End of range of source IP address
#[serde( #[serde(rename = "@srcipto", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcipto"),
skip_serializing_if = "Option::is_none"
)]
pub srcipto: Option<IPv>, pub srcipto: Option<IPv>,
/// Start of range of destination IP address /// Start of range of destination IP address
#[serde( #[serde(rename = "@dstipfrom", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstipfrom"),
skip_serializing_if = "Option::is_none"
)]
pub dstipfrom: Option<IPv>, pub dstipfrom: Option<IPv>,
/// End of range of destination IP address /// End of range of destination IP address
#[serde( #[serde(rename = "@dstipto", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstipto"),
skip_serializing_if = "Option::is_none"
)]
pub dstipto: Option<IPv>, pub dstipto: Option<IPv>,
#[serde( #[serde(rename = "@srcportstart", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcportstart"),
skip_serializing_if = "Option::is_none"
)]
pub srcportstart: Option<u16>, pub srcportstart: Option<u16>,
#[serde( #[serde(rename = "@srcportend", skip_serializing_if = "Option::is_none")]
rename(serialize = "@srcportend"),
skip_serializing_if = "Option::is_none"
)]
pub srcportend: Option<u16>, pub srcportend: Option<u16>,
#[serde( #[serde(rename = "@dstportstart", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstportstart"),
skip_serializing_if = "Option::is_none"
)]
pub dstportstart: Option<u16>, pub dstportstart: Option<u16>,
#[serde( #[serde(rename = "@dstportend", skip_serializing_if = "Option::is_none")]
rename(serialize = "@dstportend"),
skip_serializing_if = "Option::is_none"
)]
pub dstportend: Option<u16>, pub dstportend: Option<u16>,
#[serde(rename(serialize = "@state"), skip_serializing_if = "Option::is_none")] #[serde(rename = "@state", skip_serializing_if = "Option::is_none")]
pub state: Option<String>, pub state: Option<String>,
#[serde( #[serde(rename = "@comment", skip_serializing_if = "Option::is_none")]
rename(serialize = "@comment"),
skip_serializing_if = "Option::is_none"
)]
pub comment: Option<String>, pub comment: Option<String>,
} }
#[derive(serde::Serialize, serde::Deserialize, Debug, Default)] #[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
#[serde(rename = "rule")] #[serde(rename = "rule")]
pub struct NetworkFilterRuleXML { pub struct NetworkFilterRuleXML {
#[serde(rename(serialize = "@action"))] #[serde(rename = "@action")]
pub action: String, pub action: String,
#[serde(rename(serialize = "@direction"))] #[serde(rename = "@direction")]
pub direction: String, pub direction: String,
#[serde(rename(serialize = "@priority"))] #[serde(rename = "@priority")]
pub priority: Option<i32>, pub priority: Option<i32>,
/// Match all protocols /// Match all protocols
@ -297,23 +186,15 @@ pub struct NetworkFilterRuleXML {
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(rename = "filter")] #[serde(rename = "filter")]
pub struct NetworkFilterXML { pub struct NetworkFilterXML {
#[serde(rename(serialize = "@name"))] #[serde(rename = "@name")]
pub name: String, pub name: String,
#[serde( #[serde(rename = "@chain", skip_serializing_if = "Option::is_none", default)]
rename(serialize = "@chain"),
skip_serializing_if = "Option::is_none",
default
)]
pub chain: Option<String>, pub chain: Option<String>,
#[serde( #[serde(skip_serializing_if = "Option::is_none", rename = "@priority", default)]
skip_serializing_if = "Option::is_none",
rename(serialize = "@priority"),
default
)]
pub priority: Option<i32>, pub priority: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<XMLUuid>, pub uuid: Option<XMLUuid>,
#[serde(default, rename = "filterref", skip_serializing_if = "Vec::is_empty")] #[serde(default, rename = "filterref")]
pub filterrefs: Vec<NetworkFilterRefXML>, pub filterrefs: Vec<NetworkFilterRefXML>,
#[serde(default, rename = "rule", skip_serializing_if = "Vec::is_empty")] #[serde(default, rename = "rule", skip_serializing_if = "Vec::is_empty")]
pub rules: Vec<NetworkFilterRuleXML>, pub rules: Vec<NetworkFilterRuleXML>,
@ -321,24 +202,10 @@ pub struct NetworkFilterXML {
impl NetworkFilterXML { impl NetworkFilterXML {
pub fn parse_xml<D: Display>(xml: D) -> anyhow::Result<Self> { pub fn parse_xml<D: Display>(xml: D) -> anyhow::Result<Self> {
let xml = xml.to_string(); Ok(quick_xml::de::from_str(&xml.to_string())?)
}
// We need to put all filter refs at the same location pub fn into_xml(self) -> anyhow::Result<String> {
let mut filter_refs = Vec::new(); Ok(quick_xml::se::to_string(&self)?)
let xml = lazy_regex::regex_replace_all!(r#"<filterref.*/>"#, &xml, |r: &str| {
filter_refs.push(r.to_string());
if r.contains('\n') {
log::warn!("A filterref contain a new line. This is a symptom of a new unsupported child attribute of <filterref /> object!");
}
""
});
let filter_refs = filter_refs.join("\n");
let xml = xml.replace("</filter>", &format!("{filter_refs}</filter>"));
log::debug!("Effective NW filter rule parsed: {xml}");
Ok(serde_xml_rs::from_str(&xml)?)
} }
} }

View File

@ -400,10 +400,10 @@ pub struct NetworkFilterRule {
/// Network filter definition /// Network filter definition
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct NetworkFilter { pub struct NetworkFilter {
name: NetworkFilterName, pub name: NetworkFilterName,
chain: Option<NetworkFilterChain>, chain: Option<NetworkFilterChain>,
priority: Option<i32>, priority: Option<i32>,
uuid: Option<XMLUuid>, pub uuid: Option<XMLUuid>,
/// Referenced filters rules /// Referenced filters rules
join_filters: Vec<NetworkFilterName>, join_filters: Vec<NetworkFilterName>,
rules: Vec<NetworkFilterRule>, rules: Vec<NetworkFilterRule>,