forked from NationTech/harmony
wip: New crate opnsense-config
This commit is contained in:
parent
6a5ebdbac7
commit
32cea6c3ff
85
harmony-rs/Cargo.lock
generated
85
harmony-rs/Cargo.lock
generated
@ -124,7 +124,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -400,7 +400,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -428,7 +428,7 @@ checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -695,7 +695,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -822,6 +822,12 @@ version = "0.14.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@ -1208,7 +1214,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1229,6 +1235,20 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opnsense-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"russh",
|
||||||
|
"russh-keys",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"xml-rs",
|
||||||
|
"yaserde",
|
||||||
|
"yaserde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "p256"
|
name = "p256"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
@ -1826,7 +1846,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1998,6 +2018,17 @@ version = "2.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.77"
|
version = "2.0.77"
|
||||||
@ -2072,7 +2103,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2115,7 +2146,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2177,7 +2208,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2298,7 +2329,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2332,7 +2363,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@ -2542,6 +2573,12 @@ dependencies = [
|
|||||||
"tap",
|
"tap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml-rs"
|
||||||
|
version = "0.8.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xml_dom"
|
name = "xml_dom"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
@ -2554,6 +2591,30 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaserde"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8198a8ee4113411b7be1086e10b654f83653c01e4bd176fb98fe9d11951af5e"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"xml-rs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaserde_derive"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82eaa312529cc56b0df120253c804a8c8d593d2b5fe8deb5402714f485f62d79"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
"xml-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.35"
|
version = "0.7.35"
|
||||||
@ -2572,7 +2633,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -3,6 +3,7 @@ resolver = "2"
|
|||||||
members = [
|
members = [
|
||||||
"private_repos/*",
|
"private_repos/*",
|
||||||
"harmony",
|
"harmony",
|
||||||
|
"opnsense-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
@ -18,3 +19,5 @@ async-trait = "0.1.82"
|
|||||||
tokio = { version = "1.40.0", features = ["io-std"] }
|
tokio = { version = "1.40.0", features = ["io-std"] }
|
||||||
cidr = "0.2.3"
|
cidr = "0.2.3"
|
||||||
xml_dom = "0.2.8"
|
xml_dom = "0.2.8"
|
||||||
|
russh = "0.45.0"
|
||||||
|
russh-keys = "0.45.0"
|
||||||
|
|||||||
14
harmony-rs/opnsense-config/Cargo.toml
Normal file
14
harmony-rs/opnsense-config/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "opnsense-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
russh = { workspace = true }
|
||||||
|
russh-keys = { workspace = true }
|
||||||
|
yaserde = "0.11.1"
|
||||||
|
yaserde_derive = "0.11.1"
|
||||||
|
xml-rs = "0.8"
|
||||||
|
thiserror = "1.0"
|
||||||
|
async-trait = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
38
harmony-rs/opnsense-config/adr/001-yaserde.md
Normal file
38
harmony-rs/opnsense-config/adr/001-yaserde.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Architecture Decision Record: Using yaserde for OPNsense Config Parsing
|
||||||
|
|
||||||
|
- Status : Proposed
|
||||||
|
- Author : Jean-Gabriel Gill-Couture
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
We need to parse and manipulate the OPNsense config.xml file in our Rust crate. We considered several XML parsing libraries, including quick-xml, xml-dom, minidom and yaserde. Each library has its own strengths and trade-offs in terms of performance, ease of use, and robustness.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
We have decided to use yaserde for parsing and manipulating the OPNsense config.xml file.
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
|
||||||
|
1. Type Safety: yaserde allows us to define a complete Rust representation of the config.xml structure. This provides strong type safety and makes it easier to catch errors at compile-time rather than runtime.
|
||||||
|
|
||||||
|
2. Robustness: By mapping the entire config structure to Rust types, we ensure that our code interacts with the config in a well-defined manner. This reduces the risk of runtime errors due to unexpected XML structures.
|
||||||
|
|
||||||
|
3. Ease of Use: Working with native Rust types is more intuitive and less error-prone than manipulating XML directly. This can lead to more maintainable and readable code.
|
||||||
|
|
||||||
|
4. Memory Usage: While yaserde may use more memory than streaming parsers like quick-xml, the OPNsense config files are typically not large enough for this to be a significant concern. We prioritize robustness and ease of use over minimal memory usage in this context.
|
||||||
|
|
||||||
|
5. Serialization/Deserialization: yaserde provides both deserialization (XML to Rust structs) and serialization (Rust structs to XML) out of the box, which simplifies our implementation.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Positive:
|
||||||
|
- Increased type safety and robustness in handling the config.xml structure.
|
||||||
|
- More intuitive API for developers working with the config.
|
||||||
|
- Easier to extend and maintain the code that interacts with different parts of the config.
|
||||||
|
|
||||||
|
Negative:
|
||||||
|
- It will be harder to maintain when there are breaking changes in the config.xml format. Any structural changes in the XML will require corresponding updates to our Rust struct definitions.
|
||||||
|
- Slightly higher memory usage compared to streaming parsers.
|
||||||
|
- Initial development time may be longer due to the need to define the entire config structure upfront.
|
||||||
|
|
||||||
|
We accept the trade-off of potentially more difficult maintenance in the face of breaking config.xml changes, as we believe the benefits of increased robustness and type safety outweigh this drawback. When OPNsense releases updates that change the config.xml structure, we will need to update our Rust struct definitions accordingly.
|
||||||
55
harmony-rs/opnsense-config/src/config.rs
Normal file
55
harmony-rs/opnsense-config/src/config.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use crate::error::Error;
|
||||||
|
use crate::modules::opnsense::OPNsense;
|
||||||
|
use russh::client::{Config as SshConfig, Handler};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use russh_keys::key;
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
opnsense: OPNsense,
|
||||||
|
ssh_config: Arc<SshConfig>,
|
||||||
|
host: String,
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub async fn new(host: &str, username: &str, key_path: &str) -> Result<Self, Error> {
|
||||||
|
let key = russh_keys::load_secret_key(key_path, None).expect("Secret key failed loading");
|
||||||
|
let config = SshConfig::default();
|
||||||
|
let config = Arc::new(config);
|
||||||
|
|
||||||
|
let mut ssh = russh::client::connect(config.clone(), host, Handler).await?;
|
||||||
|
ssh.authenticate_publickey(username, key).await?;
|
||||||
|
|
||||||
|
let (xml, _) = ssh.exec(true, "cat /conf/config.xml").await?;
|
||||||
|
let xml = String::from_utf8(xml).map_err(|e| Error::Config(e.to_string()))?;
|
||||||
|
|
||||||
|
let opnsense = yaserde::de::from_str(&xml).map_err(|e| Error::Xml(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
opnsense,
|
||||||
|
ssh_config: config,
|
||||||
|
host: host.to_string(),
|
||||||
|
username: username.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_opnsense(&self) -> &OPNsense {
|
||||||
|
&self.opnsense
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_opnsense_mut(&mut self) -> &mut OPNsense {
|
||||||
|
&mut self.opnsense
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn save(&self) -> Result<(), Error> {
|
||||||
|
let xml = yaserde::ser::to_string(&self.opnsense).map_err(|e| Error::Xml(e.to_string()))?;
|
||||||
|
|
||||||
|
let mut ssh = russh::client::connect(self.ssh_config.clone(), &self.host, Handler).await?;
|
||||||
|
ssh.authenticate_publickey(&self.username, key).await?;
|
||||||
|
|
||||||
|
ssh.exec(true, &format!("echo '{}' > /conf/config.xml", xml))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
13
harmony-rs/opnsense-config/src/error.rs
Normal file
13
harmony-rs/opnsense-config/src/error.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("XML error: {0}")]
|
||||||
|
Xml(String),
|
||||||
|
#[error("SSH error: {0}")]
|
||||||
|
Ssh(#[from] russh::Error),
|
||||||
|
#[error("I/O error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("Config error: {0}")]
|
||||||
|
Config(String),
|
||||||
|
}
|
||||||
6
harmony-rs/opnsense-config/src/lib.rs
Normal file
6
harmony-rs/opnsense-config/src/lib.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pub mod config;
|
||||||
|
pub mod modules;
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
pub use config::Config;
|
||||||
|
pub use error::Error;
|
||||||
24
harmony-rs/opnsense-config/src/modules/dhcp.rs
Normal file
24
harmony-rs/opnsense-config/src/modules/dhcp.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use super::opnsense::{OPNsense, StaticMap};
|
||||||
|
|
||||||
|
pub struct DhcpConfig<'a> {
|
||||||
|
opnsense: &'a mut OPNsense,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DhcpConfig<'a> {
|
||||||
|
pub fn new(opnsense: &'a mut OPNsense) -> Self {
|
||||||
|
Self { opnsense }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_static_mapping(&mut self, mac: String, ipaddr: String, hostname: String) {
|
||||||
|
let static_map = StaticMap {
|
||||||
|
mac,
|
||||||
|
ipaddr,
|
||||||
|
hostname,
|
||||||
|
};
|
||||||
|
self.opnsense.dhcpd.lan.staticmaps.push(static_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_static_mappings(&self) -> &[StaticMap] {
|
||||||
|
&self.opnsense.dhcpd.lan.staticmaps
|
||||||
|
}
|
||||||
|
}
|
||||||
2
harmony-rs/opnsense-config/src/modules/mod.rs
Normal file
2
harmony-rs/opnsense-config/src/modules/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod opnsense;
|
||||||
|
pub mod dhcp;
|
||||||
44
harmony-rs/opnsense-config/src/modules/opnsense.rs
Normal file
44
harmony-rs/opnsense-config/src/modules/opnsense.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use yaserde_derive::{YaDeserialize, YaSerialize};
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||||
|
#[yaserde(rename = "opnsense")]
|
||||||
|
pub struct OPNsense {
|
||||||
|
#[yaserde(rename = "dhcpd")]
|
||||||
|
pub dhcpd: Dhcpd,
|
||||||
|
// Add other top-level elements as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||||
|
pub struct Dhcpd {
|
||||||
|
#[yaserde(rename = "lan")]
|
||||||
|
pub lan: DhcpInterface,
|
||||||
|
// Add other interfaces as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||||
|
pub struct DhcpInterface {
|
||||||
|
#[yaserde(rename = "enable")]
|
||||||
|
pub enable: bool,
|
||||||
|
#[yaserde(rename = "range")]
|
||||||
|
pub range: DhcpRange,
|
||||||
|
#[yaserde(rename = "staticmap")]
|
||||||
|
pub staticmaps: Vec<StaticMap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||||
|
pub struct DhcpRange {
|
||||||
|
#[yaserde(rename = "from")]
|
||||||
|
pub from: String,
|
||||||
|
#[yaserde(rename = "to")]
|
||||||
|
pub to: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||||
|
pub struct StaticMap {
|
||||||
|
#[yaserde(rename = "mac")]
|
||||||
|
pub mac: String,
|
||||||
|
#[yaserde(rename = "ipaddr")]
|
||||||
|
pub ipaddr: String,
|
||||||
|
#[yaserde(rename = "hostname")]
|
||||||
|
pub hostname: String,
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user