diff --git a/harmony-rs/opnsense-config/src/config.rs b/harmony-rs/opnsense-config/src/config.rs index 233ae69..947e774 100644 --- a/harmony-rs/opnsense-config/src/config.rs +++ b/harmony-rs/opnsense-config/src/config.rs @@ -170,13 +170,16 @@ impl Config { #[cfg(test)] mod tests { + use crate::infra::yaserde::to_xml_str; + use super::*; use std::path::PathBuf; + use pretty_assertions::assert_eq; #[tokio::test] async fn test_load_config_from_local_file() { let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - test_file_path.push("src/tests/data/config-structure.xml"); + test_file_path.push("src/tests/data/config-full-1.xml"); let config_file_path = test_file_path.to_str().unwrap().to_string(); println!("File path {config_file_path}"); @@ -188,13 +191,9 @@ mod tests { println!("Config {:?}", config); - let yaserde_cfg = yaserde::ser::Config { perform_indent: true, - .. Default::default() - }; - let serialized = yaserde::ser::to_string_with_config(&config.opnsense, &yaserde_cfg).unwrap(); + let serialized = to_xml_str(&config.opnsense).unwrap(); fs::write("/tmp/serialized.xml", &serialized).unwrap(); - std::process::Command::new("xmllint").arg("/tmp/serialized.xml").arg("--output").arg("/tmp/serialized.xmllint.xml").status().expect("xmllint failed"); assert_eq!(config_file_str, serialized); } diff --git a/harmony-rs/opnsense-config/src/infra/generic_xml.rs b/harmony-rs/opnsense-config/src/infra/generic_xml.rs index 1e70a71..dfeae73 100644 --- a/harmony-rs/opnsense-config/src/infra/generic_xml.rs +++ b/harmony-rs/opnsense-config/src/infra/generic_xml.rs @@ -118,7 +118,10 @@ impl YaSerializeTrait for RawXml { #[cfg(test)] mod test { + use crate::infra::yaserde::to_xml_str; + use super::*; + use pretty_assertions::assert_eq; use yaserde_derive::YaDeserialize; use yaserde_derive::YaSerialize; @@ -172,9 +175,46 @@ mod test { #[test] fn rawxml_should_serialize_complex_documents() { - let xml = r#"1060s00101024102401ignore204816384"#; + let xml = r#" + + + + + + + + + + 1 + 0 + 60s + + 0 + 0 + 1 + + 0 + + + 1024 + + + 1024 + + + 0 + + 1 + ignore + 2048 + 16384 + + + + +"#; let rawxml: RawXml = yaserde::de::from_str(xml).unwrap(); - assert_eq!(yaserde::ser::to_string(&rawxml).unwrap(), xml); + assert_eq!(to_xml_str(&rawxml).unwrap(), xml); } #[test] diff --git a/harmony-rs/opnsense-config/src/infra/mod.rs b/harmony-rs/opnsense-config/src/infra/mod.rs index 2563d9c..9c67f9a 100644 --- a/harmony-rs/opnsense-config/src/infra/mod.rs +++ b/harmony-rs/opnsense-config/src/infra/mod.rs @@ -1,4 +1,4 @@ - pub mod generic_xml; pub mod maybe_string; +pub mod yaserde; diff --git a/harmony-rs/opnsense-config/src/infra/yaserde.rs b/harmony-rs/opnsense-config/src/infra/yaserde.rs new file mode 100644 index 0000000..bdd2fcd --- /dev/null +++ b/harmony-rs/opnsense-config/src/infra/yaserde.rs @@ -0,0 +1,20 @@ +use yaserde::YaSerialize; + +pub fn to_xml_str(model: &T) -> Result { + let yaserde_cfg = yaserde::ser::Config { + perform_indent: true, + write_document_declaration: false, + pad_self_closing: false, + ..Default::default() + }; + let serialized = yaserde::ser::to_string_with_config::(model, &yaserde_cfg)?; + + // Opnsense does not specify encoding in the document declaration + // + // yaserde / xml-rs does not allow disabling the encoding attribute in the + // document declaration + // + // So here we just manually prefix the xml document with the exact document declaration + // that opnsense uses + Ok(format!("\n{serialized}\n")) +} diff --git a/harmony-rs/opnsense-config/src/modules/dhcp.rs b/harmony-rs/opnsense-config/src/modules/dhcp.rs index 92972dc..ef18aaf 100644 --- a/harmony-rs/opnsense-config/src/modules/dhcp.rs +++ b/harmony-rs/opnsense-config/src/modules/dhcp.rs @@ -67,6 +67,8 @@ pub struct DhcpRange { #[cfg(test)] mod test { + use crate::infra::yaserde::to_xml_str; + use super::*; use pretty_assertions::assert_eq; @@ -75,19 +77,15 @@ mod test { let dhcpd: Dhcpd = yaserde::de::from_str(SERIALIZED_DHCPD).expect("Deserialize Dhcpd failed"); - let yaserde_cfg = yaserde::ser::Config { - perform_indent: true, - write_document_declaration: false, - ..Default::default() - }; assert_eq!( - yaserde::ser::to_string_with_config(&dhcpd, &yaserde_cfg) + to_xml_str(&dhcpd) .expect("Serialize Dhcpd failed"), SERIALIZED_DHCPD ); } - const SERIALIZED_DHCPD: &str = " + const SERIALIZED_DHCPD: &str = " + 1 192.168.20.1 @@ -140,5 +138,5 @@ mod test { -"; +\n"; } diff --git a/harmony-rs/opnsense-config/src/modules/opnsense.rs b/harmony-rs/opnsense-config/src/modules/opnsense.rs index 9daa437..78011b0 100644 --- a/harmony-rs/opnsense-config/src/modules/opnsense.rs +++ b/harmony-rs/opnsense-config/src/modules/opnsense.rs @@ -73,9 +73,11 @@ pub struct Revision { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Options { - pub path: MaybeString, - pub host: MaybeString, - pub code: MaybeString, + pub path: Option, + pub host: Option, + pub code: Option, + pub send: Option, + pub expect: Option, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] @@ -87,36 +89,39 @@ pub struct Filters { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Rule { #[yaserde(attribute)] - pub uuid: MaybeString, + pub uuid: Option, #[yaserde(rename = "associated-rule-id")] - pub associated_rule_id: MaybeString, + pub associated_rule_id: Option, #[yaserde(rename = "type")] - pub r#type: MaybeString, + pub r#type: Option, pub interface: String, pub ipprotocol: String, - pub statetype: String, - pub descr: String, - pub direction: MaybeString, - pub category: MaybeString, - pub quick: MaybeString, - pub protocol: String, + pub statetype: Option, + pub descr: Option, + pub direction: Option, + pub category: Option, + pub quick: Option, + pub protocol: Option, pub source: Source, + pub icmptype: Option, pub destination: Destination, pub updated: Option, - pub created: Created, - pub disabled: Option, + pub created: Option, + pub disabled: Option, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Source { pub any: Option, + pub network: Option, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Destination { - pub network: MaybeString, - pub address: MaybeString, - pub port: Option, + pub network: Option, + pub address: Option, + pub port: Option, + pub any: Option, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] @@ -343,7 +348,7 @@ pub struct Snmpd { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Syslog { - pub reverse: Option, + pub reverse: Option, pub preservelogs: i32, } @@ -368,10 +373,11 @@ pub struct NatRule { pub ipprotocol: String, pub descr: MaybeString, pub tag: MaybeString, - pub tagged: Option, + pub tagged: Option, pub poolopts: PoolOpts, #[yaserde(rename = "associated-rule-id")] - pub associated_rule_id: String, + pub associated_rule_id: Option, + pub disabled: Option, pub target: String, #[yaserde(rename = "local-port")] pub local_port: i32, @@ -439,7 +445,7 @@ pub struct OPNsenseConfig { #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] #[yaserde(rename = "IDS")] -struct IDS { +pub struct IDS { #[yaserde(attribute)] version: String, rules: MaybeString, @@ -501,30 +507,30 @@ pub struct ConfigInterfaces { } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] -struct Vxlan { +pub struct Vxlan { #[yaserde(attribute)] version: String, } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] -struct Loopback { +pub struct Loopback { #[yaserde(attribute)] version: String, } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] #[yaserde(rename = "monit")] -struct Monit { +pub struct Monit { #[yaserde(attribute)] version: String, general: GeneralMonit, alert: Option, - service: Option, - test: Option, + service: Vec, + test: Vec, } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] -struct GeneralMonit { +pub struct GeneralMonit { enabled: u8, interval: u32, startdelay: u32, @@ -537,20 +543,30 @@ struct GeneralMonit { sslverify: u8, logfile: String, statefile: MaybeString, - eventqueuePath: MaybeString, - eventqueueSlots: MaybeString, - httpdEnabled: u8, - httpdUsername: String, - httpdPassword: String, - httpdPort: u16, - httpdAllow: MaybeString, - mmonitUrl: MaybeString, - mmonitTimeout: u32, - mmonitRegisterCredentials: u8, + #[yaserde(rename = "eventqueuePath")] + event_queue_path: MaybeString, + #[yaserde(rename = "eventqueueSlots")] + event_queue_slots: MaybeString, + #[yaserde(rename = "httpdEnabled")] + httpd_enabled: u8, + #[yaserde(rename = "httpdUsername")] + httpd_username: String, + #[yaserde(rename = "httpdPassword")] + httpd_password: String, + #[yaserde(rename = "httpdPort")] + httpd_port: u16, + #[yaserde(rename = "httpdAllow")] + httpd_allow: MaybeString, + #[yaserde(rename = "mmonitUrl")] + mmonit_url: MaybeString, + #[yaserde(rename = "mmonitTimeout")] + mmonit_timeout: u32, + #[yaserde(rename = "mmonitRegisterCredentials")] + mmonit_register_credentials: u8, } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] -struct Alert { +pub struct Alert { #[yaserde(attribute)] uuid: String, enabled: u8, @@ -563,7 +579,7 @@ struct Alert { } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] -struct Service { +pub struct Service { #[yaserde(attribute)] uuid: String, enabled: u8, @@ -587,7 +603,7 @@ struct Service { } #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] -struct Test { +pub struct Test { #[yaserde(attribute)] uuid: String, name: String, @@ -635,7 +651,7 @@ pub struct Capture { #[yaserde(rename = "interfaces")] pub interfaces: MaybeString, #[yaserde(rename = "egress_only")] - pub egress_only: Option, + pub egress_only: MaybeString, #[yaserde(rename = "version")] pub version: MaybeString, #[yaserde(rename = "targets")] @@ -725,7 +741,7 @@ pub struct Firewall { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct LvTemplate { #[yaserde(attribute)] - pub version: MaybeString, + pub version: String, #[yaserde(rename = "templates")] pub templates: Option, } @@ -733,7 +749,7 @@ pub struct LvTemplate { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Category { #[yaserde(attribute)] - pub version: MaybeString, + pub version: String, #[yaserde(rename = "categories")] pub categories: Option, } @@ -747,7 +763,7 @@ pub struct Categories { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct CategoryItem { #[yaserde(attribute)] - pub uuid: MaybeString, + pub uuid: String, #[yaserde(rename = "name")] pub name: MaybeString, #[yaserde(rename = "auto")] @@ -759,7 +775,7 @@ pub struct CategoryItem { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Alias { #[yaserde(attribute)] - pub version: MaybeString, + pub version: String, #[yaserde(rename = "geoip")] pub geoip: Option, #[yaserde(rename = "aliases")] @@ -786,13 +802,13 @@ pub struct AliasItem { pub name: String, #[yaserde(rename = "type")] pub r#type: String, + pub proto: MaybeString, pub interface: MaybeString, pub counters: String, pub updatefreq: MaybeString, pub content: String, pub categories: MaybeString, pub description: MaybeString, - pub proto: MaybeString, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] @@ -1707,19 +1723,21 @@ pub struct HAProxyHealthCheck { pub tcp_send_value: MaybeString, #[yaserde(rename = "tcp_matchType")] pub tcp_match_type: MaybeString, + pub tcp_negate: MaybeString, #[yaserde(rename = "tcp_matchValue")] pub tcp_match_value: MaybeString, - pub tcp_negate: MaybeString, - #[yaserde(rename = "agentPort")] pub agent_port: MaybeString, pub mysql_user: MaybeString, pub mysql_post41: MaybeString, pub pgsql_user: MaybeString, - #[yaserde(alias = "smtpDomain")] pub smtp_domain: MaybeString, pub esmtp_domain: MaybeString, + #[yaserde(rename = "agentPort")] + pub agent_port_uppercase: MaybeString, #[yaserde(rename = "dbUser")] pub db_user: MaybeString, + #[yaserde(rename = "smtpDomain")] + pub smtp_domain_uppercase: MaybeString, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] diff --git a/harmony-rs/opnsense-config/src/tests/data/config-full-1.xml b/harmony-rs/opnsense-config/src/tests/data/config-full-1.xml index 1feaa2e..fd2853d 100644 --- a/harmony-rs/opnsense-config/src/tests/data/config-full-1.xml +++ b/harmony-rs/opnsense-config/src/tests/data/config-full-1.xml @@ -28,28 +28,22 @@ default - - Source routing is another way for an attacker to try to reach non-routable addresses behind your box. + Source routing is another way for an attacker to try to reach non-routable addresses behind your box. It can also be used to probe for information about your internal networks. These functions come enabled - as part of the standard FreeBSD core system. - + as part of the standard FreeBSD core system. net.inet.ip.sourceroute default - - Source routing is another way for an attacker to try to reach non-routable addresses behind your box. + Source routing is another way for an attacker to try to reach non-routable addresses behind your box. It can also be used to probe for information about your internal networks. These functions come enabled - as part of the standard FreeBSD core system. - + as part of the standard FreeBSD core system. net.inet.ip.accept_sourceroute default - - This option turns off the logging of redirect packets because there is no limit and this could fill - up your logs consuming your whole hard drive. - + This option turns off the logging of redirect packets because there is no limit and this could fill + up your logs consuming your whole hard drive. net.inet.icmp.log_redirect default @@ -190,17 +184,14 @@ Enable/disable sending of ICMP redirects in response to IP packets for which a better, - and for the sender directly reachable, route and next hop is known. - + and for the sender directly reachable, route and next hop is known. net.inet.ip.redirect 0 - - Redirect attacks are the purposeful mass-issuing of ICMP type 5 packets. In a normal network, redirects + Redirect attacks are the purposeful mass-issuing of ICMP type 5 packets. In a normal network, redirects to the end stations should not be required. This option enables the NIC to drop all inbound ICMP redirect - packets without returning a response. - + packets without returning a response. net.inet.icmp.drop_redirect 1 @@ -1039,10 +1030,10 @@ in 1 icmp - echoreq 1 + echoreq wanip @@ -1058,20 +1049,20 @@ + nat_618812d37b8193.31302503 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
x3690_3
22
- - - nat_618812d37b8193.31302503 root@192.168.1.118 @@ -1079,20 +1070,20 @@
+ nat_64fa19f4acba11.80049900 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
192.168.20.150
7860
- - - nat_64fa19f4acba11.80049900 root@172.12.0.10 @@ -1100,20 +1091,20 @@
+ nat_64fb1fbba71e29.76190279 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
192.168.20.150
7861
- - - nat_64fb1fbba71e29.76190279 root@172.12.0.10 @@ -1121,20 +1112,20 @@
+ nat_64fb1fcea6d8b7.62653343 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
192.168.20.150
7862
- - - nat_64fb1fcea6d8b7.62653343 root@172.12.0.10 @@ -1142,20 +1133,20 @@
+ nat_64fb1fdb48ff18.28912920 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
192.168.20.150
7863
- - - nat_64fb1fdb48ff18.28912920 root@172.12.0.10 @@ -1163,20 +1154,20 @@
+ nat_651ffc35e573d9.09092618 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
192.168.20.140
22
- - - nat_651ffc35e573d9.09092618 root@172.12.0.11 @@ -1185,19 +1176,19 @@
nat_65aed5a66c4f65.25454286 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
192.168.20.140
8081
- - root@192.168.20.100 @@ -1206,19 +1197,19 @@
nat_65aed67d497580.58958916 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
192.168.20.160
8080
- - root@192.168.20.100 @@ -1227,19 +1218,19 @@
nat_65aed6961c4ea7.81903986 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
192.168.20.160
4000
- - root@192.168.20.100 @@ -1248,19 +1239,19 @@
nat_65f4a5b928c9a7.52477383 + wan + inet + keep state + + + tcp 1 - wan - keep state - tcp - inet
192.168.20.115
5000
- - root@192.168.20.100 @@ -1269,19 +1260,19 @@
nat_662bb59baf7573.98640354 + wan + inet + keep state + Redirecting to someservice1 on somehost9 + + tcp 1 - wan - keep state - tcp - inet
192.168.20.115
11434
- Redirecting to someservice1 on somehost9 - root@192.168.20.100 @@ -1290,19 +1281,19 @@
nat_663c3458b7b5e4.19986620 + wan + inet + keep state + Redirecting to someservice81 on somehost9 + + tcp 1 - wan - keep state - tcp - inet
192.168.20.115
27017
- Redirecting to someservice81 on somehost9 - root@172.12.0.8 @@ -1311,19 +1302,19 @@
nat_663d85b2e3b364.53108170 + wan + inet + keep state + Redirecting to someservice858 on somehost545 + + tcp 1 - wan - keep state - tcp - inet
192.168.20.163
8888
- Redirecting to someservice858 on somehost545 - root@172.12.0.10 @@ -1332,19 +1323,19 @@
nat_666c8932142ed6.34062700 + wan + inet + keep state + Redirecting to someservice858 on somehost9 + + tcp 1 - wan - keep state - tcp - inet
192.168.20.115
8888
- Redirecting to someservice858 on somehost9 - root@192.168.20.100 @@ -1353,19 +1344,19 @@
nat_667cd97504d870.57128970 + wan + inet + keep state + Redirecting to someservice858 on somehost545 + + tcp 1 - wan - keep state - tcp - inet
192.168.20.163
8889
- Redirecting to someservice858 on somehost545 - root@172.12.0.8 @@ -1401,9 +1392,9 @@
pass + lan inet Default allow LAN to any rule - lan lan @@ -1413,9 +1404,9 @@ pass + lan inet6 Default allow LAN IPv6 to any rule - lan lan @@ -1474,19 +1465,19 @@ nat_6709763b6a6748.85579760 + wan + inet + keep state + port forwarding for reconfig of someservice2 somehost3 + + tcp 1 - wan - keep state - tcp - inet
192.168.20.132
55555
- port forwarding for reconfig of someservice2 somehost3 - root@172.12.0.12 @@ -1496,19 +1487,19 @@
nat_670979b3279551.73601303 + wan + inet + keep state + port forwarding for virtual ip for someservice2 servers + + tcp 1 - wan - keep state - tcp - inet
192.168.20.1
55555
- port forwarding for virtual ip for someservice2 servers - root@172.12.0.12