From c2f9bebd09884887ccdd109ebeb66d1d3165bcfc Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 20 Mar 2023 11:20:39 -0400 Subject: [PATCH 01/46] [wip] work on tfclient build system --- Cargo.lock | 354 ++++++++++++++++++++++++++++++++++++++----- tfclient/Cargo.toml | 12 ++ tfclient/build.rs | 298 ++++++++++++++++++++++++++++++++++++ tfclient/src/main.rs | 34 ++++- 4 files changed, 657 insertions(+), 41 deletions(-) create mode 100644 tfclient/build.rs diff --git a/Cargo.lock b/Cargo.lock index 63782ef..2781e43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -92,7 +92,7 @@ checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -242,6 +242,43 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce38afc168d8665cfc75c7b1dd9672e50716a137f433f070991619744a67342a" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "clap_lex" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -428,7 +465,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.107", ] [[package]] @@ -445,7 +482,7 @@ checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -488,7 +525,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -576,6 +613,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -611,6 +669,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "filetime" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.45.0", +] + [[package]] name = "flate2" version = "1.0.25" @@ -852,12 +922,24 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + [[package]] name = "hkdf" version = "0.12.3" @@ -934,6 +1016,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1017,6 +1112,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd6da19f25979c7270e70fa95ab371ec3b701cd0eefc47667a09785b3c59155" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "ipnet" version = "2.7.1" @@ -1026,6 +1132,18 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1077,6 +1195,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -1310,7 +1434,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1332,6 +1456,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + [[package]] name = "overload" version = "0.1.1" @@ -1422,7 +1552,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1505,10 +1635,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] -name = "proc-macro2" -version = "1.0.50" +name = "proc-macro-error" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.107", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" dependencies = [ "unicode-ident", ] @@ -1521,7 +1675,7 @@ checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "version_check", "yansi", ] @@ -1543,9 +1697,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1626,7 +1780,7 @@ checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1654,12 +1808,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "reqwest" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "winapi", + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", ] [[package]] @@ -1713,7 +1895,7 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn", + "syn 1.0.107", "unicode-xid", ] @@ -1744,6 +1926,20 @@ dependencies = [ "uncased", ] +[[package]] +name = "rustix" +version = "0.36.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fe885c3a125aa45213b68cc1472a49880cb5923dc23f522ad2791b882228778" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + [[package]] name = "rustversion" version = "1.0.11" @@ -1808,22 +2004,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.157" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "707de5fcf5df2b5788fca98dd7eab490bc2fd9b7ef1404defc462833b83f25ca" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.157" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "78997f4555c22a7971214540c4a661291970619afd56de19f77e0de86296e1e5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.2", ] [[package]] @@ -1846,6 +2042,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha1" version = "0.10.5" @@ -2019,7 +2227,7 @@ dependencies = [ "sha2", "sqlx-core", "sqlx-rt", - "syn", + "syn 1.0.107", "url", ] @@ -2063,6 +2271,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.4.1" @@ -2080,6 +2294,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d3276aee1fa0c33612917969b5172b5be2db051232a6e4826f1a1a9191b045" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -2088,22 +2313,32 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "unicode-xid", ] [[package]] -name = "tempfile" -version = "3.3.0" +name = "tar" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys 0.42.0", ] [[package]] @@ -2118,6 +2353,17 @@ dependencies = [ [[package]] name = "tfclient" version = "0.1.0" +dependencies = [ + "clap", + "flate2", + "hex", + "hex-literal", + "reqwest", + "serde", + "sha2", + "tar", + "tempfile", +] [[package]] name = "thiserror" @@ -2136,7 +2382,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2229,7 +2475,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2355,7 +2601,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2561,7 +2807,7 @@ checksum = "c1b300a878652a387d2a0de915bdae8f1a548f0c6d45e072fe2688794b656cc9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2631,10 +2877,22 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -2653,7 +2911,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2839,6 +3097,15 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "x25519-dalek" version = "2.0.0-pre.1" @@ -2850,6 +3117,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + [[package]] name = "yansi" version = "0.5.1" @@ -2873,6 +3149,6 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "synstructure", ] diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 04938fe..be9c617 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -2,7 +2,19 @@ name = "tfclient" version = "0.1.0" edition = "2021" +description = "An open-source reimplementation of a Defined Networking-compatible client" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "4.1.10", features = ["derive"] } +hex-literal = "0.3.4" + +[build-dependencies] +serde = { version = "1.0.157", features = ["derive"] } +reqwest = { version = "0.11.14", features = ["blocking", "json"] } +flate2 = "1.0.25" +tar = "0.4.38" +hex = "0.4.3" +tempfile = "3.4.0" +sha2 = "0.10.6" \ No newline at end of file diff --git a/tfclient/build.rs b/tfclient/build.rs new file mode 100644 index 0000000..27fbdfe --- /dev/null +++ b/tfclient/build.rs @@ -0,0 +1,298 @@ +/* +[ + { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/1", + "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", + "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", + "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", + "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0", + "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0", + "id": 1, + "node_id": "MDc6UmVsZWFzZTE=", + "tag_name": "v1.0.0", + "target_commitish": "master", + "name": "v1.0.0", + "body": "Description of the release", + "draft": false, + "prerelease": false, + "created_at": "2013-02-27T19:35:32Z", + "published_at": "2013-02-27T19:35:32Z", + "author": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assets": [ + { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/assets/1", + "browser_download_url": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip", + "id": 1, + "node_id": "MDEyOlJlbGVhc2VBc3NldDE=", + "name": "example.zip", + "label": "short description", + "state": "uploaded", + "content_type": "application/zip", + "size": 1024, + "download_count": 42, + "created_at": "2013-02-27T19:35:32Z", + "updated_at": "2013-02-27T19:35:32Z", + "uploader": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + } + } + ] + } +] + */ + + +use std::fs; +use std::fs::{File, remove_file}; +use std::io::{Read, Write}; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use std::process::{Command, Output}; +use flate2::read::GzDecoder; +use reqwest::blocking::Response; +use reqwest::header::HeaderMap; +use tar::Archive; +use tempfile::{NamedTempFile, tempfile}; + +#[derive(serde::Deserialize, Debug)] +struct GithubRelease { + url: String, + html_url: String, + assets_url: String, + upload_url: String, + tarball_url: String, + zipball_url: String, + id: i64, + node_id: String, + tag_name: String, + target_commitish: String, + name: String, + body: String, + draft: bool, + prerelease: bool, + created_at: String, + published_at: String, + author: GithubUser, + assets: Vec +} + +#[derive(serde::Deserialize, Debug)] +struct GithubUser { + login: String, + id: i64, + node_id: String, + avatar_url: String, + gravatar_id: String, + url: String, + html_url: String, + followers_url: String, + following_url: String, + gists_url: String, + starred_url: String, + subscriptions_url: String, + organizations_url: String, + repos_url: String, + events_url: String, + received_events_url: String, + r#type: String, + site_admin: bool +} + +#[derive(serde::Deserialize, Debug)] +struct GithubReleaseAsset { + url: String, + browser_download_url: String, + id: i64, + node_id: String, + name: String, + label: String, + state: String, + content_type: String, + size: i64, + download_count: i64, + created_at: String, + updated_at: String, + uploader: GithubUser +} + +fn main() { + println!("[*] Fetching nebula releaseinfo..."); + + let mut headers = HeaderMap::new(); + let mut has_api_key = false; + + if let Ok(api_key) = std::env::var("GH_API_KEY") { + headers.insert("Authorization", format!("Bearer {}", api_key).parse().unwrap()); + has_api_key = true; + } + + let client = reqwest::blocking::Client::builder().user_agent("curl/7.57.1").default_headers(headers).build().unwrap(); + + let resp: Response = client.get("https://api.github.com/repos/slackhq/nebula/releases/latest").send().unwrap(); + + if resp.headers().get("X-Ratelimit-Remaining").unwrap().to_str().unwrap() == "0" { + println!("You've been ratelimited from the GitHub API. Wait a while (1 hour)"); + if !has_api_key { + println!("You can also set a GitHub API key with the environment variable GH_API_KEY, which will increase your ratelimit ( a lot )"); + } + panic!("Ratelimited"); + } + + + let release: GithubRelease = resp.json().unwrap(); + + println!("[*] Fetching target triplet..."); + let target = std::env::var("TARGET").unwrap(); + + println!("[*] Compiling for target {}", target); + + let target_file = match target.as_str() { + "x86_64-apple-darwin" | "aarch64-apple-darwin" => "nebula-darwin", + "x86_64-unknown-freebsd" => "nebula-freebsd-amd64", + "x86_64-unknown-linux-gnu" => "nebula-linux-amd64", + "armv5te-unknown-linux-gnueabi" => "nebula-linux-arm-5", + "arm-unknown-linux-gnueabi" | "arm-unknown-linux-gnueabihf" => "nebula-linux-arm-6", + "armv7-unknown-linux-gnueabihf" | "armv7-unknown-linux-gnueabi" => "nebula-linux-arm-7", + "aarch64-unknown-linux-gnu" => "nebula-linux-arm64", + "x86_64-pc-windows-msvc" => "nebula-windows-amd64", + "aarch64-pc-windows-msvc" => "nebula-windows-arm64", + _ => { + println!("This architecture is not supported yet :("); + println!("Nebula has a limited set of architectures it is able to function on."); + println!("tfclient can only be compiled on these architectures."); + println!("See https://github.com/slackhq/nebula/releases for a list of supported architectures"); + println!("Is your system supported by Nebula? Shoot a message to the mailing list. Include the target triplet (above) in your response, as well as a link to the functioning Nebula binary. We will happily add your machine to the list!"); + panic!("Unsupported architecture"); + } + }; + + println!("[*] Embedding {} {}", target_file, release.name); + + let download = release.assets.iter().find(|r| r.name == format!("{}.tar.gz", target_file)).expect("That architecture isn't avaliable :("); + + println!("[*] Downloading {}.tar.gz ({}, {} bytes) from {}", target_file, target, download.size, download.browser_download_url); + + let response = reqwest::blocking::get(&download.browser_download_url).unwrap(); + let content = response.bytes().unwrap().to_vec(); + let bytes = content.as_slice(); + + let tar = GzDecoder::new(bytes); + let mut archive = Archive::new(tar); + let entries = archive.entries().unwrap(); + + let mut nebula_bin = vec![]; + let mut nebula_cert_bin = vec![]; + let mut shasum = vec![]; + + for entry in entries { + let mut entry = entry.unwrap(); + if entry.path().unwrap() == Path::new("nebula") || entry.path().unwrap() == Path::new("nebula.exe") { + nebula_bin.reserve(entry.size() as usize); + entry.read_to_end(&mut nebula_bin).unwrap(); + } else if entry.path().unwrap() == Path::new("nebula-cert") || entry.path().unwrap() == Path::new("nebula-cert.exe") { + nebula_cert_bin.reserve(entry.size() as usize); + entry.read_to_end(&mut nebula_cert_bin).unwrap(); + } else if entry.path().unwrap() == Path::new("SHASUM256.txt") { + shasum.reserve(entry.size() as usize); + entry.read_to_end(&mut shasum).unwrap(); + } + } + + if nebula_bin.is_empty() { + panic!("[x] Release did not contain nebula binary"); + } + if nebula_cert_bin.is_empty() { + panic!("[x] Release did not contain nebula_cert binary"); + } + + let mut nebula_file = File::create(format!("{}/nebula.bin", std::env::var("OUT_DIR").unwrap())).unwrap(); + nebula_file.write_all(&nebula_bin).unwrap(); + + codegen_version(&nebula_bin, "nebula.bin", "NEBULA"); + + let mut nebula_cert_file = File::create(format!("{}/nebula_cert.bin", std::env::var("OUT_DIR").unwrap())).unwrap(); + nebula_cert_file.write_all(&nebula_cert_bin).unwrap(); + + codegen_version(&nebula_cert_bin, "nebula_cert.bin", "NEBULA_CERT"); + + // get version info and codegen + + println!("cargo:rerun-if-changed=build.rs"); +} + +fn codegen_version(bin: &[u8], fp: &str, name: &str) { + // get version + let output = execim(bin, &vec!["-version"]); + let stdout = output.stdout; + let stdout_str = String::from_utf8(stdout).unwrap(); + if !stdout_str.starts_with("Version: ") { + panic!("Binary did not have expected version output. Unable to get version info."); + } + let mut version = stdout_str.split(' ').collect::>()[1].to_string(); + version.pop(); + + let code = format!("// This code was automatically @generated by build.rs. It should not be modified.\npub const {}_BIN: &[u8] = include_bytes!(concat!(env!(\"OUT_DIR\"), \"/{}\"));\npub const {}_VERSION: &str = \"{}\";", name, fp, name, version); + + let mut file = File::create(format!("{}/{}.rs", std::env::var("OUT_DIR").unwrap(), fp)).unwrap(); + file.write_all(code.as_bytes()).unwrap(); +} + +#[cfg(not(unix))] +fn execim(buf: &[u8], args: &Vec<&str>) -> Output { + let mut file = File::create("tmpexec.bin").unwrap(); + file.write_all(buf).unwrap(); + std::mem::drop(file); + let output = Command::new("./tmpexec.bin").args(args).output().unwrap(); + remove_file("./tmpexec.bin").unwrap(); + output +} + +#[cfg(unix)] +fn execim(buf: &[u8], args: &Vec<&str>) -> Output { + let mut file = File::create("tmpexec.bin").unwrap(); + file.write_all(buf).unwrap(); + let metadata = file.metadata().unwrap(); + let mut permissions = metadata.permissions(); + permissions.set_mode(0o0755); + fs::set_permissions("./tmpexec.bin", permissions).unwrap(); + std::mem::drop(file); + let output = Command::new("./tmpexec.bin").args(args).output().unwrap(); + remove_file("./tmpexec.bin").unwrap(); + output +} \ No newline at end of file diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index 82c783f..f420b51 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -14,6 +14,36 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -fn main() { - println!("Hello, world!"); +pub mod nebula_bin { + include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); } +pub mod nebula_cert_bin { + include!(concat!(env!("OUT_DIR"), "/nebula_cert.bin.rs")); +} + +use clap::{Parser, ArgAction}; + +#[derive(Parser)] +#[command(author = "c0repwn3r", version, about, long_about = None)] +#[clap(disable_version_flag = true)] +struct Cli { + #[arg(short = 'v', long = "version", action = ArgAction::SetTrue)] + #[clap(global = true)] + version: bool + + +} + +fn main() { + let args = Cli::parse(); + + if args.version { + print_version(); + } +} + +fn print_version() { + println!("tfclient v{}", env!("CARGO_PKG_VERSION")); + println!("embedded nebula v{}", crate::nebula_bin::NEBULA_VERSION); + println!("embedded nebula-cert v{}", crate::nebula_cert_bin::NEBULA_CERT_VERSION); +} \ No newline at end of file From a7a644c80d0ac8c96dcb12ee0b93526b92981946 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 20 Mar 2023 11:24:49 -0400 Subject: [PATCH 02/46] [wip] work on tfclient build system (cleanup) --- tfclient/build.rs | 51 +++-------------------------------------------- 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/tfclient/build.rs b/tfclient/build.rs index 27fbdfe..beeb87d 100644 --- a/tfclient/build.rs +++ b/tfclient/build.rs @@ -88,67 +88,22 @@ use flate2::read::GzDecoder; use reqwest::blocking::Response; use reqwest::header::HeaderMap; use tar::Archive; -use tempfile::{NamedTempFile, tempfile}; + #[derive(serde::Deserialize, Debug)] struct GithubRelease { - url: String, - html_url: String, - assets_url: String, - upload_url: String, - tarball_url: String, - zipball_url: String, - id: i64, - node_id: String, - tag_name: String, - target_commitish: String, name: String, - body: String, - draft: bool, - prerelease: bool, - created_at: String, - published_at: String, - author: GithubUser, assets: Vec } #[derive(serde::Deserialize, Debug)] -struct GithubUser { - login: String, - id: i64, - node_id: String, - avatar_url: String, - gravatar_id: String, - url: String, - html_url: String, - followers_url: String, - following_url: String, - gists_url: String, - starred_url: String, - subscriptions_url: String, - organizations_url: String, - repos_url: String, - events_url: String, - received_events_url: String, - r#type: String, - site_admin: bool -} +struct GithubUser {} #[derive(serde::Deserialize, Debug)] struct GithubReleaseAsset { - url: String, browser_download_url: String, - id: i64, - node_id: String, name: String, - label: String, - state: String, - content_type: String, - size: i64, - download_count: i64, - created_at: String, - updated_at: String, - uploader: GithubUser + size: i64 } fn main() { From a4fda57da8dc9df5092909538613fe5e849cbc9e Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 20 Mar 2023 11:34:46 -0400 Subject: [PATCH 03/46] finish version command, add version to trifid-pki and bump trifid-pki --- Cargo.lock | 10 +---- tfclient/Cargo.toml | 2 +- tfclient/build.rs | 89 ++++--------------------------------------- tfclient/src/main.rs | 4 +- trifid-pki/Cargo.toml | 2 +- trifid-pki/src/lib.rs | 5 ++- 6 files changed, 16 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2781e43..eee7009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -934,12 +934,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-literal" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" - [[package]] name = "hkdf" version = "0.12.3" @@ -2357,12 +2351,12 @@ dependencies = [ "clap", "flate2", "hex", - "hex-literal", "reqwest", "serde", "sha2", "tar", "tempfile", + "trifid-pki", ] [[package]] @@ -2671,7 +2665,7 @@ dependencies = [ [[package]] name = "trifid-pki" -version = "0.1.4" +version = "0.1.5" dependencies = [ "ed25519-dalek", "hex", diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index be9c617..3291b5f 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -8,7 +8,7 @@ description = "An open-source reimplementation of a Defined Networking-compatibl [dependencies] clap = { version = "4.1.10", features = ["derive"] } -hex-literal = "0.3.4" +trifid-pki = { version = "0.1.5", path = "../trifid-pki" } [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/build.rs b/tfclient/build.rs index beeb87d..b1138e6 100644 --- a/tfclient/build.rs +++ b/tfclient/build.rs @@ -1,83 +1,3 @@ -/* -[ - { - "url": "https://api.github.com/repos/octocat/Hello-World/releases/1", - "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", - "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", - "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", - "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0", - "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0", - "id": 1, - "node_id": "MDc6UmVsZWFzZTE=", - "tag_name": "v1.0.0", - "target_commitish": "master", - "name": "v1.0.0", - "body": "Description of the release", - "draft": false, - "prerelease": false, - "created_at": "2013-02-27T19:35:32Z", - "published_at": "2013-02-27T19:35:32Z", - "author": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "assets": [ - { - "url": "https://api.github.com/repos/octocat/Hello-World/releases/assets/1", - "browser_download_url": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip", - "id": 1, - "node_id": "MDEyOlJlbGVhc2VBc3NldDE=", - "name": "example.zip", - "label": "short description", - "state": "uploaded", - "content_type": "application/zip", - "size": 1024, - "download_count": 42, - "created_at": "2013-02-27T19:35:32Z", - "updated_at": "2013-02-27T19:35:32Z", - "uploader": { - "login": "octocat", - "id": 1, - "node_id": "MDQ6VXNlcjE=", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - } - } - ] - } -] - */ - - use std::fs; use std::fs::{File, remove_file}; use std::io::{Read, Write}; @@ -107,6 +27,11 @@ struct GithubReleaseAsset { } fn main() { + if Path::new(&format!("{}/{}", std::env::var("OUT_DIR").unwrap(), "noredownload")).exists() && std::env::var("TFBUILD_FORCE_REDOWNLOAD").is_err() { + println!("noredownload exists and TFBUILD_FORCE_REDOWNLOAD is not set. Not redoing build process."); + return; + } + println!("[*] Fetching nebula releaseinfo..."); let mut headers = HeaderMap::new(); @@ -206,8 +131,8 @@ fn main() { codegen_version(&nebula_cert_bin, "nebula_cert.bin", "NEBULA_CERT"); - // get version info and codegen - + // Indicate to cargo and ourselves that we have already downloaded and codegenned + File::create(format!("{}/{}", std::env::var("OUT_DIR").unwrap(), "noredownload")).unwrap(); println!("cargo:rerun-if-changed=build.rs"); } diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index f420b51..d4d2582 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -43,7 +43,5 @@ fn main() { } fn print_version() { - println!("tfclient v{}", env!("CARGO_PKG_VERSION")); - println!("embedded nebula v{}", crate::nebula_bin::NEBULA_VERSION); - println!("embedded nebula-cert v{}", crate::nebula_cert_bin::NEBULA_CERT_VERSION); + println!("tfclient v{} linked to trifid-pki v{}, embedding nebula v{} and nebula-cert v{}", env!("CARGO_PKG_VERSION"), trifid_pki::TRIFID_PKI_VERSION, crate::nebula_bin::NEBULA_VERSION, crate::nebula_cert_bin::NEBULA_CERT_VERSION); } \ No newline at end of file diff --git a/trifid-pki/Cargo.toml b/trifid-pki/Cargo.toml index ce4daba..a6fdb94 100644 --- a/trifid-pki/Cargo.toml +++ b/trifid-pki/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trifid-pki" -version = "0.1.4" +version = "0.1.5" edition = "2021" description = "A rust implementation of the Nebula PKI system" license = "AGPL-3.0-or-later" diff --git a/trifid-pki/src/lib.rs b/trifid-pki/src/lib.rs index 7323013..4c67fe0 100644 --- a/trifid-pki/src/lib.rs +++ b/trifid-pki/src/lib.rs @@ -57,4 +57,7 @@ pub mod cert; pub(crate) mod cert_codec; #[cfg(test)] #[macro_use] -pub mod test; \ No newline at end of file +pub mod test; + +/// Get the compiled version of trifid-pki. +pub const TRIFID_PKI_VERSION: &str = env!("CARGO_PKG_VERSION"); \ No newline at end of file From 2d7626317d6d91fdb8c1c7502dacc6a65a715ee3 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 20 Mar 2023 13:36:15 -0400 Subject: [PATCH 04/46] cli to run nebula and nebula-cert, and remove cache dirs --- Cargo.lock | 62 ++++++++++++++++- tfclient/Cargo.toml | 5 ++ tfclient/src/dirs.rs | 5 ++ tfclient/src/embedded_nebula.rs | 104 ++++++++++++++++++++++++++++ tfclient/src/main.rs | 119 +++++++++++++++++++++++++++++++- tfclient/src/util.rs | 9 +++ 6 files changed, 300 insertions(+), 4 deletions(-) create mode 100644 tfclient/src/dirs.rs create mode 100644 tfclient/src/embedded_nebula.rs create mode 100644 tfclient/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index eee7009..43ba615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "const-oid" version = "0.9.1" @@ -554,7 +565,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" +dependencies = [ + "dirs-sys 0.4.0", ] [[package]] @@ -568,6 +588,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" +dependencies = [ + "libc", + "redox_users", + "windows-sys 0.45.0", +] + [[package]] name = "dotenvy" version = "0.15.6" @@ -1393,6 +1424,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.17.0" @@ -2094,6 +2134,19 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +[[package]] +name = "simple_logger" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78beb34673091ccf96a8816fce8bfd30d1292c7621ca2bcb5f2ba0fae4f558d" +dependencies = [ + "atty", + "colored", + "log", + "time 0.3.17", + "windows-sys 0.42.0", +] + [[package]] name = "slab" version = "0.4.7" @@ -2170,7 +2223,7 @@ dependencies = [ "bytes", "crc", "crossbeam-queue", - "dirs", + "dirs 4.0.0", "dotenvy", "either", "event-listener", @@ -2349,11 +2402,14 @@ name = "tfclient" version = "0.1.0" dependencies = [ "clap", + "dirs 5.0.0", "flate2", "hex", + "log", "reqwest", "serde", "sha2", + "simple_logger", "tar", "tempfile", "trifid-pki", @@ -2406,6 +2462,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", + "libc", + "num_threads", "serde", "time-core", "time-macros", diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 3291b5f..4976a67 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -9,6 +9,11 @@ description = "An open-source reimplementation of a Defined Networking-compatibl [dependencies] clap = { version = "4.1.10", features = ["derive"] } trifid-pki = { version = "0.1.5", path = "../trifid-pki" } +dirs = "5.0.0" +log = "0.4.17" +simple_logger = "4.1.0" +sha2 = "0.10.6" +hex = "0.4.3" [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/dirs.rs b/tfclient/src/dirs.rs new file mode 100644 index 0000000..038984a --- /dev/null +++ b/tfclient/src/dirs.rs @@ -0,0 +1,5 @@ +use std::path::PathBuf; + +pub fn get_data_dir() -> Option { + dirs::data_dir().map(|f| f.join("tfclient/")) +} \ No newline at end of file diff --git a/tfclient/src/embedded_nebula.rs b/tfclient/src/embedded_nebula.rs new file mode 100644 index 0000000..adaaed2 --- /dev/null +++ b/tfclient/src/embedded_nebula.rs @@ -0,0 +1,104 @@ +use std::error::Error; +use std::fs; +use std::fs::File; +use std::io::Write; +use std::os::unix::fs::PermissionsExt; +use std::path::PathBuf; +use std::process::{Child, Command, Output}; +use log::debug; +use crate::dirs::get_data_dir; +use crate::util::sha256; + +pub fn extract_embedded_nebula() -> Result> { + let data_dir = get_data_dir().ok_or("Unable to get platform-specific data dir")?; + if !data_dir.exists() { + fs::create_dir_all(&data_dir)?; + debug!("Created data directory {}", data_dir.as_path().display()); + } + + let bin_dir = data_dir.join("cache/"); + let hash_dir = bin_dir.join(format!("{}/", sha256(crate::nebula_bin::NEBULA_BIN))); + + if !hash_dir.exists() { + fs::create_dir_all(&hash_dir)?; + debug!("Created directory {}", hash_dir.as_path().display()); + } + + let executable_postfix = if cfg!(windows) { ".exe" } else { "" }; + let executable_name = format!("nebula-{}{}", crate::nebula_bin::NEBULA_VERSION, executable_postfix); + + let file_path = hash_dir.join(executable_name); + + if file_path.exists() { + // Already extracted + return Ok(file_path); + } + let mut file = File::create(&file_path)?; + file.write_all(crate::nebula_bin::NEBULA_BIN)?; + + debug!("Extracted nebula to {}", file_path.as_path().display()); + + Ok(file_path) +} + +pub fn extract_embedded_nebula_cert() -> Result> { + let data_dir = get_data_dir().ok_or("Unable to get platform-specific data dir")?; + if !data_dir.exists() { + fs::create_dir_all(&data_dir)?; + debug!("Created data directory {}", data_dir.as_path().display()); + } + + let bin_dir = data_dir.join("cache/"); + let hash_dir = bin_dir.join(format!("{}/", sha256(crate::nebula_cert_bin::NEBULA_CERT_BIN))); + + if !hash_dir.exists() { + fs::create_dir_all(&hash_dir)?; + debug!("Created directory {}", hash_dir.as_path().display()); + } + + let executable_postfix = if cfg!(windows) { ".exe" } else { "" }; + let executable_name = format!("nebula-cert-{}{}", crate::nebula_cert_bin::NEBULA_CERT_VERSION, executable_postfix); + + let file_path = hash_dir.join(executable_name); + + if file_path.exists() { + // Already extracted + return Ok(file_path); + } + + let mut file = File::create(&file_path)?; + file.write_all(crate::nebula_cert_bin::NEBULA_CERT_BIN)?; + + debug!("Extracted nebula-cert to {}", file_path.as_path().display()); + + Ok(file_path) +} + +#[cfg(unix)] +pub fn _setup_permissions(path: &PathBuf) -> Result<(), Box> { + let meta = path.metadata()?; + let mut perms = meta.permissions(); + perms.set_mode(0o0755); + debug!("Setting permissions on {} to 755", path.as_path().display()); + fs::set_permissions(path, perms)?; + Ok(()) +} + +#[cfg(windows)] +pub fn _setup_permissions() -> Result<(), Box> { + Ok(()) +} + +pub fn run_embedded_nebula(args: &[String]) -> Result> { + let path = extract_embedded_nebula()?; + debug!("Running {} with args {:?}", path.as_path().display(), args); + _setup_permissions(&path)?; + Ok(Command::new(path).args(args).spawn()?) +} + +pub fn run_embedded_nebula_cert(args: &[String]) -> Result> { + let path = extract_embedded_nebula_cert()?; + debug!("Running {} with args {:?}", path.as_path().display(), args); + _setup_permissions(&path)?; + Ok(Command::new(path).args(args).spawn()?) +} \ No newline at end of file diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index d4d2582..8d07da2 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -14,6 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +pub mod embedded_nebula; +pub mod dirs; +pub mod util; + pub mod nebula_bin { include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); } @@ -21,7 +25,14 @@ pub mod nebula_cert_bin { include!(concat!(env!("OUT_DIR"), "/nebula_cert.bin.rs")); } -use clap::{Parser, ArgAction}; +use std::error::Error; +use std::fs; +use std::process::Child; +use clap::{Parser, ArgAction, Subcommand}; +use log::{error, info}; +use simple_logger::SimpleLogger; +use crate::dirs::get_data_dir; +use crate::embedded_nebula::{run_embedded_nebula, run_embedded_nebula_cert}; #[derive(Parser)] #[command(author = "c0repwn3r", version, about, long_about = None)] @@ -29,17 +40,121 @@ use clap::{Parser, ArgAction}; struct Cli { #[arg(short = 'v', long = "version", action = ArgAction::SetTrue)] #[clap(global = true)] - version: bool + version: bool, + #[command(subcommand)] + subcommand: Commands +} +#[derive(Subcommand)] +enum Commands { + /// Run the `nebula` binary. This is useful if you want to do debugging with tfclient's internal nebula. + RunNebula { + /// Arguments to pass to the `nebula` binary + #[clap(trailing_var_arg=true, allow_hyphen_values=true)] + args: Vec + }, + /// Run the `nebula-cert` binary. This is useful if you want to mess with certificates. Note: tfclient does not actually use nebula-cert for certificate operations, and instead uses trifid-pki internally + RunNebulaCert { + /// Arguments to pass to the `nebula-cert` binary + #[clap(trailing_var_arg=true, allow_hyphen_values=true)] + args: Vec + }, + /// Clear any cached data that tfclient may have added + ClearCache {} } fn main() { + SimpleLogger::new().init().unwrap(); + let args = Cli::parse(); if args.version { print_version(); } + + match args.subcommand { + Commands::RunNebula { args } => { + match run_embedded_nebula(&args) { + Ok(mut c) => { + match c.wait() { + Ok(stat) => { + match stat.code() { + Some(code) => { + if code != 0 { + error!("Nebula process exited with nonzero status code {}", code); + } + std::process::exit(code); + }, + None => { + info!("Nebula process terminated by signal"); + std::process::exit(0); + } + } + }, + Err(e) => { + error!("Unable to wait for child to exit: {}", e); + std::process::exit(1); + } + } + }, + Err(e) => { + error!("Unable to start nebula binary: {}", e); + std::process::exit(1); + } + } + }, + Commands::ClearCache { .. } => { + let data_dir = match get_data_dir() { + Some(dir) => dir, + None => { + error!("Unable to get platform-specific data dir"); + std::process::exit(1); + } + }; + match fs::remove_dir_all(&data_dir) { + Ok(_) => (), + Err(e) => { + error!("Unable to delete data dir: {}", e); + std::process::exit(0); + } + } + info!("Removed data dir {}", data_dir.as_path().display()); + + info!("Removed all cached data."); + std::process::exit(0); + }, + Commands::RunNebulaCert { args } => { + match run_embedded_nebula_cert(&args) { + Ok(mut c) => { + match c.wait() { + Ok(stat) => { + match stat.code() { + Some(code) => { + if code != 0 { + error!("nebula-cert process exited with nonzero status code {}", code); + } + std::process::exit(code); + }, + None => { + info!("nebula-cert process terminated by signal"); + std::process::exit(0); + } + } + }, + Err(e) => { + error!("Unable to wait for child to exit: {}", e); + std::process::exit(1); + } + } + }, + Err(e) => { + error!("Unable to start nebula-cert binary: {}", e); + std::process::exit(1); + } + } + } + } } fn print_version() { diff --git a/tfclient/src/util.rs b/tfclient/src/util.rs new file mode 100644 index 0000000..a87effb --- /dev/null +++ b/tfclient/src/util.rs @@ -0,0 +1,9 @@ +use sha2::Sha256; +use sha2::Digest; + +pub fn sha256(bytes: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.update(bytes); + let digest = hasher.finalize(); + hex::encode(digest) +} \ No newline at end of file From c2263099aaa7000e800432df479aa7b42b91e507 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 20 Mar 2023 13:38:43 -0400 Subject: [PATCH 05/46] cargo-fix --- tfclient/src/embedded_nebula.rs | 2 +- tfclient/src/main.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tfclient/src/embedded_nebula.rs b/tfclient/src/embedded_nebula.rs index adaaed2..76b6ffc 100644 --- a/tfclient/src/embedded_nebula.rs +++ b/tfclient/src/embedded_nebula.rs @@ -4,7 +4,7 @@ use std::fs::File; use std::io::Write; use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; -use std::process::{Child, Command, Output}; +use std::process::{Child, Command}; use log::debug; use crate::dirs::get_data_dir; use crate::util::sha256; diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index 8d07da2..1aac422 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -25,9 +25,9 @@ pub mod nebula_cert_bin { include!(concat!(env!("OUT_DIR"), "/nebula_cert.bin.rs")); } -use std::error::Error; + use std::fs; -use std::process::Child; + use clap::{Parser, ArgAction, Subcommand}; use log::{error, info}; use simple_logger::SimpleLogger; From 45ce30b8cb300977cdd716f9b46f34fec57e3e69 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 20 Mar 2023 13:53:58 -0400 Subject: [PATCH 06/46] add BUILDING.md documentation --- tfclient/BUILDING.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tfclient/BUILDING.md diff --git a/tfclient/BUILDING.md b/tfclient/BUILDING.md new file mode 100644 index 0000000..410347c --- /dev/null +++ b/tfclient/BUILDING.md @@ -0,0 +1,33 @@ +tfclient has a more involved build process than the rest of trifid due to the fact that it embeds the distribution nebula and nebula-cert binaries. + +## Clearing the Nebula binary cache + +Compiling tfclient may take longer than usual every once in a while because the build system downloads the latest version of nebula. +After this has been done once, the generated code will be cached in the target directory. If you want to redownload it (e.g. if Nebula is updated), you can: + +- run `cargo clean`, or +- delete `target/debug/build/tfclient-[HASH]/out/noredownload`, or +- set the environment variable `TFBUILD_FORCE_REDOWNLOAD=1` + +The 3rd option is the best, as it will not signifigantly increase compile times *or* need you to find the build hash. + +## Set a GitHub API key + +Since the build process involves making calls to the GitHub API, you may get ratelimited. +To provide a GitHub API token to increase your ratelimit, set `GH_API_KEY` to your GitHub API key. + +## Unsupported architecture :( + +If you're compiling on (or for) an architecture that tfclient currently does not support, you will get the following message: + +```text +[*] Compiling for target: SOME-TARGET-TRIPLET-HERE +This architecture is not supported yet :( +Nebula has a limited set of architectures it is able to function on. +tfclient can only be compiled on these architectures. +See https://github.com/slackhq/nebula/releases for a list of supported architectures. +Is your system supported by Nebula? Shoot a message to the mailing list. Include the target triplet (above) in your response, as well as a link to the functioning Nebula binary. We will happily add your machine to the list! +``` + +The `tfclient` build system attempts to check if your architecture is unsupported. It does this by checking your target triplet. +If this is a false alarm, and a build of nebula is available and fully functional on your machine, please shoot a message to the mailing list with your target triplet and a link to which build of nebula works on your machine. We will update the build script and update the repo! From 1daaf5466fafc324cc3947957ff013acfc9fef2d Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Tue, 21 Mar 2023 13:00:01 -0400 Subject: [PATCH 07/46] [wip] tfclient work (cli and service generators) --- Cargo.lock | 46 ++++++++--------- tfclient/Cargo.toml | 3 ++ tfclient/src/config.rs | 42 ++++++++++++++++ tfclient/src/daemon.rs | 32 ++++++++++++ tfclient/src/dirs.rs | 8 +++ tfclient/src/main.rs | 66 ++++++++++++++++++++++++- tfclient/src/nebulaworker.rs | 5 ++ tfclient/src/service/codegen/mod.rs | 9 ++++ tfclient/src/service/codegen/systemd.rs | 14 ++++++ tfclient/src/service/mod.rs | 12 +++++ tfclient/src/service/systemd.rs | 22 +++++++++ 11 files changed, 236 insertions(+), 23 deletions(-) create mode 100644 tfclient/src/config.rs create mode 100644 tfclient/src/daemon.rs create mode 100644 tfclient/src/nebulaworker.rs create mode 100644 tfclient/src/service/codegen/mod.rs create mode 100644 tfclient/src/service/codegen/systemd.rs create mode 100644 tfclient/src/service/mod.rs create mode 100644 tfclient/src/service/systemd.rs diff --git a/Cargo.lock b/Cargo.lock index 43ba615..9960b99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1365,15 +1365,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nom8" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" -dependencies = [ - "memchr", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2038,22 +2029,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.157" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707de5fcf5df2b5788fca98dd7eab490bc2fd9b7ef1404defc462833b83f25ca" +checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.157" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78997f4555c22a7971214540c4a661291970619afd56de19f77e0de86296e1e5" +checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.2", + "syn 2.0.4", ] [[package]] @@ -2343,9 +2334,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59d3276aee1fa0c33612917969b5172b5be2db051232a6e4826f1a1a9191b045" +checksum = "2c622ae390c9302e214c31013517c2061ecb2699935882c60a9b37f82f8625ae" dependencies = [ "proc-macro2", "quote", @@ -2412,7 +2403,9 @@ dependencies = [ "simple_logger", "tar", "tempfile", + "toml 0.7.3", "trifid-pki", + "url", ] [[package]] @@ -2576,9 +2569,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772c1426ab886e7362aedf4abc9c0d1348a979517efedfc25862944d10137af0" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" dependencies = [ "serde", "serde_spanned", @@ -2597,15 +2590,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.1" +version = "0.19.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90a238ee2e6ede22fb95350acc78e21dc40da00bb66c0334bde83de4ed89424e" +checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274" dependencies = [ "indexmap", - "nom8", "serde", "serde_spanned", "toml_datetime", + "winnow", ] [[package]] @@ -2713,7 +2706,7 @@ dependencies = [ "sha2", "sqlx", "tokio", - "toml 0.7.1", + "toml 0.7.3", "totp-rs", "trifid-pki", "url", @@ -3149,6 +3142,15 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "winnow" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 4976a67..968fc53 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -14,6 +14,9 @@ log = "0.4.17" simple_logger = "4.1.0" sha2 = "0.10.6" hex = "0.4.3" +url = "2.3.1" +toml = "0.7.3" +serde = { version = "1.0.158", features = ["derive"] } [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs new file mode 100644 index 0000000..d9d7597 --- /dev/null +++ b/tfclient/src/config.rs @@ -0,0 +1,42 @@ +use std::error::Error; +use std::fs; +use log::{debug, info}; +use serde::{Deserialize, Serialize}; +use crate::dirs::{get_config_dir, get_config_file}; + +pub const DEFAULT_PORT: u16 = 8157; +fn default_port() -> u16 { DEFAULT_PORT } + +#[derive(Serialize, Deserialize)] +pub struct TFClientConfig { + #[serde(default = "default_port")] + listen_port: u16 +} + +pub fn create_config(instance: &str) -> Result<(), Box> { + info!("Creating config directory..."); + fs::create_dir_all(get_config_dir(instance).ok_or("Unable to load config dir")?)?; + info!("Copying default config file to config directory..."); + let config = TFClientConfig { + listen_port: DEFAULT_PORT + }; + let config_str = toml::to_string(&config)?; + fs::write(get_config_file(instance).ok_or("Unable to load config dir")?, config_str)?; + Ok(()) +} + +pub fn load_config(instance: &str) -> Result> { + info!("Loading config..."); + let config_file = get_config_file(instance).ok_or("Unable to load config dir")?; + + if !config_file.exists() { + create_config(instance)?; + } + + debug!("opening {}", config_file.as_path().display()); + let config_str = fs::read_to_string(config_file)?; + debug!("parsing config file"); + let config: TFClientConfig = toml::from_str(&config_str)?; + info!("Loaded config successfully"); + Ok(config) +} \ No newline at end of file diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs new file mode 100644 index 0000000..e6d36e9 --- /dev/null +++ b/tfclient/src/daemon.rs @@ -0,0 +1,32 @@ +use log::{error, info, warn}; +use url::Url; +use crate::config::load_config; + +pub fn daemon_main(name: String, server: String) { + // Validate the `server` + info!("Checking server url..."); + let api_base = match Url::parse(&server) { + Ok(u) => u, + Err(e) => { + error!("Invalid server url `{}`: {}", server, e); + std::process::exit(1); + } + }; + match api_base.scheme() { + "http" => { warn!("HTTP api urls are not reccomended. Please switch to HTTPS if possible.") }, + "https" => (), + _ => { + error!("Unsupported protocol `{}` (expected one of http, https)", api_base.scheme()); + std::process::exit(1); + } + } + + info!("Loading config..."); + let config = match load_config(&name) { + Ok(cfg) => cfg, + Err(e) => { + error!("Error loading configuration: {}", e); + std::process::exit(1); + } + }; +} \ No newline at end of file diff --git a/tfclient/src/dirs.rs b/tfclient/src/dirs.rs index 038984a..24d85ad 100644 --- a/tfclient/src/dirs.rs +++ b/tfclient/src/dirs.rs @@ -2,4 +2,12 @@ use std::path::PathBuf; pub fn get_data_dir() -> Option { dirs::data_dir().map(|f| f.join("tfclient/")) +} + +pub fn get_config_dir(instance: &str) -> Option { + dirs::config_dir().map(|f| f.join("tfclient/").join(format!("{}/", instance))) +} + +pub fn get_config_file(instance: &str) -> Option { + get_config_dir(instance).map(|f| f.join("tfclient.toml")) } \ No newline at end of file diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index 1aac422..8c7f9f0 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -17,6 +17,10 @@ pub mod embedded_nebula; pub mod dirs; pub mod util; +pub mod nebulaworker; +pub mod daemon; +pub mod config; +pub mod service; pub mod nebula_bin { include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); @@ -40,6 +44,7 @@ use crate::embedded_nebula::{run_embedded_nebula, run_embedded_nebula_cert}; struct Cli { #[arg(short = 'v', long = "version", action = ArgAction::SetTrue)] #[clap(global = true)] + /// Print the tfclient version, as well as the trifid-pki version and the version of the embedded nebula and nebula-cert binaries version: bool, #[command(subcommand)] @@ -61,7 +66,58 @@ enum Commands { args: Vec }, /// Clear any cached data that tfclient may have added - ClearCache {} + ClearCache {}, + + /// Install the tfclient system service + Install { + #[clap(short, long, default_value = "tfclient")] + /// Optional service name used to run multiple tfclient instances. Specify the same name on all other cli sub-commands (start, uninstall, etc.) to refer to this installed instance + name: String, + #[clap(short, long)] + /// Server to use for API calls. + server: String + }, + + /// Uninstall the tfclient system service + Uninstall { + #[clap(short, long, default_value = "tfclient")] + /// Service name specified on install + name: String + }, + + /// Start the tfclient system service + Start { + #[clap(short, long, default_value = "tfclient")] + /// Service name specified on install + name: String + }, + + /// Stop the tfclient system service + Stop { + #[clap(short, long, default_value = "tfclient")] + /// Service name specified on start + name: String + }, + + /// Run the tfclient daemon in the foreground + Run { + #[clap(short, long, default_value = "tfclient")] + /// Service name specified on install + name: String, + #[clap(short, long)] + /// Server to use for API calls. + server: String + }, + + /// Enroll this host using a trifid-api enrollment code + Enroll { + #[clap(short, long, default_value = "tfclient")] + /// Service name specified on install + name: String, + #[clap(short, long)] + /// Enrollment code used to enroll this node + code: String, + } } fn main() { @@ -154,6 +210,14 @@ fn main() { } } } + Commands::Install { .. } => {} + Commands::Uninstall { .. } => {} + Commands::Start { .. } => {} + Commands::Stop { .. } => {} + Commands::Run { name, server } => { + daemon::daemon_main(name, server); + } + Commands::Enroll { .. } => {} } } diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs new file mode 100644 index 0000000..0882bf9 --- /dev/null +++ b/tfclient/src/nebulaworker.rs @@ -0,0 +1,5 @@ +// Code to handle the command socket worker + +pub fn socketworker_main() { + +} \ No newline at end of file diff --git a/tfclient/src/service/codegen/mod.rs b/tfclient/src/service/codegen/mod.rs new file mode 100644 index 0000000..547ff34 --- /dev/null +++ b/tfclient/src/service/codegen/mod.rs @@ -0,0 +1,9 @@ +pub mod systemd; + +use std::error::Error; +use std::path::PathBuf; + +pub trait ServiceFileGenerator { + fn create_service_files(bin_path: PathBuf, name: &str) -> Result<(), Box>; + fn delete_service_files(name: &str) -> Result<(), Box>; +} \ No newline at end of file diff --git a/tfclient/src/service/codegen/systemd.rs b/tfclient/src/service/codegen/systemd.rs new file mode 100644 index 0000000..0820264 --- /dev/null +++ b/tfclient/src/service/codegen/systemd.rs @@ -0,0 +1,14 @@ +use std::error::Error; +use std::path::PathBuf; +use crate::service::codegen::ServiceFileGenerator; + +pub struct SystemDServiceFileGenerator {} +impl ServiceFileGenerator for SystemDServiceFileGenerator { + fn create_service_files(bin_path: PathBuf, name: &str) -> Result<(), Box> { + todo!() + } + + fn delete_service_files(name: &str) -> Result<(), Box> { + todo!() + } +} \ No newline at end of file diff --git a/tfclient/src/service/mod.rs b/tfclient/src/service/mod.rs new file mode 100644 index 0000000..c5ff059 --- /dev/null +++ b/tfclient/src/service/mod.rs @@ -0,0 +1,12 @@ +use std::error::Error; +use std::path::PathBuf; + +pub mod codegen; +pub mod systemd; + +pub trait ServiceManager { + fn install(bin_path: PathBuf, name: &str) -> Result<(), Box>; + fn uninstall(name: &str) -> Result<(), Box>; + fn start(name: &str) -> Result<(), Box>; + fn stop(name: &str) -> Result<(), Box>; +} \ No newline at end of file diff --git a/tfclient/src/service/systemd.rs b/tfclient/src/service/systemd.rs new file mode 100644 index 0000000..42bb249 --- /dev/null +++ b/tfclient/src/service/systemd.rs @@ -0,0 +1,22 @@ +use std::error::Error; +use std::path::PathBuf; +use crate::service::ServiceManager; + +pub struct SystemDServiceManager {} +impl ServiceManager for SystemDServiceManager { + fn install(bin_path: PathBuf, name: &str) -> Result<(), Box> { + todo!() + } + + fn uninstall(name: &str) -> Result<(), Box> { + todo!() + } + + fn start(name: &str) -> Result<(), Box> { + todo!() + } + + fn stop(name: &str) -> Result<(), Box> { + todo!() + } +} \ No newline at end of file From 6c1b8f090f6146e614bfeaf874b1b047c8173846 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 22 Mar 2023 14:34:06 -0400 Subject: [PATCH 08/46] [wip] thread workers --- Cargo.lock | 5 +- tfclient/Cargo.toml | 1 + tfclient/src/apiworker.rs | 11 +++ tfclient/src/config.rs | 2 +- tfclient/src/daemon.rs | 90 ++++++++++++++++++++----- tfclient/src/main.rs | 19 ++++-- tfclient/src/nebulaworker.rs | 12 +++- tfclient/src/service/codegen/mod.rs | 2 +- tfclient/src/service/codegen/systemd.rs | 40 ++++++++++- tfclient/src/service/detect.rs | 29 ++++++++ tfclient/src/service/entry.rs | 82 ++++++++++++++++++++++ tfclient/src/service/macos.rs | 22 ++++++ tfclient/src/service/mod.rs | 13 ++-- tfclient/src/service/runit.rs | 22 ++++++ tfclient/src/service/systemd.rs | 80 +++++++++++++++++++--- tfclient/src/service/windows.rs | 22 ++++++ tfclient/src/socketworker.rs | 13 ++++ tfclient/src/util.rs | 20 ++++++ 18 files changed, 444 insertions(+), 41 deletions(-) create mode 100644 tfclient/src/apiworker.rs create mode 100644 tfclient/src/service/detect.rs create mode 100644 tfclient/src/service/entry.rs create mode 100644 tfclient/src/service/macos.rs create mode 100644 tfclient/src/service/runit.rs create mode 100644 tfclient/src/service/windows.rs create mode 100644 tfclient/src/socketworker.rs diff --git a/Cargo.lock b/Cargo.lock index 9960b99..8419086 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2049,9 +2049,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", @@ -2399,6 +2399,7 @@ dependencies = [ "log", "reqwest", "serde", + "serde_json", "sha2", "simple_logger", "tar", diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 968fc53..499e6ee 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -17,6 +17,7 @@ hex = "0.4.3" url = "2.3.1" toml = "0.7.3" serde = { version = "1.0.158", features = ["derive"] } +serde_json = "1.0.94" [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs new file mode 100644 index 0000000..8948166 --- /dev/null +++ b/tfclient/src/apiworker.rs @@ -0,0 +1,11 @@ +use std::sync::mpsc::Receiver; +use crate::config::TFClientConfig; +use crate::daemon::ThreadMessageSender; + +pub enum APIWorkerMessage { + +} + +pub fn apiworker_main(config: TFClientConfig, transmitters: ThreadMessageSender, rx: Receiver) { + +} \ No newline at end of file diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index d9d7597..5f82572 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -7,7 +7,7 @@ use crate::dirs::{get_config_dir, get_config_file}; pub const DEFAULT_PORT: u16 = 8157; fn default_port() -> u16 { DEFAULT_PORT } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct TFClientConfig { #[serde(default = "default_port")] listen_port: u16 diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index e6d36e9..8918fab 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -1,25 +1,17 @@ +use std::sync::mpsc; +use std::sync::mpsc::Sender; +use std::thread; use log::{error, info, warn}; use url::Url; +use crate::apiworker::{apiworker_main, APIWorkerMessage}; use crate::config::load_config; +use crate::nebulaworker::{nebulaworker_main, NebulaWorkerMessage}; +use crate::socketworker::{socketworker_main, SocketWorkerMessage}; +use crate::util::check_server_url; pub fn daemon_main(name: String, server: String) { // Validate the `server` - info!("Checking server url..."); - let api_base = match Url::parse(&server) { - Ok(u) => u, - Err(e) => { - error!("Invalid server url `{}`: {}", server, e); - std::process::exit(1); - } - }; - match api_base.scheme() { - "http" => { warn!("HTTP api urls are not reccomended. Please switch to HTTPS if possible.") }, - "https" => (), - _ => { - error!("Unsupported protocol `{}` (expected one of http, https)", api_base.scheme()); - std::process::exit(1); - } - } + check_server_url(&server); info!("Loading config..."); let config = match load_config(&name) { @@ -29,4 +21,70 @@ pub fn daemon_main(name: String, server: String) { std::process::exit(1); } }; + + info!("Starting API thread..."); + + let (tx_api, rx_api) = mpsc::channel::(); + let (tx_socket, rx_socket) = mpsc::channel::(); + let (tx_nebula, rx_nebula) = mpsc::channel::(); + + let transmitter = ThreadMessageSender { + socket_thread: tx_socket, + api_thread: tx_api, + nebula_thread: tx_nebula + }; + + let config_api = config.clone(); + let transmitter_api = transmitter.clone(); + let api_thread = thread::spawn(move || { + apiworker_main(config_api, transmitter_api, rx_api); + }); + + info!("Starting Nebula thread..."); + let config_nebula = config.clone(); + let transmitter_nebula = transmitter.clone(); + let nebula_thread = thread::spawn(move || { + nebulaworker_main(config_nebula, transmitter_nebula, rx_nebula); + }); + + info!("Starting socket worker thread..."); + let socket_thread = thread::spawn(move || { + socketworker_main(config, transmitter, rx_socket); + }); + + info!("Waiting for socket thread to exit..."); + match socket_thread.join() { + Ok(_) => (), + Err(_) => { + error!("Error waiting for socket thread to exit."); + std::process::exit(1); + } + } + + info!("Waiting for API thread to exit..."); + match api_thread.join() { + Ok(_) => (), + Err(_) => { + error!("Error waiting for api thread to exit."); + std::process::exit(1); + } + } + + info!("Waiting for Nebula thread to exit..."); + match nebula_thread.join() { + Ok(_) => (), + Err(_) => { + error!("Error waiting for nebula thread to exit."); + std::process::exit(1); + } + } + + info!("All threads exited"); +} + +#[derive(Clone)] +pub struct ThreadMessageSender { + socket_thread: Sender, + api_thread: Sender, + nebula_thread: Sender } \ No newline at end of file diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index 8c7f9f0..49f95b8 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -21,6 +21,8 @@ pub mod nebulaworker; pub mod daemon; pub mod config; pub mod service; +pub mod apiworker; +pub mod socketworker; pub mod nebula_bin { include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); @@ -37,6 +39,7 @@ use log::{error, info}; use simple_logger::SimpleLogger; use crate::dirs::get_data_dir; use crate::embedded_nebula::{run_embedded_nebula, run_embedded_nebula_cert}; +use crate::service::entry::{cli_install, cli_start, cli_stop, cli_uninstall}; #[derive(Parser)] #[command(author = "c0repwn3r", version, about, long_about = None)] @@ -210,10 +213,18 @@ fn main() { } } } - Commands::Install { .. } => {} - Commands::Uninstall { .. } => {} - Commands::Start { .. } => {} - Commands::Stop { .. } => {} + Commands::Install { server, name } => { + cli_install(&name, &server); + } + Commands::Uninstall { name } => { + cli_uninstall(&name); + } + Commands::Start { name } => { + cli_start(&name); + } + Commands::Stop { name } => { + cli_stop(&name); + } Commands::Run { name, server } => { daemon::daemon_main(name, server); } diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index 0882bf9..80a0551 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -1,5 +1,13 @@ -// Code to handle the command socket worker +// Code to handle the nebula worker -pub fn socketworker_main() { +use std::sync::mpsc::Receiver; +use crate::config::TFClientConfig; +use crate::daemon::ThreadMessageSender; + +pub enum NebulaWorkerMessage { + +} + +pub fn nebulaworker_main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) { } \ No newline at end of file diff --git a/tfclient/src/service/codegen/mod.rs b/tfclient/src/service/codegen/mod.rs index 547ff34..a570ad9 100644 --- a/tfclient/src/service/codegen/mod.rs +++ b/tfclient/src/service/codegen/mod.rs @@ -4,6 +4,6 @@ use std::error::Error; use std::path::PathBuf; pub trait ServiceFileGenerator { - fn create_service_files(bin_path: PathBuf, name: &str) -> Result<(), Box>; + fn create_service_files(bin_path: PathBuf, name: &str, server: &str) -> Result<(), Box>; fn delete_service_files(name: &str) -> Result<(), Box>; } \ No newline at end of file diff --git a/tfclient/src/service/codegen/systemd.rs b/tfclient/src/service/codegen/systemd.rs index 0820264..b05db5d 100644 --- a/tfclient/src/service/codegen/systemd.rs +++ b/tfclient/src/service/codegen/systemd.rs @@ -1,14 +1,48 @@ use std::error::Error; use std::path::PathBuf; +use log::debug; use crate::service::codegen::ServiceFileGenerator; +use std::fmt::Write; +use std::fs; pub struct SystemDServiceFileGenerator {} impl ServiceFileGenerator for SystemDServiceFileGenerator { - fn create_service_files(bin_path: PathBuf, name: &str) -> Result<(), Box> { - todo!() + fn create_service_files(bin_path: PathBuf, name: &str, server: &str) -> Result<(), Box> { + debug!("Generating a unit file..."); + + let mut unit_file = String::new(); + writeln!(unit_file, "[Unit]")?; + writeln!(unit_file, "Description=A client for Defined Networking compatible overlay networks (instance {})", name)?; + writeln!(unit_file, "Wants=basic.target network-online.target")?; + writeln!(unit_file, "After=basic.target network.target network-online.target")?; + writeln!(unit_file)?; + writeln!(unit_file, "[Service]")?; + writeln!(unit_file, "SyslogIdentifier=tfclient-{}", name)?; + writeln!(unit_file, "ExecStart={} run --server {} --name {}", bin_path.as_path().display(), server, name)?; + writeln!(unit_file, "Restart=always")?; + writeln!(unit_file)?; + writeln!(unit_file, "[Install]")?; + writeln!(unit_file, "WantedBy=multi-user.target")?; + + fs::write(format!("/usr/lib/systemd/system/{}.service", SystemDServiceFileGenerator::get_service_file_name(name)), unit_file)?; + + debug!("Installed unit file"); + + Ok(()) } fn delete_service_files(name: &str) -> Result<(), Box> { - todo!() + debug!("Deleting unit file..."); + + fs::remove_file(format!("/usr/lib/systemd/system/{}.service", SystemDServiceFileGenerator::get_service_file_name(name)))?; + + debug!("Removed unit file"); + + Ok(()) + } +} +impl SystemDServiceFileGenerator { + pub fn get_service_file_name(name: &str) -> String { + format!("tfclient_i-{}", name) } } \ No newline at end of file diff --git a/tfclient/src/service/detect.rs b/tfclient/src/service/detect.rs new file mode 100644 index 0000000..42a76a4 --- /dev/null +++ b/tfclient/src/service/detect.rs @@ -0,0 +1,29 @@ +use std::path::Path; +use log::info; +use crate::service::macos::OSXServiceManager; +use crate::service::runit::RunitServiceManager; +use crate::service::ServiceManager; +use crate::service::systemd::SystemDServiceManager; +use crate::service::windows::WindowsServiceManager; + +pub fn detect_service() -> Option> { + if cfg!(windows) { + return Some(Box::new(WindowsServiceManager {})); + } + if cfg!(macos) { + return Some(Box::new(OSXServiceManager {})); + } + detect_unix_service_manager() +} + +pub fn detect_unix_service_manager() -> Option> { + if Path::new("/etc/runit/1").exists() { + info!("Detected Runit service supervision (confidence: 100%, /etc/runit/1 exists)"); + return Some(Box::new(RunitServiceManager {})) + } + if Path::new("/var/lib/systemd").exists() { + info!("Detected SystemD service supervision (confidence: 100%, /var/lib/systemd exists)"); + return Some(Box::new(SystemDServiceManager {})); + } + None +} \ No newline at end of file diff --git a/tfclient/src/service/entry.rs b/tfclient/src/service/entry.rs new file mode 100644 index 0000000..65e8da9 --- /dev/null +++ b/tfclient/src/service/entry.rs @@ -0,0 +1,82 @@ +use std::env::current_exe; +use log::{error, info, warn}; +use crate::service::detect::detect_service; +use crate::util::check_server_url; + +pub fn cli_start(name: &str) { + info!("Detecting service manager..."); + let service = detect_service(); + if let Some(sm) = service { + match sm.start(name) { + Ok(_) => (), + Err(e) => { + error!("Error starting service: {}", e); + std::process::exit(1); + } + } + } else { + error!("Unable to determine which service manager to use. Could not start."); + std::process::exit(1); + } +} + +pub fn cli_stop(name: &str) { + info!("Detecting service manager..."); + let service = detect_service(); + if let Some(sm) = service { + match sm.stop(name) { + Ok(_) => (), + Err(e) => { + error!("Error starting service: {}", e); + std::process::exit(1); + } + } + } else { + error!("Unable to determine which service manager to use. Could not stop."); + std::process::exit(1); + } +} + +pub fn cli_install(name: &str, server: &str) { + info!("Checking server url..."); + check_server_url(server); + + info!("Detecting service manager..."); + let service = detect_service(); + if let Some(sm) = service { + let current_file = match current_exe() { + Ok(e) => e, + Err(e) => { + error!("Unable to get current binary: {}", e); + std::process::exit(1); + } + }; + match sm.install(current_file, name, server) { + Ok(_) => (), + Err(e) => { + error!("Error creating service files: {}", e); + std::process::exit(1); + } + } + } else { + error!("Unable to determine which service manager to use. Could not install."); + std::process::exit(1); + } +} + +pub fn cli_uninstall(name: &str) { + info!("Detecting service manager..."); + let service = detect_service(); + if let Some(sm) = service { + match sm.uninstall(name) { + Ok(_) => (), + Err(e) => { + error!("Error removing service files: {}", e); + std::process::exit(1); + } + } + } else { + error!("Unable to determine which service manager to use. Could not install."); + std::process::exit(1); + } +} \ No newline at end of file diff --git a/tfclient/src/service/macos.rs b/tfclient/src/service/macos.rs new file mode 100644 index 0000000..765ec6f --- /dev/null +++ b/tfclient/src/service/macos.rs @@ -0,0 +1,22 @@ +use std::error::Error; +use std::path::PathBuf; +use crate::service::ServiceManager; + +pub struct OSXServiceManager {} +impl ServiceManager for OSXServiceManager { + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + todo!() + } + + fn uninstall(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn start(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn stop(&self, name: &str) -> Result<(), Box> { + todo!() + } +} \ No newline at end of file diff --git a/tfclient/src/service/mod.rs b/tfclient/src/service/mod.rs index c5ff059..e00c8fb 100644 --- a/tfclient/src/service/mod.rs +++ b/tfclient/src/service/mod.rs @@ -3,10 +3,15 @@ use std::path::PathBuf; pub mod codegen; pub mod systemd; +pub mod detect; +pub mod entry; +pub mod windows; +pub mod macos; +pub mod runit; pub trait ServiceManager { - fn install(bin_path: PathBuf, name: &str) -> Result<(), Box>; - fn uninstall(name: &str) -> Result<(), Box>; - fn start(name: &str) -> Result<(), Box>; - fn stop(name: &str) -> Result<(), Box>; + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box>; + fn uninstall(&self, name: &str) -> Result<(), Box>; + fn start(&self, name: &str) -> Result<(), Box>; + fn stop(&self, name: &str) -> Result<(), Box>; } \ No newline at end of file diff --git a/tfclient/src/service/runit.rs b/tfclient/src/service/runit.rs new file mode 100644 index 0000000..c6c4a45 --- /dev/null +++ b/tfclient/src/service/runit.rs @@ -0,0 +1,22 @@ +use std::error::Error; +use std::path::PathBuf; +use crate::service::ServiceManager; + +pub struct RunitServiceManager {} +impl ServiceManager for RunitServiceManager { + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + todo!() + } + + fn uninstall(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn start(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn stop(&self, name: &str) -> Result<(), Box> { + todo!() + } +} \ No newline at end of file diff --git a/tfclient/src/service/systemd.rs b/tfclient/src/service/systemd.rs index 42bb249..05d3cc3 100644 --- a/tfclient/src/service/systemd.rs +++ b/tfclient/src/service/systemd.rs @@ -1,22 +1,86 @@ use std::error::Error; use std::path::PathBuf; +use std::process::Command; +use log::{error, info}; +use crate::service::codegen::ServiceFileGenerator; +use crate::service::codegen::systemd::SystemDServiceFileGenerator; use crate::service::ServiceManager; pub struct SystemDServiceManager {} impl ServiceManager for SystemDServiceManager { - fn install(bin_path: PathBuf, name: &str) -> Result<(), Box> { - todo!() + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + info!("Installing for SystemD"); + + SystemDServiceFileGenerator::create_service_files(bin_path, name, server_url)?; + + info!("Enabling the SystemD service"); + + let out = Command::new("systemctl").args(["enable", &SystemDServiceFileGenerator::get_service_file_name(name)]).output()?; + if !out.status.success() { + error!("Error enabling the SystemD service (command exited with non-zero exit code)"); + error!("stdout:"); + error!("{}", String::from_utf8(out.stdout)?); + error!("stderr:"); + error!("{}", String::from_utf8(out.stderr)?); + return Err("Command exited with non-zero exit code".into()); + } + + info!("Installation successful"); + + Ok(()) } - fn uninstall(name: &str) -> Result<(), Box> { - todo!() + fn uninstall(&self, name: &str) -> Result<(), Box> { + info!("Uninstalling SystemD service files"); + + info!("Disabling the SystemD service"); + + let out = Command::new("systemctl").args(["disable", &SystemDServiceFileGenerator::get_service_file_name(name)]).output()?; + if !out.status.success() { + error!("Error disabling the SystemD service (command exited with non-zero exit code)"); + error!("stdout:"); + error!("{}", String::from_utf8(out.stdout)?); + error!("stderr:"); + error!("{}", String::from_utf8(out.stderr)?); + return Err("Command exited with non-zero exit code".into()); + } + + info!("Removing the service files"); + + SystemDServiceFileGenerator::delete_service_files(name)?; + + Ok(()) } - fn start(name: &str) -> Result<(), Box> { - todo!() + fn start(&self, name: &str) -> Result<(), Box> { + info!("Starting the SystemD service"); + + let out = Command::new("systemctl").args(["start", &SystemDServiceFileGenerator::get_service_file_name(name)]).output()?; + if !out.status.success() { + error!("Error starting the SystemD service (command exited with non-zero exit code)"); + error!("stdout:"); + error!("{}", String::from_utf8(out.stdout)?); + error!("stderr:"); + error!("{}", String::from_utf8(out.stderr)?); + return Err("Command exited with non-zero exit code".into()); + } + + Ok(()) } - fn stop(name: &str) -> Result<(), Box> { - todo!() + fn stop(&self, name: &str) -> Result<(), Box> { + info!("Stopping the SystemD service"); + + let out = Command::new("systemctl").args(["stop", &SystemDServiceFileGenerator::get_service_file_name(name)]).output()?; + if !out.status.success() { + error!("Error stopping the SystemD service (command exited with non-zero exit code)"); + error!("stdout:"); + error!("{}", String::from_utf8(out.stdout)?); + error!("stderr:"); + error!("{}", String::from_utf8(out.stderr)?); + return Err("Command exited with non-zero exit code".into()); + } + + Ok(()) } } \ No newline at end of file diff --git a/tfclient/src/service/windows.rs b/tfclient/src/service/windows.rs new file mode 100644 index 0000000..4b17651 --- /dev/null +++ b/tfclient/src/service/windows.rs @@ -0,0 +1,22 @@ +use std::error::Error; +use std::path::PathBuf; +use crate::service::ServiceManager; + +pub struct WindowsServiceManager {} +impl ServiceManager for WindowsServiceManager { + fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + todo!() + } + + fn uninstall(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn start(&self, name: &str) -> Result<(), Box> { + todo!() + } + + fn stop(&self, name: &str) -> Result<(), Box> { + todo!() + } +} \ No newline at end of file diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs new file mode 100644 index 0000000..5dbd94a --- /dev/null +++ b/tfclient/src/socketworker.rs @@ -0,0 +1,13 @@ +// Code to handle the nebula worker + +use std::sync::mpsc::Receiver; +use crate::config::TFClientConfig; +use crate::daemon::ThreadMessageSender; + +pub enum SocketWorkerMessage { + +} + +pub fn socketworker_main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) { + +} \ No newline at end of file diff --git a/tfclient/src/util.rs b/tfclient/src/util.rs index a87effb..eeecaca 100644 --- a/tfclient/src/util.rs +++ b/tfclient/src/util.rs @@ -1,9 +1,29 @@ +use log::{error, warn}; use sha2::Sha256; use sha2::Digest; +use url::Url; pub fn sha256(bytes: &[u8]) -> String { let mut hasher = Sha256::new(); hasher.update(bytes); let digest = hasher.finalize(); hex::encode(digest) +} + +pub fn check_server_url(server: &str) { + let api_base = match Url::parse(&server) { + Ok(u) => u, + Err(e) => { + error!("Invalid server url `{}`: {}", server, e); + std::process::exit(1); + } + }; + match api_base.scheme() { + "http" => { warn!("HTTP api urls are not reccomended. Please switch to HTTPS if possible.") }, + "https" => (), + _ => { + error!("Unsupported protocol `{}` (expected one of http, https)", api_base.scheme()); + std::process::exit(1); + } + } } \ No newline at end of file From a0a116cbba6205db323bb9b4272e63f197634297 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 22 Mar 2023 14:36:36 -0400 Subject: [PATCH 09/46] cargo-fix --- tfclient/src/apiworker.rs | 2 +- tfclient/src/daemon.rs | 4 ++-- tfclient/src/nebulaworker.rs | 2 +- tfclient/src/service/entry.rs | 2 +- tfclient/src/service/macos.rs | 8 ++++---- tfclient/src/service/runit.rs | 8 ++++---- tfclient/src/service/windows.rs | 8 ++++---- tfclient/src/socketworker.rs | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 8948166..134c085 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -6,6 +6,6 @@ pub enum APIWorkerMessage { } -pub fn apiworker_main(config: TFClientConfig, transmitters: ThreadMessageSender, rx: Receiver) { +pub fn apiworker_main(_config: TFClientConfig, _transmitters: ThreadMessageSender, _rx: Receiver) { } \ No newline at end of file diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index 8918fab..c0623d6 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -1,8 +1,8 @@ use std::sync::mpsc; use std::sync::mpsc::Sender; use std::thread; -use log::{error, info, warn}; -use url::Url; +use log::{error, info}; + use crate::apiworker::{apiworker_main, APIWorkerMessage}; use crate::config::load_config; use crate::nebulaworker::{nebulaworker_main, NebulaWorkerMessage}; diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index 80a0551..5549775 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -8,6 +8,6 @@ pub enum NebulaWorkerMessage { } -pub fn nebulaworker_main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) { +pub fn nebulaworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSender, _rx: Receiver) { } \ No newline at end of file diff --git a/tfclient/src/service/entry.rs b/tfclient/src/service/entry.rs index 65e8da9..beff268 100644 --- a/tfclient/src/service/entry.rs +++ b/tfclient/src/service/entry.rs @@ -1,5 +1,5 @@ use std::env::current_exe; -use log::{error, info, warn}; +use log::{error, info}; use crate::service::detect::detect_service; use crate::util::check_server_url; diff --git a/tfclient/src/service/macos.rs b/tfclient/src/service/macos.rs index 765ec6f..c1c7c8d 100644 --- a/tfclient/src/service/macos.rs +++ b/tfclient/src/service/macos.rs @@ -4,19 +4,19 @@ use crate::service::ServiceManager; pub struct OSXServiceManager {} impl ServiceManager for OSXServiceManager { - fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + fn install(&self, _bin_path: PathBuf, _name: &str, _server_url: &str) -> Result<(), Box> { todo!() } - fn uninstall(&self, name: &str) -> Result<(), Box> { + fn uninstall(&self, _name: &str) -> Result<(), Box> { todo!() } - fn start(&self, name: &str) -> Result<(), Box> { + fn start(&self, _name: &str) -> Result<(), Box> { todo!() } - fn stop(&self, name: &str) -> Result<(), Box> { + fn stop(&self, _name: &str) -> Result<(), Box> { todo!() } } \ No newline at end of file diff --git a/tfclient/src/service/runit.rs b/tfclient/src/service/runit.rs index c6c4a45..db07e08 100644 --- a/tfclient/src/service/runit.rs +++ b/tfclient/src/service/runit.rs @@ -4,19 +4,19 @@ use crate::service::ServiceManager; pub struct RunitServiceManager {} impl ServiceManager for RunitServiceManager { - fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + fn install(&self, _bin_path: PathBuf, _name: &str, _server_url: &str) -> Result<(), Box> { todo!() } - fn uninstall(&self, name: &str) -> Result<(), Box> { + fn uninstall(&self, _name: &str) -> Result<(), Box> { todo!() } - fn start(&self, name: &str) -> Result<(), Box> { + fn start(&self, _name: &str) -> Result<(), Box> { todo!() } - fn stop(&self, name: &str) -> Result<(), Box> { + fn stop(&self, _name: &str) -> Result<(), Box> { todo!() } } \ No newline at end of file diff --git a/tfclient/src/service/windows.rs b/tfclient/src/service/windows.rs index 4b17651..8006a06 100644 --- a/tfclient/src/service/windows.rs +++ b/tfclient/src/service/windows.rs @@ -4,19 +4,19 @@ use crate::service::ServiceManager; pub struct WindowsServiceManager {} impl ServiceManager for WindowsServiceManager { - fn install(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box> { + fn install(&self, _bin_path: PathBuf, _name: &str, _server_url: &str) -> Result<(), Box> { todo!() } - fn uninstall(&self, name: &str) -> Result<(), Box> { + fn uninstall(&self, _name: &str) -> Result<(), Box> { todo!() } - fn start(&self, name: &str) -> Result<(), Box> { + fn start(&self, _name: &str) -> Result<(), Box> { todo!() } - fn stop(&self, name: &str) -> Result<(), Box> { + fn stop(&self, _name: &str) -> Result<(), Box> { todo!() } } \ No newline at end of file diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs index 5dbd94a..af86f4e 100644 --- a/tfclient/src/socketworker.rs +++ b/tfclient/src/socketworker.rs @@ -8,6 +8,6 @@ pub enum SocketWorkerMessage { } -pub fn socketworker_main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) { +pub fn socketworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSender, _rx: Receiver) { } \ No newline at end of file From 76dc3fc4772de920f4f33628950710d2382f40f2 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 23 Mar 2023 13:17:37 -0400 Subject: [PATCH 10/46] command socktes na djjhbfxvr5d ghgd9k7bvtx h --- Cargo.lock | 29 ++++ tfclient/Cargo.toml | 1 + tfclient/src/apiworker.rs | 29 +++- tfclient/src/config.rs | 2 +- tfclient/src/daemon.rs | 48 ++++++- tfclient/src/nebulaworker.rs | 29 +++- tfclient/src/socketworker.rs | 252 ++++++++++++++++++++++++++++++++++- 7 files changed, 374 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8419086..aecf850 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,6 +424,16 @@ dependencies = [ "cipher", ] +[[package]] +name = "ctrlc" +version = "3.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" +dependencies = [ + "nix", + "windows-sys 0.45.0", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1355,6 +1365,18 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "static_assertions", +] + [[package]] name = "nom" version = "7.1.3" @@ -2299,6 +2321,12 @@ dependencies = [ "loom", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.2" @@ -2393,6 +2421,7 @@ name = "tfclient" version = "0.1.0" dependencies = [ "clap", + "ctrlc", "dirs 5.0.0", "flate2", "hex", diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 499e6ee..d1590d6 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -18,6 +18,7 @@ url = "2.3.1" toml = "0.7.3" serde = { version = "1.0.158", features = ["derive"] } serde_json = "1.0.94" +ctrlc = "3.2.5" [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 134c085..1ee773e 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -1,11 +1,32 @@ -use std::sync::mpsc::Receiver; +use std::sync::mpsc::{Receiver, TryRecvError}; +use log::{error, info}; use crate::config::TFClientConfig; use crate::daemon::ThreadMessageSender; pub enum APIWorkerMessage { - + Shutdown } -pub fn apiworker_main(_config: TFClientConfig, _transmitters: ThreadMessageSender, _rx: Receiver) { - +pub fn apiworker_main(_config: TFClientConfig, _transmitters: ThreadMessageSender, rx: Receiver) { + loop { + match rx.try_recv() { + Ok(msg) => { + match msg { + APIWorkerMessage::Shutdown => { + info!("recv on command socket: shutdown, stopping"); + break; + } + } + }, + Err(e) => { + match e { + TryRecvError::Empty => {} + TryRecvError::Disconnected => { + error!("apiworker command socket disconnected, shutting down to prevent orphaning"); + break; + } + } + } + } + } } \ No newline at end of file diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index 5f82572..d5b10e4 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -10,7 +10,7 @@ fn default_port() -> u16 { DEFAULT_PORT } #[derive(Serialize, Deserialize, Clone)] pub struct TFClientConfig { #[serde(default = "default_port")] - listen_port: u16 + pub listen_port: u16 } pub fn create_config(instance: &str) -> Result<(), Box> { diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index c0623d6..29b7aaa 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -5,6 +5,7 @@ use log::{error, info}; use crate::apiworker::{apiworker_main, APIWorkerMessage}; use crate::config::load_config; +use crate::main; use crate::nebulaworker::{nebulaworker_main, NebulaWorkerMessage}; use crate::socketworker::{socketworker_main, SocketWorkerMessage}; use crate::util::check_server_url; @@ -22,7 +23,7 @@ pub fn daemon_main(name: String, server: String) { } }; - info!("Starting API thread..."); + info!("Creating transmitter"); let (tx_api, rx_api) = mpsc::channel::(); let (tx_socket, rx_socket) = mpsc::channel::(); @@ -34,6 +35,42 @@ pub fn daemon_main(name: String, server: String) { nebula_thread: tx_nebula }; + let mainthread_transmitter = transmitter.clone(); + + info!("Setting signal trap..."); + + match ctrlc::set_handler(move || { + info!("Ctrl-C detected. Stopping threads..."); + match mainthread_transmitter.nebula_thread.send(NebulaWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to nebula worker thread: {}", e); + } + } + match mainthread_transmitter.api_thread.send(APIWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to api worker thread: {}", e); + } + } + match mainthread_transmitter.socket_thread.send(SocketWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to socket worker thread: {}", e); + } + } + }) { + Ok(_) => (), + Err(e) => { + error!("Unable to set sigtrap: {}", e); + std::process::exit(1); + } + } + + info!("Starting API thread..."); + + + let config_api = config.clone(); let transmitter_api = transmitter.clone(); let api_thread = thread::spawn(move || { @@ -60,6 +97,7 @@ pub fn daemon_main(name: String, server: String) { std::process::exit(1); } } + info!("Socket thread exited"); info!("Waiting for API thread to exit..."); match api_thread.join() { @@ -69,6 +107,7 @@ pub fn daemon_main(name: String, server: String) { std::process::exit(1); } } + info!("API thread exited"); info!("Waiting for Nebula thread to exit..."); match nebula_thread.join() { @@ -78,13 +117,14 @@ pub fn daemon_main(name: String, server: String) { std::process::exit(1); } } + info!("Nebula thread exited"); info!("All threads exited"); } #[derive(Clone)] pub struct ThreadMessageSender { - socket_thread: Sender, - api_thread: Sender, - nebula_thread: Sender + pub socket_thread: Sender, + pub api_thread: Sender, + pub nebula_thread: Sender } \ No newline at end of file diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index 5549775..3bef8e6 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -1,13 +1,34 @@ // Code to handle the nebula worker -use std::sync::mpsc::Receiver; +use std::sync::mpsc::{Receiver, TryRecvError}; +use log::{error, info}; use crate::config::TFClientConfig; use crate::daemon::ThreadMessageSender; pub enum NebulaWorkerMessage { - + Shutdown } -pub fn nebulaworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSender, _rx: Receiver) { - +pub fn nebulaworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSender, rx: Receiver) { + loop { + match rx.try_recv() { + Ok(msg) => { + match msg { + NebulaWorkerMessage::Shutdown => { + info!("recv on command socket: shutdown, stopping"); + break; + } + } + }, + Err(e) => { + match e { + TryRecvError::Empty => {} + TryRecvError::Disconnected => { + error!("nebulaworker command socket disconnected, shutting down to prevent orphaning"); + break; + } + } + } + } + } } \ No newline at end of file diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs index af86f4e..71d0c80 100644 --- a/tfclient/src/socketworker.rs +++ b/tfclient/src/socketworker.rs @@ -1,13 +1,259 @@ // Code to handle the nebula worker -use std::sync::mpsc::Receiver; +use std::error::Error; +use std::{io, thread}; +use std::io::{BufRead, BufReader, BufWriter, Read, Write}; +use std::net::{IpAddr, Shutdown, SocketAddr, TcpListener, TcpStream}; +use std::sync::mpsc::{Receiver, TryRecvError}; +use log::{debug, error, info, trace, warn}; +use serde::{Deserialize, Serialize}; +use crate::apiworker::APIWorkerMessage; use crate::config::TFClientConfig; use crate::daemon::ThreadMessageSender; +use crate::nebulaworker::NebulaWorkerMessage; pub enum SocketWorkerMessage { - + Shutdown } -pub fn socketworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSender, _rx: Receiver) { +pub fn socketworker_main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) { + info!("socketworker_main called, entering realmain"); + match _main(config, transmitter, rx) { + Ok(_) => (), + Err(e) => { + error!("Error in socket thread: {}", e); + } + }; +} +fn _main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) -> Result<(), Box> { + let listener = TcpListener::bind(SocketAddr::new(IpAddr::from([127, 0, 0, 1]), config.listen_port))?; + listener.set_nonblocking(true)?; + + loop { + match listener.accept() { + Ok(stream) => { + let transmitter_clone = transmitter.clone(); + thread::spawn(|| { + match handle_stream(stream, transmitter_clone) { + Ok(_) => (), + Err(e) => { + error!("Error in client thread: {}", e); + } + } + }); + }, + Err(e) if e.kind() == io::ErrorKind::WouldBlock => (), + Err(e) => { Err(e)?; } + } + + match rx.try_recv() { + Ok(msg) => { + match msg { + SocketWorkerMessage::Shutdown => { + info!("recv on command socket: shutdown, stopping"); + break; + } + } + }, + Err(e) => { + match e { + TryRecvError::Empty => {} + TryRecvError::Disconnected => { + error!("socketworker command socket disconnected, shutting down to prevent orphaning"); + break; + } + } + } + } + } + + Ok(()) +} + +fn handle_stream(stream: (TcpStream, SocketAddr), transmitter: ThreadMessageSender) -> Result<(), io::Error> { + info!("Incoming client"); + match handle_client(stream.0, transmitter) { + Ok(()) => (), + Err(e) if e.kind() == io::ErrorKind::TimedOut => { + warn!("Client timed out, connection aborted"); + }, + Err(e) if e.kind() == io::ErrorKind::NotConnected => { + warn!("Client connection severed"); + }, + Err(e) if e.kind() == io::ErrorKind::BrokenPipe => { + warn!("Client connection returned error: broken pipe"); + }, + Err(e) if e.kind() == io::ErrorKind::ConnectionAborted => { + warn!("Client aborted connection"); + }, + Err(e) => { + error!("Error in client handler: {}", e); + return Err(e); + } + }; + Ok(()) +} + +fn handle_client(stream: TcpStream, transmitter: ThreadMessageSender) -> Result<(), io::Error> { + info!("Handling connection from {}", stream.peer_addr()?); + + let mut client = Client { + state: ClientState::WaitHello, + reader: BufReader::new(&stream), + writer: BufWriter::new(&stream), + stream: &stream + }; + + loop { + let mut command = String::new(); + client.reader.read_line(&mut command)?; + + let command: JsonMessage = serde_json::from_str(&command)?; + + trace!("recv {:?} from {}", command, client.stream.peer_addr()?); + + let should_disconnect; + + match client.state { + ClientState::WaitHello => { + should_disconnect = waithello_handle(&mut client, &transmitter, command)?; + } + ClientState::SentHello => { + should_disconnect = senthello_handle(&mut client, &transmitter, command)?; + } + } + + if should_disconnect { break; } + } + + // Gracefully close the connection + stream.shutdown(Shutdown::Both)?; + + Ok(()) +} + +struct Client<'a> { + state: ClientState, + reader: BufReader<&'a TcpStream>, + writer: BufWriter<&'a TcpStream>, + stream: &'a TcpStream +} + +fn waithello_handle(client: &mut Client, _transmitter: &ThreadMessageSender, command: JsonMessage) -> Result { + trace!("state: WaitHello, handing with waithello_handle"); + let mut should_disconnect = false; + + match command { + JsonMessage::Hello { version } => { + if version != JSON_API_VERSION { + should_disconnect = true; + client.stream.write_all(&ctob(JsonMessage::Goodbye { + reason: DisconnectReason::UnsupportedVersion { + expected: JSON_API_VERSION, + got: version + } + }))?; + } + client.stream.write_all(&ctob(JsonMessage::Hello { + version: JSON_API_VERSION + }))?; + client.state = ClientState::SentHello; + trace!("setting state to SentHello"); + }, + JsonMessage::Goodbye { reason } => { + info!("Client sent disconnect: {:?}", reason); + should_disconnect = true; + }, + _ => { + debug!("message type unexpected in WaitHello state"); + should_disconnect = true; + client.stream.write_all(&ctob(JsonMessage::Goodbye { + reason: DisconnectReason::UnexpectedMessageType, + }))?; + } + } + + Ok(should_disconnect) +} + +fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, command: JsonMessage) -> Result { + trace!("state: SentHello, handing with senthello_handle"); + let mut should_disconnect = false; + + match command { + JsonMessage::Goodbye { reason } => { + info!("Client sent disconnect: {:?}", reason); + should_disconnect = true; + }, + + JsonMessage::Shutdown {} => { + info!("Requested to shutdown by local control socket. Sending shutdown message to threads"); + match transmitter.nebula_thread.send(NebulaWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to nebula worker thread: {}", e); + } + } + match transmitter.api_thread.send(APIWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to api worker thread: {}", e); + } + } + match transmitter.socket_thread.send(SocketWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to socket worker thread: {}", e); + } + } + } + + _ => { + debug!("message type unexpected in SentHello state"); + should_disconnect = true; + client.stream.write_all(&ctob(JsonMessage::Goodbye { + reason: DisconnectReason::UnexpectedMessageType, + }))?; + } + } + + Ok(should_disconnect) +} + +fn ctob(command: JsonMessage) -> Vec { + let command_str = serde_json::to_string(&command).unwrap() + "\n"; + command_str.into_bytes() +} + +enum ClientState { + WaitHello, + SentHello +} + +pub const JSON_API_VERSION: i32 = 1; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "method")] +enum JsonMessage { + #[serde(rename = "hello")] + Hello { + version: i32 + }, + #[serde(rename = "goodbye")] + Goodbye { + reason: DisconnectReason + }, + #[serde(rename = "shutdown")] + Shutdown {} +} + +#[derive(Serialize, Deserialize, Debug)] +enum DisconnectReason { + #[serde(rename = "unsupported_version")] + UnsupportedVersion { expected: i32, got: i32 }, + #[serde(rename = "unexpected_message_type")] + UnexpectedMessageType, + #[serde(rename = "done")] + Done } \ No newline at end of file From 990758c27e388b131241ab0d51069f044e0c7b58 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 23 Mar 2023 13:50:21 -0400 Subject: [PATCH 11/46] piuszgdpiugdszfoigfxliudxfg fixup thing and stuff and consistentifiy thhhhhhh --- tfclient/src/config.rs | 49 +++++++++++++++++++++++++++++++++- tfclient/src/daemon.rs | 2 +- tfclient/src/dirs.rs | 8 ++++++ tfclient/src/socketworker.rs | 51 ++++++++++++++++++++++++++++-------- 4 files changed, 97 insertions(+), 13 deletions(-) diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index d5b10e4..9341f6c 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -2,7 +2,7 @@ use std::error::Error; use std::fs; use log::{debug, info}; use serde::{Deserialize, Serialize}; -use crate::dirs::{get_config_dir, get_config_file}; +use crate::dirs::{get_cdata_dir, get_cdata_file, get_config_dir, get_config_file, get_data_dir}; pub const DEFAULT_PORT: u16 = 8157; fn default_port() -> u16 { DEFAULT_PORT } @@ -13,6 +13,11 @@ pub struct TFClientConfig { pub listen_port: u16 } +#[derive(Serialize, Deserialize, Clone)] +pub struct TFClientData { + pub host_id: Option +} + pub fn create_config(instance: &str) -> Result<(), Box> { info!("Creating config directory..."); fs::create_dir_all(get_config_dir(instance).ok_or("Unable to load config dir")?)?; @@ -39,4 +44,46 @@ pub fn load_config(instance: &str) -> Result> { let config: TFClientConfig = toml::from_str(&config_str)?; info!("Loaded config successfully"); Ok(config) +} + +pub fn create_cdata(instance: &str) -> Result<(), Box> { + info!("Creating data directory..."); + fs::create_dir_all(get_cdata_dir(instance).ok_or("Unable to load data dir")?)?; + info!("Copying default data file to config directory..."); + let config = TFClientData { host_id: None }; + let config_str = toml::to_string(&config)?; + fs::write(get_cdata_file(instance).ok_or("Unable to load data dir")?, config_str)?; + Ok(()) +} + +pub fn load_cdata(instance: &str) -> Result> { + info!("Loading cdata..."); + let config_file = get_cdata_file(instance).ok_or("Unable to load cdata dir")?; + + if !config_file.exists() { + create_cdata(instance)?; + } + + debug!("opening {}", config_file.as_path().display()); + let config_str = fs::read_to_string(config_file)?; + debug!("parsing cdata file"); + let config: TFClientData = toml::from_str(&config_str)?; + info!("Loaded cdata successfully"); + Ok(config) +} + +pub fn save_cdata(instance: &str, data: TFClientData) -> Result<(), Box> { + info!("Saving cdata..."); + let config_file = get_cdata_file(instance).ok_or("Unable to load cdata dir")?; + + if !config_file.exists() { + create_cdata(instance)?; + } + + debug!("serializing cdata file"); + let config: String = toml::to_string(&data)?; + debug!("writing to {}", config_file.as_path().display()); + fs::write(config_file, config)?; + info!("Saved cdata successfully"); + Ok(()) } \ No newline at end of file diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index 29b7aaa..c462e6d 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -86,7 +86,7 @@ pub fn daemon_main(name: String, server: String) { info!("Starting socket worker thread..."); let socket_thread = thread::spawn(move || { - socketworker_main(config, transmitter, rx_socket); + socketworker_main(config, name.clone(), transmitter, rx_socket); }); info!("Waiting for socket thread to exit..."); diff --git a/tfclient/src/dirs.rs b/tfclient/src/dirs.rs index 24d85ad..d7dd0c3 100644 --- a/tfclient/src/dirs.rs +++ b/tfclient/src/dirs.rs @@ -10,4 +10,12 @@ pub fn get_config_dir(instance: &str) -> Option { pub fn get_config_file(instance: &str) -> Option { get_config_dir(instance).map(|f| f.join("tfclient.toml")) +} + +pub fn get_cdata_dir(instance: &str) -> Option { + dirs::config_dir().map(|f| f.join("tfclient_data/").join(format!("{}/", instance))) +} + +pub fn get_cdata_file(instance: &str) -> Option { + get_cdata_dir(instance).map(|f| f.join("tfclient.toml")) } \ No newline at end of file diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs index 71d0c80..a48c8b6 100644 --- a/tfclient/src/socketworker.rs +++ b/tfclient/src/socketworker.rs @@ -5,10 +5,11 @@ use std::{io, thread}; use std::io::{BufRead, BufReader, BufWriter, Read, Write}; use std::net::{IpAddr, Shutdown, SocketAddr, TcpListener, TcpStream}; use std::sync::mpsc::{Receiver, TryRecvError}; +use clap::builder::Str; use log::{debug, error, info, trace, warn}; use serde::{Deserialize, Serialize}; use crate::apiworker::APIWorkerMessage; -use crate::config::TFClientConfig; +use crate::config::{load_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; use crate::nebulaworker::NebulaWorkerMessage; @@ -16,9 +17,9 @@ pub enum SocketWorkerMessage { Shutdown } -pub fn socketworker_main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) { +pub fn socketworker_main(config: TFClientConfig, instance: String, transmitter: ThreadMessageSender, rx: Receiver) { info!("socketworker_main called, entering realmain"); - match _main(config, transmitter, rx) { + match _main(config, instance, transmitter, rx) { Ok(_) => (), Err(e) => { error!("Error in socket thread: {}", e); @@ -26,7 +27,7 @@ pub fn socketworker_main(config: TFClientConfig, transmitter: ThreadMessageSende }; } -fn _main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver) -> Result<(), Box> { +fn _main(config: TFClientConfig, instance: String, transmitter: ThreadMessageSender, rx: Receiver) -> Result<(), Box> { let listener = TcpListener::bind(SocketAddr::new(IpAddr::from([127, 0, 0, 1]), config.listen_port))?; listener.set_nonblocking(true)?; @@ -34,8 +35,10 @@ fn _main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver< match listener.accept() { Ok(stream) => { let transmitter_clone = transmitter.clone(); + let config_clone = config.clone(); + let instance_clone = instance.clone(); thread::spawn(|| { - match handle_stream(stream, transmitter_clone) { + match handle_stream(stream, transmitter_clone, config_clone, instance_clone) { Ok(_) => (), Err(e) => { error!("Error in client thread: {}", e); @@ -71,9 +74,9 @@ fn _main(config: TFClientConfig, transmitter: ThreadMessageSender, rx: Receiver< Ok(()) } -fn handle_stream(stream: (TcpStream, SocketAddr), transmitter: ThreadMessageSender) -> Result<(), io::Error> { +fn handle_stream(stream: (TcpStream, SocketAddr), transmitter: ThreadMessageSender, config: TFClientConfig, instance: String) -> Result<(), io::Error> { info!("Incoming client"); - match handle_client(stream.0, transmitter) { + match handle_client(stream.0, transmitter, config, instance) { Ok(()) => (), Err(e) if e.kind() == io::ErrorKind::TimedOut => { warn!("Client timed out, connection aborted"); @@ -95,14 +98,16 @@ fn handle_stream(stream: (TcpStream, SocketAddr), transmitter: ThreadMessageSend Ok(()) } -fn handle_client(stream: TcpStream, transmitter: ThreadMessageSender) -> Result<(), io::Error> { +fn handle_client(stream: TcpStream, transmitter: ThreadMessageSender, config: TFClientConfig, instance: String) -> Result<(), io::Error> { info!("Handling connection from {}", stream.peer_addr()?); let mut client = Client { state: ClientState::WaitHello, reader: BufReader::new(&stream), writer: BufWriter::new(&stream), - stream: &stream + stream: &stream, + config, + instance, }; loop { @@ -137,7 +142,9 @@ struct Client<'a> { state: ClientState, reader: BufReader<&'a TcpStream>, writer: BufWriter<&'a TcpStream>, - stream: &'a TcpStream + stream: &'a TcpStream, + config: TFClientConfig, + instance: String } fn waithello_handle(client: &mut Client, _transmitter: &ThreadMessageSender, command: JsonMessage) -> Result { @@ -207,6 +214,20 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm error!("Error sending shutdown message to socket worker thread: {}", e); } } + }, + + JsonMessage::GetHostID {} => { + let data = match load_cdata(&client.instance) { + Ok(d) => d, + Err(e) => { + error!("Error loading cdata: {}", e); + panic!("{}", e); // TODO: Find a better way of handling this + } + }; + client.stream.write_all(&ctob(JsonMessage::HostID { + has_id: data.host_id.is_some(), + id: data.host_id + }))?; } _ => { @@ -245,10 +266,18 @@ enum JsonMessage { reason: DisconnectReason }, #[serde(rename = "shutdown")] - Shutdown {} + Shutdown {}, + #[serde(rename = "get_host_id")] + GetHostID {}, + #[serde(rename = "host_id")] + HostID { + has_id: bool, + id: Option + } } #[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "type")] enum DisconnectReason { #[serde(rename = "unsupported_version")] UnsupportedVersion { expected: i32, got: i32 }, From 8a607733a3abbbbf18a2be1ed731eed2f288eea2 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 27 Mar 2023 12:32:26 -0400 Subject: [PATCH 12/46] generate keys for api --- tfclient/src/apiworker.rs | 47 +++++++++++++++++++++++++++++++++++++-- tfclient/src/config.rs | 6 +++-- tfclient/src/daemon.rs | 3 ++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 1ee773e..1b2707e 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -1,13 +1,56 @@ use std::sync::mpsc::{Receiver, TryRecvError}; use log::{error, info}; -use crate::config::TFClientConfig; +use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; +use trifid_pki::rand_core::OsRng; +use trifid_pki::x25519_dalek::StaticSecret; +use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; pub enum APIWorkerMessage { Shutdown } -pub fn apiworker_main(_config: TFClientConfig, _transmitters: ThreadMessageSender, rx: Receiver) { +pub fn apiworker_main(config: TFClientConfig, instance: String, _transmitters: ThreadMessageSender, rx: Receiver) { + // Generate dhPubkey and edPubkey if it doesn't exist + // Load vardata + let mut vdata = match load_cdata(&instance) { + Ok(d) => d, + Err(e) => { + error!("Error loading vdata: {}", e); + error!("APIWorker exiting with error"); + return; + } + }; + + if vdata.ed_privkey.is_none() { + info!("Generating ed25519 key"); + let mut csprng = OsRng; + let key = SigningKey::generate(&mut csprng); + let ed_key_bytes = key.to_bytes().to_vec(); + vdata.ed_privkey = Some(ed_key_bytes.try_into().unwrap()); + } + if vdata.dh_privkey.is_none() { + info!("Generating ecdh key"); + let mut csprng = OsRng; + let key = StaticSecret::new(&mut csprng); + let dh_key_bytes = key.to_bytes(); + vdata.dh_privkey = Some(dh_key_bytes); + } + + info!("Loading keys"); + let ed_key = SigningKey::from_bytes(&SecretKey::from(vdata.ed_privkey.unwrap())); + let dh_key = StaticSecret::from(vdata.dh_privkey.unwrap()); + info!("Keys loaded successfully"); + + // Save vardata + match save_cdata(&instance, vdata) { + Ok(_) => (), + Err(e) => { + error!("Error saving vdata: {}", e); + error!("APIWorker exiting with error"); + return; + } + } loop { match rx.try_recv() { Ok(msg) => { diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index 9341f6c..39be10e 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -15,7 +15,9 @@ pub struct TFClientConfig { #[derive(Serialize, Deserialize, Clone)] pub struct TFClientData { - pub host_id: Option + pub host_id: Option, + pub ed_privkey: Option<[u8; 32]>, + pub dh_privkey: Option<[u8; 32]> } pub fn create_config(instance: &str) -> Result<(), Box> { @@ -50,7 +52,7 @@ pub fn create_cdata(instance: &str) -> Result<(), Box> { info!("Creating data directory..."); fs::create_dir_all(get_cdata_dir(instance).ok_or("Unable to load data dir")?)?; info!("Copying default data file to config directory..."); - let config = TFClientData { host_id: None }; + let config = TFClientData { host_id: None, ed_privkey: None, dh_privkey: None }; let config_str = toml::to_string(&config)?; fs::write(get_cdata_file(instance).ok_or("Unable to load data dir")?, config_str)?; Ok(()) diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index c462e6d..7f16b30 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -73,8 +73,9 @@ pub fn daemon_main(name: String, server: String) { let config_api = config.clone(); let transmitter_api = transmitter.clone(); + let name_api = name.clone(); let api_thread = thread::spawn(move || { - apiworker_main(config_api, transmitter_api, rx_api); + apiworker_main(config_api, name_api, transmitter_api, rx_api); }); info!("Starting Nebula thread..."); From b291d47459e0665484e4f288a296f7d19e9ebe68 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Tue, 28 Mar 2023 12:16:00 -0400 Subject: [PATCH 13/46] tfclient enrollment successful --- Cargo.lock | 10 +++-- api/v2/enroll.txt | 3 ++ tfclient/Cargo.toml | 3 ++ tfclient/src/api.rs | 70 ++++++++++++++++++++++++++++++++++ tfclient/src/apiworker.rs | 73 ++++++++++++++++++++++++++++++++++-- tfclient/src/config.rs | 9 ++++- tfclient/src/daemon.rs | 3 +- tfclient/src/main.rs | 21 ++++++++++- tfclient/src/socketclient.rs | 58 ++++++++++++++++++++++++++++ tfclient/src/socketworker.rs | 16 ++++++-- 10 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 tfclient/src/api.rs create mode 100644 tfclient/src/socketclient.rs diff --git a/Cargo.lock b/Cargo.lock index aecf850..652bc4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,9 +219,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "js-sys", @@ -1856,9 +1856,9 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "reqwest" -version = "0.11.14" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" dependencies = [ "base64 0.21.0", "bytes", @@ -2420,6 +2420,8 @@ dependencies = [ name = "tfclient" version = "0.1.0" dependencies = [ + "base64 0.21.0", + "chrono", "clap", "ctrlc", "dirs 5.0.0", diff --git a/api/v2/enroll.txt b/api/v2/enroll.txt index bc3a41e..afead04 100644 --- a/api/v2/enroll.txt +++ b/api/v2/enroll.txt @@ -7,6 +7,9 @@ Connection: close {"code":"xM22QsIzd4F0nLDTbh86RCSYwelfU_Hshqt-7u4yy_Y","dhPubkey":"LS0tLS1CRUdJTiBORUJVTEEgWDI1NTE5IFBVQkxJQyBLRVktLS0tLQpqZW9aaDZZYUNNSHZKK04zWGRlQ1hCbHo3dm5saTBjL1NlQ1hVR3lYbEIwPQotLS0tLUVORCBORUJVTEEgWDI1NTE5IFBVQkxJQyBLRVktLS0tLQo=","edPubkey":"LS0tLS1CRUdJTiBORUJVTEEgRUQyNTUxOSBQVUJMSUMgS0VZLS0tLS0KWHE0RG9mUGJoQzBubjc4VEhRWUxhNC83V1Ixei9iU1kzSm9pRzNRZ1VMcz0KLS0tLS1FTkQgTkVCVUxBIEVEMjU1MTkgUFVCTElDIEtFWS0tLS0tCg==","timestamp":"2023-02-01T13:24:56.380006369-05:00"} +2023-02-01T13:24:56.380006369-05:00 +%Y-%m-%dT%H:%M:%S.%f-%:z + HTTP/2 200 OK Cache-Control: no-store Content-Security-Policy: default-src 'none' diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index d1590d6..0639ee2 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -19,6 +19,9 @@ toml = "0.7.3" serde = { version = "1.0.158", features = ["derive"] } serde_json = "1.0.94" ctrlc = "3.2.5" +reqwest = { version = "0.11.16", features = ["blocking"] } +base64 = "0.21.0" +chrono = "0.4.24" [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/api.rs b/tfclient/src/api.rs new file mode 100644 index 0000000..5d5653a --- /dev/null +++ b/tfclient/src/api.rs @@ -0,0 +1,70 @@ +use std::error::Error; +use log::trace; +use reqwest::blocking::Client; +use serde::{Serialize, Deserialize}; +use url::Url; + +#[derive(Serialize, Deserialize)] +pub struct EnrollRequest { + pub code: String, + #[serde(rename = "dhPubkey")] + pub dh_pubkey: String, + #[serde(rename = "edPubkey")] + pub ed_pubkey: String, + pub timestamp: String, +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollResponseMetadata {} + +#[derive(Serialize, Deserialize)] +pub struct EnrollResponseOrganization { + pub id: String, + pub name: String, +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollResponseData { + pub config: String, + #[serde(rename = "hostID")] + pub host_id: String, + pub counter: i64, + #[serde(rename = "trustedKeys")] + pub trusted_keys: String, + pub organization: EnrollResponseOrganization, +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollResponse { + pub data: EnrollResponseData, + pub metadata: EnrollResponseMetadata, +} + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +pub enum APIResponse { + Error(EnrollError), + Success(EnrollResponse) +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollError { + pub errors: Vec +} +#[derive(Serialize, Deserialize)] +pub struct EnrollErrorSingular { + pub code: String, + pub message: String +} + +pub fn enroll(server: &Url, request: &EnrollRequest) -> Result> { + let endpoint = server.join("/v2/enroll")?; + let client = Client::new(); + + let text = serde_json::to_string(request)?; + + trace!("sending enroll: {}", text); + + let resp = client.post(endpoint).body(text).send()?; + Ok(resp.json()?) +} \ No newline at end of file diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 1b2707e..b749fdf 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -1,16 +1,24 @@ use std::sync::mpsc::{Receiver, TryRecvError}; -use log::{error, info}; +use base64::Engine; +use chrono::Local; +use log::{error, info, warn}; +use url::Url; +use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; use trifid_pki::rand_core::OsRng; use trifid_pki::x25519_dalek::StaticSecret; +use crate::api::{APIResponse, enroll, EnrollRequest}; use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; pub enum APIWorkerMessage { - Shutdown + Shutdown, + Enroll { code: String } } -pub fn apiworker_main(config: TFClientConfig, instance: String, _transmitters: ThreadMessageSender, rx: Receiver) { +pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver) { + let server = Url::parse(&url).unwrap(); + // Generate dhPubkey and edPubkey if it doesn't exist // Load vardata let mut vdata = match load_cdata(&instance) { @@ -58,6 +66,65 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, _transmitters: T APIWorkerMessage::Shutdown => { info!("recv on command socket: shutdown, stopping"); break; + }, + APIWorkerMessage::Enroll { code } => { + info!("recv on command socket: enroll {}", code); + let mut cdata = match load_cdata(&instance) { + Ok(c) => c, + Err(e) => { + error!("error in api worker thread: {}", e); + error!("APIWorker exiting with error"); + return; + } + }; + if cdata.host_id.is_some() { + warn!("enrollment failed: already enrolled"); + continue; + } + let dh_encoded = base64::engine::general_purpose::STANDARD.encode(serialize_x25519_public(&dh_key.to_bytes())); + let ed_encoded = base64::engine::general_purpose::STANDARD.encode(serialize_ed25519_public(&ed_key.to_bytes())); + let req = EnrollRequest { + code, + dh_pubkey: dh_encoded, + ed_pubkey: ed_encoded, + timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), + }; + let res = match enroll(&server, &req) { + Ok(res) => res, + Err(e) => { + error!("error in api worker thread: {}", e); + error!("APIWorker exiting with error"); + return; + } + }; + let resp = match res { + APIResponse::Error(e) => { + error!("error with enrollment: {}: {}", e.errors[0].code, e.errors[0].message); + continue; + } + APIResponse::Success(resp) => resp + }; + + info!("Enrolled with server. Host-ID {} config count {}", resp.data.host_id, resp.data.counter); + info!("NebulaCAPool {}, org {} {}", resp.data.trusted_keys, resp.data.organization.name, resp.data.organization.id); + info!("Config: {}", resp.data.config); + + cdata.host_id = Some(resp.data.host_id); + cdata.counter = resp.data.counter as i32; + cdata.ca_pool = Some(resp.data.trusted_keys); + cdata.org_name = Some(resp.data.organization.name); + cdata.org_id = Some(resp.data.organization.id); + cdata.config = Some(resp.data.config); + + // Save vardata + match save_cdata(&instance, cdata) { + Ok(_) => (), + Err(e) => { + error!("Error saving cdata: {}", e); + error!("APIWorker exiting with error"); + return; + } + } } } }, diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index 39be10e..1eb41fc 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -17,7 +17,12 @@ pub struct TFClientConfig { pub struct TFClientData { pub host_id: Option, pub ed_privkey: Option<[u8; 32]>, - pub dh_privkey: Option<[u8; 32]> + pub dh_privkey: Option<[u8; 32]>, + pub counter: i32, + pub ca_pool: Option, + pub org_id: Option, + pub org_name: Option, + pub config: Option } pub fn create_config(instance: &str) -> Result<(), Box> { @@ -52,7 +57,7 @@ pub fn create_cdata(instance: &str) -> Result<(), Box> { info!("Creating data directory..."); fs::create_dir_all(get_cdata_dir(instance).ok_or("Unable to load data dir")?)?; info!("Copying default data file to config directory..."); - let config = TFClientData { host_id: None, ed_privkey: None, dh_privkey: None }; + let config = TFClientData { host_id: None, ed_privkey: None, dh_privkey: None, counter: 0, ca_pool: None, org_id: None, org_name: None, config: None }; let config_str = toml::to_string(&config)?; fs::write(get_cdata_file(instance).ok_or("Unable to load data dir")?, config_str)?; Ok(()) diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index 7f16b30..052efc8 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -74,8 +74,9 @@ pub fn daemon_main(name: String, server: String) { let config_api = config.clone(); let transmitter_api = transmitter.clone(); let name_api = name.clone(); + let server_api = server.clone(); let api_thread = thread::spawn(move || { - apiworker_main(config_api, name_api, transmitter_api, rx_api); + apiworker_main(config_api, name_api, server_api,transmitter_api, rx_api); }); info!("Starting Nebula thread..."); diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index 49f95b8..5331186 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -23,6 +23,8 @@ pub mod config; pub mod service; pub mod apiworker; pub mod socketworker; +pub mod api; +pub mod socketclient; pub mod nebula_bin { include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); @@ -37,6 +39,7 @@ use std::fs; use clap::{Parser, ArgAction, Subcommand}; use log::{error, info}; use simple_logger::SimpleLogger; +use crate::config::load_config; use crate::dirs::get_data_dir; use crate::embedded_nebula::{run_embedded_nebula, run_embedded_nebula_cert}; use crate::service::entry::{cli_install, cli_start, cli_stop, cli_uninstall}; @@ -228,7 +231,23 @@ fn main() { Commands::Run { name, server } => { daemon::daemon_main(name, server); } - Commands::Enroll { .. } => {} + Commands::Enroll { name, code } => { + info!("Loading config..."); + let config = match load_config(&name) { + Ok(cfg) => cfg, + Err(e) => { + error!("Error loading configuration: {}", e); + std::process::exit(1); + } + }; + match socketclient::enroll(&code, &config) { + Ok(_) => (), + Err(e) => { + error!("Error sending enrollment request: {}", e); + std::process::exit(1); + } + }; + } } } diff --git a/tfclient/src/socketclient.rs b/tfclient/src/socketclient.rs new file mode 100644 index 0000000..5ce1337 --- /dev/null +++ b/tfclient/src/socketclient.rs @@ -0,0 +1,58 @@ +use std::error::Error; +use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::net::{IpAddr, SocketAddr, TcpStream}; +use log::{error, info}; +use crate::config::TFClientConfig; +use crate::socketworker::{ctob, DisconnectReason, JSON_API_VERSION, JsonMessage}; + +pub fn enroll(code: &str, config: &TFClientConfig) -> Result<(), Box> { + info!("Connecting to local command socket..."); + let mut stream = TcpStream::connect(SocketAddr::new(IpAddr::from([127, 0, 0, 1]), config.listen_port))?; + let mut stream2 = stream.try_clone()?; + let mut reader = BufReader::new(&stream2); + + info!("Sending Hello..."); + stream.write_all(&ctob(JsonMessage::Hello { + version: JSON_API_VERSION, + }))?; + info!("Waiting for hello..."); + let msg = read_msg(&mut reader)?; + match msg { + JsonMessage::Hello { .. } => { + info!("Server sent hello, connection established") + } + JsonMessage::Goodbye { reason } => { + error!("Disconnected by server. Reason: {:?}", reason); + return Err("Disconnected by server".into()); + } + _ => { + error!("Server returned unexpected message: {:?}", msg); + error!("Sending goodbye and exiting"); + stream.write_all(&ctob(JsonMessage::Goodbye { + reason: DisconnectReason::UnexpectedMessageType, + }))?; + return Err("Unexpected message type by server".into()); + } + } + + info!("Sending enroll request..."); + stream.write_all(&ctob(JsonMessage::Enroll { + code: code.to_string(), + }))?; + + info!("Sending disconnect..."); + stream.write_all(&ctob(JsonMessage::Goodbye { + reason: DisconnectReason::Done, + }))?; + + info!("Sent enroll request to tfclient daemon. Check logs to see if the enrollment was successful."); + + Ok(()) +} + +fn read_msg(reader: &mut BufReader<&TcpStream>) -> Result> { + let mut str = String::new(); + reader.read_line(&mut str)?; + let msg: JsonMessage = serde_json::from_str(&str)?; + Ok(msg) +} \ No newline at end of file diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs index a48c8b6..389edb8 100644 --- a/tfclient/src/socketworker.rs +++ b/tfclient/src/socketworker.rs @@ -228,6 +228,12 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm has_id: data.host_id.is_some(), id: data.host_id }))?; + }, + + JsonMessage::Enroll { code } => { + info!("Client sent enroll with code {}", code); + info!("Sending enroll request to apiworker"); + transmitter.api_thread.send(APIWorkerMessage::Enroll { code }).unwrap(); } _ => { @@ -242,7 +248,7 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm Ok(should_disconnect) } -fn ctob(command: JsonMessage) -> Vec { +pub fn ctob(command: JsonMessage) -> Vec { let command_str = serde_json::to_string(&command).unwrap() + "\n"; command_str.into_bytes() } @@ -256,7 +262,7 @@ pub const JSON_API_VERSION: i32 = 1; #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "method")] -enum JsonMessage { +pub enum JsonMessage { #[serde(rename = "hello")] Hello { version: i32 @@ -273,12 +279,16 @@ enum JsonMessage { HostID { has_id: bool, id: Option + }, + #[serde(rename = "enroll")] + Enroll { + code: String } } #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type")] -enum DisconnectReason { +pub enum DisconnectReason { #[serde(rename = "unsupported_version")] UnsupportedVersion { expected: i32, got: i32 }, #[serde(rename = "unexpected_message_type")] From 05d452bc506d85d8a4ac8193674f8b20cef3ffaf Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Tue, 28 Mar 2023 13:10:11 -0400 Subject: [PATCH 14/46] trifid-pki changes and config work --- Cargo.lock | 4 +++- tfclient/Cargo.toml | 4 +++- tfclient/src/apiworker.rs | 26 +++++++++++++++++++++++--- tfclient/src/config.rs | 8 ++++++-- trifid-pki/Cargo.toml | 9 +++++++-- trifid-pki/src/ca.rs | 9 +++++++-- trifid-pki/src/cert.rs | 8 ++++++++ 7 files changed, 57 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 652bc4c..c703f11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2427,6 +2427,7 @@ dependencies = [ "dirs 5.0.0", "flate2", "hex", + "ipnet", "log", "reqwest", "serde", @@ -2748,7 +2749,7 @@ dependencies = [ [[package]] name = "trifid-pki" -version = "0.1.5" +version = "0.1.6" dependencies = [ "ed25519-dalek", "hex", @@ -2757,6 +2758,7 @@ dependencies = [ "quick-protobuf", "rand", "rand_core 0.6.4", + "serde", "sha2", "x25519-dalek", ] diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 0639ee2..9c21ab1 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -8,7 +8,7 @@ description = "An open-source reimplementation of a Defined Networking-compatibl [dependencies] clap = { version = "4.1.10", features = ["derive"] } -trifid-pki = { version = "0.1.5", path = "../trifid-pki" } +trifid-pki = { version = "0.1.6", path = "../trifid-pki", features = ["serde_derive"] } dirs = "5.0.0" log = "0.4.17" simple_logger = "4.1.0" @@ -22,6 +22,8 @@ ctrlc = "3.2.5" reqwest = { version = "0.11.16", features = ["blocking"] } base64 = "0.21.0" chrono = "0.4.24" +ipnet = "2.7.1" + [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index b749fdf..3eacefe 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -3,6 +3,7 @@ use base64::Engine; use chrono::Local; use log::{error, info, warn}; use url::Url; +use trifid_pki::ca::NebulaCAPool; use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; use trifid_pki::rand_core::OsRng; @@ -106,15 +107,34 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr }; info!("Enrolled with server. Host-ID {} config count {}", resp.data.host_id, resp.data.counter); - info!("NebulaCAPool {}, org {} {}", resp.data.trusted_keys, resp.data.organization.name, resp.data.organization.id); + info!("KeyPool {}, org {} {}", resp.data.trusted_keys, resp.data.organization.name, resp.data.organization.id); info!("Config: {}", resp.data.config); + // Decode the CAPool and config + let key_pool_pem = match base64::engine::general_purpose::STANDARD.decode(resp.data.trusted_keys) { + Ok(p) => p, + Err(e) => { + error!("error with enrollment: {}", e); + continue; + } + }; + + let config = match base64::engine::general_purpose::STANDARD.decode(resp.data.config) { + Ok(p) => p, + Err(e) => { + error!("error with enrollment: {}", e); + continue; + } + }; + let config_str = String::from_utf8(config).unwrap(); + + cdata.host_id = Some(resp.data.host_id); cdata.counter = resp.data.counter as i32; - cdata.ca_pool = Some(resp.data.trusted_keys); + cdata.key_pool = Some(String::from_utf8(key_pool_pem).unwrap()); cdata.org_name = Some(resp.data.organization.name); cdata.org_id = Some(resp.data.organization.id); - cdata.config = Some(resp.data.config); + cdata.config = Some(config_str); // Save vardata match save_cdata(&instance, cdata) { diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index 1eb41fc..f65831e 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -1,7 +1,11 @@ +use std::collections::HashMap; use std::error::Error; use std::fs; +use std::net::{Ipv4Addr, SocketAddrV4}; +use ipnet::Ipv4Net; use log::{debug, info}; use serde::{Deserialize, Serialize}; +use trifid_pki::ca::NebulaCAPool; use crate::dirs::{get_cdata_dir, get_cdata_file, get_config_dir, get_config_file, get_data_dir}; pub const DEFAULT_PORT: u16 = 8157; @@ -19,7 +23,7 @@ pub struct TFClientData { pub ed_privkey: Option<[u8; 32]>, pub dh_privkey: Option<[u8; 32]>, pub counter: i32, - pub ca_pool: Option, + pub key_pool: Option, pub org_id: Option, pub org_name: Option, pub config: Option @@ -57,7 +61,7 @@ pub fn create_cdata(instance: &str) -> Result<(), Box> { info!("Creating data directory..."); fs::create_dir_all(get_cdata_dir(instance).ok_or("Unable to load data dir")?)?; info!("Copying default data file to config directory..."); - let config = TFClientData { host_id: None, ed_privkey: None, dh_privkey: None, counter: 0, ca_pool: None, org_id: None, org_name: None, config: None }; + let config = TFClientData { host_id: None, ed_privkey: None, dh_privkey: None, counter: 0, key_pool: None, org_id: None, org_name: None, config: None }; let config_str = toml::to_string(&config)?; fs::write(get_cdata_file(instance).ok_or("Unable to load data dir")?, config_str)?; Ok(()) diff --git a/trifid-pki/Cargo.toml b/trifid-pki/Cargo.toml index a6fdb94..4c50a20 100644 --- a/trifid-pki/Cargo.toml +++ b/trifid-pki/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trifid-pki" -version = "0.1.5" +version = "0.1.6" edition = "2021" description = "A rust implementation of the Nebula PKI system" license = "AGPL-3.0-or-later" @@ -19,4 +19,9 @@ quick-protobuf = "0.8.1" hex = "0.4.3" sha2 = "0.10.6" rand_core = "0.6.4" -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" +serde = { version = "1", features = ["derive"], optional = true } + +[features] +default = [] +serde_derive = ["serde", "ipnet/serde"] \ No newline at end of file diff --git a/trifid-pki/src/ca.rs b/trifid-pki/src/ca.rs index 66dc20a..d5b3aaf 100644 --- a/trifid-pki/src/ca.rs +++ b/trifid-pki/src/ca.rs @@ -7,9 +7,13 @@ use std::time::SystemTime; use ed25519_dalek::VerifyingKey; use crate::cert::{deserialize_nebula_certificate_from_pem, NebulaCertificate}; +#[cfg(feature = "serde_derive")] +use serde::{Serialize, Deserialize}; + /// A pool of trusted CA certificates, and certificates that should be blocked. /// This is equivalent to the `pki` section in a typical Nebula config.yml. -#[derive(Default)] +#[derive(Default, Clone)] +#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] pub struct NebulaCAPool { /// The list of CA root certificates that should be trusted. pub cas: HashMap, @@ -102,8 +106,9 @@ impl NebulaCAPool { } } -#[derive(Debug)] /// A list of errors that can happen when working with a CA Pool +#[derive(Debug)] +#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] pub enum CaPoolError { /// Tried to add a non-CA cert to the CA pool NotACA, diff --git a/trifid-pki/src/cert.rs b/trifid-pki/src/cert.rs index e328307..c5495e6 100644 --- a/trifid-pki/src/cert.rs +++ b/trifid-pki/src/cert.rs @@ -15,6 +15,9 @@ use crate::ca::NebulaCAPool; use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails}; use sha2::Digest; +#[cfg(feature = "serde_derive")] +use serde::{Serialize, Deserialize}; + /// The length, in bytes, of public keys pub const PUBLIC_KEY_LENGTH: i32 = 32; @@ -31,6 +34,7 @@ pub const ED25519_PUBLIC_KEY_BANNER: &str = "NEBULA ED25519 PUBLIC KEY"; /// A Nebula PKI certificate #[derive(Debug, Clone)] +#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] pub struct NebulaCertificate { /// The signed data of this certificate pub details: NebulaCertificateDetails, @@ -40,6 +44,7 @@ pub struct NebulaCertificate { /// The signed details contained in a Nebula PKI certificate #[derive(Debug, Clone)] +#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] pub struct NebulaCertificateDetails { /// The name of the identity this certificate was issued for pub name: String, @@ -63,6 +68,7 @@ pub struct NebulaCertificateDetails { /// A list of errors that can occur parsing certificates #[derive(Debug)] +#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] pub enum CertificateError { /// Attempted to deserialize a certificate from an empty byte array EmptyByteArray, @@ -186,6 +192,7 @@ pub fn deserialize_nebula_certificate(bytes: &[u8]) -> Result Date: Tue, 28 Mar 2023 20:42:36 -0400 Subject: [PATCH 15/46] cargo-fix p1 --- .gitignore | 3 ++- tfclient/src/apiworker.rs | 4 ++-- tfclient/src/config.rs | 10 +++++----- tfclient/src/daemon.rs | 2 +- tfclient/src/socketclient.rs | 4 ++-- tfclient/src/socketworker.rs | 4 ++-- trifid-api/docker-compose.yml | 9 +++++++++ 7 files changed, 23 insertions(+), 13 deletions(-) create mode 100644 trifid-api/docker-compose.yml diff --git a/.gitignore b/.gitignore index 1de5659..3208d77 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -target \ No newline at end of file +target +pg_data \ No newline at end of file diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 3eacefe..1a9035a 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -3,7 +3,7 @@ use base64::Engine; use chrono::Local; use log::{error, info, warn}; use url::Url; -use trifid_pki::ca::NebulaCAPool; + use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; use trifid_pki::rand_core::OsRng; @@ -17,7 +17,7 @@ pub enum APIWorkerMessage { Enroll { code: String } } -pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver) { +pub fn apiworker_main(_config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver) { let server = Url::parse(&url).unwrap(); // Generate dhPubkey and edPubkey if it doesn't exist diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index f65831e..9d86a0b 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -1,12 +1,12 @@ -use std::collections::HashMap; + use std::error::Error; use std::fs; -use std::net::{Ipv4Addr, SocketAddrV4}; -use ipnet::Ipv4Net; + + use log::{debug, info}; use serde::{Deserialize, Serialize}; -use trifid_pki::ca::NebulaCAPool; -use crate::dirs::{get_cdata_dir, get_cdata_file, get_config_dir, get_config_file, get_data_dir}; + +use crate::dirs::{get_cdata_dir, get_cdata_file, get_config_dir, get_config_file}; pub const DEFAULT_PORT: u16 = 8157; fn default_port() -> u16 { DEFAULT_PORT } diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index 052efc8..06019f6 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -5,7 +5,7 @@ use log::{error, info}; use crate::apiworker::{apiworker_main, APIWorkerMessage}; use crate::config::load_config; -use crate::main; + use crate::nebulaworker::{nebulaworker_main, NebulaWorkerMessage}; use crate::socketworker::{socketworker_main, SocketWorkerMessage}; use crate::util::check_server_url; diff --git a/tfclient/src/socketclient.rs b/tfclient/src/socketclient.rs index 5ce1337..62e0449 100644 --- a/tfclient/src/socketclient.rs +++ b/tfclient/src/socketclient.rs @@ -1,5 +1,5 @@ use std::error::Error; -use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::io::{BufRead, BufReader, Write}; use std::net::{IpAddr, SocketAddr, TcpStream}; use log::{error, info}; use crate::config::TFClientConfig; @@ -8,7 +8,7 @@ use crate::socketworker::{ctob, DisconnectReason, JSON_API_VERSION, JsonMessage} pub fn enroll(code: &str, config: &TFClientConfig) -> Result<(), Box> { info!("Connecting to local command socket..."); let mut stream = TcpStream::connect(SocketAddr::new(IpAddr::from([127, 0, 0, 1]), config.listen_port))?; - let mut stream2 = stream.try_clone()?; + let stream2 = stream.try_clone()?; let mut reader = BufReader::new(&stream2); info!("Sending Hello..."); diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs index 389edb8..7a7e831 100644 --- a/tfclient/src/socketworker.rs +++ b/tfclient/src/socketworker.rs @@ -2,10 +2,10 @@ use std::error::Error; use std::{io, thread}; -use std::io::{BufRead, BufReader, BufWriter, Read, Write}; +use std::io::{BufRead, BufReader, BufWriter, Write}; use std::net::{IpAddr, Shutdown, SocketAddr, TcpListener, TcpStream}; use std::sync::mpsc::{Receiver, TryRecvError}; -use clap::builder::Str; + use log::{debug, error, info, trace, warn}; use serde::{Deserialize, Serialize}; use crate::apiworker::APIWorkerMessage; diff --git a/trifid-api/docker-compose.yml b/trifid-api/docker-compose.yml new file mode 100644 index 0000000..9f37e27 --- /dev/null +++ b/trifid-api/docker-compose.yml @@ -0,0 +1,9 @@ +# This is a docker-compose file to fire up a dev database with some persistence. +version: '3' +services: + postgres: + image: postgres + ports: + - "5432:5432" + volumes: + - "../pg_data:/var/lib/postgresql/data" \ No newline at end of file From 2923339197d9065fdfeaafb3c4afa6ec33bf1958 Mon Sep 17 00:00:00 2001 From: core Date: Tue, 28 Mar 2023 20:43:24 -0400 Subject: [PATCH 16/46] goodbye docker-compose --- trifid-api/docker-compose.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 trifid-api/docker-compose.yml diff --git a/trifid-api/docker-compose.yml b/trifid-api/docker-compose.yml deleted file mode 100644 index 9f37e27..0000000 --- a/trifid-api/docker-compose.yml +++ /dev/null @@ -1,9 +0,0 @@ -# This is a docker-compose file to fire up a dev database with some persistence. -version: '3' -services: - postgres: - image: postgres - ports: - - "5432:5432" - volumes: - - "../pg_data:/var/lib/postgresql/data" \ No newline at end of file From 26ef187ff34c6699afa3035556dd07f0f738b92f Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 29 Mar 2023 09:37:03 -0400 Subject: [PATCH 17/46] start porting dnapi --- Cargo.lock | 11 +++ tfclient/Cargo.toml | 2 +- tfclient/src/api.rs | 179 +++++++++++++++++++++++++---------- tfclient/src/apiworker.rs | 20 +++- tfclient/src/daemon.rs | 32 ++++++- tfclient/src/dnapi/mod.rs | 1 + tfclient/src/main.rs | 1 + tfclient/src/socketworker.rs | 7 ++ tfclient/src/timerworker.rs | 47 +++++++++ 9 files changed, 241 insertions(+), 59 deletions(-) create mode 100644 tfclient/src/dnapi/mod.rs create mode 100644 tfclient/src/timerworker.rs diff --git a/Cargo.lock b/Cargo.lock index c703f11..375dbef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,16 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +[[package]] +name = "base64-serde" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7" +dependencies = [ + "base64 0.21.0", + "serde", +] + [[package]] name = "base64ct" version = "1.5.3" @@ -2421,6 +2431,7 @@ name = "tfclient" version = "0.1.0" dependencies = [ "base64 0.21.0", + "base64-serde", "chrono", "clap", "ctrlc", diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 9c21ab1..df081aa 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -23,7 +23,7 @@ reqwest = { version = "0.11.16", features = ["blocking"] } base64 = "0.21.0" chrono = "0.4.24" ipnet = "2.7.1" - +base64-serde = "0.7.0" [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/api.rs b/tfclient/src/api.rs index 5d5653a..fd3e77e 100644 --- a/tfclient/src/api.rs +++ b/tfclient/src/api.rs @@ -1,61 +1,13 @@ use std::error::Error; +use base64_serde::base64_serde_type; use log::trace; use reqwest::blocking::Client; use serde::{Serialize, Deserialize}; use url::Url; -#[derive(Serialize, Deserialize)] -pub struct EnrollRequest { - pub code: String, - #[serde(rename = "dhPubkey")] - pub dh_pubkey: String, - #[serde(rename = "edPubkey")] - pub ed_pubkey: String, - pub timestamp: String, -} +const ENDPOINT_V1: &str = "/v1/dnclient"; -#[derive(Serialize, Deserialize)] -pub struct EnrollResponseMetadata {} - -#[derive(Serialize, Deserialize)] -pub struct EnrollResponseOrganization { - pub id: String, - pub name: String, -} - -#[derive(Serialize, Deserialize)] -pub struct EnrollResponseData { - pub config: String, - #[serde(rename = "hostID")] - pub host_id: String, - pub counter: i64, - #[serde(rename = "trustedKeys")] - pub trusted_keys: String, - pub organization: EnrollResponseOrganization, -} - -#[derive(Serialize, Deserialize)] -pub struct EnrollResponse { - pub data: EnrollResponseData, - pub metadata: EnrollResponseMetadata, -} - -#[derive(Serialize, Deserialize)] -#[serde(untagged)] -pub enum APIResponse { - Error(EnrollError), - Success(EnrollResponse) -} - -#[derive(Serialize, Deserialize)] -pub struct EnrollError { - pub errors: Vec -} -#[derive(Serialize, Deserialize)] -pub struct EnrollErrorSingular { - pub code: String, - pub message: String -} +base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); pub fn enroll(server: &Url, request: &EnrollRequest) -> Result> { let endpoint = server.join("/v2/enroll")?; @@ -67,4 +19,127 @@ pub fn enroll(server: &Url, request: &EnrollRequest) -> Result +} + +#[derive(Serialize, Deserialize)] +pub struct RequestWrapper { + #[serde(rename = "type")] + pub message_type: String, + #[serde(with = "Base64Standard")] + pub value: Vec, + pub timestamp: String +} + +#[derive(Serialize, Deserialize)] +pub struct SignedResponseWrapper { + pub data: SignedResponse +} + +#[derive(Serialize, Deserialize)] +pub struct SignedResponse { + pub version: i32, + #[serde(with = "Base64Standard")] + pub message: Vec, + #[serde(with = "Base64Standard")] + pub signature: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct CheckForUpdateResponseWrapper { + pub data: CheckForUpdateResponse +} + +#[derive(Serialize, Deserialize)] +pub struct CheckForUpdateResponse { + #[serde(rename = "updateAvailable")] + pub update_available: bool +} + +#[derive(Serialize, Deserialize)] +pub struct DoUpdateRequest { + #[serde(rename = "edPubkeyPEM")] + #[serde(with = "Base64Standard")] + pub ed_pubkey_pem: Vec, + #[serde(rename = "dhPubkeyPEM")] + #[serde(with = "Base64Standard")] + pub dh_pubkey_pem: Vec, + #[serde(with = "Base64Standard")] + pub nonce: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct DoUpdateResponse { + #[serde(with = "Base64Standard")] + pub config: Vec, + pub counter: u32, + #[serde(with = "Base64Standard")] + pub nonce: Vec, + #[serde(rename = "trustedKeys")] + #[serde(with = "Base64Standard")] + pub trusted_keys: Vec +} + +const ENROLL_ENDPOINT: &str = "/v2/enroll"; + +#[derive(Serialize, Deserialize)] +pub struct EnrollRequest { + pub code: String, + #[serde(rename = "dhPubkey")] + #[serde(with = "Base64Standard")] + pub dh_pubkey: Vec, + #[serde(rename = "edPubkey")] + #[serde(with = "Base64Standard")] + pub ed_pubkey: Vec, + pub timestamp: String +} + + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +pub enum EnrollResponse { + Success { + data: EnrollResponseData + }, + Error { + errors: APIErrors + } +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollResponseData { + #[serde(with = "Base64Standard")] + pub config: Vec, + #[serde(rename = "hostID")] + pub host_id: String, + pub counter: u32, + #[serde(rename = "trustedKeys")] + #[serde(with = "Base64Standard")] + pub trusted_keys: Vec, + pub organization: EnrollResponseDataOrg +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollResponseDataOrg { + pub id: String, + pub name: String +} + +#[derive(Serialize, Deserialize)] +pub struct APIError { + pub code: String, + pub message: String, + pub path: Option +} + +pub type APIErrors = Vec; \ No newline at end of file diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 3eacefe..2d1c2fc 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -3,7 +3,6 @@ use base64::Engine; use chrono::Local; use log::{error, info, warn}; use url::Url; -use trifid_pki::ca::NebulaCAPool; use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; use trifid_pki::rand_core::OsRng; @@ -14,7 +13,8 @@ use crate::daemon::ThreadMessageSender; pub enum APIWorkerMessage { Shutdown, - Enroll { code: String } + Enroll { code: String }, + Timer } pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver) { @@ -68,6 +68,22 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr info!("recv on command socket: shutdown, stopping"); break; }, + APIWorkerMessage::Timer => { + info!("updating config"); + let mut cdata = match load_cdata(&instance) { + Ok(c) => c, + Err(e) => { + error!("error in api worker thread: {}", e); + error!("APIWorker exiting with error"); + return; + } + }; + if cdata.host_id.is_none() { + info!("not enrolled, cannot perform config update"); + continue; + } + + }, APIWorkerMessage::Enroll { code } => { info!("recv on command socket: enroll {}", code); let mut cdata = match load_cdata(&instance) { diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index 052efc8..64ff697 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -8,6 +8,7 @@ use crate::config::load_config; use crate::main; use crate::nebulaworker::{nebulaworker_main, NebulaWorkerMessage}; use crate::socketworker::{socketworker_main, SocketWorkerMessage}; +use crate::timerworker::{timer_main, TimerWorkerMessage}; use crate::util::check_server_url; pub fn daemon_main(name: String, server: String) { @@ -28,11 +29,13 @@ pub fn daemon_main(name: String, server: String) { let (tx_api, rx_api) = mpsc::channel::(); let (tx_socket, rx_socket) = mpsc::channel::(); let (tx_nebula, rx_nebula) = mpsc::channel::(); + let (tx_timer, rx_timer) = mpsc::channel::(); let transmitter = ThreadMessageSender { socket_thread: tx_socket, api_thread: tx_api, - nebula_thread: tx_nebula + nebula_thread: tx_nebula, + timer_thread: tx_timer, }; let mainthread_transmitter = transmitter.clone(); @@ -59,6 +62,12 @@ pub fn daemon_main(name: String, server: String) { error!("Error sending shutdown message to socket worker thread: {}", e); } } + match mainthread_transmitter.timer_thread.send(TimerWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to timer worker thread: {}", e); + } + } }) { Ok(_) => (), Err(e) => { @@ -69,8 +78,6 @@ pub fn daemon_main(name: String, server: String) { info!("Starting API thread..."); - - let config_api = config.clone(); let transmitter_api = transmitter.clone(); let name_api = name.clone(); @@ -86,6 +93,12 @@ pub fn daemon_main(name: String, server: String) { nebulaworker_main(config_nebula, transmitter_nebula, rx_nebula); }); + info!("Starting timer thread..."); + let timer_transmitter = transmitter.clone(); + let timer_thread = thread::spawn(move || { + timer_main(timer_transmitter, rx_timer); + }); + info!("Starting socket worker thread..."); let socket_thread = thread::spawn(move || { socketworker_main(config, name.clone(), transmitter, rx_socket); @@ -111,6 +124,16 @@ pub fn daemon_main(name: String, server: String) { } info!("API thread exited"); + info!("Waiting for timer thread to exit..."); + match timer_thread.join() { + Ok(_) => (), + Err(_) => { + error!("Error waiting for timer thread to exit."); + std::process::exit(1); + } + } + info!("Timer thread exited"); + info!("Waiting for Nebula thread to exit..."); match nebula_thread.join() { Ok(_) => (), @@ -128,5 +151,6 @@ pub fn daemon_main(name: String, server: String) { pub struct ThreadMessageSender { pub socket_thread: Sender, pub api_thread: Sender, - pub nebula_thread: Sender + pub nebula_thread: Sender, + pub timer_thread: Sender } \ No newline at end of file diff --git a/tfclient/src/dnapi/mod.rs b/tfclient/src/dnapi/mod.rs new file mode 100644 index 0000000..e5697e1 --- /dev/null +++ b/tfclient/src/dnapi/mod.rs @@ -0,0 +1 @@ +/// Essentially a direct port of https://github.com/DefinedNet/dnapi \ No newline at end of file diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index 5331186..e8e11fa 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -25,6 +25,7 @@ pub mod apiworker; pub mod socketworker; pub mod api; pub mod socketclient; +pub mod timerworker; pub mod nebula_bin { include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs index 389edb8..6085826 100644 --- a/tfclient/src/socketworker.rs +++ b/tfclient/src/socketworker.rs @@ -12,6 +12,7 @@ use crate::apiworker::APIWorkerMessage; use crate::config::{load_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; use crate::nebulaworker::NebulaWorkerMessage; +use crate::timerworker::TimerWorkerMessage; pub enum SocketWorkerMessage { Shutdown @@ -214,6 +215,12 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm error!("Error sending shutdown message to socket worker thread: {}", e); } } + match transmitter.timer_thread.send(TimerWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to timer worker thread: {}", e); + } + } }, JsonMessage::GetHostID {} => { diff --git a/tfclient/src/timerworker.rs b/tfclient/src/timerworker.rs new file mode 100644 index 0000000..7fabba1 --- /dev/null +++ b/tfclient/src/timerworker.rs @@ -0,0 +1,47 @@ +use std::ops::Add; +use std::sync::mpsc::{Receiver, TryRecvError}; +use std::time::{Duration, SystemTime}; +use log::{error, info}; +use crate::apiworker::APIWorkerMessage; +use crate::daemon::ThreadMessageSender; + +pub enum TimerWorkerMessage { + Shutdown +} + +pub fn timer_main(tx: ThreadMessageSender, rx: Receiver) { + let mut api_reload_timer = SystemTime::now().add(Duration::from_secs(60)); + + loop { + match rx.try_recv() { + Ok(msg) => { + match msg { + TimerWorkerMessage::Shutdown => { + info!("recv on command socket: shutdown, stopping"); + break; + } + } + }, + Err(e) => { + match e { + TryRecvError::Empty => {} + TryRecvError::Disconnected => { + error!("timerworker command socket disconnected, shutting down to prevent orphaning"); + break; + } + } + } + } + + if SystemTime::now().gt(&api_reload_timer) { + info!("Timer triggered: API_RELOAD_TIMER"); + api_reload_timer = SystemTime::now().add(Duration::from_secs(60)); + match tx.api_thread.send(APIWorkerMessage::Timer) { + Ok(_) => (), + Err(e) => { + error!("Error sending timer message to api worker thread: {}", e); + } + } + } + } +} \ No newline at end of file From 553a95d6bccf89e3d70606b2eed0decd7c0338ed Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 29 Mar 2023 11:18:33 -0400 Subject: [PATCH 18/46] some work --- Cargo.lock | 27 +++-- Cargo.toml | 3 +- dnapi-rs/Cargo.toml | 22 ++++ dnapi-rs/src/client.rs | 1 + dnapi-rs/src/credentials.rs | 38 +++++++ dnapi-rs/src/lib.rs | 20 ++++ dnapi-rs/src/message.rs | 197 ++++++++++++++++++++++++++++++++++++ tfclient/Cargo.toml | 1 + tfclient/src/api.rs | 145 -------------------------- tfclient/src/apiworker.rs | 2 +- tfclient/src/dnapi/mod.rs | 1 - tfclient/src/main.rs | 1 - trifid-pki/src/cert.rs | 20 ++++ trifid-pki/src/test.rs | 12 ++- 14 files changed, 334 insertions(+), 156 deletions(-) create mode 100644 dnapi-rs/Cargo.toml create mode 100644 dnapi-rs/src/client.rs create mode 100644 dnapi-rs/src/credentials.rs create mode 100644 dnapi-rs/src/lib.rs create mode 100644 dnapi-rs/src/message.rs delete mode 100644 tfclient/src/api.rs delete mode 100644 tfclient/src/dnapi/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 375dbef..4bcaaa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,6 +619,20 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "dnapi-rs" +version = "0.1.0" +dependencies = [ + "base64 0.21.0", + "base64-serde", + "log", + "reqwest", + "serde", + "serde_json", + "trifid-pki", + "url", +] + [[package]] name = "dotenvy" version = "0.15.6" @@ -2061,18 +2075,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.158" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.158" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", @@ -2081,9 +2095,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" dependencies = [ "itoa", "ryu", @@ -2436,6 +2450,7 @@ dependencies = [ "clap", "ctrlc", "dirs 5.0.0", + "dnapi-rs", "flate2", "hex", "ipnet", diff --git a/Cargo.toml b/Cargo.toml index 1ad3cfa..31bce56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,6 @@ members = [ "trifid-api", "tfclient", - "trifid-pki" + "trifid-pki", + "dnapi-rs" ] \ No newline at end of file diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml new file mode 100644 index 0000000..8f4b645 --- /dev/null +++ b/dnapi-rs/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "dnapi-rs" +version = "0.1.0" +edition = "2021" +description = "A rust client for the Defined Networking API" +license = "AGPL-3.0-or-later" +documentation = "https://docs.rs/dnapi-rs" +homepage = "https://git.e3t.cc/~core/trifid" +repository = "https://git.e3t.cc/~core/trifid" + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0.159", features = ["derive"] } +base64-serde = "0.7.0" +log = "0.4.17" +reqwest = { version = "0.11.16", features = ["blocking", "json"] } +url = "2.3.1" +base64 = "0.21.0" +serde_json = "1.0.95" +trifid-pki = { version = "0.1.6", path = "../trifid-pki" } \ No newline at end of file diff --git a/dnapi-rs/src/client.rs b/dnapi-rs/src/client.rs new file mode 100644 index 0000000..4dc1f83 --- /dev/null +++ b/dnapi-rs/src/client.rs @@ -0,0 +1 @@ +//! Client structs to handle communication with the Defined Networking API. \ No newline at end of file diff --git a/dnapi-rs/src/credentials.rs b/dnapi-rs/src/credentials.rs new file mode 100644 index 0000000..4d55ad4 --- /dev/null +++ b/dnapi-rs/src/credentials.rs @@ -0,0 +1,38 @@ +//! Contains the `Credentials` struct, which contains all keys, IDs, organizations and other identity-related and security-related data that is persistent in a `Client` + +use std::error::Error; +use trifid_pki::cert::{deserialize_ed25519_public, serialize_ed25519_public}; +use trifid_pki::ed25519_dalek::{SigningKey, VerifyingKey}; + +/// Contains information necessary to make requests against the `DNClient` API. +pub struct Credentials { + /// The assigned Host ID that this client represents + pub host_id: String, + /// The ed25519 private key used to sign requests against the API + pub ed_privkey: SigningKey, + /// The counter used in the other API requests. It is unknown what the purpose of this is, but the original client persists it and it is needed for API calls. + pub counter: u32, + /// The set of trusted ed25519 keys that may be used by the API to sign API responses. + pub trusted_keys: Vec +} + +/// Converts an array of `VerifyingKey`s to a singular bundle of PEM-encoded keys +pub fn ed25519_public_keys_to_pem(keys: &[VerifyingKey]) -> Vec { + let mut res = vec![]; + + for key in keys { + res.append(&mut serialize_ed25519_public(&key.to_bytes())); + } + + res +} + +pub fn ed25519_public_keys_from_pem(pem: Vec) -> Result, Box> { + let mut keys = vec![]; + + for key in keys.chunks(32) { + + } + + Ok(keys) +} \ No newline at end of file diff --git a/dnapi-rs/src/lib.rs b/dnapi-rs/src/lib.rs new file mode 100644 index 0000000..3cc1117 --- /dev/null +++ b/dnapi-rs/src/lib.rs @@ -0,0 +1,20 @@ +//! # dnapi-rs +//! **dnapi-rs** is a Rust-native crate for interacting with the Defined Networking client API. It is a direct port of `dnapi`, an officially maintained API client by Defined Networking. +//! +//! This crate is maintained as a part of the trifid project. Check out the other crates in [the git repository](https://git.e3t.cc/~core/trifid). + +#![warn(clippy::pedantic)] +#![warn(clippy::nursery)] +#![deny(clippy::unwrap_used)] +#![deny(clippy::expect_used)] +#![deny(missing_docs)] +#![deny(clippy::missing_errors_doc)] +#![deny(clippy::missing_panics_doc)] +#![deny(clippy::missing_safety_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::too_many_lines)] +#![allow(clippy::module_name_repetitions)] + +pub mod message; +pub mod client; +pub mod credentials; \ No newline at end of file diff --git a/dnapi-rs/src/message.rs b/dnapi-rs/src/message.rs new file mode 100644 index 0000000..e52140e --- /dev/null +++ b/dnapi-rs/src/message.rs @@ -0,0 +1,197 @@ +//! Models for interacting with the Defined Networking API. + +use base64_serde::base64_serde_type; +use serde::{Serialize, Deserialize}; + +/// The version 1 `DNClient` API endpoint +const ENDPOINT_V1: &str = "/v1/dnclient"; + +base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); + +#[derive(Serialize, Deserialize)] +/// `RequestV1` is the version 1 `DNClient` request message. +pub struct RequestV1 { + /// Version is always 1 + pub version: i32, + #[serde(rename = "hostID")] + /// The Host ID of this dnclient instance + pub host_id: String, + /// The counter last returned by the server + pub counter: u32, + #[serde(with = "Base64Standard")] + /// A base64-encoded message + pub message: Vec, + #[serde(with = "Base64Standard")] + /// An ed25519 signature over the `message`, which can be verified with the host's previously enrolled ed25519 public key + pub signature: Vec +} + +#[derive(Serialize, Deserialize)] +/// `RequestWrapper` wraps a `DNClient` request message. It consists of a +/// type and value, with the type string indicating how to interpret the value blob. +pub struct RequestWrapper { + #[serde(rename = "type")] + /// The type of the message. Used to determine how `value` is encoded + pub message_type: String, + #[serde(with = "Base64Standard")] + /// A base64-encoded arbitrary message, the type of which is stated in `message_type` + pub value: Vec, + /// The timestamp of when this message was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or: + /// <4-digit year>--T::. + /// For example: + /// `2023-03-29T09:56:42.380006369-04:00` + /// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4` + pub timestamp: String +} + +#[derive(Serialize, Deserialize)] +/// `SignedResponseWrapper` contains a response message and a signature to validate inside `data`. +pub struct SignedResponseWrapper { + /// The response data contained in this message + pub data: SignedResponse +} + +#[derive(Serialize, Deserialize)] +/// `SignedResponse` contains a response message and a signature to validate. +pub struct SignedResponse { + /// The API version - always 1 + pub version: i32, + #[serde(with = "Base64Standard")] + /// The Base64-encoded message signed inside this message + pub message: Vec, + #[serde(with = "Base64Standard")] + /// The ed25519 signature over the `message` + pub signature: Vec +} + +#[derive(Serialize, Deserialize)] +/// `CheckForUpdateResponseWrapper` contains a response to `CheckForUpdate` inside "data." +pub struct CheckForUpdateResponseWrapper { + /// The response data contained in this message + pub data: CheckForUpdateResponse +} + +#[derive(Serialize, Deserialize)] +/// `CheckForUpdateResponse` is the response generated for a `CheckForUpdate` request. +pub struct CheckForUpdateResponse { + #[serde(rename = "updateAvailable")] + /// Set to true if a config update is available + pub update_available: bool +} + +#[derive(Serialize, Deserialize)] +/// `DoUpdateRequest` is the request sent for a `DoUpdate` request. +pub struct DoUpdateRequest { + #[serde(rename = "edPubkeyPEM")] + #[serde(with = "Base64Standard")] + /// The new ed25519 public key that should be used for future API requests + pub ed_pubkey_pem: Vec, + #[serde(rename = "dhPubkeyPEM")] + #[serde(with = "Base64Standard")] + /// The new ECDH public key that the Nebula certificate should be signed for + pub dh_pubkey_pem: Vec, + #[serde(with = "Base64Standard")] + /// A randomized value used to uniquely identify this request. + /// The original client uses a randomized, 16-byte value here, which dnapi-rs replicates + pub nonce: Vec +} + +#[derive(Serialize, Deserialize)] +/// A server response to a `DoUpdateRequest`, with the updated config and key information +pub struct DoUpdateResponse { + #[serde(with = "Base64Standard")] + /// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran + pub config: Vec, + /// The new config counter. It is unknown what the purpose of this is, but the original client keeps track of it and it is used later in the api + pub counter: u32, + #[serde(with = "Base64Standard")] + /// The same base64-encoded nonce that was sent in the `DoUpdateRequest`. + pub nonce: Vec, + #[serde(rename = "trustedKeys")] + #[serde(with = "Base64Standard")] + /// A new set of trusted ed25519 keys that can be used by the server to sign messages. + pub trusted_keys: Vec +} + +/// The REST enrollment endpoint +const ENROLL_ENDPOINT: &str = "/v2/enroll"; + +#[derive(Serialize, Deserialize)] +/// `EnrollRequest` is issued to the `ENROLL_ENDPOINT` to enroll this `dnclient` with a dnapi organization +pub struct EnrollRequest { + /// The enrollment code given by the API server. + pub code: String, + #[serde(rename = "dhPubkey")] + #[serde(with = "Base64Standard")] + /// The ECDH public-key that should be used to sign the Nebula certificate given to this node. + pub dh_pubkey: Vec, + #[serde(rename = "edPubkey")] + #[serde(with = "Base64Standard")] + /// The Ed25519 public-key that this node will use to sign messages sent to the API. + pub ed_pubkey: Vec, + /// The timestamp of when this request was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or: + /// <4-digit year>--T::. + /// For example: + /// `2023-03-29T09:56:42.380006369-04:00` + /// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4` + pub timestamp: String +} + + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +/// The response to an `EnrollRequest` +pub enum EnrollResponse { + /// A successful enrollment, with a `data` field pointing to an `EnrollResponseData` + Success { + /// The response data from this response + data: EnrollResponseData + }, + /// An unsuccessful enrollment, with an `errors` field pointing to an array of `APIError`s. + Error { + /// A list of `APIError`s that happened while trying to enroll. `APIErrors` is a type alias to `Vec` + errors: APIErrors + } +} + +#[derive(Serialize, Deserialize)] +/// The data included in an successful enrollment. +pub struct EnrollResponseData { + #[serde(with = "Base64Standard")] + /// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran + pub config: Vec, + #[serde(rename = "hostID")] + /// The server-side Host ID that this node now has. + pub host_id: String, + /// The new config counter. It is unknown what the purpose of this is, but the original client keeps track of it and it is used later in the api + pub counter: u32, + #[serde(rename = "trustedKeys")] + #[serde(with = "Base64Standard")] + /// A new set of trusted ed25519 keys that can be used by the server to sign messages. + pub trusted_keys: Vec, + /// The organization data that this node is now a part of + pub organization: EnrollResponseDataOrg +} + +#[derive(Serialize, Deserialize)] +/// The organization data that this node is now a part of +pub struct EnrollResponseDataOrg { + /// The organization ID that this node is now a part of + pub id: String, + /// The name of the organization that this node is now a part of + pub name: String +} + +#[derive(Serialize, Deserialize)] +/// `APIError` represents a single error returned in an API error response. +pub struct APIError { + /// The error code + pub code: String, + /// The human-readable error message + pub message: String, + /// An optional path to where the error occured + pub path: Option +} + +/// A type alias to a array of `APIErrors`. Just for parity with dnapi. +pub type APIErrors = Vec; \ No newline at end of file diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index df081aa..56fa277 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -24,6 +24,7 @@ base64 = "0.21.0" chrono = "0.4.24" ipnet = "2.7.1" base64-serde = "0.7.0" +dnapi-rs = { version = "0.1.0", path = "../dnapi-rs" } [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/api.rs b/tfclient/src/api.rs deleted file mode 100644 index fd3e77e..0000000 --- a/tfclient/src/api.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::error::Error; -use base64_serde::base64_serde_type; -use log::trace; -use reqwest::blocking::Client; -use serde::{Serialize, Deserialize}; -use url::Url; - -const ENDPOINT_V1: &str = "/v1/dnclient"; - -base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); - -pub fn enroll(server: &Url, request: &EnrollRequest) -> Result> { - let endpoint = server.join("/v2/enroll")?; - let client = Client::new(); - - let text = serde_json::to_string(request)?; - - trace!("sending enroll: {}", text); - - let resp = client.post(endpoint).body(text).send()?; - Ok(resp.json()?) -} - -#[derive(Serialize, Deserialize)] -pub struct RequestV1 { - pub version: i32, - #[serde(rename = "hostID")] - pub host_id: String, - pub counter: u32, - pub message: String, - #[serde(with = "Base64Standard")] - pub signature: Vec -} - -#[derive(Serialize, Deserialize)] -pub struct RequestWrapper { - #[serde(rename = "type")] - pub message_type: String, - #[serde(with = "Base64Standard")] - pub value: Vec, - pub timestamp: String -} - -#[derive(Serialize, Deserialize)] -pub struct SignedResponseWrapper { - pub data: SignedResponse -} - -#[derive(Serialize, Deserialize)] -pub struct SignedResponse { - pub version: i32, - #[serde(with = "Base64Standard")] - pub message: Vec, - #[serde(with = "Base64Standard")] - pub signature: Vec -} - -#[derive(Serialize, Deserialize)] -pub struct CheckForUpdateResponseWrapper { - pub data: CheckForUpdateResponse -} - -#[derive(Serialize, Deserialize)] -pub struct CheckForUpdateResponse { - #[serde(rename = "updateAvailable")] - pub update_available: bool -} - -#[derive(Serialize, Deserialize)] -pub struct DoUpdateRequest { - #[serde(rename = "edPubkeyPEM")] - #[serde(with = "Base64Standard")] - pub ed_pubkey_pem: Vec, - #[serde(rename = "dhPubkeyPEM")] - #[serde(with = "Base64Standard")] - pub dh_pubkey_pem: Vec, - #[serde(with = "Base64Standard")] - pub nonce: Vec -} - -#[derive(Serialize, Deserialize)] -pub struct DoUpdateResponse { - #[serde(with = "Base64Standard")] - pub config: Vec, - pub counter: u32, - #[serde(with = "Base64Standard")] - pub nonce: Vec, - #[serde(rename = "trustedKeys")] - #[serde(with = "Base64Standard")] - pub trusted_keys: Vec -} - -const ENROLL_ENDPOINT: &str = "/v2/enroll"; - -#[derive(Serialize, Deserialize)] -pub struct EnrollRequest { - pub code: String, - #[serde(rename = "dhPubkey")] - #[serde(with = "Base64Standard")] - pub dh_pubkey: Vec, - #[serde(rename = "edPubkey")] - #[serde(with = "Base64Standard")] - pub ed_pubkey: Vec, - pub timestamp: String -} - - -#[derive(Serialize, Deserialize)] -#[serde(untagged)] -pub enum EnrollResponse { - Success { - data: EnrollResponseData - }, - Error { - errors: APIErrors - } -} - -#[derive(Serialize, Deserialize)] -pub struct EnrollResponseData { - #[serde(with = "Base64Standard")] - pub config: Vec, - #[serde(rename = "hostID")] - pub host_id: String, - pub counter: u32, - #[serde(rename = "trustedKeys")] - #[serde(with = "Base64Standard")] - pub trusted_keys: Vec, - pub organization: EnrollResponseDataOrg -} - -#[derive(Serialize, Deserialize)] -pub struct EnrollResponseDataOrg { - pub id: String, - pub name: String -} - -#[derive(Serialize, Deserialize)] -pub struct APIError { - pub code: String, - pub message: String, - pub path: Option -} - -pub type APIErrors = Vec; \ No newline at end of file diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index b410f4a..a5f85ce 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -8,7 +8,7 @@ use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; use trifid_pki::rand_core::OsRng; use trifid_pki::x25519_dalek::StaticSecret; -use crate::api::{APIResponse, enroll, EnrollRequest}; +use crate::message::{APIResponse, enroll, EnrollRequest}; use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; diff --git a/tfclient/src/dnapi/mod.rs b/tfclient/src/dnapi/mod.rs deleted file mode 100644 index e5697e1..0000000 --- a/tfclient/src/dnapi/mod.rs +++ /dev/null @@ -1 +0,0 @@ -/// Essentially a direct port of https://github.com/DefinedNet/dnapi \ No newline at end of file diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index e8e11fa..4c0adfe 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -23,7 +23,6 @@ pub mod config; pub mod service; pub mod apiworker; pub mod socketworker; -pub mod api; pub mod socketclient; pub mod timerworker; diff --git a/trifid-pki/src/cert.rs b/trifid-pki/src/cert.rs index c5495e6..190baf0 100644 --- a/trifid-pki/src/cert.rs +++ b/trifid-pki/src/cert.rs @@ -313,6 +313,26 @@ pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result, Box Result>, Box> { + let mut keys = vec![]; + let pems = pem::parse_many(bytes)?; + + for pem in pems { + if pem.tag != ED25519_PUBLIC_KEY_BANNER { + return Err(KeyError::WrongPemTag.into()) + } + if pem.contents.len() != 64 { + return Err(KeyError::Not64Bytes.into()) + } + keys.push(pem.contents); + } + + Ok(keys) +} + impl NebulaCertificate { /// Sign a nebula certificate with the provided private key /// # Errors diff --git a/trifid-pki/src/test.rs b/trifid-pki/src/test.rs index 55f465e..3d328dc 100644 --- a/trifid-pki/src/test.rs +++ b/trifid-pki/src/test.rs @@ -6,7 +6,7 @@ use std::net::Ipv4Addr; use std::ops::{Add, Sub}; use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; use ipnet::Ipv4Net; -use crate::cert::{CertificateValidity, deserialize_ed25519_private, deserialize_ed25519_public, deserialize_nebula_certificate, deserialize_nebula_certificate_from_pem, deserialize_x25519_private, deserialize_x25519_public, NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private, serialize_ed25519_public, serialize_x25519_private, serialize_x25519_public}; +use crate::cert::{CertificateValidity, deserialize_ed25519_private, deserialize_ed25519_public, deserialize_ed25519_public_many, deserialize_nebula_certificate, deserialize_nebula_certificate_from_pem, deserialize_x25519_private, deserialize_x25519_public, NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private, serialize_ed25519_public, serialize_x25519_private, serialize_x25519_public}; use std::str::FromStr; use ed25519_dalek::{SigningKey, VerifyingKey}; use quick_protobuf::{MessageWrite, Writer}; @@ -300,6 +300,16 @@ fn ed25519_serialization() { assert!(deserialize_ed25519_private(&[0u8; 32]).is_err()); assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes); assert!(deserialize_ed25519_public(&[0u8; 32]).is_err()); + + let mut bytes = vec![]; + bytes.append(&mut serialize_ed25519_public(&[0u8; 64])); + bytes.append(&mut serialize_ed25519_public(&[1u8; 64])); + let deser = deserialize_ed25519_public_many(&bytes).unwrap(); + assert_eq!(deser[0], [0u8; 64]); + assert_eq!(deser[1], [1u8; 64]); + + bytes.append(&mut serialize_ed25519_public(&[1u8; 63])); + deserialize_ed25519_public_many(&bytes).unwrap_err(); } #[test] From 201374fba432cbe03a21e243727a42707fbd5511 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 29 Mar 2023 12:44:54 -0400 Subject: [PATCH 19/46] finish porting credentials.go --- dnapi-rs/src/credentials.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/dnapi-rs/src/credentials.rs b/dnapi-rs/src/credentials.rs index 4d55ad4..01b3ce7 100644 --- a/dnapi-rs/src/credentials.rs +++ b/dnapi-rs/src/credentials.rs @@ -1,7 +1,7 @@ //! Contains the `Credentials` struct, which contains all keys, IDs, organizations and other identity-related and security-related data that is persistent in a `Client` use std::error::Error; -use trifid_pki::cert::{deserialize_ed25519_public, serialize_ed25519_public}; +use trifid_pki::cert::{deserialize_ed25519_public_many, serialize_ed25519_public}; use trifid_pki::ed25519_dalek::{SigningKey, VerifyingKey}; /// Contains information necessary to make requests against the `DNClient` API. @@ -27,11 +27,16 @@ pub fn ed25519_public_keys_to_pem(keys: &[VerifyingKey]) -> Vec { res } -pub fn ed25519_public_keys_from_pem(pem: Vec) -> Result, Box> { +/// Converts a set of PEM-encoded ed25519 public keys, and converts them into an array of `VerifyingKey`s. +/// # Errors +/// This function will return an error if the PEM could not be decoded, or if any of the encoded keys are invalid. +pub fn ed25519_public_keys_from_pem(pem: &[u8]) -> Result, Box> { + let pems = deserialize_ed25519_public_many(pem)?; let mut keys = vec![]; - for key in keys.chunks(32) { - + #[allow(clippy::unwrap_used)] + for pem in pems { + keys.push(VerifyingKey::from_bytes(&pem.try_into().unwrap_or_else(|_| unreachable!()))?); } Ok(keys) From eaf4cee4ee3be3feb2051209df8b72be837a5a5b Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 29 Mar 2023 13:13:50 -0400 Subject: [PATCH 20/46] port crypto.go --- Cargo.lock | 1 + dnapi-rs/Cargo.toml | 3 ++- dnapi-rs/src/crypto.rs | 41 +++++++++++++++++++++++++++++++++++++++++ dnapi-rs/src/lib.rs | 3 ++- 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 dnapi-rs/src/crypto.rs diff --git a/Cargo.lock b/Cargo.lock index 4bcaaa2..22d5dcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -626,6 +626,7 @@ dependencies = [ "base64 0.21.0", "base64-serde", "log", + "rand", "reqwest", "serde", "serde_json", diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 8f4b645..87b08d6 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -19,4 +19,5 @@ reqwest = { version = "0.11.16", features = ["blocking", "json"] } url = "2.3.1" base64 = "0.21.0" serde_json = "1.0.95" -trifid-pki = { version = "0.1.6", path = "../trifid-pki" } \ No newline at end of file +trifid-pki = { version = "0.1.6", path = "../trifid-pki" } +rand = "0.8.5" \ No newline at end of file diff --git a/dnapi-rs/src/crypto.rs b/dnapi-rs/src/crypto.rs new file mode 100644 index 0000000..c05e814 --- /dev/null +++ b/dnapi-rs/src/crypto.rs @@ -0,0 +1,41 @@ +//! Functions for generating keys and nonces for use in API calls + +use rand::Rng; +use rand::rngs::OsRng; +use trifid_pki::cert::{serialize_x25519_private, serialize_x25519_public}; +use trifid_pki::ed25519_dalek::{SigningKey, VerifyingKey}; +use trifid_pki::x25519_dalek::{PublicKey, StaticSecret}; + +/// Generate a new random set of Nebula (Diffie-Hellman) and Ed25519 (API calls) keys for use in your client +pub fn new_keys() -> (Vec, Vec, VerifyingKey, SigningKey) { + let (dh_pub, dh_priv) = new_nebula_keypair(); + let (ed_pub, ed_priv) = new_ed25519_keypair(); + (dh_pub, dh_priv, ed_pub, ed_priv) +} + +/// Generate a new PEM-encoded Nebula keypair +pub fn new_nebula_keypair() -> (Vec, Vec) { + let (pub_key, priv_key) = new_x25519_keypair(); + let pub_key_encoded = serialize_x25519_public(&pub_key); + let priv_key_encoded = serialize_x25519_private(&priv_key); + (pub_key_encoded, priv_key_encoded) +} + +/// Generate a new 32-byte X25519 keypair +pub fn new_x25519_keypair() -> ([u8; 32], [u8; 32]) { + let priv_key = StaticSecret::new(&mut OsRng); + let pub_key = PublicKey::from(&priv_key); + (pub_key.to_bytes(), priv_key.to_bytes()) +} + +/// Generate a new random Ed25519 signing keypair for signing API calls +pub fn new_ed25519_keypair() -> (VerifyingKey, SigningKey) { + let secret = SigningKey::generate(&mut OsRng); + let public = secret.verifying_key(); + (public, secret) +} + +/// Generates a 16-byte random nonce for use in API calls +pub fn nonce() -> [u8; 16] { + rand::thread_rng().gen() +} \ No newline at end of file diff --git a/dnapi-rs/src/lib.rs b/dnapi-rs/src/lib.rs index 3cc1117..8603e30 100644 --- a/dnapi-rs/src/lib.rs +++ b/dnapi-rs/src/lib.rs @@ -17,4 +17,5 @@ pub mod message; pub mod client; -pub mod credentials; \ No newline at end of file +pub mod credentials; +pub mod crypto; \ No newline at end of file From 5cbab46d3b99780639c4acf8a5ecaae48f046f01 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 29 Mar 2023 14:31:07 -0400 Subject: [PATCH 21/46] some work on porting client --- Cargo.lock | 1 + dnapi-rs/Cargo.toml | 8 ++- dnapi-rs/src/client.rs | 1 - dnapi-rs/src/client_async.rs | 0 dnapi-rs/src/client_blocking.rs | 102 ++++++++++++++++++++++++++++++++ dnapi-rs/src/lib.rs | 11 ++++ dnapi-rs/src/message.rs | 4 +- 7 files changed, 123 insertions(+), 4 deletions(-) delete mode 100644 dnapi-rs/src/client.rs create mode 100644 dnapi-rs/src/client_async.rs create mode 100644 dnapi-rs/src/client_blocking.rs diff --git a/Cargo.lock b/Cargo.lock index 22d5dcf..7f829bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,6 +625,7 @@ version = "0.1.0" dependencies = [ "base64 0.21.0", "base64-serde", + "chrono", "log", "rand", "reqwest", diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 87b08d6..682e4e6 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -20,4 +20,10 @@ url = "2.3.1" base64 = "0.21.0" serde_json = "1.0.95" trifid-pki = { version = "0.1.6", path = "../trifid-pki" } -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" +chrono = "0.4.24" + +[features] +default = ["blocking"] +blocking = ["reqwest/blocking"] +async = [] \ No newline at end of file diff --git a/dnapi-rs/src/client.rs b/dnapi-rs/src/client.rs deleted file mode 100644 index 4dc1f83..0000000 --- a/dnapi-rs/src/client.rs +++ /dev/null @@ -1 +0,0 @@ -//! Client structs to handle communication with the Defined Networking API. \ No newline at end of file diff --git a/dnapi-rs/src/client_async.rs b/dnapi-rs/src/client_async.rs new file mode 100644 index 0000000..e69de29 diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs new file mode 100644 index 0000000..780cc32 --- /dev/null +++ b/dnapi-rs/src/client_blocking.rs @@ -0,0 +1,102 @@ +//! Client structs to handle communication with the Defined Networking API. This is the blocking client API - if you want async instead, set no-default-features and enable the async feature instead. + +use std::error::Error; +use chrono::Local; +use log::{debug, error}; +use url::Url; +use trifid_pki::cert::serialize_ed25519_public; +use crate::credentials::{Credentials, ed25519_public_keys_from_pem}; +use crate::crypto::new_keys; +use crate::message::{ENROLL_ENDPOINT, EnrollRequest, EnrollResponse}; + +/// A type alias to abstract return types +pub type NebulaConfig = Vec; + +/// A type alias to abstract DH private keys +pub type DHPrivateKeyPEM = Vec; + +/// A combination of persistent data and HTTP client used for communicating with the API. +pub struct Client { + http_client: reqwest::blocking::Client, + server_url: Url +} + +/// A struct containing organization metadata returned as a result of enrollment +pub struct EnrollMeta { + /// The server organization ID this node is now a member of + pub organization_id: String, + /// The server organization name this node is now a member of + pub organization_name: String +} + +impl Client { + /// Create a new `Client` configured with the given User-Agent and API base. + /// # Errors + /// This function will return an error if the reqwest Client could not be created. + pub fn new(user_agent: String, api_base: Url) -> Result> { + let client = reqwest::blocking::Client::builder().user_agent(user_agent).build()?; + Ok(Self { + http_client: client, + server_url: api_base + }) + } + + /// Issues an enrollment request against the REST API using the given enrollment code, passing along a + /// locally generated DH X25519 Nebula key to be signed by the CA, and an Ed25519 key for future API + /// authentication. On success it returns the Nebula config generated by the server, a Nebula private key PEM, + /// credentials to be used for future DN API requests, and an object containing organization information. + /// # Errors + /// This function will return an error in any of the following situations: + /// - the server_url is invalid + /// - the HTTP request fails + /// - the HTTP response is missing X-Request-ID + /// - X-Request-ID isn't valid UTF-8 + /// - the server returns an error + /// - the server returns invalid JSON + /// - the `trusted_keys` field is invalid + pub fn enroll(&self, code: &str) -> Result<(NebulaConfig, DHPrivateKeyPEM, Credentials, EnrollMeta), Box> { + debug!("making enrollment request to API {{server: {}, code: {}}}", self.server_url, code); + + let (dh_pubkey_pem, dh_privkey_pem, ed_pubkey, ed_privkey) = new_keys(); + + let req_json = serde_json::to_string(&EnrollRequest { + code: code.to_string(), + dh_pubkey: dh_pubkey_pem, + ed_pubkey: serialize_ed25519_public(ed_pubkey.as_bytes()), + timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), + })?; + + let resp = self.http_client.post(self.server_url.join(ENROLL_ENDPOINT)?).body(req_json).send()?; + + let req_id = resp.headers().get("X-Request-ID").ok_or("Response missing X-Request-ID")?.to_str()?; + debug!("enrollment request complete {{req_id: {}}}", req_id); + + let resp: EnrollResponse = resp.json()?; + + let r = match resp { + EnrollResponse::Success { data } => data, + EnrollResponse::Error { errors } => { + error!("unexpected error during enrollment: {}", errors[0].message); + return Err(errors[0].message.clone().into()); + } + }; + + let meta = EnrollMeta { + organization_id: r.organization.id, + organization_name: r.organization.name, + }; + + let trusted_keys = ed25519_public_keys_from_pem(&r.trusted_keys)?; + + let creds = Credentials { + host_id: r.host_id, + ed_privkey, + counter: r.counter, + trusted_keys, + }; + + Ok((r.config, dh_privkey_pem, creds, meta)) + } + + +} \ No newline at end of file diff --git a/dnapi-rs/src/lib.rs b/dnapi-rs/src/lib.rs index 8603e30..1317543 100644 --- a/dnapi-rs/src/lib.rs +++ b/dnapi-rs/src/lib.rs @@ -15,7 +15,18 @@ #![allow(clippy::too_many_lines)] #![allow(clippy::module_name_repetitions)] +#[cfg(all(feature = "blocking", feature = "async"))] +compile_error!("Cannot compile with both blocking and async features at the same time. Please pick one or the other."); + pub mod message; + +#[cfg(feature = "blocking")] +#[path = "client_blocking.rs"] pub mod client; + +#[cfg(feature = "async")] +#[path = "client_async.rs"] +pub mod client; + pub mod credentials; pub mod crypto; \ No newline at end of file diff --git a/dnapi-rs/src/message.rs b/dnapi-rs/src/message.rs index e52140e..31f4a29 100644 --- a/dnapi-rs/src/message.rs +++ b/dnapi-rs/src/message.rs @@ -4,7 +4,7 @@ use base64_serde::base64_serde_type; use serde::{Serialize, Deserialize}; /// The version 1 `DNClient` API endpoint -const ENDPOINT_V1: &str = "/v1/dnclient"; +pub const ENDPOINT_V1: &str = "/v1/dnclient"; base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); @@ -114,7 +114,7 @@ pub struct DoUpdateResponse { } /// The REST enrollment endpoint -const ENROLL_ENDPOINT: &str = "/v2/enroll"; +pub const ENROLL_ENDPOINT: &str = "/v2/enroll"; #[derive(Serialize, Deserialize)] /// `EnrollRequest` is issued to the `ENROLL_ENDPOINT` to enroll this `dnclient` with a dnapi organization From 244bc3babb300580bc034d1e96a62b575a983eb5 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 29 Mar 2023 16:55:51 -0400 Subject: [PATCH 22/46] work on client --- dnapi-rs/src/client_blocking.rs | 43 ++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index 780cc32..f568f8e 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -3,11 +3,13 @@ use std::error::Error; use chrono::Local; use log::{debug, error}; +use reqwest::StatusCode; use url::Url; use trifid_pki::cert::serialize_ed25519_public; +use trifid_pki::ed25519_dalek::{Signer, SigningKey}; use crate::credentials::{Credentials, ed25519_public_keys_from_pem}; use crate::crypto::new_keys; -use crate::message::{ENROLL_ENDPOINT, EnrollRequest, EnrollResponse}; +use crate::message::{ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper}; /// A type alias to abstract return types pub type NebulaConfig = Vec; @@ -98,5 +100,44 @@ impl Client { Ok((r.config, dh_privkey_pem, creds, meta)) } + /// Wraps and signs the given req_type and value, and then makes the API call. + /// On success, returns the response body. + /// # Errors + /// This function will return an error if: + /// - serialization in any step fails + /// - if the server_url is invalid + /// - if the request could not be sent + pub fn post_dnclient(&self, req_type: &str, value: &[u8], host_id: &str, counter: u32, ed_privkey: SigningKey) -> Result, Box> { + let encoded_msg = serde_json::to_string(&RequestWrapper { + message_type: req_type.to_string(), + value: value.to_vec(), + timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), + })?; + let encoded_msg_bytes = encoded_msg.into_bytes(); + let signature = ed_privkey.sign(&encoded_msg_bytes).to_vec(); + let body = RequestV1 { + version: 1, + host_id: host_id.to_string(), + counter, + message: encoded_msg_bytes, + signature, + }; + let post_body = serde_json::to_string(&body)?; + + let resp = self.http_client.post(self.server_url.join(ENDPOINT_V1)?).body(post_body).send()?; + + match resp.status() { + StatusCode::OK => { + Ok(resp.bytes()?.to_vec()) + }, + StatusCode::FORBIDDEN => { + Err("Forbidden".into()) + }, + _ => { + error!("dnclient endpoint returned bad status code {}", resp.status()); + Err("dnclient endpoint returned error".into()) + } + } + } } \ No newline at end of file From e37d768eade9b0efab399dc85a451a94067d8ffd Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 29 Mar 2023 16:58:20 -0400 Subject: [PATCH 23/46] Remove submodule --- .gitmodules | 3 --- dnapi | 1 - 2 files changed, 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 dnapi diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 5eabc69..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "dnapi"] - path = dnapi - url = https://github.com/DefinedNet/dnapi diff --git a/dnapi b/dnapi deleted file mode 160000 index 6f56f05..0000000 --- a/dnapi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6f56f055f9912755979e27942a91efcf794a82dc From f301684c3ac55bda40acdafe2bcd347f247744b7 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 17:42:16 -0400 Subject: [PATCH 24/46] finish porting dnapi --- .idea/trifid.iml | 1 + dnapi-rs/src/client_blocking.rs | 68 +++++++++++++++++++++++++++++++-- dnapi-rs/src/message.rs | 5 +++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/.idea/trifid.iml b/.idea/trifid.iml index bef05f9..ecc578a 100644 --- a/.idea/trifid.iml +++ b/.idea/trifid.iml @@ -5,6 +5,7 @@ + diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index f568f8e..8b04920 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -6,10 +6,10 @@ use log::{debug, error}; use reqwest::StatusCode; use url::Url; use trifid_pki::cert::serialize_ed25519_public; -use trifid_pki::ed25519_dalek::{Signer, SigningKey}; +use trifid_pki::ed25519_dalek::{Signature, Signer, SigningKey, Verifier}; use crate::credentials::{Credentials, ed25519_public_keys_from_pem}; -use crate::crypto::new_keys; -use crate::message::{ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper}; +use crate::crypto::{new_keys, nonce}; +use crate::message::{CHECK_FOR_UPDATE, CheckForUpdateResponseWrapper, DO_UPDATE, DoUpdateRequest, DoUpdateResponse, ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper, SignedResponseWrapper}; /// A type alias to abstract return types pub type NebulaConfig = Vec; @@ -100,6 +100,66 @@ impl Client { Ok((r.config, dh_privkey_pem, creds, meta)) } + /// Send a signed message to the DNClient API to learn if there is a new configuration available. + pub fn check_for_update(&self, creds: &Credentials) -> Result> { + let body = self.post_dnclient(CHECK_FOR_UPDATE, &[], &creds.host_id, creds.counter, &creds.ed_privkey)?; + + let result: CheckForUpdateResponseWrapper = serde_json::from_slice(&body)?; + + Ok(result.data.update_available) + } + + /// Send a signed message to the DNClient API to fetch the new configuration update. During this call a new + /// DH X25519 keypair is generated for the new Nebula certificate as well as a new Ed25519 keypair for DNClient API + /// communication. On success it returns the new config, a Nebula private key PEM to be inserted into the config + /// and new DNClient API credentials + pub fn do_update(&self, creds: &Credentials) -> Result<(NebulaConfig, DHPrivateKeyPEM, Credentials), Box> { + let (dh_pubkey_pem, dh_privkey_pem, ed_pubkey, ed_privkey) = new_keys(); + + let update_keys = DoUpdateRequest { + ed_pubkey_pem: serialize_ed25519_public(ed_pubkey.as_bytes()), + dh_pubkey_pem, + nonce: nonce().to_vec(), + }; + + let update_keys_blob = serde_json::to_vec(&update_keys)?; + + let resp = self.post_dnclient(DO_UPDATE, &update_keys_blob, &creds.host_id, creds.counter, &creds.ed_privkey)?; + + let result_wrapper: SignedResponseWrapper = serde_json::from_slice(&resp)?; + + let mut valid = false; + + for ca_pubkey in creds.trusted_keys { + if ca_pubkey.verify(&result_wrapper.data.message, &Signature::from_slice(&result_wrapper.data.signature)?).is_ok() { + valid = true; + break; + } + } + + if !valid { + return Err("Failed to verify signed API result".into()) + } + + let result: DoUpdateResponse = serde_json::from_slice(&result_wrapper.data.message)?; + + if result.nonce != update_keys.nonce { + error!("nonce mismatch between request {:x?} and response {:x?}", result.nonce, update_keys.nonce); + return Err(format!("nonce mismatch between request and response").into()) + } + + let trusted_keys = ed25519_public_keys_from_pem(&result.trusted_keys)?; + + let new_creds = Credentials { + host_id: creds.host_id.clone(), + ed_privkey, + counter: result.counter, + trusted_keys, + }; + + Ok((result.config, dh_privkey_pem, new_creds)) + } + /// Wraps and signs the given req_type and value, and then makes the API call. /// On success, returns the response body. /// # Errors @@ -107,7 +167,7 @@ impl Client { /// - serialization in any step fails /// - if the server_url is invalid /// - if the request could not be sent - pub fn post_dnclient(&self, req_type: &str, value: &[u8], host_id: &str, counter: u32, ed_privkey: SigningKey) -> Result, Box> { + pub fn post_dnclient(&self, req_type: &str, value: &[u8], host_id: &str, counter: u32, ed_privkey: &SigningKey) -> Result, Box> { let encoded_msg = serde_json::to_string(&RequestWrapper { message_type: req_type.to_string(), value: value.to_vec(), diff --git a/dnapi-rs/src/message.rs b/dnapi-rs/src/message.rs index 31f4a29..b262105 100644 --- a/dnapi-rs/src/message.rs +++ b/dnapi-rs/src/message.rs @@ -6,6 +6,11 @@ use serde::{Serialize, Deserialize}; /// The version 1 `DNClient` API endpoint pub const ENDPOINT_V1: &str = "/v1/dnclient"; +/// The CheckForUpdate message type +pub const CHECK_FOR_UPDATE: &str = "CheckForUpdate"; +/// The DoUpdate message type +pub const DO_UPDATE: &str = "DoUpdate"; + base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); #[derive(Serialize, Deserialize)] From 9cea470b43ddec5ab1efbb8c749b839f1c2af37f Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 17:46:52 -0400 Subject: [PATCH 25/46] fixes --- dnapi-rs/src/client_blocking.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index 8b04920..46fe4e8 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -101,6 +101,8 @@ impl Client { } /// Send a signed message to the DNClient API to learn if there is a new configuration available. + /// # Errors + /// This function returns an error if the dnclient request fails, or the server returns invalid data. pub fn check_for_update(&self, creds: &Credentials) -> Result> { let body = self.post_dnclient(CHECK_FOR_UPDATE, &[], &creds.host_id, creds.counter, &creds.ed_privkey)?; @@ -113,6 +115,13 @@ impl Client { /// DH X25519 keypair is generated for the new Nebula certificate as well as a new Ed25519 keypair for DNClient API /// communication. On success it returns the new config, a Nebula private key PEM to be inserted into the config /// and new DNClient API credentials + /// # Errors + /// This function returns an error in any of the following scenarios: + /// - if the message could not be serialized + /// - if the request fails + /// - if the response could not be deserialized + /// - if the signature is invalid + /// - if the keys are invalid pub fn do_update(&self, creds: &Credentials) -> Result<(NebulaConfig, DHPrivateKeyPEM, Credentials), Box> { let (dh_pubkey_pem, dh_privkey_pem, ed_pubkey, ed_privkey) = new_keys(); @@ -130,7 +139,7 @@ impl Client { let mut valid = false; - for ca_pubkey in creds.trusted_keys { + for ca_pubkey in &creds.trusted_keys { if ca_pubkey.verify(&result_wrapper.data.message, &Signature::from_slice(&result_wrapper.data.signature)?).is_ok() { valid = true; break; From ec7b4a8ea4c07eee00ddf7200c4ec095fc800f59 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 17:49:02 -0400 Subject: [PATCH 26/46] cleanup --- dnapi-rs/src/client_blocking.rs | 16 ++++++++-------- dnapi-rs/src/crypto.rs | 2 +- dnapi-rs/src/message.rs | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index 46fe4e8..f75c21c 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -49,7 +49,7 @@ impl Client { /// credentials to be used for future DN API requests, and an object containing organization information. /// # Errors /// This function will return an error in any of the following situations: - /// - the server_url is invalid + /// - the `server_url` is invalid /// - the HTTP request fails /// - the HTTP response is missing X-Request-ID /// - X-Request-ID isn't valid UTF-8 @@ -100,7 +100,7 @@ impl Client { Ok((r.config, dh_privkey_pem, creds, meta)) } - /// Send a signed message to the DNClient API to learn if there is a new configuration available. + /// Send a signed message to the `DNClient` API to learn if there is a new configuration available. /// # Errors /// This function returns an error if the dnclient request fails, or the server returns invalid data. pub fn check_for_update(&self, creds: &Credentials) -> Result> { @@ -111,10 +111,10 @@ impl Client { Ok(result.data.update_available) } - /// Send a signed message to the DNClient API to fetch the new configuration update. During this call a new - /// DH X25519 keypair is generated for the new Nebula certificate as well as a new Ed25519 keypair for DNClient API + /// Send a signed message to the `DNClient` API to fetch the new configuration update. During this call a new + /// DH X25519 keypair is generated for the new Nebula certificate as well as a new Ed25519 keypair for `DNClient` API /// communication. On success it returns the new config, a Nebula private key PEM to be inserted into the config - /// and new DNClient API credentials + /// and new `DNClient` API credentials /// # Errors /// This function returns an error in any of the following scenarios: /// - if the message could not be serialized @@ -154,7 +154,7 @@ impl Client { if result.nonce != update_keys.nonce { error!("nonce mismatch between request {:x?} and response {:x?}", result.nonce, update_keys.nonce); - return Err(format!("nonce mismatch between request and response").into()) + return Err("nonce mismatch between request and response".into()) } let trusted_keys = ed25519_public_keys_from_pem(&result.trusted_keys)?; @@ -169,12 +169,12 @@ impl Client { Ok((result.config, dh_privkey_pem, new_creds)) } - /// Wraps and signs the given req_type and value, and then makes the API call. + /// Wraps and signs the given `req_type` and value, and then makes the API call. /// On success, returns the response body. /// # Errors /// This function will return an error if: /// - serialization in any step fails - /// - if the server_url is invalid + /// - if the `server_url` is invalid /// - if the request could not be sent pub fn post_dnclient(&self, req_type: &str, value: &[u8], host_id: &str, counter: u32, ed_privkey: &SigningKey) -> Result, Box> { let encoded_msg = serde_json::to_string(&RequestWrapper { diff --git a/dnapi-rs/src/crypto.rs b/dnapi-rs/src/crypto.rs index c05e814..d5f87d9 100644 --- a/dnapi-rs/src/crypto.rs +++ b/dnapi-rs/src/crypto.rs @@ -23,7 +23,7 @@ pub fn new_nebula_keypair() -> (Vec, Vec) { /// Generate a new 32-byte X25519 keypair pub fn new_x25519_keypair() -> ([u8; 32], [u8; 32]) { - let priv_key = StaticSecret::new(&mut OsRng); + let priv_key = StaticSecret::new(OsRng); let pub_key = PublicKey::from(&priv_key); (pub_key.to_bytes(), priv_key.to_bytes()) } diff --git a/dnapi-rs/src/message.rs b/dnapi-rs/src/message.rs index b262105..ce1e891 100644 --- a/dnapi-rs/src/message.rs +++ b/dnapi-rs/src/message.rs @@ -6,9 +6,9 @@ use serde::{Serialize, Deserialize}; /// The version 1 `DNClient` API endpoint pub const ENDPOINT_V1: &str = "/v1/dnclient"; -/// The CheckForUpdate message type +/// The `CheckForUpdate` message type pub const CHECK_FOR_UPDATE: &str = "CheckForUpdate"; -/// The DoUpdate message type +/// The `DoUpdate` message type pub const DO_UPDATE: &str = "DoUpdate"; base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); From b602a401b7b61b4ab1c2d21d02e2d064b7e92b52 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 17:55:34 -0400 Subject: [PATCH 27/46] fix --- Cargo.lock | 2 +- trifid-pki/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f829bf..d79d4f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2777,7 +2777,7 @@ dependencies = [ [[package]] name = "trifid-pki" -version = "0.1.6" +version = "0.1.7" dependencies = [ "ed25519-dalek", "hex", diff --git a/trifid-pki/Cargo.toml b/trifid-pki/Cargo.toml index 4c50a20..1ff64c2 100644 --- a/trifid-pki/Cargo.toml +++ b/trifid-pki/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trifid-pki" -version = "0.1.6" +version = "0.1.7" edition = "2021" description = "A rust implementation of the Nebula PKI system" license = "AGPL-3.0-or-later" From 5150a5b7a7b4b8ca5eceb58a2e2df1da29ca9169 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 17:57:45 -0400 Subject: [PATCH 28/46] add readme --- dnapi-rs/Cargo.toml | 2 +- dnapi-rs/README.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 dnapi-rs/README.md diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 682e4e6..c206fe8 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dnapi-rs" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = "A rust client for the Defined Networking API" license = "AGPL-3.0-or-later" diff --git a/dnapi-rs/README.md b/dnapi-rs/README.md new file mode 100644 index 0000000..854582d --- /dev/null +++ b/dnapi-rs/README.md @@ -0,0 +1,4 @@ +# dnapi-rs +**dnapi-rs** is a Rust-native crate for interacting with the Defined Networking client API. It is a direct port of `dnapi`, an officially maintained API client by Defined Networking. + +This crate is maintained as a part of the trifid project. Check out the other crates in [the git repository](https://git.e3t.cc/~core/trifid). \ No newline at end of file From 9bc94d3a38bc3dbec2bf74617f26c5cfbe5ab3d9 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:00:09 -0400 Subject: [PATCH 29/46] add readme (cargo-lock changes) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d79d4f6..89d7b68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -621,7 +621,7 @@ dependencies = [ [[package]] name = "dnapi-rs" -version = "0.1.0" +version = "0.1.1" dependencies = [ "base64 0.21.0", "base64-serde", From 80e2200a6e9706aaef7fb101b47955da5fcf28cf Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:13:04 -0400 Subject: [PATCH 30/46] async client --- Cargo.lock | 12 +- dnapi-rs/Cargo.toml | 2 +- dnapi-rs/src/client_async.rs | 212 +++++++++++++++++++++++++++++++++++ tfclient/Cargo.toml | 2 +- tfclient/src/apiworker.rs | 2 - tfclient/src/config.rs | 8 +- 6 files changed, 222 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89d7b68..18ca831 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,13 +86,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.11", ] [[package]] @@ -2092,7 +2092,7 @@ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn 2.0.4", + "syn 2.0.11", ] [[package]] @@ -2388,9 +2388,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.4" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c622ae390c9302e214c31013517c2061ecb2699935882c60a9b37f82f8625ae" +checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40" dependencies = [ "proc-macro2", "quote", diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index c206fe8..8cb1bd3 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dnapi-rs" -version = "0.1.1" +version = "0.1.2" edition = "2021" description = "A rust client for the Defined Networking API" license = "AGPL-3.0-or-later" diff --git a/dnapi-rs/src/client_async.rs b/dnapi-rs/src/client_async.rs index e69de29..2aa2099 100644 --- a/dnapi-rs/src/client_async.rs +++ b/dnapi-rs/src/client_async.rs @@ -0,0 +1,212 @@ +//! Client structs to handle communication with the Defined Networking API. This is the async client API - if you want blocking instead, enable the blocking (or default) feature instead. + +use std::error::Error; +use chrono::Local; +use log::{debug, error}; +use reqwest::StatusCode; +use url::Url; +use trifid_pki::cert::serialize_ed25519_public; +use trifid_pki::ed25519_dalek::{Signature, Signer, SigningKey, Verifier}; +use crate::credentials::{Credentials, ed25519_public_keys_from_pem}; +use crate::crypto::{new_keys, nonce}; +use crate::message::{CHECK_FOR_UPDATE, CheckForUpdateResponseWrapper, DO_UPDATE, DoUpdateRequest, DoUpdateResponse, ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper, SignedResponseWrapper}; + +/// A type alias to abstract return types +pub type NebulaConfig = Vec; + +/// A type alias to abstract DH private keys +pub type DHPrivateKeyPEM = Vec; + +/// A combination of persistent data and HTTP client used for communicating with the API. +pub struct Client { + http_client: reqwest::Client, + server_url: Url +} + +/// A struct containing organization metadata returned as a result of enrollment +pub struct EnrollMeta { + /// The server organization ID this node is now a member of + pub organization_id: String, + /// The server organization name this node is now a member of + pub organization_name: String +} + +impl Client { + /// Create a new `Client` configured with the given User-Agent and API base. + /// # Errors + /// This function will return an error if the reqwest Client could not be created. + pub fn new(user_agent: String, api_base: Url) -> Result> { + let client = reqwest::Client::builder().user_agent(user_agent).build()?; + Ok(Self { + http_client: client, + server_url: api_base + }) + } + + /// Issues an enrollment request against the REST API using the given enrollment code, passing along a + /// locally generated DH X25519 Nebula key to be signed by the CA, and an Ed25519 key for future API + /// authentication. On success it returns the Nebula config generated by the server, a Nebula private key PEM, + /// credentials to be used for future DN API requests, and an object containing organization information. + /// # Errors + /// This function will return an error in any of the following situations: + /// - the `server_url` is invalid + /// - the HTTP request fails + /// - the HTTP response is missing X-Request-ID + /// - X-Request-ID isn't valid UTF-8 + /// - the server returns an error + /// - the server returns invalid JSON + /// - the `trusted_keys` field is invalid + pub async fn enroll(&self, code: &str) -> Result<(NebulaConfig, DHPrivateKeyPEM, Credentials, EnrollMeta), Box> { + debug!("making enrollment request to API {{server: {}, code: {}}}", self.server_url, code); + + let (dh_pubkey_pem, dh_privkey_pem, ed_pubkey, ed_privkey) = new_keys(); + + let req_json = serde_json::to_string(&EnrollRequest { + code: code.to_string(), + dh_pubkey: dh_pubkey_pem, + ed_pubkey: serialize_ed25519_public(ed_pubkey.as_bytes()), + timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), + })?; + + let resp = self.http_client.post(self.server_url.join(ENROLL_ENDPOINT)?).body(req_json).send().await?; + + let req_id = resp.headers().get("X-Request-ID").ok_or("Response missing X-Request-ID")?.to_str()?; + debug!("enrollment request complete {{req_id: {}}}", req_id); + + let resp: EnrollResponse = resp.json().await?; + + let r = match resp { + EnrollResponse::Success { data } => data, + EnrollResponse::Error { errors } => { + error!("unexpected error during enrollment: {}", errors[0].message); + return Err(errors[0].message.clone().into()); + } + }; + + let meta = EnrollMeta { + organization_id: r.organization.id, + organization_name: r.organization.name, + }; + + let trusted_keys = ed25519_public_keys_from_pem(&r.trusted_keys)?; + + let creds = Credentials { + host_id: r.host_id, + ed_privkey, + counter: r.counter, + trusted_keys, + }; + + Ok((r.config, dh_privkey_pem, creds, meta)) + } + + /// Send a signed message to the `DNClient` API to learn if there is a new configuration available. + /// # Errors + /// This function returns an error if the dnclient request fails, or the server returns invalid data. + pub async fn check_for_update(&self, creds: &Credentials) -> Result> { + let body = self.post_dnclient(CHECK_FOR_UPDATE, &[], &creds.host_id, creds.counter, &creds.ed_privkey).await?; + + let result: CheckForUpdateResponseWrapper = serde_json::from_slice(&body)?; + + Ok(result.data.update_available) + } + + /// Send a signed message to the `DNClient` API to fetch the new configuration update. During this call a new + /// DH X25519 keypair is generated for the new Nebula certificate as well as a new Ed25519 keypair for `DNClient` API + /// communication. On success it returns the new config, a Nebula private key PEM to be inserted into the config + /// and new `DNClient` API credentials + /// # Errors + /// This function returns an error in any of the following scenarios: + /// - if the message could not be serialized + /// - if the request fails + /// - if the response could not be deserialized + /// - if the signature is invalid + /// - if the keys are invalid + pub async fn do_update(&self, creds: &Credentials) -> Result<(NebulaConfig, DHPrivateKeyPEM, Credentials), Box> { + let (dh_pubkey_pem, dh_privkey_pem, ed_pubkey, ed_privkey) = new_keys(); + + let update_keys = DoUpdateRequest { + ed_pubkey_pem: serialize_ed25519_public(ed_pubkey.as_bytes()), + dh_pubkey_pem, + nonce: nonce().to_vec(), + }; + + let update_keys_blob = serde_json::to_vec(&update_keys)?; + + let resp = self.post_dnclient(DO_UPDATE, &update_keys_blob, &creds.host_id, creds.counter, &creds.ed_privkey).await?; + + let result_wrapper: SignedResponseWrapper = serde_json::from_slice(&resp)?; + + let mut valid = false; + + for ca_pubkey in &creds.trusted_keys { + if ca_pubkey.verify(&result_wrapper.data.message, &Signature::from_slice(&result_wrapper.data.signature)?).is_ok() { + valid = true; + break; + } + } + + if !valid { + return Err("Failed to verify signed API result".into()) + } + + let result: DoUpdateResponse = serde_json::from_slice(&result_wrapper.data.message)?; + + if result.nonce != update_keys.nonce { + error!("nonce mismatch between request {:x?} and response {:x?}", result.nonce, update_keys.nonce); + return Err("nonce mismatch between request and response".into()) + } + + let trusted_keys = ed25519_public_keys_from_pem(&result.trusted_keys)?; + + let new_creds = Credentials { + host_id: creds.host_id.clone(), + ed_privkey, + counter: result.counter, + trusted_keys, + }; + + Ok((result.config, dh_privkey_pem, new_creds)) + } + + /// Wraps and signs the given `req_type` and value, and then makes the API call. + /// On success, returns the response body. + /// # Errors + /// This function will return an error if: + /// - serialization in any step fails + /// - if the `server_url` is invalid + /// - if the request could not be sent + pub async fn post_dnclient(&self, req_type: &str, value: &[u8], host_id: &str, counter: u32, ed_privkey: &SigningKey) -> Result, Box> { + let encoded_msg = serde_json::to_string(&RequestWrapper { + message_type: req_type.to_string(), + value: value.to_vec(), + timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), + })?; + let encoded_msg_bytes = encoded_msg.into_bytes(); + let signature = ed_privkey.sign(&encoded_msg_bytes).to_vec(); + let body = RequestV1 { + version: 1, + host_id: host_id.to_string(), + counter, + message: encoded_msg_bytes, + signature, + }; + + let post_body = serde_json::to_string(&body)?; + + let resp = self.http_client.post(self.server_url.join(ENDPOINT_V1)?).body(post_body).send().await?; + + match resp.status() { + StatusCode::OK => { + Ok(resp.bytes().await?.to_vec()) + }, + StatusCode::FORBIDDEN => { + Err("Forbidden".into()) + }, + _ => { + error!("dnclient endpoint returned bad status code {}", resp.status()); + Err("dnclient endpoint returned error".into()) + } + } + } +} \ No newline at end of file diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 56fa277..339ec88 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -24,7 +24,7 @@ base64 = "0.21.0" chrono = "0.4.24" ipnet = "2.7.1" base64-serde = "0.7.0" -dnapi-rs = { version = "0.1.0", path = "../dnapi-rs" } +dnapi-rs = { version = "0.1.1", path = "../dnapi-rs" } [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index a5f85ce..6ad2ae9 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -3,12 +3,10 @@ use base64::Engine; use chrono::Local; use log::{error, info, warn}; use url::Url; -use trifid_pki::ca::NebulaCAPool; use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; use trifid_pki::rand_core::OsRng; use trifid_pki::x25519_dalek::StaticSecret; -use crate::message::{APIResponse, enroll, EnrollRequest}; use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index 9d86a0b..dfe48af 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -5,6 +5,7 @@ use std::fs; use log::{debug, info}; use serde::{Deserialize, Serialize}; +use dnapi_rs::credentials::Credentials; use crate::dirs::{get_cdata_dir, get_cdata_file, get_config_dir, get_config_file}; @@ -20,13 +21,8 @@ pub struct TFClientConfig { #[derive(Serialize, Deserialize, Clone)] pub struct TFClientData { pub host_id: Option, - pub ed_privkey: Option<[u8; 32]>, pub dh_privkey: Option<[u8; 32]>, - pub counter: i32, - pub key_pool: Option, - pub org_id: Option, - pub org_name: Option, - pub config: Option + pub creds: Option } pub fn create_config(instance: &str) -> Result<(), Box> { From c3d68c00f8816c123b4261f56711f518c65fee8e Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:14:01 -0400 Subject: [PATCH 31/46] cargo-lock update --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 18ca831..6010dc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -621,7 +621,7 @@ dependencies = [ [[package]] name = "dnapi-rs" -version = "0.1.1" +version = "0.1.2" dependencies = [ "base64 0.21.0", "base64-serde", From ef7d0f4b2ca11cc2a2dbedaa103016fa616798cd Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:26:04 -0400 Subject: [PATCH 32/46] update stuff to fix stuff --- Cargo.lock | 15 +++++++++++- dnapi-rs/Cargo.toml | 2 +- dnapi-rs/src/client_async.rs | 2 ++ dnapi-rs/src/client_blocking.rs | 2 ++ dnapi-rs/src/credentials.rs | 2 ++ tfclient/Cargo.toml | 4 ++-- tfclient/src/apiworker.rs | 42 +-------------------------------- tfclient/src/config.rs | 1 - trifid-pki/Cargo.toml | 4 ++-- 9 files changed, 26 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6010dc4..258c003 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,6 +453,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", + "serde", "subtle", "zeroize", ] @@ -648,6 +649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf420a7ec85d98495b0c34aa4a58ca117f982ffbece111aeb545160148d7010" dependencies = [ "pkcs8", + "serde", "signature", ] @@ -661,6 +663,7 @@ dependencies = [ "ed25519", "rand_core 0.6.4", "serde", + "serde_bytes", "sha2", "zeroize", ] @@ -2084,6 +2087,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.159" @@ -2777,7 +2789,7 @@ dependencies = [ [[package]] name = "trifid-pki" -version = "0.1.7" +version = "0.1.8" dependencies = [ "ed25519-dalek", "hex", @@ -3230,6 +3242,7 @@ checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df" dependencies = [ "curve25519-dalek 3.2.0", "rand_core 0.6.4", + "serde", "zeroize", ] diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 8cb1bd3..1307825 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -19,7 +19,7 @@ reqwest = { version = "0.11.16", features = ["blocking", "json"] } url = "2.3.1" base64 = "0.21.0" serde_json = "1.0.95" -trifid-pki = { version = "0.1.6", path = "../trifid-pki" } +trifid-pki = { version = "0.1.6", path = "../trifid-pki", features = ["serde_derive"] } rand = "0.8.5" chrono = "0.4.24" diff --git a/dnapi-rs/src/client_async.rs b/dnapi-rs/src/client_async.rs index 2aa2099..c4d37df 100644 --- a/dnapi-rs/src/client_async.rs +++ b/dnapi-rs/src/client_async.rs @@ -10,6 +10,7 @@ use trifid_pki::ed25519_dalek::{Signature, Signer, SigningKey, Verifier}; use crate::credentials::{Credentials, ed25519_public_keys_from_pem}; use crate::crypto::{new_keys, nonce}; use crate::message::{CHECK_FOR_UPDATE, CheckForUpdateResponseWrapper, DO_UPDATE, DoUpdateRequest, DoUpdateResponse, ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper, SignedResponseWrapper}; +use serde::{Serialize, Deserialize}; /// A type alias to abstract return types pub type NebulaConfig = Vec; @@ -23,6 +24,7 @@ pub struct Client { server_url: Url } +#[derive(Serialize, Deserialize)] /// A struct containing organization metadata returned as a result of enrollment pub struct EnrollMeta { /// The server organization ID this node is now a member of diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index f75c21c..2ee13fc 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -10,6 +10,7 @@ use trifid_pki::ed25519_dalek::{Signature, Signer, SigningKey, Verifier}; use crate::credentials::{Credentials, ed25519_public_keys_from_pem}; use crate::crypto::{new_keys, nonce}; use crate::message::{CHECK_FOR_UPDATE, CheckForUpdateResponseWrapper, DO_UPDATE, DoUpdateRequest, DoUpdateResponse, ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper, SignedResponseWrapper}; +use serde::{Serialize, Deserialize}; /// A type alias to abstract return types pub type NebulaConfig = Vec; @@ -23,6 +24,7 @@ pub struct Client { server_url: Url } +#[derive(Serialize, Deserialize)] /// A struct containing organization metadata returned as a result of enrollment pub struct EnrollMeta { /// The server organization ID this node is now a member of diff --git a/dnapi-rs/src/credentials.rs b/dnapi-rs/src/credentials.rs index 01b3ce7..c54ad7a 100644 --- a/dnapi-rs/src/credentials.rs +++ b/dnapi-rs/src/credentials.rs @@ -3,7 +3,9 @@ use std::error::Error; use trifid_pki::cert::{deserialize_ed25519_public_many, serialize_ed25519_public}; use trifid_pki::ed25519_dalek::{SigningKey, VerifyingKey}; +use serde::{Serialize, Deserialize}; +#[derive(Serialize, Deserialize)] /// Contains information necessary to make requests against the `DNClient` API. pub struct Credentials { /// The assigned Host ID that this client represents diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 339ec88..fde466b 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -8,7 +8,7 @@ description = "An open-source reimplementation of a Defined Networking-compatibl [dependencies] clap = { version = "4.1.10", features = ["derive"] } -trifid-pki = { version = "0.1.6", path = "../trifid-pki", features = ["serde_derive"] } +trifid-pki = { version = "0.1.8", path = "../trifid-pki", features = ["serde_derive"] } dirs = "5.0.0" log = "0.4.17" simple_logger = "4.1.0" @@ -24,7 +24,7 @@ base64 = "0.21.0" chrono = "0.4.24" ipnet = "2.7.1" base64-serde = "0.7.0" -dnapi-rs = { version = "0.1.1", path = "../dnapi-rs" } +dnapi-rs = { version = "0.1.2", path = "../dnapi-rs" } [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 6ad2ae9..42c47cb 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -19,46 +19,6 @@ pub enum APIWorkerMessage { pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver) { let server = Url::parse(&url).unwrap(); - // Generate dhPubkey and edPubkey if it doesn't exist - // Load vardata - let mut vdata = match load_cdata(&instance) { - Ok(d) => d, - Err(e) => { - error!("Error loading vdata: {}", e); - error!("APIWorker exiting with error"); - return; - } - }; - - if vdata.ed_privkey.is_none() { - info!("Generating ed25519 key"); - let mut csprng = OsRng; - let key = SigningKey::generate(&mut csprng); - let ed_key_bytes = key.to_bytes().to_vec(); - vdata.ed_privkey = Some(ed_key_bytes.try_into().unwrap()); - } - if vdata.dh_privkey.is_none() { - info!("Generating ecdh key"); - let mut csprng = OsRng; - let key = StaticSecret::new(&mut csprng); - let dh_key_bytes = key.to_bytes(); - vdata.dh_privkey = Some(dh_key_bytes); - } - - info!("Loading keys"); - let ed_key = SigningKey::from_bytes(&SecretKey::from(vdata.ed_privkey.unwrap())); - let dh_key = StaticSecret::from(vdata.dh_privkey.unwrap()); - info!("Keys loaded successfully"); - - // Save vardata - match save_cdata(&instance, vdata) { - Ok(_) => (), - Err(e) => { - error!("Error saving vdata: {}", e); - error!("APIWorker exiting with error"); - return; - } - } loop { match rx.try_recv() { Ok(msg) => { @@ -93,7 +53,7 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr return; } }; - if cdata.host_id.is_some() { + if cdata. { warn!("enrollment failed: already enrolled"); continue; } diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index dfe48af..bda7a22 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -20,7 +20,6 @@ pub struct TFClientConfig { #[derive(Serialize, Deserialize, Clone)] pub struct TFClientData { - pub host_id: Option, pub dh_privkey: Option<[u8; 32]>, pub creds: Option } diff --git a/trifid-pki/Cargo.toml b/trifid-pki/Cargo.toml index 1ff64c2..0cb0bee 100644 --- a/trifid-pki/Cargo.toml +++ b/trifid-pki/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trifid-pki" -version = "0.1.7" +version = "0.1.8" edition = "2021" description = "A rust implementation of the Nebula PKI system" license = "AGPL-3.0-or-later" @@ -24,4 +24,4 @@ serde = { version = "1", features = ["derive"], optional = true } [features] default = [] -serde_derive = ["serde", "ipnet/serde"] \ No newline at end of file +serde_derive = ["serde", "ipnet/serde", "x25519-dalek/serde", "ed25519-dalek/serde"] \ No newline at end of file From 6b4fe0b75fea777a6c9e96e2b066a97ae6d0d778 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:27:26 -0400 Subject: [PATCH 33/46] update version --- dnapi-rs/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 1307825..288b233 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dnapi-rs" -version = "0.1.2" +version = "0.1.3" edition = "2021" description = "A rust client for the Defined Networking API" license = "AGPL-3.0-or-later" From e5d58f053ac646094d2e4d715289eb1cdadc8f94 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:27:55 -0400 Subject: [PATCH 34/46] update lockfiles --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 258c003..babfa02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,7 +622,7 @@ dependencies = [ [[package]] name = "dnapi-rs" -version = "0.1.2" +version = "0.1.3" dependencies = [ "base64 0.21.0", "base64-serde", From a77e126319b56736f7e8a064d7f696da32c1c8e8 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:29:30 -0400 Subject: [PATCH 35/46] fixup cloneability --- Cargo.lock | 2 +- dnapi-rs/Cargo.toml | 2 +- dnapi-rs/src/credentials.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index babfa02..23732c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,7 +622,7 @@ dependencies = [ [[package]] name = "dnapi-rs" -version = "0.1.3" +version = "0.1.4" dependencies = [ "base64 0.21.0", "base64-serde", diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 288b233..8de3842 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dnapi-rs" -version = "0.1.3" +version = "0.1.4" edition = "2021" description = "A rust client for the Defined Networking API" license = "AGPL-3.0-or-later" diff --git a/dnapi-rs/src/credentials.rs b/dnapi-rs/src/credentials.rs index c54ad7a..01a0b3d 100644 --- a/dnapi-rs/src/credentials.rs +++ b/dnapi-rs/src/credentials.rs @@ -5,7 +5,7 @@ use trifid_pki::cert::{deserialize_ed25519_public_many, serialize_ed25519_public use trifid_pki::ed25519_dalek::{SigningKey, VerifyingKey}; use serde::{Serialize, Deserialize}; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] /// Contains information necessary to make requests against the `DNClient` API. pub struct Credentials { /// The assigned Host ID that this client represents From bd76d760f441a2f107009c5c3da186fe4c56ef7f Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:44:46 -0400 Subject: [PATCH 36/46] dnapi-rs - make EnrollMeta cloneable tfclient - finish enrollment routine --- Cargo.lock | 2 +- dnapi-rs/Cargo.toml | 9 ++--- dnapi-rs/src/client_async.rs | 2 +- dnapi-rs/src/client_blocking.rs | 2 +- dnapi-rs/src/lib.rs | 12 ++----- tfclient/Cargo.toml | 2 +- tfclient/src/apiworker.rs | 64 ++++++++++----------------------- tfclient/src/config.rs | 6 ++-- tfclient/src/dirs.rs | 4 +++ tfclient/src/nebulaworker.rs | 6 +++- tfclient/src/socketworker.rs | 4 +-- 11 files changed, 42 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23732c9..38e7455 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,7 +622,7 @@ dependencies = [ [[package]] name = "dnapi-rs" -version = "0.1.4" +version = "0.1.5" dependencies = [ "base64 0.21.0", "base64-serde", diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 8de3842..8160729 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dnapi-rs" -version = "0.1.4" +version = "0.1.5" edition = "2021" description = "A rust client for the Defined Networking API" license = "AGPL-3.0-or-later" @@ -21,9 +21,4 @@ base64 = "0.21.0" serde_json = "1.0.95" trifid-pki = { version = "0.1.6", path = "../trifid-pki", features = ["serde_derive"] } rand = "0.8.5" -chrono = "0.4.24" - -[features] -default = ["blocking"] -blocking = ["reqwest/blocking"] -async = [] \ No newline at end of file +chrono = "0.4.24" \ No newline at end of file diff --git a/dnapi-rs/src/client_async.rs b/dnapi-rs/src/client_async.rs index c4d37df..90d8266 100644 --- a/dnapi-rs/src/client_async.rs +++ b/dnapi-rs/src/client_async.rs @@ -24,7 +24,7 @@ pub struct Client { server_url: Url } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] /// A struct containing organization metadata returned as a result of enrollment pub struct EnrollMeta { /// The server organization ID this node is now a member of diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index 2ee13fc..c0c2cdf 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -24,7 +24,7 @@ pub struct Client { server_url: Url } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] /// A struct containing organization metadata returned as a result of enrollment pub struct EnrollMeta { /// The server organization ID this node is now a member of diff --git a/dnapi-rs/src/lib.rs b/dnapi-rs/src/lib.rs index 1317543..b9b63d8 100644 --- a/dnapi-rs/src/lib.rs +++ b/dnapi-rs/src/lib.rs @@ -15,18 +15,10 @@ #![allow(clippy::too_many_lines)] #![allow(clippy::module_name_repetitions)] -#[cfg(all(feature = "blocking", feature = "async"))] -compile_error!("Cannot compile with both blocking and async features at the same time. Please pick one or the other."); - pub mod message; -#[cfg(feature = "blocking")] -#[path = "client_blocking.rs"] -pub mod client; - -#[cfg(feature = "async")] -#[path = "client_async.rs"] -pub mod client; +pub mod client_blocking; +pub mod client_async; pub mod credentials; pub mod crypto; \ No newline at end of file diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index fde466b..f8d138f 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -24,7 +24,7 @@ base64 = "0.21.0" chrono = "0.4.24" ipnet = "2.7.1" base64-serde = "0.7.0" -dnapi-rs = { version = "0.1.2", path = "../dnapi-rs" } +dnapi-rs = { version = "0.1.5", path = "../dnapi-rs" } [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 42c47cb..3d02828 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -1,14 +1,17 @@ +use std::fs; use std::sync::mpsc::{Receiver, TryRecvError}; use base64::Engine; use chrono::Local; use log::{error, info, warn}; use url::Url; +use dnapi_rs::client_blocking::Client; use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; use trifid_pki::rand_core::OsRng; use trifid_pki::x25519_dalek::StaticSecret; use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; +use crate::dirs::get_nebulaconfig_file; pub enum APIWorkerMessage { Shutdown, @@ -19,6 +22,8 @@ pub enum APIWorkerMessage { pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver) { let server = Url::parse(&url).unwrap(); + let client = Client::new(format!("tfclient/{}", env!("CARGO_PKG_VERSION")), server).unwrap(); + loop { match rx.try_recv() { Ok(msg) => { @@ -37,7 +42,7 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr return; } }; - if cdata.host_id.is_none() { + if cdata.creds.is_none() { info!("not enrolled, cannot perform config update"); continue; } @@ -53,63 +58,30 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr return; } }; - if cdata. { + if cdata.creds.is_some() { warn!("enrollment failed: already enrolled"); continue; } - let dh_encoded = base64::engine::general_purpose::STANDARD.encode(serialize_x25519_public(&dh_key.to_bytes())); - let ed_encoded = base64::engine::general_purpose::STANDARD.encode(serialize_ed25519_public(&ed_key.to_bytes())); - let req = EnrollRequest { - code, - dh_pubkey: dh_encoded, - ed_pubkey: ed_encoded, - timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), - }; - let res = match enroll(&server, &req) { - Ok(res) => res, - Err(e) => { - error!("error in api worker thread: {}", e); - error!("APIWorker exiting with error"); - return; - } - }; - let resp = match res { - APIResponse::Error(e) => { - error!("error with enrollment: {}: {}", e.errors[0].code, e.errors[0].message); - continue; - } - APIResponse::Success(resp) => resp - }; - info!("Enrolled with server. Host-ID {} config count {}", resp.data.host_id, resp.data.counter); - info!("KeyPool {}, org {} {}", resp.data.trusted_keys, resp.data.organization.name, resp.data.organization.id); - info!("Config: {}", resp.data.config); - - // Decode the CAPool and config - let key_pool_pem = match base64::engine::general_purpose::STANDARD.decode(resp.data.trusted_keys) { - Ok(p) => p, + let (config, dh_privkey, creds, meta) = match client.enroll(&code) { + Ok(resp) => resp, Err(e) => { - error!("error with enrollment: {}", e); + error!("error with enrollment request: {}", e); continue; } }; - let config = match base64::engine::general_purpose::STANDARD.decode(resp.data.config) { - Ok(p) => p, + match fs::write(get_nebulaconfig_file(&instance).expect("Unable to determine nebula config file location"), config) { + Ok(_) => (), Err(e) => { - error!("error with enrollment: {}", e); + error!("unable to save nebula config: {}", e); continue; } - }; - let config_str = String::from_utf8(config).unwrap(); + } - - cdata.host_id = Some(resp.data.host_id); - cdata.counter = resp.data.counter as i32; - cdata.key_pool = Some(String::from_utf8(key_pool_pem).unwrap()); - cdata.org_name = Some(resp.data.organization.name); - cdata.org_id = Some(resp.data.organization.id); - cdata.config = Some(config_str); + cdata.creds = Some(creds); + cdata.dh_privkey = Some(dh_privkey.try_into().expect("32 != 32")); + cdata.meta = Some(meta); // Save vardata match save_cdata(&instance, cdata) { @@ -120,6 +92,8 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr return; } } + + info!("Configuration updated. Sending signal to Nebula worker thread"); } } }, diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index bda7a22..c3b6adb 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -5,6 +5,7 @@ use std::fs; use log::{debug, info}; use serde::{Deserialize, Serialize}; +use dnapi_rs::client_blocking::EnrollMeta; use dnapi_rs::credentials::Credentials; use crate::dirs::{get_cdata_dir, get_cdata_file, get_config_dir, get_config_file}; @@ -21,7 +22,8 @@ pub struct TFClientConfig { #[derive(Serialize, Deserialize, Clone)] pub struct TFClientData { pub dh_privkey: Option<[u8; 32]>, - pub creds: Option + pub creds: Option, + pub meta: Option } pub fn create_config(instance: &str) -> Result<(), Box> { @@ -56,7 +58,7 @@ pub fn create_cdata(instance: &str) -> Result<(), Box> { info!("Creating data directory..."); fs::create_dir_all(get_cdata_dir(instance).ok_or("Unable to load data dir")?)?; info!("Copying default data file to config directory..."); - let config = TFClientData { host_id: None, ed_privkey: None, dh_privkey: None, counter: 0, key_pool: None, org_id: None, org_name: None, config: None }; + let config = TFClientData { dh_privkey: None, creds: None, meta: None }; let config_str = toml::to_string(&config)?; fs::write(get_cdata_file(instance).ok_or("Unable to load data dir")?, config_str)?; Ok(()) diff --git a/tfclient/src/dirs.rs b/tfclient/src/dirs.rs index d7dd0c3..0010aa1 100644 --- a/tfclient/src/dirs.rs +++ b/tfclient/src/dirs.rs @@ -18,4 +18,8 @@ pub fn get_cdata_dir(instance: &str) -> Option { pub fn get_cdata_file(instance: &str) -> Option { get_cdata_dir(instance).map(|f| f.join("tfclient.toml")) +} + +pub fn get_nebulaconfig_file(instance: &str) -> Option { + get_cdata_dir(instance).map(|f| f.join("nebula.sk_embedded.yml")) } \ No newline at end of file diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index 3bef8e6..d54df49 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -6,7 +6,8 @@ use crate::config::TFClientConfig; use crate::daemon::ThreadMessageSender; pub enum NebulaWorkerMessage { - Shutdown + Shutdown, + ConfigUpdated } pub fn nebulaworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSender, rx: Receiver) { @@ -17,6 +18,9 @@ pub fn nebulaworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSen NebulaWorkerMessage::Shutdown => { info!("recv on command socket: shutdown, stopping"); break; + }, + NebulaWorkerMessage::ConfigUpdated => { + info!("our configuration has been updated - reloading"); } } }, diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs index 21e0d6d..a87ec21 100644 --- a/tfclient/src/socketworker.rs +++ b/tfclient/src/socketworker.rs @@ -232,8 +232,8 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm } }; client.stream.write_all(&ctob(JsonMessage::HostID { - has_id: data.host_id.is_some(), - id: data.host_id + has_id: data.creds.is_some(), + id: data.creds.map(|c| c.host_id) }))?; }, From 69162cea0563b4602fd2f593a9f50665cf1be7b8 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:52:15 -0400 Subject: [PATCH 37/46] finish apiworker --- tfclient/src/apiworker.rs | 109 +++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 3d02828..6f328db 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -12,6 +12,7 @@ use trifid_pki::x25519_dalek::StaticSecret; use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; use crate::dirs::get_nebulaconfig_file; +use crate::nebulaworker::NebulaWorkerMessage; pub enum APIWorkerMessage { Shutdown, @@ -19,7 +20,7 @@ pub enum APIWorkerMessage { Timer } -pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver) { +pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, tx: ThreadMessageSender, rx: Receiver) { let server = Url::parse(&url).unwrap(); let client = Client::new(format!("tfclient/{}", env!("CARGO_PKG_VERSION")), server).unwrap(); @@ -44,9 +45,106 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr }; if cdata.creds.is_none() { info!("not enrolled, cannot perform config update"); + match save_cdata(&instance, cdata) { + Ok(_) => (), + Err(e) => { + error!("Error saving cdata: {}", e); + error!("APIWorker exiting with error"); + return; + } + } + continue; + } + let creds = cdata.clone().creds.unwrap_or_else(|| unreachable!()); + + info!("checking for update"); + let update_available = match client.check_for_update(&creds) { + Ok(ua) => ua, + Err(e) => { + error!("error checking for config update: {}", e); + match save_cdata(&instance, cdata) { + Ok(_) => (), + Err(e) => { + error!("Error saving cdata: {}", e); + error!("APIWorker exiting with error"); + return; + } + } + continue; + } + }; + + if !update_available { + match save_cdata(&instance, cdata) { + Ok(_) => (), + Err(e) => { + error!("Error saving cdata: {}", e); + error!("APIWorker exiting with error"); + return; + } + } + info!("no config update available"); continue; } + info!("updated configuration is avaliable"); + info!("updating configuration"); + + let (config, dh_privkey, creds) = match client.do_update(&creds) { + Ok(d) => d, + Err(e) => { + error!("error requesting updating config: {}", e); + match save_cdata(&instance, cdata) { + Ok(_) => (), + Err(e) => { + error!("Error saving cdata: {}", e); + error!("APIWorker exiting with error"); + return; + } + } + continue; + } + }; + + cdata.creds = Some(creds); + cdata.dh_privkey = Some(dh_privkey.try_into().expect("32 != 32")); + + match fs::write(get_nebulaconfig_file(&instance).expect("Unable to determine nebula config file location"), config) { + Ok(_) => (), + Err(e) => { + error!("unable to save nebula config: {}", e); + match save_cdata(&instance, cdata) { + Ok(_) => (), + Err(e) => { + error!("Error saving cdata: {}", e); + error!("APIWorker exiting with error"); + return; + } + } + continue; + } + } + + match save_cdata(&instance, cdata) { + Ok(_) => (), + Err(e) => { + error!("Error saving cdata: {}", e); + error!("APIWorker exiting with error"); + return; + } + } + + info!("configuration updated successfully!"); + info!("sending signal to nebula thread to reload config"); + + match tx.nebula_thread.send(NebulaWorkerMessage::ConfigUpdated) { + Ok(_) => (), + Err(e) => { + error!("unable to tell nebula thread to update config: {}", e); + error!("APIWorker exiting with error"); + return; + } + } }, APIWorkerMessage::Enroll { code } => { info!("recv on command socket: enroll {}", code); @@ -94,6 +192,15 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr } info!("Configuration updated. Sending signal to Nebula worker thread"); + + match tx.nebula_thread.send(NebulaWorkerMessage::ConfigUpdated) { + Ok(_) => (), + Err(e) => { + error!("unable to tell nebula thread to update config: {}", e); + error!("APIWorker exiting with error"); + return; + } + } } } }, From 099e157fb21f089edc54e0ac47655343136a3aa2 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 18:53:56 -0400 Subject: [PATCH 38/46] fix missed validity check --- Cargo.lock | 2 +- dnapi-rs/Cargo.toml | 2 +- dnapi-rs/src/client_blocking.rs | 5 +++++ tfclient/Cargo.toml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38e7455..d2384c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,7 +622,7 @@ dependencies = [ [[package]] name = "dnapi-rs" -version = "0.1.5" +version = "0.1.6" dependencies = [ "base64 0.21.0", "base64-serde", diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 8160729..2991ff6 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dnapi-rs" -version = "0.1.5" +version = "0.1.6" edition = "2021" description = "A rust client for the Defined Networking API" license = "AGPL-3.0-or-later" diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index c0c2cdf..5b372e8 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -159,6 +159,11 @@ impl Client { return Err("nonce mismatch between request and response".into()) } + if result.counter <= creds.counter { + error!("counter in request {} should be less than counter in response {}", creds.counter, result.counter); + return Err("received older config than what we already had".into()) + } + let trusted_keys = ed25519_public_keys_from_pem(&result.trusted_keys)?; let new_creds = Credentials { diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index f8d138f..7dce9aa 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -24,7 +24,7 @@ base64 = "0.21.0" chrono = "0.4.24" ipnet = "2.7.1" base64-serde = "0.7.0" -dnapi-rs = { version = "0.1.5", path = "../dnapi-rs" } +dnapi-rs = { version = "0.1.6", path = "../dnapi-rs" } [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } From b93bc8d84635a5897e339c26160869d41055fb2e Mon Sep 17 00:00:00 2001 From: core Date: Thu, 30 Mar 2023 07:29:02 -0400 Subject: [PATCH 39/46] upd --- tfclient/src/config.rs | 52 ++++++++++++++++++++++++++++++++++-- tfclient/src/daemon.rs | 3 ++- tfclient/src/nebulaworker.rs | 14 ++++++++-- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index c3b6adb..ff14660 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -1,6 +1,7 @@ - +use std::collections::HashMap; use std::error::Error; use std::fs; +use std::net::{Ipv4Addr, SocketAddrV4}; use log::{debug, info}; @@ -94,4 +95,51 @@ pub fn save_cdata(instance: &str, data: TFClientData) -> Result<(), Box>, + pub lighthouse: Option +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigPki { + pub ca: String, + pub cert: String, + pub key: String, + #[serde(default = "empty_vec")] + pub blocklist: Vec, + #[serde(default = "bool_false")] + pub disconnect_invalid: bool +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigLighthouse { + #[serde(default = "bool_false")] + pub am_lighthouse: bool, + #[serde(default = "bool_false")] + pub serve_dns: bool, + pub dns: Option, + #[serde(default = "u32_10")] + pub interval: u32, + #[serde(default = "empty_vec")] + pub hosts: Vec, + #[serde(default = "empty_hashmap")] +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigLighthouseDns { + pub host: Ipv4Addr, + #[serde(default = "u16_53")] + pub port: u16 +} + +// Default values for serde +fn empty_vec() -> Vec { vec![] } +fn empty_hashmap() -> HashMap { HashMap::new() } +fn bool_false() -> bool { false } +fn u16_53() -> u16 { 53 } +fn u32_10() -> u32 { 10 } \ No newline at end of file diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index 00c56a2..7d912e0 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -89,8 +89,9 @@ pub fn daemon_main(name: String, server: String) { info!("Starting Nebula thread..."); let config_nebula = config.clone(); let transmitter_nebula = transmitter.clone(); + let name_nebula = name.clone(); let nebula_thread = thread::spawn(move || { - nebulaworker_main(config_nebula, transmitter_nebula, rx_nebula); + nebulaworker_main(config_nebula, name, transmitter_nebula, rx_nebula); }); info!("Starting timer thread..."); diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index d54df49..7cb685a 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -2,7 +2,7 @@ use std::sync::mpsc::{Receiver, TryRecvError}; use log::{error, info}; -use crate::config::TFClientConfig; +use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; pub enum NebulaWorkerMessage { @@ -10,7 +10,17 @@ pub enum NebulaWorkerMessage { ConfigUpdated } -pub fn nebulaworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSender, rx: Receiver) { +pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter: ThreadMessageSender, rx: Receiver) { + let cdata = match load_cdata(&instance) { + Ok(data) => data, + Err(e) => { + error!("unable to load cdata: {}", e); + error!("nebula thread exiting with error"); + return; + } + }; + + // dont need to save it, because we do not, in any circumstance, write to it loop { match rx.try_recv() { Ok(msg) => { From 6fde5bc19ed2135a04d61215bc085d440d893aeb Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 30 Mar 2023 09:26:31 -0400 Subject: [PATCH 40/46] keep working on config representation --- tfclient/src/config.rs | 220 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 2 deletions(-) diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index ff14660..8882b26 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::error::Error; use std::fs; use std::net::{Ipv4Addr, SocketAddrV4}; +use ipnet::{IpNet, Ipv4Net}; use log::{debug, info}; @@ -101,8 +102,27 @@ pub fn save_cdata(instance: &str, data: TFClientData) -> Result<(), Box>, - pub lighthouse: Option + #[serde(skip_serializing_if = "is_none")] + pub lighthouse: Option, + #[serde(skip_serializing_if = "is_none")] + pub listen: Option, + #[serde(skip_serializing_if = "is_none")] + pub punchy: Option, + #[serde(default = "cipher_aes")] + #[serde(skip_serializing_if = "is_cipher_aes")] + pub cipher: NebulaConfigCipher, + #[serde(default = "empty_vec")] + #[serde(skip_serializing_if = "is_empty_vec")] + pub preferred_ranges: Vec, + #[serde(skip_serializing_if = "is_none")] + pub relay: Option, + #[serde(skip_serializing_if = "is_none")] + pub tun: Option, + #[serde(skip_serializing_if = "is_none")] + pub logging: Option, + } #[derive(Serialize, Deserialize)] @@ -111,35 +131,231 @@ pub struct NebulaConfigPki { pub cert: String, pub key: String, #[serde(default = "empty_vec")] + #[serde(skip_serializing_if = "is_empty_vec")] pub blocklist: Vec, #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] pub disconnect_invalid: bool } #[derive(Serialize, Deserialize)] pub struct NebulaConfigLighthouse { #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] pub am_lighthouse: bool, #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] pub serve_dns: bool, + #[serde(skip_serializing_if = "is_none")] pub dns: Option, #[serde(default = "u32_10")] + #[serde(skip_serializing_if = "is_u32_10")] pub interval: u32, #[serde(default = "empty_vec")] + #[serde(skip_serializing_if = "is_empty_vec")] pub hosts: Vec, #[serde(default = "empty_hashmap")] + #[serde(skip_serializing_if = "is_empty_hashmap")] + pub remote_allow_list: HashMap, + #[serde(default = "empty_hashmap")] + #[serde(skip_serializing_if = "is_empty_hashmap")] + pub local_allow_list: HashMap, // `interfaces` is not supported } #[derive(Serialize, Deserialize)] pub struct NebulaConfigLighthouseDns { pub host: Ipv4Addr, #[serde(default = "u16_53")] + #[serde(skip_serializing_if = "is_u16_53")] pub port: u16 } +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigListen { + #[serde(default = "ipv4_0000")] + #[serde(skip_serializing_if = "is_ipv4_0000")] + pub host: Ipv4Addr, + #[serde(default = "u16_0")] + #[serde(skip_serializing_if = "is_u16_0")] + pub port: u16, + #[serde(default = "u32_64")] + #[serde(skip_serializing_if = "is_u32_64")] + pub batch: u32, + #[serde(skip_serializing_if = "is_none")] + pub read_buffer: Option, + #[serde(skip_serializing_if = "is_none")] + pub write_buffer: Option +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigPunchy { + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub punch: bool, + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub respond: bool, + #[serde(default = "string_1s")] + #[serde(skip_serializing_if = "is_string_1s")] + pub delay: String +} + +#[derive(Serialize, Deserialize)] +pub enum NebulaConfigCipher { + #[serde(rename = "aes")] + Aes, + #[serde(rename = "chachapoly")] + ChaChaPoly +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigRelay { + #[serde(default = "empty_vec")] + #[serde(skip_serializing_if = "is_empty_vec")] + pub relays: Vec, + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub am_relay: bool, + #[serde(default = "bool_true")] + #[serde(skip_serializing_if = "is_bool_true")] + pub use_relays: bool +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigTun { + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub disabled: bool, + #[serde(skip_serializing_if = "is_none")] + pub dev: Option, + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub drop_local_broadcast: bool, + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub drop_multicast: bool, + #[serde(default = "u64_500")] + #[serde(skip_serializing_if = "is_u64_500")] + pub tx_queue: u64, + #[serde(default = "u64_1300")] + #[serde(skip_serializing_if = "is_u64_1300")] + pub mtu: u64, + #[serde(default = "empty_vec")] + #[serde(skip_serializing_if = "is_empty_vec")] + pub routes: Vec, + #[serde(default = "empty_vec")] + #[serde(skip_serializing_if = "is_empty_vec")] + pub unsafe_routes: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigTunRouteOverride { + pub mtu: u64, + pub route: Ipv4Net +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigTunUnsafeRoute { + pub route: Ipv4Net, + pub via: Ipv4Addr, + #[serde(default = "u64_1300")] + #[serde(skip_serializing_if = "is_u64_1300")] + pub mtu: u64, + #[serde(default = "i64_100")] + #[serde(skip_serializing_if = "is_i64_100")] + pub metric: i64 +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigLogging { + #[serde(default = "loglevel_info")] + #[serde(skip_serializing_if = "is_loglevel_info")] + pub level: NebulaConfigLoggingLevel, + #[serde(default = "format_text")] + #[serde(skip_serializing_if = "is_format_text")] + pub format: NebulaConfigLoggingFormat, + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub disable_timestamp: bool, + #[serde(default = "timestamp")] + #[serde(skip_serializing_if = "is_timestamp")] + pub timestamp_format: String +} + +#[derive(Serialize, Deserialize)] +pub enum NebulaConfigLoggingLevel { + #[serde(rename = "panic")] + Panic, + #[serde(rename = "fatal")] + Fatal, + #[serde(rename = "error")] + Error, + #[serde(rename = "warning")] + Warning, + #[serde(rename = "info")] + Info, + #[serde(rename = "debug")] + Debug +} + +#[derive(Serialize, Deserialize)] +pub enum NebulaConfigLoggingFormat { + #[serde(rename = "json")] + Json, + #[serde(rename = "text")] + Text +} + // Default values for serde fn empty_vec() -> Vec { vec![] } +fn is_empty_vec(v: &Vec) -> bool { v.is_empty() } + fn empty_hashmap() -> HashMap { HashMap::new() } +fn is_empty_hashmap(h: &HashMap) -> bool { h.is_empty() } + fn bool_false() -> bool { false } +fn is_bool_false(b: &bool) -> bool { !*b } + +fn bool_true() -> bool { true } +fn is_bool_true(b: &bool) -> bool { *b } + fn u16_53() -> u16 { 53 } -fn u32_10() -> u32 { 10 } \ No newline at end of file +fn is_u16_53(u: &u16) -> bool { *u == 53 } + +fn u32_10() -> u32 { 10 } +fn is_u32_10(u: &u32) -> bool { *u == 10 } + +fn ipv4_0000() -> Ipv4Addr { Ipv4Addr::new(0, 0, 0, 0) } +fn is_ipv4_0000(i: &Ipv4Addr) -> bool { *i == ipv4_0000() } + +fn u16_0() -> u16 { 0 } +fn is_u16_0(u: &u16) -> bool { *u == 0 } + +fn u32_64() -> u32 { 64 } +fn is_u32_64(u: &u32) -> bool { *u == 64 } + +fn string_1s() -> String { "1s".to_string() } +fn is_string_1s(s: &str) -> bool { s == "1s" } + +fn cipher_aes() -> NebulaConfigCipher { NebulaConfigCipher::Aes } +fn is_cipher_aes(c: &NebulaConfigCipher) -> bool { matches!(c, NebulaConfigCipher::Aes) } + +fn u64_500() -> u64 { 500 } +fn is_u64_500(u: &u64) -> bool { *u == 500 } + +fn u64_1300() -> u64 { 1300 } +fn is_u64_1300(u: &u64) -> bool { *u == 1300 } + +fn i64_100() -> i64 { 100 } +fn is_i64_100(i: &i64) -> bool { *i == 100 } + +fn loglevel_info() -> NebulaConfigLoggingLevel { NebulaConfigLoggingLevel::Info } +fn is_loglevel_info(l: &NebulaConfigLoggingLevel) -> bool { matches!(l, NebulaConfigLoggingLevel::Info) } + +fn format_text() -> NebulaConfigLoggingFormat { NebulaConfigLoggingFormat::Text } +fn is_format_text(f: &NebulaConfigLoggingFormat) -> bool { matches!(f, NebulaConfigLoggingFormat::Text) } + +fn timestamp() -> String { "2006-01-02T15:04:05Z07:00".to_string() } +fn is_timestamp(s: &str) -> bool { s == "2006-01-02T15:04:05Z07:00" } + +fn is_none(o: &Option) -> bool { o.is_none() } \ No newline at end of file From 820a9fada7363ffa93de7b70335ddf83a0341f9b Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 30 Mar 2023 10:10:21 -0400 Subject: [PATCH 41/46] keep working on config representation pt2 --- tfclient/src/config.rs | 99 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index 8882b26..c14e62e 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -122,7 +122,22 @@ pub struct NebulaConfig { pub tun: Option, #[serde(skip_serializing_if = "is_none")] pub logging: Option, + #[serde(skip_serializing_if = "is_none")] + pub sshd: Option, + // FIREWALL + + #[serde(default = "u64_1")] + #[serde(skip_serializing_if = "is_u64_1")] + pub routines: u64, + + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub stats: Option, + + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub local_range: Option } #[derive(Serialize, Deserialize)] @@ -306,6 +321,80 @@ pub enum NebulaConfigLoggingFormat { Text } +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigSshd { + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub enabled: bool, + pub listen: SocketAddrV4, + pub host_key: String, + #[serde(default = "empty_vec")] + #[serde(skip_serializing_if = "is_empty_vec")] + pub authorized_users: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigSshdAuthorizedUser { + pub user: String, + #[serde(default = "empty_vec")] + #[serde(skip_serializing_if = "is_empty_vec")] + pub keys: Vec +} + +#[derive(Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum NebulaConfigStats { + #[serde(rename = "graphite")] + Graphite(NebulaConfigStatsGraphite), + #[serde(rename = "prometheus")] + Prometheus(NebulaConfigStatsPrometheus) +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigStatsGraphite { + #[serde(default = "string_nebula")] + #[serde(skip_serializing_if = "is_string_nebula")] + pub prefix: String, + #[serde(default = "protocol_tcp")] + #[serde(skip_serializing_if = "is_protocol_tcp")] + pub protocol: NebulaConfigStatsGraphiteProtocol, + pub host: SocketAddrV4, + pub interval: String, + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub message_metrics: bool, + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub lighthouse_metrics: bool +} + +#[derive(Serialize, Deserialize)] +pub enum NebulaConfigStatsGraphiteProtocol { + #[serde(rename = "tcp")] + Tcp, + #[serde(rename = "udp")] + Udp +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigStatsPrometheus { + pub listen: String, + pub path: String, + #[serde(default = "string_nebula")] + #[serde(skip_serializing_if = "is_string_nebula")] + pub namespace: String, + #[serde(default = "string_nebula")] + #[serde(skip_serializing_if = "is_string_nebula")] + pub subsystem: String, + pub interval: String, + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub message_metrics: bool, + #[serde(default = "bool_false")] + #[serde(skip_serializing_if = "is_bool_false")] + pub lighthouse_metrics: bool +} + // Default values for serde fn empty_vec() -> Vec { vec![] } fn is_empty_vec(v: &Vec) -> bool { v.is_empty() } @@ -358,4 +447,14 @@ fn is_format_text(f: &NebulaConfigLoggingFormat) -> bool { matches!(f, NebulaCon fn timestamp() -> String { "2006-01-02T15:04:05Z07:00".to_string() } fn is_timestamp(s: &str) -> bool { s == "2006-01-02T15:04:05Z07:00" } +fn u64_1() -> u64 { 1 } +fn is_u64_1(u: &u64) -> bool { *u == 1 } + +fn string_nebula() -> String { "nebula".to_string() } +fn is_string_nebula(s: &str) -> bool { s == "nebula" } + +fn protocol_tcp() -> NebulaConfigStatsGraphiteProtocol { NebulaConfigStatsGraphiteProtocol::Tcp } +fn is_protocol_tcp(p: &NebulaConfigStatsGraphiteProtocol) -> bool { matches!(p, NebulaConfigStatsGraphiteProtocol::Tcp) } + +fn none() -> Option { None } fn is_none(o: &Option) -> bool { o.is_none() } \ No newline at end of file From bb9db54113372ff6482cab79f60117222ad34ec5 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 30 Mar 2023 10:22:34 -0400 Subject: [PATCH 42/46] finish config representation --- tfclient/src/config.rs | 69 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index c14e62e..0ada404 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -125,7 +125,8 @@ pub struct NebulaConfig { #[serde(skip_serializing_if = "is_none")] pub sshd: Option, - // FIREWALL + #[serde(skip_serializing_if = "is_none")] + pub firewall: Option, #[serde(default = "u64_1")] #[serde(skip_serializing_if = "is_u64_1")] @@ -395,7 +396,73 @@ pub struct NebulaConfigStatsPrometheus { pub lighthouse_metrics: bool } +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigFirewall { + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub conntrack: Option, + + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub inbound: Option>, + + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub outbound: Option>, +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigFirewallConntrack { + #[serde(default = "string_12m")] + #[serde(skip_serializing_if = "is_string_12m")] + pub tcp_timeout: String, + #[serde(default = "string_3m")] + #[serde(skip_serializing_if = "is_string_3m")] + pub udp_timeout: String, + #[serde(default = "string_10m")] + #[serde(skip_serializing_if = "is_string_10m")] + pub default_timeout: String +} + +#[derive(Serialize, Deserialize)] +pub struct NebulaConfigFirewallRule { + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub port: Option, + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub proto: Option, + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub ca_name: Option, + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub ca_sha: Option, + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub host: Option, + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub group: Option, + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub groups: Option>, + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub cidr: Option +} + // Default values for serde + +fn string_12m() -> String { "12m".to_string() } +fn is_string_12m(s: &str) -> bool { s == "12m" } + +fn string_3m() -> String { "3m".to_string() } +fn is_string_3m(s: &str) -> bool { s == "3m" } + +fn string_10m() -> String { "10m".to_string() } +fn is_string_10m(s: &str) -> bool { s == "10m" } + fn empty_vec() -> Vec { vec![] } fn is_empty_vec(v: &Vec) -> bool { v.is_empty() } From 7513eb8318f890917c9538e2c745339ab88dcaf7 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 30 Mar 2023 10:43:09 -0400 Subject: [PATCH 43/46] finish nebula impl --- Cargo.lock | 20 ++++++++ tfclient/Cargo.toml | 1 + tfclient/src/config.rs | 50 ++++++++++---------- tfclient/src/daemon.rs | 5 +- tfclient/src/nebulaworker.rs | 89 ++++++++++++++++++++++++++++++++++-- 5 files changed, 135 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2384c5..fc1592a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2139,6 +2139,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.5" @@ -2472,6 +2485,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "serde_yaml", "sha2", "simple_logger", "tar", @@ -2889,6 +2903,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c" + [[package]] name = "url" version = "2.3.1" diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 7dce9aa..bd16e92 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -25,6 +25,7 @@ chrono = "0.4.24" ipnet = "2.7.1" base64-serde = "0.7.0" dnapi-rs = { version = "0.1.6", path = "../dnapi-rs" } +serde_yaml = "0.9.19" [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index 0ada404..ed92faa 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -15,7 +15,7 @@ use crate::dirs::{get_cdata_dir, get_cdata_file, get_config_dir, get_config_file pub const DEFAULT_PORT: u16 = 8157; fn default_port() -> u16 { DEFAULT_PORT } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct TFClientConfig { #[serde(default = "default_port")] pub listen_port: u16 @@ -23,7 +23,7 @@ pub struct TFClientConfig { #[derive(Serialize, Deserialize, Clone)] pub struct TFClientData { - pub dh_privkey: Option<[u8; 32]>, + pub dh_privkey: Option>, pub creds: Option, pub meta: Option } @@ -98,7 +98,7 @@ pub fn save_cdata(instance: &str, data: TFClientData) -> Result<(), Box } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigPki { pub ca: String, pub cert: String, @@ -154,7 +154,7 @@ pub struct NebulaConfigPki { pub disconnect_invalid: bool } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigLighthouse { #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] @@ -178,7 +178,7 @@ pub struct NebulaConfigLighthouse { pub local_allow_list: HashMap, // `interfaces` is not supported } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigLighthouseDns { pub host: Ipv4Addr, #[serde(default = "u16_53")] @@ -186,7 +186,7 @@ pub struct NebulaConfigLighthouseDns { pub port: u16 } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigListen { #[serde(default = "ipv4_0000")] #[serde(skip_serializing_if = "is_ipv4_0000")] @@ -203,7 +203,7 @@ pub struct NebulaConfigListen { pub write_buffer: Option } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigPunchy { #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] @@ -216,7 +216,7 @@ pub struct NebulaConfigPunchy { pub delay: String } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub enum NebulaConfigCipher { #[serde(rename = "aes")] Aes, @@ -224,7 +224,7 @@ pub enum NebulaConfigCipher { ChaChaPoly } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigRelay { #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] @@ -237,7 +237,7 @@ pub struct NebulaConfigRelay { pub use_relays: bool } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigTun { #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] @@ -264,13 +264,13 @@ pub struct NebulaConfigTun { pub unsafe_routes: Vec } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigTunRouteOverride { pub mtu: u64, pub route: Ipv4Net } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigTunUnsafeRoute { pub route: Ipv4Net, pub via: Ipv4Addr, @@ -282,7 +282,7 @@ pub struct NebulaConfigTunUnsafeRoute { pub metric: i64 } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigLogging { #[serde(default = "loglevel_info")] #[serde(skip_serializing_if = "is_loglevel_info")] @@ -298,7 +298,7 @@ pub struct NebulaConfigLogging { pub timestamp_format: String } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub enum NebulaConfigLoggingLevel { #[serde(rename = "panic")] Panic, @@ -314,7 +314,7 @@ pub enum NebulaConfigLoggingLevel { Debug } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub enum NebulaConfigLoggingFormat { #[serde(rename = "json")] Json, @@ -322,7 +322,7 @@ pub enum NebulaConfigLoggingFormat { Text } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigSshd { #[serde(default = "bool_false")] #[serde(skip_serializing_if = "is_bool_false")] @@ -334,7 +334,7 @@ pub struct NebulaConfigSshd { pub authorized_users: Vec } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigSshdAuthorizedUser { pub user: String, #[serde(default = "empty_vec")] @@ -342,7 +342,7 @@ pub struct NebulaConfigSshdAuthorizedUser { pub keys: Vec } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(tag = "type")] pub enum NebulaConfigStats { #[serde(rename = "graphite")] @@ -351,7 +351,7 @@ pub enum NebulaConfigStats { Prometheus(NebulaConfigStatsPrometheus) } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigStatsGraphite { #[serde(default = "string_nebula")] #[serde(skip_serializing_if = "is_string_nebula")] @@ -369,7 +369,7 @@ pub struct NebulaConfigStatsGraphite { pub lighthouse_metrics: bool } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub enum NebulaConfigStatsGraphiteProtocol { #[serde(rename = "tcp")] Tcp, @@ -377,7 +377,7 @@ pub enum NebulaConfigStatsGraphiteProtocol { Udp } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigStatsPrometheus { pub listen: String, pub path: String, @@ -396,7 +396,7 @@ pub struct NebulaConfigStatsPrometheus { pub lighthouse_metrics: bool } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigFirewall { #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] @@ -411,7 +411,7 @@ pub struct NebulaConfigFirewall { pub outbound: Option>, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigFirewallConntrack { #[serde(default = "string_12m")] #[serde(skip_serializing_if = "is_string_12m")] @@ -424,7 +424,7 @@ pub struct NebulaConfigFirewallConntrack { pub default_timeout: String } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigFirewallRule { #[serde(default = "none")] #[serde(skip_serializing_if = "is_none")] diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index 7d912e0..6f59054 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -91,7 +91,7 @@ pub fn daemon_main(name: String, server: String) { let transmitter_nebula = transmitter.clone(); let name_nebula = name.clone(); let nebula_thread = thread::spawn(move || { - nebulaworker_main(config_nebula, name, transmitter_nebula, rx_nebula); + nebulaworker_main(config_nebula, name_nebula, transmitter_nebula, rx_nebula); }); info!("Starting timer thread..."); @@ -101,8 +101,9 @@ pub fn daemon_main(name: String, server: String) { }); info!("Starting socket worker thread..."); + let name_socket = name.clone(); let socket_thread = thread::spawn(move || { - socketworker_main(config, name.clone(), transmitter, rx_socket); + socketworker_main(config, name_socket, transmitter, rx_socket); }); info!("Waiting for socket thread to exit..."); diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index 7cb685a..eadc28a 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -1,15 +1,36 @@ // Code to handle the nebula worker +use std::error::Error; +use std::fs; use std::sync::mpsc::{Receiver, TryRecvError}; -use log::{error, info}; -use crate::config::{load_cdata, save_cdata, TFClientConfig}; +use log::{debug, error, info}; +use crate::config::{load_cdata, NebulaConfig, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; +use crate::dirs::get_nebulaconfig_file; +use crate::embedded_nebula::run_embedded_nebula; pub enum NebulaWorkerMessage { Shutdown, ConfigUpdated } +fn insert_private_key(instance: &str) -> Result<(), Box> { + let cdata = load_cdata(instance)?; + let key = cdata.dh_privkey.ok_or("Missing private key")?; + + let config_str = fs::read_to_string(get_nebulaconfig_file(instance).ok_or("Could not get config file location")?)?; + let mut config: NebulaConfig = serde_yaml::from_str(&config_str)?; + + config.pki.key = String::from_utf8(key)?; + + debug!("inserted private key into config: {:?}", config); + + let config_str = serde_yaml::to_string(&config)?; + fs::write(get_nebulaconfig_file(instance).ok_or("Could not get config file location")?, config_str)?; + + Ok(()) +} + pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter: ThreadMessageSender, rx: Receiver) { let cdata = match load_cdata(&instance) { Ok(data) => data, @@ -20,6 +41,28 @@ pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter } }; + info!("fixing config..."); + match insert_private_key(&instance) { + Ok(_) => { + info!("config fixed (private-key embedded)"); + }, + Err(e) => { + error!("unable to fix config: {}", e); + error!("nebula thread exiting with error"); + return; + } + } + info!("starting nebula child..."); + let mut child = match run_embedded_nebula(&["-config".to_string(), get_nebulaconfig_file(&instance).unwrap().to_str().unwrap().to_string()]) { + Ok(c) => c, + Err(e) => { + error!("unable to start embedded nebula binary: {}", e); + error!("nebula thread exiting with error"); + return; + } + }; + info!("nebula process started"); + // dont need to save it, because we do not, in any circumstance, write to it loop { match rx.try_recv() { @@ -27,10 +70,50 @@ pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter match msg { NebulaWorkerMessage::Shutdown => { info!("recv on command socket: shutdown, stopping"); + info!("shutting down nebula binary"); + match child.kill() { + Ok(_) => { + debug!("nebula process exited"); + }, + Err(e) => { + error!("nebula process already exited: {}", e); + } + } + info!("nebula shut down"); break; }, NebulaWorkerMessage::ConfigUpdated => { - info!("our configuration has been updated - reloading"); + info!("our configuration has been updated - restarting"); + debug!("killing existing process"); + match child.kill() { + Ok(_) => { + debug!("nebula process exited"); + }, + Err(e) => { + error!("nebula process already exited: {}", e); + } + } + debug!("fixing config..."); + match insert_private_key(&instance) { + Ok(_) => { + debug!("config fixed (private-key embedded)"); + }, + Err(e) => { + error!("unable to fix config: {}", e); + error!("nebula thread exiting with error"); + return; + } + } + debug!("restarting nebula process"); + child = match run_embedded_nebula(&["-config".to_string(), get_nebulaconfig_file(&instance).unwrap().to_str().unwrap().to_string()]) { + Ok(c) => c, + Err(e) => { + error!("unable to start embedded nebula binary: {}", e); + error!("nebula thread exiting with error"); + return; + } + }; + debug!("nebula process restarted"); } } }, From fa40dcda5b2dc1fedc846aece85f90aa4b5e7b67 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 30 Mar 2023 12:13:29 -0400 Subject: [PATCH 44/46] tfclient is done! woo! --- Cargo.lock | 4 ++-- dnapi-rs/Cargo.toml | 2 +- dnapi-rs/src/client_async.rs | 11 +++++++++-- dnapi-rs/src/client_blocking.rs | 18 +++++++++++++++--- dnapi-rs/src/message.rs | 5 ++--- tfclient/Cargo.toml | 2 +- tfclient/src/config.rs | 17 ++++++++++++----- tfclient/src/nebulaworker.rs | 24 +++++++++++++++++++++++- trifid-pki/Cargo.toml | 2 +- trifid-pki/src/cert.rs | 8 ++++---- trifid-pki/src/test.rs | 23 ++++++++++++----------- 11 files changed, 82 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc1592a..66579ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,7 +622,7 @@ dependencies = [ [[package]] name = "dnapi-rs" -version = "0.1.6" +version = "0.1.7" dependencies = [ "base64 0.21.0", "base64-serde", @@ -2803,7 +2803,7 @@ dependencies = [ [[package]] name = "trifid-pki" -version = "0.1.8" +version = "0.1.9" dependencies = [ "ed25519-dalek", "hex", diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 2991ff6..7a0bebc 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dnapi-rs" -version = "0.1.6" +version = "0.1.7" edition = "2021" description = "A rust client for the Defined Networking API" license = "AGPL-3.0-or-later" diff --git a/dnapi-rs/src/client_async.rs b/dnapi-rs/src/client_async.rs index 90d8266..3badec2 100644 --- a/dnapi-rs/src/client_async.rs +++ b/dnapi-rs/src/client_async.rs @@ -11,6 +11,7 @@ use crate::credentials::{Credentials, ed25519_public_keys_from_pem}; use crate::crypto::{new_keys, nonce}; use crate::message::{CHECK_FOR_UPDATE, CheckForUpdateResponseWrapper, DO_UPDATE, DoUpdateRequest, DoUpdateResponse, ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper, SignedResponseWrapper}; use serde::{Serialize, Deserialize}; +use base64::Engine; /// A type alias to abstract return types pub type NebulaConfig = Vec; @@ -185,12 +186,18 @@ impl Client { timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), })?; let encoded_msg_bytes = encoded_msg.into_bytes(); - let signature = ed_privkey.sign(&encoded_msg_bytes).to_vec(); + let b64_msg = base64::engine::general_purpose::STANDARD.encode(encoded_msg_bytes); + let b64_msg_bytes = b64_msg.as_bytes(); + let signature = ed_privkey.sign(b64_msg_bytes).to_vec(); + + ed_privkey.verify(b64_msg_bytes, &Signature::from_slice(&signature)?)?; + debug!("signature valid via clientside check"); + let body = RequestV1 { version: 1, host_id: host_id.to_string(), counter, - message: encoded_msg_bytes, + message: b64_msg, signature, }; diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index 5b372e8..bd932e9 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -1,8 +1,9 @@ //! Client structs to handle communication with the Defined Networking API. This is the blocking client API - if you want async instead, set no-default-features and enable the async feature instead. use std::error::Error; +use base64::Engine; use chrono::Local; -use log::{debug, error}; +use log::{debug, error, trace}; use reqwest::StatusCode; use url::Url; use trifid_pki::cert::serialize_ed25519_public; @@ -90,6 +91,9 @@ impl Client { organization_name: r.organization.name, }; + debug!("parsing public keys"); + + let trusted_keys = ed25519_public_keys_from_pem(&r.trusted_keys)?; let creds = Credentials { @@ -190,17 +194,25 @@ impl Client { timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), })?; let encoded_msg_bytes = encoded_msg.into_bytes(); - let signature = ed_privkey.sign(&encoded_msg_bytes).to_vec(); + let b64_msg = base64::engine::general_purpose::STANDARD.encode(encoded_msg_bytes); + let b64_msg_bytes = b64_msg.as_bytes(); + let signature = ed_privkey.sign(b64_msg_bytes).to_vec(); + + ed_privkey.verify(b64_msg_bytes, &Signature::from_slice(&signature)?)?; + debug!("signature valid via clientside check"); + let body = RequestV1 { version: 1, host_id: host_id.to_string(), counter, - message: encoded_msg_bytes, + message: b64_msg, signature, }; let post_body = serde_json::to_string(&body)?; + trace!("sending dnclient request {}", post_body); + let resp = self.http_client.post(self.server_url.join(ENDPOINT_V1)?).body(post_body).send()?; match resp.status() { diff --git a/dnapi-rs/src/message.rs b/dnapi-rs/src/message.rs index ce1e891..d1360ed 100644 --- a/dnapi-rs/src/message.rs +++ b/dnapi-rs/src/message.rs @@ -23,9 +23,8 @@ pub struct RequestV1 { pub host_id: String, /// The counter last returned by the server pub counter: u32, - #[serde(with = "Base64Standard")] - /// A base64-encoded message - pub message: Vec, + /// A base64-encoded message. This must be previously base64-encoded, as the signature is signed over the base64-encoded data. + pub message: String, #[serde(with = "Base64Standard")] /// An ed25519 signature over the `message`, which can be verified with the host's previously enrolled ed25519 public key pub signature: Vec diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index bd16e92..6d920e2 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -24,7 +24,7 @@ base64 = "0.21.0" chrono = "0.4.24" ipnet = "2.7.1" base64-serde = "0.7.0" -dnapi-rs = { version = "0.1.6", path = "../dnapi-rs" } +dnapi-rs = { version = "0.1.7", path = "../dnapi-rs" } serde_yaml = "0.9.19" [build-dependencies] diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index ed92faa..517a09b 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -145,7 +145,9 @@ pub struct NebulaConfig { pub struct NebulaConfigPki { pub ca: String, pub cert: String, - pub key: String, + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub key: Option, #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub blocklist: Vec, @@ -180,7 +182,9 @@ pub struct NebulaConfigLighthouse { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigLighthouseDns { - pub host: Ipv4Addr, + #[serde(default = "string_empty")] + #[serde(skip_serializing_if = "is_string_empty")] + pub host: String, #[serde(default = "u16_53")] #[serde(skip_serializing_if = "is_u16_53")] pub port: u16 @@ -188,9 +192,9 @@ pub struct NebulaConfigLighthouseDns { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigListen { - #[serde(default = "ipv4_0000")] - #[serde(skip_serializing_if = "is_ipv4_0000")] - pub host: Ipv4Addr, + #[serde(default = "string_empty")] + #[serde(skip_serializing_if = "is_string_empty")] + pub host: String, #[serde(default = "u16_0")] #[serde(skip_serializing_if = "is_u16_0")] pub port: u16, @@ -520,6 +524,9 @@ fn is_u64_1(u: &u64) -> bool { *u == 1 } fn string_nebula() -> String { "nebula".to_string() } fn is_string_nebula(s: &str) -> bool { s == "nebula" } +fn string_empty() -> String { String::new() } +fn is_string_empty(s: &str) -> bool { s == "" } + fn protocol_tcp() -> NebulaConfigStatsGraphiteProtocol { NebulaConfigStatsGraphiteProtocol::Tcp } fn is_protocol_tcp(p: &NebulaConfigStatsGraphiteProtocol) -> bool { matches!(p, NebulaConfigStatsGraphiteProtocol::Tcp) } diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index eadc28a..02c1230 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -3,6 +3,7 @@ use std::error::Error; use std::fs; use std::sync::mpsc::{Receiver, TryRecvError}; +use std::time::{Duration, SystemTime}; use log::{debug, error, info}; use crate::config::{load_cdata, NebulaConfig, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; @@ -15,13 +16,16 @@ pub enum NebulaWorkerMessage { } fn insert_private_key(instance: &str) -> Result<(), Box> { + if !get_nebulaconfig_file(instance).ok_or("Could not get config file location")?.exists() { + return Ok(()); // cant insert private key into a file that does not exist - BUT. we can gracefully handle nebula crashing - we cannot gracefully handle this fn failing + } let cdata = load_cdata(instance)?; let key = cdata.dh_privkey.ok_or("Missing private key")?; let config_str = fs::read_to_string(get_nebulaconfig_file(instance).ok_or("Could not get config file location")?)?; let mut config: NebulaConfig = serde_yaml::from_str(&config_str)?; - config.pki.key = String::from_utf8(key)?; + config.pki.key = Some(String::from_utf8(key)?); debug!("inserted private key into config: {:?}", config); @@ -63,8 +67,25 @@ pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter }; info!("nebula process started"); + let mut last_restart_time = SystemTime::now(); + // dont need to save it, because we do not, in any circumstance, write to it loop { + if let Ok(e) = child.try_wait() { + if e.is_some() && SystemTime::now() > last_restart_time + Duration::from_secs(5) { + info!("nebula process has exited, restarting"); + child = match run_embedded_nebula(&["-config".to_string(), get_nebulaconfig_file(&instance).unwrap().to_str().unwrap().to_string()]) { + Ok(c) => c, + Err(e) => { + error!("unable to start embedded nebula binary: {}", e); + error!("nebula thread exiting with error"); + return; + } + }; + info!("nebula process started"); + last_restart_time = SystemTime::now(); + } + } match rx.try_recv() { Ok(msg) => { match msg { @@ -113,6 +134,7 @@ pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter return; } }; + last_restart_time = SystemTime::now(); debug!("nebula process restarted"); } } diff --git a/trifid-pki/Cargo.toml b/trifid-pki/Cargo.toml index 0cb0bee..fae5fb2 100644 --- a/trifid-pki/Cargo.toml +++ b/trifid-pki/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trifid-pki" -version = "0.1.8" +version = "0.1.9" edition = "2021" description = "A rust implementation of the Nebula PKI system" license = "AGPL-3.0-or-later" diff --git a/trifid-pki/src/cert.rs b/trifid-pki/src/cert.rs index 190baf0..60b43fd 100644 --- a/trifid-pki/src/cert.rs +++ b/trifid-pki/src/cert.rs @@ -307,8 +307,8 @@ pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result, Box Result>, Box if pem.tag != ED25519_PUBLIC_KEY_BANNER { return Err(KeyError::WrongPemTag.into()) } - if pem.contents.len() != 64 { - return Err(KeyError::Not64Bytes.into()) + if pem.contents.len() != 32 { + return Err(KeyError::Not32Bytes.into()) } keys.push(pem.contents); } diff --git a/trifid-pki/src/test.rs b/trifid-pki/src/test.rs index 3d328dc..2a409c5 100644 --- a/trifid-pki/src/test.rs +++ b/trifid-pki/src/test.rs @@ -296,19 +296,20 @@ fn x25519_serialization() { #[test] fn ed25519_serialization() { let bytes = [0u8; 64]; + let bytes2 = [0u8; 32]; assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes); assert!(deserialize_ed25519_private(&[0u8; 32]).is_err()); - assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes); - assert!(deserialize_ed25519_public(&[0u8; 32]).is_err()); + assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes2)).unwrap(), bytes2); + assert!(deserialize_ed25519_public(&[0u8; 64]).is_err()); let mut bytes = vec![]; - bytes.append(&mut serialize_ed25519_public(&[0u8; 64])); - bytes.append(&mut serialize_ed25519_public(&[1u8; 64])); + bytes.append(&mut serialize_ed25519_public(&[0u8; 32])); + bytes.append(&mut serialize_ed25519_public(&[1u8; 32])); let deser = deserialize_ed25519_public_many(&bytes).unwrap(); - assert_eq!(deser[0], [0u8; 64]); - assert_eq!(deser[1], [1u8; 64]); + assert_eq!(deser[0], [0u8; 32]); + assert_eq!(deser[1], [1u8; 32]); - bytes.append(&mut serialize_ed25519_public(&[1u8; 63])); + bytes.append(&mut serialize_ed25519_public(&[1u8; 33])); deserialize_ed25519_public_many(&bytes).unwrap_err(); } @@ -647,11 +648,11 @@ bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB #[test] fn test_deserialize_ed25519_public() { - let priv_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY----- -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + let pub_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY----- +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -----END NEBULA ED25519 PUBLIC KEY-----"; let short_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY----- -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -----END NEBULA ED25519 PUBLIC KEY-----"; let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY----- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== @@ -660,7 +661,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== -END NEBULA ED25519 PUBLIC KEY-----"; - deserialize_ed25519_public(priv_key).unwrap(); + deserialize_ed25519_public(pub_key).unwrap(); deserialize_ed25519_public(short_key).unwrap_err(); From fc23aaa7743b93e7de01f592e495d62e77240d33 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 30 Mar 2023 12:27:02 -0400 Subject: [PATCH 45/46] update tfclient documentation --- tfclient/Cargo.toml | 4 ++++ tfclient/README.md | 5 +++++ 2 files changed, 9 insertions(+) create mode 100644 tfclient/README.md diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 6d920e2..0e069f6 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -3,6 +3,10 @@ name = "tfclient" version = "0.1.0" edition = "2021" description = "An open-source reimplementation of a Defined Networking-compatible client" +license = "GPL-3.0-or-later" +documentation = "https://man.e3t.cc/~core/trifid-docs" +homepage = "https://man.e3t.cc/~core/trifid-docs" +repository = "https://git.e3t.cc/~core/trifid" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tfclient/README.md b/tfclient/README.md new file mode 100644 index 0000000..6848163 --- /dev/null +++ b/tfclient/README.md @@ -0,0 +1,5 @@ +# tfclient + +tfclient is an open-source Rust client for the Defined Networking Management protocol. It embeds a Nebula binary for running the actual config, and uses [dnapi-rs](https://docs.rs/dnapi-rs) for making API calls against the Defined Networking API. + +tfclient is part of the trifid project. Check out the other crates [here!](https://git.e3t.cc/~core/trifid). \ No newline at end of file From 0049ce9fcad83971965db8a50f85ada645adf630 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 30 Mar 2023 12:28:34 -0400 Subject: [PATCH 46/46] cargo-fix --- tfclient/Cargo.toml | 2 +- tfclient/src/apiworker.rs | 14 +++++++------- tfclient/src/nebulaworker.rs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 0e069f6..e58450c 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tfclient" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = "An open-source reimplementation of a Defined Networking-compatible client" license = "GPL-3.0-or-later" diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 6f328db..4246e93 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -1,14 +1,14 @@ use std::fs; use std::sync::mpsc::{Receiver, TryRecvError}; -use base64::Engine; -use chrono::Local; + + use log::{error, info, warn}; use url::Url; use dnapi_rs::client_blocking::Client; -use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; -use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; -use trifid_pki::rand_core::OsRng; -use trifid_pki::x25519_dalek::StaticSecret; + + + + use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; use crate::dirs::get_nebulaconfig_file; @@ -20,7 +20,7 @@ pub enum APIWorkerMessage { Timer } -pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, tx: ThreadMessageSender, rx: Receiver) { +pub fn apiworker_main(_config: TFClientConfig, instance: String, url: String, tx: ThreadMessageSender, rx: Receiver) { let server = Url::parse(&url).unwrap(); let client = Client::new(format!("tfclient/{}", env!("CARGO_PKG_VERSION")), server).unwrap(); diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index 02c1230..6678670 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -5,7 +5,7 @@ use std::fs; use std::sync::mpsc::{Receiver, TryRecvError}; use std::time::{Duration, SystemTime}; use log::{debug, error, info}; -use crate::config::{load_cdata, NebulaConfig, save_cdata, TFClientConfig}; +use crate::config::{load_cdata, NebulaConfig, TFClientConfig}; use crate::daemon::ThreadMessageSender; use crate::dirs::get_nebulaconfig_file; use crate::embedded_nebula::run_embedded_nebula; @@ -36,7 +36,7 @@ fn insert_private_key(instance: &str) -> Result<(), Box> { } pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter: ThreadMessageSender, rx: Receiver) { - let cdata = match load_cdata(&instance) { + let _cdata = match load_cdata(&instance) { Ok(data) => data, Err(e) => { error!("unable to load cdata: {}", e);