forked from NationTech/harmony
		
	feat(harmony): add TFTP server functionality (#10)
Introduce a new module and interface for serving files via TFTP in the HAClusterTopology structure. This includes adding the necessary dependencies, creating the `TftpServer` trait, implementing it where appropriate, and integrating its usage within the topology struct. Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/10 Co-authored-by: Jean-Gabriel Gill-Couture <jg@nationtech.io> Co-committed-by: Jean-Gabriel Gill-Couture <jg@nationtech.io>
This commit is contained in:
		
							parent
							
								
									098cb30523
								
							
						
					
					
						commit
						925e84e4d2
					
				
							
								
								
									
										310
									
								
								harmony-rs/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										310
									
								
								harmony-rs/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -529,6 +529,17 @@ dependencies = [ | |||||||
|  "subtle", |  "subtle", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "displaydoc" | ||||||
|  | version = "0.2.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn 2.0.90", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "ecdsa" | name = "ecdsa" | ||||||
| version = "0.16.9" | version = "0.16.9" | ||||||
| @ -893,6 +904,7 @@ dependencies = [ | |||||||
|  "serde", |  "serde", | ||||||
|  "serde_json", |  "serde_json", | ||||||
|  "tokio", |  "tokio", | ||||||
|  |  "url", | ||||||
|  "uuid", |  "uuid", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| @ -1057,13 +1069,142 @@ dependencies = [ | |||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "idna" | name = "icu_collections" | ||||||
| version = "0.5.0" | version = "1.5.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "unicode-bidi", |  "displaydoc", | ||||||
|  "unicode-normalization", |  "yoke", | ||||||
|  |  "zerofrom", | ||||||
|  |  "zerovec", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "icu_locid" | ||||||
|  | version = "1.5.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" | ||||||
|  | dependencies = [ | ||||||
|  |  "displaydoc", | ||||||
|  |  "litemap", | ||||||
|  |  "tinystr", | ||||||
|  |  "writeable", | ||||||
|  |  "zerovec", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "icu_locid_transform" | ||||||
|  | version = "1.5.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" | ||||||
|  | dependencies = [ | ||||||
|  |  "displaydoc", | ||||||
|  |  "icu_locid", | ||||||
|  |  "icu_locid_transform_data", | ||||||
|  |  "icu_provider", | ||||||
|  |  "tinystr", | ||||||
|  |  "zerovec", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "icu_locid_transform_data" | ||||||
|  | version = "1.5.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "icu_normalizer" | ||||||
|  | version = "1.5.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" | ||||||
|  | dependencies = [ | ||||||
|  |  "displaydoc", | ||||||
|  |  "icu_collections", | ||||||
|  |  "icu_normalizer_data", | ||||||
|  |  "icu_properties", | ||||||
|  |  "icu_provider", | ||||||
|  |  "smallvec", | ||||||
|  |  "utf16_iter", | ||||||
|  |  "utf8_iter", | ||||||
|  |  "write16", | ||||||
|  |  "zerovec", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "icu_normalizer_data" | ||||||
|  | version = "1.5.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "icu_properties" | ||||||
|  | version = "1.5.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" | ||||||
|  | dependencies = [ | ||||||
|  |  "displaydoc", | ||||||
|  |  "icu_collections", | ||||||
|  |  "icu_locid_transform", | ||||||
|  |  "icu_properties_data", | ||||||
|  |  "icu_provider", | ||||||
|  |  "tinystr", | ||||||
|  |  "zerovec", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "icu_properties_data" | ||||||
|  | version = "1.5.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "icu_provider" | ||||||
|  | version = "1.5.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" | ||||||
|  | dependencies = [ | ||||||
|  |  "displaydoc", | ||||||
|  |  "icu_locid", | ||||||
|  |  "icu_provider_macros", | ||||||
|  |  "stable_deref_trait", | ||||||
|  |  "tinystr", | ||||||
|  |  "writeable", | ||||||
|  |  "yoke", | ||||||
|  |  "zerofrom", | ||||||
|  |  "zerovec", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "icu_provider_macros" | ||||||
|  | version = "1.5.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn 2.0.90", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "idna" | ||||||
|  | version = "1.0.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" | ||||||
|  | dependencies = [ | ||||||
|  |  "idna_adapter", | ||||||
|  |  "smallvec", | ||||||
|  |  "utf8_iter", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "idna_adapter" | ||||||
|  | version = "1.2.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" | ||||||
|  | dependencies = [ | ||||||
|  |  "icu_normalizer", | ||||||
|  |  "icu_properties", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -1153,6 +1294,12 @@ version = "0.4.14" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "litemap" | ||||||
|  | version = "0.7.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "lock_api" | name = "lock_api" | ||||||
| version = "0.4.12" | version = "0.4.12" | ||||||
| @ -1384,6 +1531,8 @@ dependencies = [ | |||||||
|  "serde_json", |  "serde_json", | ||||||
|  "thiserror", |  "thiserror", | ||||||
|  "tokio", |  "tokio", | ||||||
|  |  "tokio-stream", | ||||||
|  |  "tokio-util", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -2230,6 +2379,12 @@ dependencies = [ | |||||||
|  "zeroize", |  "zeroize", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "stable_deref_trait" | ||||||
|  | version = "1.2.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "subtle" | name = "subtle" | ||||||
| version = "2.6.1" | version = "2.6.1" | ||||||
| @ -2264,6 +2419,17 @@ version = "0.1.2" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "synstructure" | ||||||
|  | version = "0.13.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn 2.0.90", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "system-configuration" | name = "system-configuration" | ||||||
| version = "0.5.1" | version = "0.5.1" | ||||||
| @ -2334,20 +2500,15 @@ dependencies = [ | |||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "tinyvec" | name = "tinystr" | ||||||
| version = "1.8.0" | version = "0.7.6" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "tinyvec_macros", |  "displaydoc", | ||||||
|  |  "zerovec", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "tinyvec_macros" |  | ||||||
| version = "0.1.1" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tokio" | name = "tokio" | ||||||
| version = "1.40.0" | version = "1.40.0" | ||||||
| @ -2388,9 +2549,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "tokio-stream" | name = "tokio-stream" | ||||||
| version = "0.1.16" | version = "0.1.17" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "futures-core", |  "futures-core", | ||||||
|  "pin-project-lite", |  "pin-project-lite", | ||||||
| @ -2399,9 +2560,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "tokio-util" | name = "tokio-util" | ||||||
| version = "0.7.12" | version = "0.7.13" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" | checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bytes", |  "bytes", | ||||||
|  "futures-core", |  "futures-core", | ||||||
| @ -2447,27 +2608,12 @@ version = "1.17.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "unicode-bidi" |  | ||||||
| version = "0.3.15" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "unicode-ident" | name = "unicode-ident" | ||||||
| version = "1.0.12" | version = "1.0.12" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "unicode-normalization" |  | ||||||
| version = "0.1.23" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" |  | ||||||
| dependencies = [ |  | ||||||
|  "tinyvec", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "universal-hash" | name = "universal-hash" | ||||||
| version = "0.5.1" | version = "0.5.1" | ||||||
| @ -2480,15 +2626,27 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "url" | name = "url" | ||||||
| version = "2.5.2" | version = "2.5.4" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "form_urlencoded", |  "form_urlencoded", | ||||||
|  "idna", |  "idna", | ||||||
|  "percent-encoding", |  "percent-encoding", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "utf16_iter" | ||||||
|  | version = "1.0.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "utf8_iter" | ||||||
|  | version = "1.0.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "utf8parse" | name = "utf8parse" | ||||||
| version = "0.2.2" | version = "0.2.2" | ||||||
| @ -2527,6 +2685,7 @@ dependencies = [ | |||||||
|  "harmony_macros", |  "harmony_macros", | ||||||
|  "log", |  "log", | ||||||
|  "tokio", |  "tokio", | ||||||
|  |  "url", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -2834,6 +2993,18 @@ dependencies = [ | |||||||
|  "tokio", |  "tokio", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "write16" | ||||||
|  | version = "1.0.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "writeable" | ||||||
|  | version = "0.5.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "wyz" | name = "wyz" | ||||||
| version = "0.5.1" | version = "0.5.1" | ||||||
| @ -2875,6 +3046,30 @@ dependencies = [ | |||||||
|  "xml-rs", |  "xml-rs", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "yoke" | ||||||
|  | version = "0.7.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" | ||||||
|  | dependencies = [ | ||||||
|  |  "serde", | ||||||
|  |  "stable_deref_trait", | ||||||
|  |  "yoke-derive", | ||||||
|  |  "zerofrom", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "yoke-derive" | ||||||
|  | version = "0.7.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn 2.0.90", | ||||||
|  |  "synstructure", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "zerocopy" | name = "zerocopy" | ||||||
| version = "0.7.35" | version = "0.7.35" | ||||||
| @ -2896,8 +3091,51 @@ dependencies = [ | |||||||
|  "syn 2.0.90", |  "syn 2.0.90", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "zerofrom" | ||||||
|  | version = "0.1.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" | ||||||
|  | dependencies = [ | ||||||
|  |  "zerofrom-derive", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "zerofrom-derive" | ||||||
|  | version = "0.1.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn 2.0.90", | ||||||
|  |  "synstructure", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "zeroize" | name = "zeroize" | ||||||
| version = "1.8.1" | version = "1.8.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "zerovec" | ||||||
|  | version = "0.10.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" | ||||||
|  | dependencies = [ | ||||||
|  |  "yoke", | ||||||
|  |  "zerofrom", | ||||||
|  |  "zerovec-derive", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "zerovec-derive" | ||||||
|  | version = "0.10.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn 2.0.90", | ||||||
|  | ] | ||||||
|  | |||||||
| @ -17,11 +17,12 @@ log = "0.4.22" | |||||||
| env_logger = "0.11.5" | env_logger = "0.11.5" | ||||||
| derive-new = "0.7.0" | derive-new = "0.7.0" | ||||||
| async-trait = "0.1.82" | async-trait = "0.1.82" | ||||||
| tokio = { version = "1.40.0", features = ["io-std"] } | tokio = { version = "1.40.0", features = ["io-std", "fs"] } | ||||||
| cidr = "0.2.3" | cidr = "0.2.3" | ||||||
| russh = "0.45.0" | russh = "0.45.0" | ||||||
| russh-keys = "0.45.0" | russh-keys = "0.45.0" | ||||||
| rand = "0.8.5" | rand = "0.8.5" | ||||||
|  | url = "2.5.4" | ||||||
| 
 | 
 | ||||||
| [workspace.dependencies.uuid] | [workspace.dependencies.uuid] | ||||||
| version = "1.11.0" | version = "1.11.0" | ||||||
|  | |||||||
| @ -12,3 +12,4 @@ tokio = { workspace = true } | |||||||
| harmony_macros = { version = "1.0.0", path = "../../harmony_macros" } | harmony_macros = { version = "1.0.0", path = "../../harmony_macros" } | ||||||
| log = { workspace = true } | log = { workspace = true } | ||||||
| env_logger = { workspace = true } | env_logger = { workspace = true } | ||||||
|  | url = { workspace = true } | ||||||
|  | |||||||
| @ -9,8 +9,8 @@ use harmony::{ | |||||||
|     infra::opnsense::OPNSenseManagementInterface, |     infra::opnsense::OPNSenseManagementInterface, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     maestro::Maestro, |     maestro::Maestro, | ||||||
|     modules::okd::{dhcp::OKDBootstrapDhcpScore, dns::OKDBootstrapDnsScore}, |     modules::{okd::{dhcp::OKDBootstrapDhcpScore, dns::OKDBootstrapDnsScore}, tftp::TftpScore}, | ||||||
|     topology::{LogicalHost, UnmanagedRouter}, |     topology::{LogicalHost, UnmanagedRouter, Url}, | ||||||
| }; | }; | ||||||
| use harmony_macros::ip; | use harmony_macros::ip; | ||||||
| 
 | 
 | ||||||
| @ -23,14 +23,10 @@ async fn main() { | |||||||
|         name: String::from("opnsense-1"), |         name: String::from("opnsense-1"), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let firewall = harmony::topology::LogicalHost { |  | ||||||
|         ip: ip!("127.0.0.1"), |  | ||||||
|         name: String::from("opnsense-1"), |  | ||||||
|     }; |  | ||||||
|     let opnsense = Arc::new( |     let opnsense = Arc::new( | ||||||
|         harmony::infra::opnsense::OPNSenseFirewall::new( |         harmony::infra::opnsense::OPNSenseFirewall::new( | ||||||
|             firewall, |             firewall, | ||||||
|             Some(2222), |             None, | ||||||
|             "lan", |             "lan", | ||||||
|             "root", |             "root", | ||||||
|             "opnsense", |             "opnsense", | ||||||
| @ -48,6 +44,7 @@ async fn main() { | |||||||
|         )), |         )), | ||||||
|         load_balancer: opnsense.clone(), |         load_balancer: opnsense.clone(), | ||||||
|         firewall: opnsense.clone(), |         firewall: opnsense.clone(), | ||||||
|  |         tftp_server: opnsense.clone(), | ||||||
|         dhcp_server: opnsense.clone(), |         dhcp_server: opnsense.clone(), | ||||||
|         dns_server: opnsense.clone(), |         dns_server: opnsense.clone(), | ||||||
|         control_plane: vec![LogicalHost { |         control_plane: vec![LogicalHost { | ||||||
| @ -81,11 +78,13 @@ async fn main() { | |||||||
| 
 | 
 | ||||||
|     // let dhcp_score = OKDBootstrapDhcpScore::new(&topology, &inventory);
 |     // let dhcp_score = OKDBootstrapDhcpScore::new(&topology, &inventory);
 | ||||||
|     // let dns_score = OKDBootstrapDnsScore::new(&topology);
 |     // let dns_score = OKDBootstrapDnsScore::new(&topology);
 | ||||||
|     let load_balancer_score = |     // let load_balancer_score =
 | ||||||
|         harmony::modules::okd::load_balancer::OKDLoadBalancerScore::new(&topology); |     //     harmony::modules::okd::load_balancer::OKDLoadBalancerScore::new(&topology);
 | ||||||
| 
 | 
 | ||||||
|  |     let tftp_score = TftpScore::new(Url::LocalFolder("../../../watchguard/tftpboot".to_string())); | ||||||
|     let maestro = Maestro::new(inventory, topology); |     let maestro = Maestro::new(inventory, topology); | ||||||
|     // maestro.interpret(dns_score).await.unwrap();
 |     // maestro.interpret(dns_score).await.unwrap();
 | ||||||
|     // maestro.interpret(dhcp_score).await.unwrap();
 |     // maestro.interpret(dhcp_score).await.unwrap();
 | ||||||
|     maestro.interpret(load_balancer_score).await.unwrap(); |     // maestro.interpret(load_balancer_score).await.unwrap();
 | ||||||
|  |     maestro.interpret(tftp_score).await.unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,3 +20,4 @@ cidr = { workspace = true } | |||||||
| opnsense-config = { path = "../opnsense-config" } | opnsense-config = { path = "../opnsense-config" } | ||||||
| opnsense-config-xml = { path = "../opnsense-config-xml" } | opnsense-config-xml = { path = "../opnsense-config-xml" } | ||||||
| uuid = { workspace = true } | uuid = { workspace = true } | ||||||
|  | url = { workspace = true } | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ pub enum InterpretName { | |||||||
|     OPNSenseDHCP, |     OPNSenseDHCP, | ||||||
|     OPNSenseDns, |     OPNSenseDns, | ||||||
|     LoadBalancer, |     LoadBalancer, | ||||||
|  |     Tftp | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl std::fmt::Display for InterpretName { | impl std::fmt::Display for InterpretName { | ||||||
| @ -22,6 +23,7 @@ impl std::fmt::Display for InterpretName { | |||||||
|             InterpretName::OPNSenseDHCP => f.write_str("OPNSenseDHCP"), |             InterpretName::OPNSenseDHCP => f.write_str("OPNSenseDHCP"), | ||||||
|             InterpretName::OPNSenseDns => f.write_str("OPNSenseDns"), |             InterpretName::OPNSenseDns => f.write_str("OPNSenseDns"), | ||||||
|             InterpretName::LoadBalancer => f.write_str("LoadBalancer"), |             InterpretName::LoadBalancer => f.write_str("LoadBalancer"), | ||||||
|  |             InterpretName::Tftp => f.write_str("Tftp"), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -52,6 +54,13 @@ impl Outcome { | |||||||
|             message: String::new(), |             message: String::new(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub fn success(message: String) -> Self { | ||||||
|  |         Self { | ||||||
|  |             status: InterpretStatus::SUCCESS, | ||||||
|  |             message, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, PartialEq, Eq)] | #[derive(Debug, Clone, PartialEq, Eq)] | ||||||
|  | |||||||
| @ -1,11 +1,13 @@ | |||||||
| mod host_binding; | mod host_binding; | ||||||
| mod load_balancer; | mod load_balancer; | ||||||
| mod router; | mod router; | ||||||
|  | mod tftp; | ||||||
| pub use load_balancer::*; | pub use load_balancer::*; | ||||||
| pub use router::*; | pub use router::*; | ||||||
| mod network; | mod network; | ||||||
| pub use host_binding::*; | pub use host_binding::*; | ||||||
| pub use network::*; | pub use network::*; | ||||||
|  | pub use tftp::*; | ||||||
| 
 | 
 | ||||||
| use std::{net::IpAddr, sync::Arc}; | use std::{net::IpAddr, sync::Arc}; | ||||||
| 
 | 
 | ||||||
| @ -16,6 +18,7 @@ pub struct HAClusterTopology { | |||||||
|     pub load_balancer: Arc<dyn LoadBalancer>, |     pub load_balancer: Arc<dyn LoadBalancer>, | ||||||
|     pub firewall: Arc<dyn Firewall>, |     pub firewall: Arc<dyn Firewall>, | ||||||
|     pub dhcp_server: Arc<dyn DhcpServer>, |     pub dhcp_server: Arc<dyn DhcpServer>, | ||||||
|  |     pub tftp_server: Arc<dyn TftpServer>, | ||||||
|     pub dns_server: Arc<dyn DnsServer>, |     pub dns_server: Arc<dyn DnsServer>, | ||||||
|     pub control_plane: Vec<LogicalHost>, |     pub control_plane: Vec<LogicalHost>, | ||||||
|     pub workers: Vec<LogicalHost>, |     pub workers: Vec<LogicalHost>, | ||||||
| @ -24,6 +27,21 @@ pub struct HAClusterTopology { | |||||||
| 
 | 
 | ||||||
| pub type IpAddress = IpAddr; | pub type IpAddress = IpAddr; | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | pub enum Url { | ||||||
|  |     LocalFolder(String), | ||||||
|  |     Remote(url::Url), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Display for Url { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         match self { | ||||||
|  |             Url::LocalFolder(path) => write!(f, "{}", path), | ||||||
|  |             Url::Remote(url) => write!(f, "{}", url), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// Represents a logical member of a cluster that provides one or more services.
 | /// Represents a logical member of a cluster that provides one or more services.
 | ||||||
| ///
 | ///
 | ||||||
| /// A LogicalHost can represent various roles within the infrastructure, such as:
 | /// A LogicalHost can represent various roles within the infrastructure, such as:
 | ||||||
| @ -75,7 +93,11 @@ impl LogicalHost { | |||||||
|     /// assert_eq!(hosts[2].ip, IpAddress::from_str("192.168.0.22").unwrap());
 |     /// assert_eq!(hosts[2].ip, IpAddress::from_str("192.168.0.22").unwrap());
 | ||||||
|     /// assert_eq!(hosts[2].name, "worker2");
 |     /// assert_eq!(hosts[2].name, "worker2");
 | ||||||
|     /// ```
 |     /// ```
 | ||||||
|     pub fn create_hosts(number_hosts: u32, start_ip: IpAddress, hostname_prefix: &str) -> Vec<LogicalHost> { |     pub fn create_hosts( | ||||||
|  |         number_hosts: u32, | ||||||
|  |         start_ip: IpAddress, | ||||||
|  |         hostname_prefix: &str, | ||||||
|  |     ) -> Vec<LogicalHost> { | ||||||
|         let mut hosts = Vec::with_capacity(number_hosts.try_into().unwrap()); |         let mut hosts = Vec::with_capacity(number_hosts.try_into().unwrap()); | ||||||
|         for i in 0..number_hosts { |         for i in 0..number_hosts { | ||||||
|             let new_ip = increment_ip(start_ip, i).expect("IP address overflow"); |             let new_ip = increment_ip(start_ip, i).expect("IP address overflow"); | ||||||
|  | |||||||
							
								
								
									
										24
									
								
								harmony-rs/harmony/src/domain/topology/tftp.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								harmony-rs/harmony/src/domain/topology/tftp.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | use crate::executors::ExecutorError; | ||||||
|  | use async_trait::async_trait; | ||||||
|  | 
 | ||||||
|  | use super::{IpAddress, Url}; | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | pub trait TftpServer: Send + Sync { | ||||||
|  |     async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError>; | ||||||
|  |     fn get_ip(&self) -> IpAddress; | ||||||
|  | 
 | ||||||
|  |     async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError>; | ||||||
|  |     async fn ensure_initialized(&self) -> Result<(), ExecutorError>; | ||||||
|  |     async fn commit_config(&self) -> Result<(), ExecutorError>; | ||||||
|  |     async fn reload_restart(&self) -> Result<(), ExecutorError>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Debug for dyn TftpServer { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         f.write_fmt(format_args!( | ||||||
|  |             "TftpServer serving files at {}", | ||||||
|  |             self.get_ip() | ||||||
|  |         )) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								harmony-rs/harmony/src/infra/opnsense/dhcp.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								harmony-rs/harmony/src/infra/opnsense/dhcp.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
|  | use log::debug; | ||||||
|  | 
 | ||||||
|  | use crate::{executors::ExecutorError, topology::{DHCPStaticEntry, DhcpServer, IpAddress, LogicalHost}}; | ||||||
|  | 
 | ||||||
|  | use super::OPNSenseFirewall; | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl DhcpServer for OPNSenseFirewall { | ||||||
|  |     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||||
|  |         OPNSenseFirewall::commit_config(self).await | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError> { | ||||||
|  |         let mac: String = String::from(&entry.mac); | ||||||
|  | 
 | ||||||
|  |         { | ||||||
|  |             let mut writable_opnsense = self.opnsense_config.write().await; | ||||||
|  |             writable_opnsense | ||||||
|  |                 .dhcp() | ||||||
|  |                 .add_static_mapping(&mac, entry.ip, &entry.name) | ||||||
|  |                 .unwrap(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         debug!("Registered {:?}", entry); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn remove_static_mapping( | ||||||
|  |         &self, | ||||||
|  |         _mac: &crate::topology::MacAddress, | ||||||
|  |     ) -> Result<(), ExecutorError> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn list_static_mappings(&self) -> Vec<(crate::topology::MacAddress, IpAddress)> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_ip(&self) -> IpAddress { | ||||||
|  |         OPNSenseFirewall::get_ip(self) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_host(&self) -> LogicalHost { | ||||||
|  |         self.host.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> { | ||||||
|  |         let ipv4 = match ip { | ||||||
|  |             std::net::IpAddr::V4(ipv4_addr) => ipv4_addr, | ||||||
|  |             std::net::IpAddr::V6(_) => todo!("ipv6 not supported yet"), | ||||||
|  |         }; | ||||||
|  |         { | ||||||
|  |             let mut writable_opnsense = self.opnsense_config.write().await; | ||||||
|  |             writable_opnsense.dhcp().set_next_server(ipv4); | ||||||
|  |             debug!("OPNsense dhcp server set next server {ipv4}"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError> { | ||||||
|  |         { | ||||||
|  |             let mut writable_opnsense = self.opnsense_config.write().await; | ||||||
|  |             writable_opnsense.dhcp().set_boot_filename(boot_filename); | ||||||
|  |             debug!("OPNsense dhcp server set boot filename {boot_filename}"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										88
									
								
								harmony-rs/harmony/src/infra/opnsense/dns.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								harmony-rs/harmony/src/infra/opnsense/dns.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | use crate::infra::opnsense::Host; | ||||||
|  | use crate::infra::opnsense::IpAddress; | ||||||
|  | use crate::infra::opnsense::LogicalHost; | ||||||
|  | use async_trait::async_trait; | ||||||
|  | use crate::{executors::ExecutorError, topology::{DnsRecord, DnsServer}}; | ||||||
|  | 
 | ||||||
|  | use super::OPNSenseFirewall; | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl DnsServer for OPNSenseFirewall { | ||||||
|  |     async fn register_hosts(&self, hosts: Vec<DnsRecord>) -> Result<(), ExecutorError> { | ||||||
|  |         let mut writable_opnsense = self.opnsense_config.write().await; | ||||||
|  |         let mut dns = writable_opnsense.dns(); | ||||||
|  |         let hosts = hosts | ||||||
|  |             .iter() | ||||||
|  |             .map(|h| { | ||||||
|  |                 Host::new( | ||||||
|  |                     h.host.clone(), | ||||||
|  |                     h.domain.clone(), | ||||||
|  |                     h.record_type.to_string(), | ||||||
|  |                     h.value.to_string(), | ||||||
|  |                 ) | ||||||
|  |             }) | ||||||
|  |             .collect(); | ||||||
|  |         dns.register_hosts(hosts); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn remove_record( | ||||||
|  |         &mut self, | ||||||
|  |         _name: &str, | ||||||
|  |         _record_type: crate::topology::DnsRecordType, | ||||||
|  |     ) -> Result<(), ExecutorError> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn list_records(&self) -> Vec<crate::topology::DnsRecord> { | ||||||
|  |         self.opnsense_config | ||||||
|  |             .write() | ||||||
|  |             .await | ||||||
|  |             .dns() | ||||||
|  |             .get_hosts() | ||||||
|  |             .iter() | ||||||
|  |             .map(|h| DnsRecord { | ||||||
|  |                 host: h.hostname.clone(), | ||||||
|  |                 domain: h.domain.clone(), | ||||||
|  |                 record_type: h | ||||||
|  |                     .rr | ||||||
|  |                     .parse() | ||||||
|  |                     .expect("received invalid record type {h.rr} from opnsense"), | ||||||
|  |                 value: h | ||||||
|  |                     .server | ||||||
|  |                     .parse() | ||||||
|  |                     .expect("received invalid ipv4 record from opnsense {h.server}"), | ||||||
|  |             }) | ||||||
|  |             .collect() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_ip(&self) -> IpAddress { | ||||||
|  |         OPNSenseFirewall::get_ip(&self) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_host(&self) -> LogicalHost { | ||||||
|  |         self.host.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn register_dhcp_leases(&self, register: bool) -> Result<(), ExecutorError> { | ||||||
|  |         let mut writable_opnsense = self.opnsense_config.write().await; | ||||||
|  |         let mut dns = writable_opnsense.dns(); | ||||||
|  |         dns.register_dhcp_leases(register); | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||||
|  |         let opnsense = self.opnsense_config.read().await; | ||||||
|  | 
 | ||||||
|  |         opnsense | ||||||
|  |             .save() | ||||||
|  |             .await | ||||||
|  |             .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; | ||||||
|  | 
 | ||||||
|  |         opnsense | ||||||
|  |             .restart_dns() | ||||||
|  |             .await | ||||||
|  |             .map_err(|e| ExecutorError::UnexpectedError(e.to_string())) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								harmony-rs/harmony/src/infra/opnsense/firewall.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								harmony-rs/harmony/src/infra/opnsense/firewall.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | use crate::{executors::ExecutorError, topology::{Firewall, FirewallRule, IpAddress, LogicalHost}}; | ||||||
|  | 
 | ||||||
|  | use super::OPNSenseFirewall; | ||||||
|  | 
 | ||||||
|  | impl Firewall for OPNSenseFirewall { | ||||||
|  |     fn add_rule(&mut self, _rule: FirewallRule) -> Result<(), ExecutorError> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn remove_rule(&mut self, _rule_id: &str) -> Result<(), ExecutorError> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn list_rules(&self) -> Vec<FirewallRule> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_ip(&self) -> IpAddress { | ||||||
|  |         OPNSenseFirewall::get_ip(self) | ||||||
|  |     } | ||||||
|  |     fn get_host(&self) -> LogicalHost { | ||||||
|  |         self.host.clone() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,11 +1,72 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
| use log::{debug, info, warn}; | use log::{debug, info, warn}; | ||||||
| use opnsense_config::Config; |  | ||||||
| use opnsense_config_xml::{Frontend, HAProxy, HAProxyBackend, HAProxyHealthCheck, HAProxyServer}; | use opnsense_config_xml::{Frontend, HAProxy, HAProxyBackend, HAProxyHealthCheck, HAProxyServer}; | ||||||
| use uuid::Uuid; | use uuid::Uuid; | ||||||
| 
 | 
 | ||||||
| use crate::topology::{ | use crate::{executors::ExecutorError, topology::{ | ||||||
|     BackendServer, HealthCheck, HttpMethod, HttpStatusCode, LoadBalancerService, |     BackendServer, HealthCheck, HttpMethod, HttpStatusCode, IpAddress, LoadBalancer, LoadBalancerService, LogicalHost | ||||||
| }; | }}; | ||||||
|  | 
 | ||||||
|  | use super::OPNSenseFirewall; | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl LoadBalancer for OPNSenseFirewall { | ||||||
|  |     fn get_ip(&self) -> IpAddress { | ||||||
|  |         OPNSenseFirewall::get_ip(self) | ||||||
|  |     } | ||||||
|  |     fn get_host(&self) -> LogicalHost { | ||||||
|  |         self.host.clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn add_service(&self, service: &LoadBalancerService) -> Result<(), ExecutorError> { | ||||||
|  |         let mut config = self.opnsense_config.write().await; | ||||||
|  |         let (frontend, backend, servers, healthcheck) = | ||||||
|  |             harmony_load_balancer_service_to_haproxy_xml(service); | ||||||
|  |         let mut load_balancer = config.load_balancer(); | ||||||
|  |         load_balancer.add_backend(backend); | ||||||
|  |         load_balancer.add_frontend(frontend); | ||||||
|  |         load_balancer.add_servers(servers); | ||||||
|  |         if let Some(healthcheck) = healthcheck { | ||||||
|  |             load_balancer.add_healthcheck(healthcheck); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn remove_service(&self, service: &LoadBalancerService) -> Result<(), ExecutorError> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||||
|  |         OPNSenseFirewall::commit_config(self).await?; | ||||||
|  |         todo!("Make sure load balancer is reloaded properly") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn ensure_initialized(&self) -> Result<(), ExecutorError> { | ||||||
|  |         let mut config = self.opnsense_config.write().await; | ||||||
|  |         let load_balancer = config.load_balancer(); | ||||||
|  |         if let Some(_) = load_balancer.get_full_config() { | ||||||
|  |             debug!("HAProxy config available in opnsense config, assuming it is already installed"); | ||||||
|  |             return Ok(()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         config.install_package("os-haproxy").await.map_err(|e| { | ||||||
|  |             ExecutorError::UnexpectedError(format!( | ||||||
|  |                 "Executor failed when trying to install os-haproxy package with error {e:?}" | ||||||
|  |             )) | ||||||
|  |         })?; | ||||||
|  |         config.load_balancer().enable(true); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn list_services(&self) -> Vec<LoadBalancerService> { | ||||||
|  |         let mut config = self.opnsense_config.write().await; | ||||||
|  |         let load_balancer = config.load_balancer(); | ||||||
|  |         let haproxy_xml_config = load_balancer.get_full_config(); | ||||||
|  |         haproxy_xml_config_to_harmony_loadbalancer(haproxy_xml_config) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| pub(crate) fn haproxy_xml_config_to_harmony_loadbalancer( | pub(crate) fn haproxy_xml_config_to_harmony_loadbalancer( | ||||||
|     haproxy: &Option<HAProxy>, |     haproxy: &Option<HAProxy>, | ||||||
| @ -1,22 +1,18 @@ | |||||||
| mod haproxy; | mod dhcp; | ||||||
|  | mod dns; | ||||||
|  | mod firewall; | ||||||
|  | mod load_balancer; | ||||||
| mod management; | mod management; | ||||||
|  | mod tftp; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; |  | ||||||
| use haproxy::{ |  | ||||||
|     haproxy_xml_config_to_harmony_loadbalancer, harmony_load_balancer_service_to_haproxy_xml, |  | ||||||
| }; |  | ||||||
| use log::debug; |  | ||||||
| pub use management::*; | pub use management::*; | ||||||
| use opnsense_config_xml::Host; | use opnsense_config_xml::Host; | ||||||
| use tokio::sync::RwLock; | use tokio::sync::RwLock; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     executors::ExecutorError, |     executors::ExecutorError, | ||||||
|     topology::{ |     topology::{IpAddress, LogicalHost}, | ||||||
|         DHCPStaticEntry, DhcpServer, DnsRecord, DnsServer, Firewall, FirewallRule, IpAddress, |  | ||||||
|         LoadBalancer, LoadBalancerService, LogicalHost, |  | ||||||
|     }, |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| @ -56,228 +52,3 @@ impl OPNSenseFirewall { | |||||||
|             .map_err(|e| ExecutorError::UnexpectedError(e.to_string())) |             .map_err(|e| ExecutorError::UnexpectedError(e.to_string())) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| impl Firewall for OPNSenseFirewall { |  | ||||||
|     fn add_rule(&mut self, _rule: FirewallRule) -> Result<(), ExecutorError> { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn remove_rule(&mut self, _rule_id: &str) -> Result<(), ExecutorError> { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn list_rules(&self) -> Vec<FirewallRule> { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_ip(&self) -> IpAddress { |  | ||||||
|         OPNSenseFirewall::get_ip(self) |  | ||||||
|     } |  | ||||||
|     fn get_host(&self) -> LogicalHost { |  | ||||||
|         self.host.clone() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[async_trait] |  | ||||||
| impl LoadBalancer for OPNSenseFirewall { |  | ||||||
|     fn get_ip(&self) -> IpAddress { |  | ||||||
|         OPNSenseFirewall::get_ip(self) |  | ||||||
|     } |  | ||||||
|     fn get_host(&self) -> LogicalHost { |  | ||||||
|         self.host.clone() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn add_service(&self, service: &LoadBalancerService) -> Result<(), ExecutorError> { |  | ||||||
|         let mut config = self.opnsense_config.write().await; |  | ||||||
|         let (frontend, backend, servers, healthcheck) = |  | ||||||
|             harmony_load_balancer_service_to_haproxy_xml(service); |  | ||||||
|         let mut load_balancer = config.load_balancer(); |  | ||||||
|         load_balancer.add_backend(backend); |  | ||||||
|         load_balancer.add_frontend(frontend); |  | ||||||
|         load_balancer.add_servers(servers); |  | ||||||
|         if let Some(healthcheck) = healthcheck { |  | ||||||
|             load_balancer.add_healthcheck(healthcheck); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn remove_service(&self, service: &LoadBalancerService) -> Result<(), ExecutorError> { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { |  | ||||||
|         OPNSenseFirewall::commit_config(self).await?; |  | ||||||
|         todo!("Make sure load balancer is reloaded properly") |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn ensure_initialized(&self) -> Result<(), ExecutorError> { |  | ||||||
|         let mut config = self.opnsense_config.write().await; |  | ||||||
|         let load_balancer = config.load_balancer(); |  | ||||||
|         if let Some(_) = load_balancer.get_full_config() { |  | ||||||
|             debug!("HAProxy config available in opnsense config, assuming it is already installed"); |  | ||||||
|             return Ok(()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         config.install_package("os-haproxy").await.map_err(|e| { |  | ||||||
|             ExecutorError::UnexpectedError(format!( |  | ||||||
|                 "Executor failed when trying to install os-haproxy package with error {e:?}" |  | ||||||
|             )) |  | ||||||
|         })?; |  | ||||||
|         config.load_balancer().enable(true); |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn list_services(&self) -> Vec<LoadBalancerService> { |  | ||||||
|         let mut config = self.opnsense_config.write().await; |  | ||||||
|         let load_balancer = config.load_balancer(); |  | ||||||
|         let haproxy_xml_config = load_balancer.get_full_config(); |  | ||||||
|         haproxy_xml_config_to_harmony_loadbalancer(haproxy_xml_config) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[async_trait] |  | ||||||
| impl DhcpServer for OPNSenseFirewall { |  | ||||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { |  | ||||||
|         OPNSenseFirewall::commit_config(self).await |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError> { |  | ||||||
|         let mac: String = String::from(&entry.mac); |  | ||||||
| 
 |  | ||||||
|         { |  | ||||||
|             let mut writable_opnsense = self.opnsense_config.write().await; |  | ||||||
|             writable_opnsense |  | ||||||
|                 .dhcp() |  | ||||||
|                 .add_static_mapping(&mac, entry.ip, &entry.name) |  | ||||||
|                 .unwrap(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         debug!("Registered {:?}", entry); |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn remove_static_mapping( |  | ||||||
|         &self, |  | ||||||
|         _mac: &crate::topology::MacAddress, |  | ||||||
|     ) -> Result<(), ExecutorError> { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn list_static_mappings(&self) -> Vec<(crate::topology::MacAddress, IpAddress)> { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_ip(&self) -> IpAddress { |  | ||||||
|         OPNSenseFirewall::get_ip(self) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_host(&self) -> LogicalHost { |  | ||||||
|         self.host.clone() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> { |  | ||||||
|         let ipv4 = match ip { |  | ||||||
|             std::net::IpAddr::V4(ipv4_addr) => ipv4_addr, |  | ||||||
|             std::net::IpAddr::V6(_) => todo!("ipv6 not supported yet"), |  | ||||||
|         }; |  | ||||||
|         { |  | ||||||
|             let mut writable_opnsense = self.opnsense_config.write().await; |  | ||||||
|             writable_opnsense.dhcp().set_next_server(ipv4); |  | ||||||
|             debug!("OPNsense dhcp server set next server {ipv4}"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError> { |  | ||||||
|         { |  | ||||||
|             let mut writable_opnsense = self.opnsense_config.write().await; |  | ||||||
|             writable_opnsense.dhcp().set_boot_filename(boot_filename); |  | ||||||
|             debug!("OPNsense dhcp server set boot filename {boot_filename}"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[async_trait] |  | ||||||
| impl DnsServer for OPNSenseFirewall { |  | ||||||
|     async fn register_hosts(&self, hosts: Vec<DnsRecord>) -> Result<(), ExecutorError> { |  | ||||||
|         let mut writable_opnsense = self.opnsense_config.write().await; |  | ||||||
|         let mut dns = writable_opnsense.dns(); |  | ||||||
|         let hosts = hosts |  | ||||||
|             .iter() |  | ||||||
|             .map(|h| { |  | ||||||
|                 Host::new( |  | ||||||
|                     h.host.clone(), |  | ||||||
|                     h.domain.clone(), |  | ||||||
|                     h.record_type.to_string(), |  | ||||||
|                     h.value.to_string(), |  | ||||||
|                 ) |  | ||||||
|             }) |  | ||||||
|             .collect(); |  | ||||||
|         dns.register_hosts(hosts); |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn remove_record( |  | ||||||
|         &mut self, |  | ||||||
|         _name: &str, |  | ||||||
|         _record_type: crate::topology::DnsRecordType, |  | ||||||
|     ) -> Result<(), ExecutorError> { |  | ||||||
|         todo!() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn list_records(&self) -> Vec<crate::topology::DnsRecord> { |  | ||||||
|         self.opnsense_config |  | ||||||
|             .write() |  | ||||||
|             .await |  | ||||||
|             .dns() |  | ||||||
|             .get_hosts() |  | ||||||
|             .iter() |  | ||||||
|             .map(|h| DnsRecord { |  | ||||||
|                 host: h.hostname.clone(), |  | ||||||
|                 domain: h.domain.clone(), |  | ||||||
|                 record_type: h |  | ||||||
|                     .rr |  | ||||||
|                     .parse() |  | ||||||
|                     .expect("received invalid record type {h.rr} from opnsense"), |  | ||||||
|                 value: h |  | ||||||
|                     .server |  | ||||||
|                     .parse() |  | ||||||
|                     .expect("received invalid ipv4 record from opnsense {h.server}"), |  | ||||||
|             }) |  | ||||||
|             .collect() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_ip(&self) -> IpAddress { |  | ||||||
|         OPNSenseFirewall::get_ip(&self) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_host(&self) -> LogicalHost { |  | ||||||
|         self.host.clone() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn register_dhcp_leases(&self, register: bool) -> Result<(), ExecutorError> { |  | ||||||
|         let mut writable_opnsense = self.opnsense_config.write().await; |  | ||||||
|         let mut dns = writable_opnsense.dns(); |  | ||||||
|         dns.register_dhcp_leases(register); |  | ||||||
| 
 |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { |  | ||||||
|         let opnsense = self.opnsense_config.read().await; |  | ||||||
| 
 |  | ||||||
|         opnsense |  | ||||||
|             .save() |  | ||||||
|             .await |  | ||||||
|             .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; |  | ||||||
| 
 |  | ||||||
|         opnsense |  | ||||||
|             .restart_dns() |  | ||||||
|             .await |  | ||||||
|             .map_err(|e| ExecutorError::UnexpectedError(e.to_string())) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										77
									
								
								harmony-rs/harmony/src/infra/opnsense/tftp.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								harmony-rs/harmony/src/infra/opnsense/tftp.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
|  | use log::{debug, info}; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     executors::ExecutorError, | ||||||
|  |     topology::{IpAddress, TftpServer, Url}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use super::OPNSenseFirewall; | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl TftpServer for OPNSenseFirewall { | ||||||
|  |     async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError> { | ||||||
|  |         let tftp_root_path = "/usr/local/tftp"; | ||||||
|  | 
 | ||||||
|  |         let config = self.opnsense_config.write().await; | ||||||
|  |         info!("Uploading files from url {url} to {tftp_root_path}"); | ||||||
|  |         match url { | ||||||
|  |             Url::LocalFolder(path) => { | ||||||
|  |                 config | ||||||
|  |                     .upload_files(path, tftp_root_path) | ||||||
|  |                     .await | ||||||
|  |                     .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; | ||||||
|  |             } | ||||||
|  |             Url::Remote(url) => todo!(), | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_ip(&self) -> IpAddress { | ||||||
|  |         OPNSenseFirewall::get_ip(self) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError> { | ||||||
|  |         info!("Setting listen_ip to {}", &ip); | ||||||
|  |         self.opnsense_config | ||||||
|  |             .write() | ||||||
|  |             .await | ||||||
|  |             .tftp() | ||||||
|  |             .listen_ip(&ip.to_string()); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||||
|  |         OPNSenseFirewall::commit_config(self).await | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn reload_restart(&self) -> Result<(), ExecutorError> { | ||||||
|  |         self.opnsense_config | ||||||
|  |             .write() | ||||||
|  |             .await | ||||||
|  |             .tftp() | ||||||
|  |             .reload_restart() | ||||||
|  |             .await | ||||||
|  |             .map_err(|e| ExecutorError::UnexpectedError(e.to_string())) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn ensure_initialized(&self) -> Result<(), ExecutorError> { | ||||||
|  |         let mut config = self.opnsense_config.write().await; | ||||||
|  |         let tftp = config.tftp(); | ||||||
|  |         if let None = tftp.get_full_config() { | ||||||
|  |             info!("Tftp config not available in opnsense config, installing package"); | ||||||
|  |             config.install_package("os-tftp").await.map_err(|e| { | ||||||
|  |                 ExecutorError::UnexpectedError(format!( | ||||||
|  |                     "Executor failed when trying to install os-tftp package with error {e:?}" | ||||||
|  |                 )) | ||||||
|  |             })?; | ||||||
|  |         } else { | ||||||
|  |             info!("Tftp config available in opnsense config, assuming it is already installed"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         info!("Enabling tftp server"); | ||||||
|  |         config.tftp().enable(true); | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -2,3 +2,4 @@ pub mod dhcp; | |||||||
| pub mod dns; | pub mod dns; | ||||||
| pub mod okd; | pub mod okd; | ||||||
| pub mod load_balancer; | pub mod load_balancer; | ||||||
|  | pub mod tftp; | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								harmony-rs/harmony/src/modules/tftp.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								harmony-rs/harmony/src/modules/tftp.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
|  | use derive_new::new; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     data::{Id, Version}, | ||||||
|  |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|  |     inventory::Inventory, | ||||||
|  |     score::Score, | ||||||
|  |     topology::{HAClusterTopology, Url}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, new, Clone)] | ||||||
|  | pub struct TftpScore { | ||||||
|  |     files_to_serve: Url, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Score for TftpScore { | ||||||
|  |     type InterpretType = TftpInterpret; | ||||||
|  | 
 | ||||||
|  |     fn create_interpret(self) -> Self::InterpretType { | ||||||
|  |         TftpInterpret::new(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, new, Clone)] | ||||||
|  | pub struct TftpInterpret { | ||||||
|  |     score: TftpScore, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl Interpret for TftpInterpret { | ||||||
|  |     async fn execute( | ||||||
|  |         &self, | ||||||
|  |         inventory: &Inventory, | ||||||
|  |         topology: &HAClusterTopology, | ||||||
|  |     ) -> Result<Outcome, InterpretError> { | ||||||
|  |         let tftp_server = &topology.tftp_server; | ||||||
|  |         tftp_server.ensure_initialized().await?; | ||||||
|  |         tftp_server.set_ip(topology.router.get_gateway()).await?; | ||||||
|  |         tftp_server.serve_files(&self.score.files_to_serve).await?; | ||||||
|  |         tftp_server.commit_config().await?; | ||||||
|  |         tftp_server.reload_restart().await?; | ||||||
|  |         Ok(Outcome::success(format!( | ||||||
|  |             "TFTP Server running and serving files from {}", | ||||||
|  |             self.score.files_to_serve | ||||||
|  |         ))) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_name(&self) -> InterpretName { | ||||||
|  |         InterpretName::Tftp | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_version(&self) -> Version { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_status(&self) -> InterpretStatus { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_children(&self) -> Vec<Id> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -453,57 +453,57 @@ pub struct OPNsenseXmlSection { | |||||||
| 
 | 
 | ||||||
| #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | ||||||
| pub struct Tftp { | pub struct Tftp { | ||||||
|     general: TftpGeneral, |     pub general: TftpGeneral, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | ||||||
| pub struct TftpGeneral { | pub struct TftpGeneral { | ||||||
|     #[yaserde(attribute)] |     #[yaserde(attribute)] | ||||||
|     version: String, |     pub version: String, | ||||||
|     enabled: u8, |     pub enabled: u8, | ||||||
|     listen: String, |     pub listen: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | ||||||
| #[yaserde(rename = "IDS")] | #[yaserde(rename = "IDS")] | ||||||
| pub struct IDS { | pub struct IDS { | ||||||
|     #[yaserde(attribute)] |     #[yaserde(attribute)] | ||||||
|     version: String, |     pub version: String, | ||||||
|     rules: MaybeString, |     pub rules: MaybeString, | ||||||
|     policies: MaybeString, |     pub policies: MaybeString, | ||||||
|     #[yaserde(rename = "userDefinedRules")] |     #[yaserde(rename = "userDefinedRules")] | ||||||
|     user_defined_rules: MaybeString, |     pub user_defined_rules: MaybeString, | ||||||
|     files: MaybeString, |     pub files: MaybeString, | ||||||
|     #[yaserde(rename = "fileTags")] |     #[yaserde(rename = "fileTags")] | ||||||
|     file_tags: MaybeString, |     pub file_tags: MaybeString, | ||||||
|     general: IDSGeneral, |     pub general: IDSGeneral, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | ||||||
| pub struct IDSGeneral { | pub struct IDSGeneral { | ||||||
|     enabled: Option<u8>, |     pub enabled: Option<u8>, | ||||||
|     ips: Option<u8>, |     pub ips: Option<u8>, | ||||||
|     promisc: Option<u8>, |     pub promisc: Option<u8>, | ||||||
|     interfaces: String, |     pub interfaces: String, | ||||||
|     homenet: String, |     pub homenet: String, | ||||||
|     #[yaserde(rename = "defaultPacketSize")] |     #[yaserde(rename = "defaultPacketSize")] | ||||||
|     default_packet_size: MaybeString, |     pub default_packet_size: MaybeString, | ||||||
|     #[yaserde(rename = "UpdateCron")] |     #[yaserde(rename = "UpdateCron")] | ||||||
|     update_cron: MaybeString, |     pub update_cron: MaybeString, | ||||||
|     #[yaserde(rename = "AlertLogrotate")] |     #[yaserde(rename = "AlertLogrotate")] | ||||||
|     alert_logrotate: String, |     pub alert_logrotate: String, | ||||||
|     #[yaserde(rename = "AlertSaveLogs")] |     #[yaserde(rename = "AlertSaveLogs")] | ||||||
|     alert_save_logs: u8, |     pub alert_save_logs: u8, | ||||||
|     #[yaserde(rename = "MPMAlgo")] |     #[yaserde(rename = "MPMAlgo")] | ||||||
|     mpm_algo: MaybeString, |     pub mpm_algo: MaybeString, | ||||||
|     detect: Detect, |     pub detect: Detect, | ||||||
|     syslog: Option<u8>, |     pub syslog: Option<u8>, | ||||||
|     syslog_eve: Option<u8>, |     pub syslog_eve: Option<u8>, | ||||||
|     #[yaserde(rename = "LogPayload")] |     #[yaserde(rename = "LogPayload")] | ||||||
|     log_payload: Option<u8>, |     pub log_payload: Option<u8>, | ||||||
|     verbosity: MaybeString, |     pub verbosity: MaybeString, | ||||||
|     #[yaserde(rename = "eveLog")] |     #[yaserde(rename = "eveLog")] | ||||||
|     eve_log: Option<RawXml>, |     pub eve_log: Option<RawXml>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | #[derive(Debug, YaSerialize, YaDeserialize, PartialEq)] | ||||||
|  | |||||||
| @ -18,6 +18,8 @@ opnsense-config-xml = { path = "../opnsense-config-xml" } | |||||||
| chrono = "0.4.38" | chrono = "0.4.38" | ||||||
| russh-sftp = "2.0.6" | russh-sftp = "2.0.6" | ||||||
| serde_json = "1.0.133" | serde_json = "1.0.133" | ||||||
|  | tokio-util = "0.7.13" | ||||||
|  | tokio-stream = "0.1.17" | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| pretty_assertions = "1.4.1" | pretty_assertions = "1.4.1" | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ use std::{sync::Arc, time::Duration}; | |||||||
| use crate::{ | use crate::{ | ||||||
|     config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, |     config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, | ||||||
|     error::Error, |     error::Error, | ||||||
|     modules::{dhcp::DhcpConfig, dns::DnsConfig, load_balancer::LoadBalancerConfig}, |     modules::{dhcp::DhcpConfig, dns::DnsConfig, load_balancer::LoadBalancerConfig, tftp::TftpConfig}, | ||||||
| }; | }; | ||||||
| use log::{info, trace}; | use log::{info, trace}; | ||||||
| use opnsense_config_xml::OPNsense; | use opnsense_config_xml::OPNsense; | ||||||
| @ -38,10 +38,18 @@ impl Config { | |||||||
|         DnsConfig::new(&mut self.opnsense, self.shell.clone()) |         DnsConfig::new(&mut self.opnsense, self.shell.clone()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn tftp(&mut self) -> TftpConfig { | ||||||
|  |         TftpConfig::new(&mut self.opnsense, self.shell.clone()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn load_balancer(&mut self) -> LoadBalancerConfig { |     pub fn load_balancer(&mut self) -> LoadBalancerConfig { | ||||||
|         LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone()) |         LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub async fn upload_files(&self, source: &str, destination: &str) ->  Result<String, Error> { | ||||||
|  |         self.shell.upload_folder(source, destination).await | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub async fn install_package(&mut self, package_name: &str) -> Result<(), Error> { |     pub async fn install_package(&mut self, package_name: &str) -> Result<(), Error> { | ||||||
|         info!("Installing opnsense package {package_name}"); |         info!("Installing opnsense package {package_name}"); | ||||||
|         let output = self.shell |         let output = self.shell | ||||||
|  | |||||||
| @ -27,19 +27,25 @@ impl SshConfigManager { | |||||||
|         let ts = chrono::Utc::now(); |         let ts = chrono::Utc::now(); | ||||||
|         let backup_filename = format!("config-{}-harmony.xml", ts.format("%s%.3f")); |         let backup_filename = format!("config-{}-harmony.xml", ts.format("%s%.3f")); | ||||||
| 
 | 
 | ||||||
|         self.opnsense_shell.exec(&format!("cp /conf/config.xml /conf/backup/{}", backup_filename)) |         self.opnsense_shell | ||||||
|  |             .exec(&format!( | ||||||
|  |                 "cp /conf/config.xml /conf/backup/{}", | ||||||
|  |                 backup_filename | ||||||
|  |             )) | ||||||
|             .await |             .await | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn move_to_live_config(&self, new_config_path: &str) -> Result<String, Error> { |     async fn move_to_live_config(&self, new_config_path: &str) -> Result<String, Error> { | ||||||
|         info!("Overwriting OPNSense /conf/config.xml with {new_config_path}"); |         info!("Overwriting OPNSense /conf/config.xml with {new_config_path}"); | ||||||
|         self.opnsense_shell.exec(&format!("mv {new_config_path} /conf/config.xml")) |         self.opnsense_shell | ||||||
|  |             .exec(&format!("mv {new_config_path} /conf/config.xml")) | ||||||
|             .await |             .await | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn reload_all_services(&self) -> Result<String, Error> { |     async fn reload_all_services(&self) -> Result<String, Error> { | ||||||
|         info!("Reloading all opnsense services"); |         info!("Reloading all opnsense services"); | ||||||
|         self.opnsense_shell.exec(&format!("configctl service reload all")) |         self.opnsense_shell | ||||||
|  |             .exec(&format!("configctl service reload all")) | ||||||
|             .await |             .await | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ use crate::Error; | |||||||
| pub trait OPNsenseShell: std::fmt::Debug + Send + Sync { | pub trait OPNsenseShell: std::fmt::Debug + Send + Sync { | ||||||
|     async fn exec(&self, command: &str) -> Result<String, Error>; |     async fn exec(&self, command: &str) -> Result<String, Error>; | ||||||
|     async fn write_content_to_temp_file(&self, content: &str) -> Result<String, Error>; |     async fn write_content_to_temp_file(&self, content: &str) -> Result<String, Error>; | ||||||
|  |     async fn upload_folder(&self, source: &str, destination: &str) -> Result<String, Error>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| @ -24,4 +25,7 @@ impl OPNsenseShell for DummyOPNSenseShell { | |||||||
|     async fn write_content_to_temp_file(&self, _content: &str) -> Result<String, Error> { |     async fn write_content_to_temp_file(&self, _content: &str) -> Result<String, Error> { | ||||||
|         unimplemented!("This is a dummy implementation"); |         unimplemented!("This is a dummy implementation"); | ||||||
|     } |     } | ||||||
|  |     async fn upload_folder(&self, _source: &str, _destination: &str) -> Result<String, Error> { | ||||||
|  |         unimplemented!("This is a dummy implementation"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ use std::{ | |||||||
|     sync::Arc, |     sync::Arc, | ||||||
|     time::{SystemTime, UNIX_EPOCH}, |     time::{SystemTime, UNIX_EPOCH}, | ||||||
| }; | }; | ||||||
|  | use tokio_stream::StreamExt; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use log::{debug, info}; | use log::{debug, info}; | ||||||
| @ -11,12 +12,15 @@ use russh::{ | |||||||
|     Channel, |     Channel, | ||||||
| }; | }; | ||||||
| use russh_keys::key; | use russh_keys::key; | ||||||
| use russh_sftp::client::SftpSession; | use russh_sftp::{client::SftpSession, protocol::OpenFlags}; | ||||||
| use tokio::io::AsyncWriteExt; | use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||||
| 
 | 
 | ||||||
| use crate::{config::SshCredentials, Error}; | use crate::{config::SshCredentials, Error}; | ||||||
| 
 | 
 | ||||||
| use super::OPNsenseShell; | use super::OPNsenseShell; | ||||||
|  | use tokio::fs::read_dir; | ||||||
|  | use tokio::fs::File; | ||||||
|  | use tokio_util::codec::{BytesCodec, FramedRead}; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct SshOPNSenseShell { | pub struct SshOPNSenseShell { | ||||||
| @ -54,6 +58,72 @@ impl OPNsenseShell for SshOPNSenseShell { | |||||||
| 
 | 
 | ||||||
|         Ok(temp_filename) |         Ok(temp_filename) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     async fn upload_folder(&self, source: &str, destination: &str) -> Result<String, Error> { | ||||||
|  |         let channel = self.get_ssh_channel().await?; | ||||||
|  |         channel | ||||||
|  |             .request_subsystem(true, "sftp") | ||||||
|  |             .await | ||||||
|  |             .expect("Should request sftp subsystem"); | ||||||
|  |         let sftp = SftpSession::new(channel.into_stream()) | ||||||
|  |             .await | ||||||
|  |             .expect("Should acquire sftp subsystem"); | ||||||
|  | 
 | ||||||
|  |         if !sftp.try_exists(destination).await? { | ||||||
|  |             info!("Creating remote directory {destination}"); | ||||||
|  |             sftp.create_dir(destination).await?; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         info!("Reading local directory {source}"); | ||||||
|  |         let mut entries = read_dir(source).await?; | ||||||
|  |         while let Some(entry) = entries.next_entry().await? { | ||||||
|  |             info!( | ||||||
|  |                 "Checking directory entry {}", | ||||||
|  |                 entry | ||||||
|  |                     .path() | ||||||
|  |                     .to_str() | ||||||
|  |                     .expect("Directory entry should have a path : {entry:?}") | ||||||
|  |             ); | ||||||
|  |             if entry.file_type().await?.is_file() { | ||||||
|  |                 debug!("Got a file"); | ||||||
|  |                 let local_path = entry.path(); | ||||||
|  |                 debug!("path {local_path:?}"); | ||||||
|  |                 let file_name = local_path.file_name().unwrap().to_string_lossy(); | ||||||
|  |                 let remote_path = format!("{}/{}", destination, file_name); | ||||||
|  |                 info!( | ||||||
|  |                     "Uploading local file {} to remote {}", | ||||||
|  |                     local_path.to_str().unwrap_or_default(), | ||||||
|  |                     remote_path | ||||||
|  |                 ); | ||||||
|  | 
 | ||||||
|  |                 debug!("Creating file {remote_path:?}"); | ||||||
|  |                 let mut remote_file = sftp.create(remote_path.as_str()).await?; | ||||||
|  | 
 | ||||||
|  |                 debug!("Writing file {remote_path:?}"); | ||||||
|  |                 let local_file = File::open(&local_path).await?; | ||||||
|  |                 let mut reader = FramedRead::new(local_file, BytesCodec::new()); | ||||||
|  | 
 | ||||||
|  |                 while let Some(result) = reader.next().await { | ||||||
|  |                     match result { | ||||||
|  |                         Ok(bytes) => { | ||||||
|  |                             if !bytes.is_empty() { | ||||||
|  |                                 remote_file.write(&bytes).await?; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         Err(e) => todo!("Error unhandled {e}"), | ||||||
|  |                     }; | ||||||
|  |                 } | ||||||
|  |             } else if entry.file_type().await?.is_dir() { | ||||||
|  |                 let sub_source = entry.path(); | ||||||
|  |                 let sub_destination = | ||||||
|  |                     format!("{}/{}", destination, entry.file_name().to_string_lossy()); | ||||||
|  |                 self.upload_folder(sub_source.to_str().unwrap(), &sub_destination) | ||||||
|  |                     .await?; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Ok(destination.to_string()) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl SshOPNSenseShell { | impl SshOPNSenseShell { | ||||||
| @ -80,6 +150,7 @@ impl SshOPNSenseShell { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn new(host: (IpAddr, u16), credentials: SshCredentials, ssh_config: Arc<Config>) -> Self { |     pub fn new(host: (IpAddr, u16), credentials: SshCredentials, ssh_config: Arc<Config>) -> Self { | ||||||
|  |         info!("Initializing SshOPNSenseShell on host {host:?}"); | ||||||
|         Self { |         Self { | ||||||
|             host, |             host, | ||||||
|             credentials, |             credentials, | ||||||
|  | |||||||
| @ -6,6 +6,8 @@ pub enum Error { | |||||||
|     Xml(String), |     Xml(String), | ||||||
|     #[error("SSH error: {0}")] |     #[error("SSH error: {0}")] | ||||||
|     Ssh(#[from] russh::Error), |     Ssh(#[from] russh::Error), | ||||||
|  |     #[error("SSH Client error: {0}")] | ||||||
|  |     SftpClient(#[from] russh_sftp::client::error::Error), | ||||||
|     #[error("Command failed : {0}")] |     #[error("Command failed : {0}")] | ||||||
|     Command(String), |     Command(String), | ||||||
|     #[error("I/O error: {0}")] |     #[error("I/O error: {0}")] | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
| pub mod dhcp; | pub mod dhcp; | ||||||
| pub mod dns; | pub mod dns; | ||||||
| pub mod load_balancer; | pub mod load_balancer; | ||||||
|  | pub mod tftp; | ||||||
|  | |||||||
							
								
								
									
										48
									
								
								harmony-rs/opnsense-config/src/modules/tftp.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								harmony-rs/opnsense-config/src/modules/tftp.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | use std::sync::Arc; | ||||||
|  | 
 | ||||||
|  | use opnsense_config_xml::{OPNsense, Tftp}; | ||||||
|  | 
 | ||||||
|  | use crate::{config::OPNsenseShell, Error}; | ||||||
|  | 
 | ||||||
|  | pub struct TftpConfig<'a> { | ||||||
|  |     opnsense: &'a mut OPNsense, | ||||||
|  |     opnsense_shell: Arc<dyn OPNsenseShell>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> TftpConfig<'a> { | ||||||
|  |     pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self { | ||||||
|  |         Self { | ||||||
|  |             opnsense, | ||||||
|  |             opnsense_shell, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn get_full_config(&self) -> &Option<Tftp> { | ||||||
|  |         &self.opnsense.opnsense.tftp | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn with_tftp<F, R>(&mut self, f: F) -> R | ||||||
|  |     where | ||||||
|  |         F: FnOnce(&mut Tftp) -> R, | ||||||
|  |     { | ||||||
|  |         match &mut self.opnsense.opnsense.tftp.as_mut() { | ||||||
|  |             Some(tftp) => f(tftp), | ||||||
|  |             None => unimplemented!("Accessing tftp config is not supported when not available yet"), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn enable(&mut self, enabled: bool) { | ||||||
|  |         self.with_tftp(|tftp| tftp.general.enabled = enabled as u8); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn listen_ip(&mut self, ip: &str) { | ||||||
|  |         self.with_tftp(|tftp| tftp.general.listen = ip.to_string()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn reload_restart(&self) -> Result<(), Error> { | ||||||
|  |         self.opnsense_shell.exec("configctl tftp stop").await?; | ||||||
|  |         self.opnsense_shell.exec("configctl template reload OPNsense/Tftp").await?; | ||||||
|  |         self.opnsense_shell.exec("configctl tftp start").await?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user