merge feat-tfclient
This commit is contained in:
commit
43636a51e9
|
@ -1 +1,2 @@
|
||||||
target
|
target
|
||||||
|
pg_data
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "dnapi"]
|
|
||||||
path = dnapi
|
|
||||||
url = https://github.com/DefinedNet/dnapi
|
|
|
@ -5,6 +5,7 @@
|
||||||
<sourceFolder url="file://$MODULE_DIR$/tfclient/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/tfclient/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/trifid-api/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/trifid-api/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/trifid-pki/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/trifid-pki/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/dnapi-rs/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|
|
@ -81,18 +81,18 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.64"
|
version = "0.1.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
|
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -154,6 +154,16 @@ version = "0.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
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]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.5.3"
|
version = "1.5.3"
|
||||||
|
@ -219,9 +229,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.23"
|
version = "0.4.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
@ -242,6 +252,43 @@ dependencies = [
|
||||||
"inout",
|
"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]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
@ -258,6 +305,17 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
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]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
|
@ -376,6 +434,16 @@ dependencies = [
|
||||||
"cipher",
|
"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]]
|
[[package]]
|
||||||
name = "curve25519-dalek"
|
name = "curve25519-dalek"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
|
@ -385,6 +453,7 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"digest 0.9.0",
|
"digest 0.9.0",
|
||||||
"rand_core 0.5.1",
|
"rand_core 0.5.1",
|
||||||
|
"serde",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
@ -428,7 +497,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"scratch",
|
"scratch",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -445,7 +514,7 @@ checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -488,7 +557,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"proc-macro2-diagnostics",
|
"proc-macro2-diagnostics",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -517,7 +586,16 @@ version = "4.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
|
@ -531,6 +609,33 @@ dependencies = [
|
||||||
"winapi",
|
"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 = "dnapi-rs"
|
||||||
|
version = "0.1.7"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.0",
|
||||||
|
"base64-serde",
|
||||||
|
"chrono",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"trifid-pki",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenvy"
|
name = "dotenvy"
|
||||||
version = "0.15.6"
|
version = "0.15.6"
|
||||||
|
@ -544,6 +649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cf420a7ec85d98495b0c34aa4a58ca117f982ffbece111aeb545160148d7010"
|
checksum = "3cf420a7ec85d98495b0c34aa4a58ca117f982ffbece111aeb545160148d7010"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pkcs8",
|
"pkcs8",
|
||||||
|
"serde",
|
||||||
"signature",
|
"signature",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -557,6 +663,7 @@ dependencies = [
|
||||||
"ed25519",
|
"ed25519",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_bytes",
|
||||||
"sha2",
|
"sha2",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
@ -576,6 +683,27 @@ dependencies = [
|
||||||
"cfg-if",
|
"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]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "2.5.3"
|
version = "2.5.3"
|
||||||
|
@ -611,6 +739,18 @@ dependencies = [
|
||||||
"version_check",
|
"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]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.25"
|
version = "1.0.25"
|
||||||
|
@ -852,6 +992,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
@ -934,6 +1080,19 @@ dependencies = [
|
||||||
"want",
|
"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]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.53"
|
version = "0.1.53"
|
||||||
|
@ -1017,6 +1176,17 @@ dependencies = [
|
||||||
"cfg-if",
|
"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]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.7.1"
|
version = "2.7.1"
|
||||||
|
@ -1026,6 +1196,18 @@ dependencies = [
|
||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
|
@ -1077,6 +1259,12 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
@ -1206,6 +1394,18 @@ dependencies = [
|
||||||
"tempfile",
|
"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]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
@ -1216,15 +1416,6 @@ dependencies = [
|
||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom8"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
@ -1275,6 +1466,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
|
@ -1310,7 +1510,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1332,6 +1532,12 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -1422,7 +1628,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"proc-macro2-diagnostics",
|
"proc-macro2-diagnostics",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1505,10 +1711,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro-error"
|
||||||
version = "1.0.50"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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 = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -1521,7 +1751,7 @@ checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"version_check",
|
"version_check",
|
||||||
"yansi",
|
"yansi",
|
||||||
]
|
]
|
||||||
|
@ -1543,9 +1773,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.23"
|
version = "1.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -1626,7 +1856,7 @@ checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1654,12 +1884,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "reqwest"
|
||||||
version = "0.5.3"
|
version = "0.11.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
|
@ -1713,7 +1971,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rocket_http",
|
"rocket_http",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1744,6 +2002,20 @@ dependencies = [
|
||||||
"uncased",
|
"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]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
|
@ -1808,29 +2080,38 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.152"
|
version = "1.0.159"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_bytes"
|
||||||
version = "1.0.152"
|
version = "0.11.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.159"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.91"
|
version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -1846,6 +2127,31 @@ dependencies = [
|
||||||
"serde",
|
"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 = "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]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
|
@ -1892,6 +2198,19 @@ version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d"
|
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]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
|
@ -1968,7 +2287,7 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"crc",
|
"crc",
|
||||||
"crossbeam-queue",
|
"crossbeam-queue",
|
||||||
"dirs",
|
"dirs 4.0.0",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"either",
|
"either",
|
||||||
"event-listener",
|
"event-listener",
|
||||||
|
@ -2019,7 +2338,7 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"sqlx-rt",
|
"sqlx-rt",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2053,6 +2372,12 @@ dependencies = [
|
||||||
"loom",
|
"loom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stringprep"
|
name = "stringprep"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -2063,6 +2388,12 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
|
@ -2080,6 +2411,17 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
version = "0.12.6"
|
version = "0.12.6"
|
||||||
|
@ -2088,22 +2430,32 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tar"
|
||||||
version = "3.3.0"
|
version = "0.4.38"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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 = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"remove_dir_all",
|
"rustix",
|
||||||
"winapi",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2118,6 +2470,30 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tfclient"
|
name = "tfclient"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.0",
|
||||||
|
"base64-serde",
|
||||||
|
"chrono",
|
||||||
|
"clap",
|
||||||
|
"ctrlc",
|
||||||
|
"dirs 5.0.0",
|
||||||
|
"dnapi-rs",
|
||||||
|
"flate2",
|
||||||
|
"hex",
|
||||||
|
"ipnet",
|
||||||
|
"log",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
"sha2",
|
||||||
|
"simple_logger",
|
||||||
|
"tar",
|
||||||
|
"tempfile",
|
||||||
|
"toml 0.7.3",
|
||||||
|
"trifid-pki",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
|
@ -2136,7 +2512,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2166,6 +2542,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
|
@ -2229,7 +2607,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2278,9 +2656,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.7.1"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "772c1426ab886e7362aedf4abc9c0d1348a979517efedfc25862944d10137af0"
|
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
|
@ -2299,15 +2677,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.19.1"
|
version = "0.19.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90a238ee2e6ede22fb95350acc78e21dc40da00bb66c0334bde83de4ed89424e"
|
checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"nom8",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2355,7 +2733,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2415,7 +2793,7 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.7.1",
|
"toml 0.7.3",
|
||||||
"totp-rs",
|
"totp-rs",
|
||||||
"trifid-pki",
|
"trifid-pki",
|
||||||
"url",
|
"url",
|
||||||
|
@ -2425,7 +2803,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "trifid-pki"
|
name = "trifid-pki"
|
||||||
version = "0.1.4"
|
version = "0.1.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"hex",
|
"hex",
|
||||||
|
@ -2434,6 +2812,7 @@ dependencies = [
|
||||||
"quick-protobuf",
|
"quick-protobuf",
|
||||||
"rand",
|
"rand",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
]
|
]
|
||||||
|
@ -2524,6 +2903,12 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
@ -2561,7 +2946,7 @@ checksum = "c1b300a878652a387d2a0de915bdae8f1a548f0c6d45e072fe2688794b656cc9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2631,10 +3016,22 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"wasm-bindgen-shared",
|
"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]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.84"
|
version = "0.2.84"
|
||||||
|
@ -2653,7 +3050,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -2839,6 +3236,24 @@ version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "x25519-dalek"
|
name = "x25519-dalek"
|
||||||
version = "2.0.0-pre.1"
|
version = "2.0.0-pre.1"
|
||||||
|
@ -2847,9 +3262,19 @@ checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"curve25519-dalek 3.2.0",
|
"curve25519-dalek 3.2.0",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
|
"serde",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xattr"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yansi"
|
name = "yansi"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -2873,6 +3298,6 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.107",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
members = [
|
members = [
|
||||||
"trifid-api",
|
"trifid-api",
|
||||||
"tfclient",
|
"tfclient",
|
||||||
"trifid-pki"
|
"trifid-pki",
|
||||||
|
"dnapi-rs"
|
||||||
]
|
]
|
|
@ -7,6 +7,9 @@ Connection: close
|
||||||
|
|
||||||
{"code":"xM22QsIzd4F0nLDTbh86RCSYwelfU_Hshqt-7u4yy_Y","dhPubkey":"LS0tLS1CRUdJTiBORUJVTEEgWDI1NTE5IFBVQkxJQyBLRVktLS0tLQpqZW9aaDZZYUNNSHZKK04zWGRlQ1hCbHo3dm5saTBjL1NlQ1hVR3lYbEIwPQotLS0tLUVORCBORUJVTEEgWDI1NTE5IFBVQkxJQyBLRVktLS0tLQo=","edPubkey":"LS0tLS1CRUdJTiBORUJVTEEgRUQyNTUxOSBQVUJMSUMgS0VZLS0tLS0KWHE0RG9mUGJoQzBubjc4VEhRWUxhNC83V1Ixei9iU1kzSm9pRzNRZ1VMcz0KLS0tLS1FTkQgTkVCVUxBIEVEMjU1MTkgUFVCTElDIEtFWS0tLS0tCg==","timestamp":"2023-02-01T13:24:56.380006369-05:00"}
|
{"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
|
HTTP/2 200 OK
|
||||||
Cache-Control: no-store
|
Cache-Control: no-store
|
||||||
Content-Security-Policy: default-src 'none'
|
Content-Security-Policy: default-src 'none'
|
||||||
|
|
1
dnapi
1
dnapi
|
@ -1 +0,0 @@
|
||||||
Subproject commit 6f56f055f9912755979e27942a91efcf794a82dc
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "dnapi-rs"
|
||||||
|
version = "0.1.7"
|
||||||
|
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", features = ["serde_derive"] }
|
||||||
|
rand = "0.8.5"
|
||||||
|
chrono = "0.4.24"
|
|
@ -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).
|
|
@ -0,0 +1,221 @@
|
||||||
|
//! 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};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use base64::Engine;
|
||||||
|
|
||||||
|
/// A type alias to abstract return types
|
||||||
|
pub type NebulaConfig = Vec<u8>;
|
||||||
|
|
||||||
|
/// A type alias to abstract DH private keys
|
||||||
|
pub type DHPrivateKeyPEM = Vec<u8>;
|
||||||
|
|
||||||
|
/// A combination of persistent data and HTTP client used for communicating with the API.
|
||||||
|
pub struct Client {
|
||||||
|
http_client: reqwest::Client,
|
||||||
|
server_url: Url
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
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<Self, Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<bool, Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<Vec<u8>, Box<dyn Error>> {
|
||||||
|
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 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: b64_msg,
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
//! 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, trace};
|
||||||
|
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};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
/// A type alias to abstract return types
|
||||||
|
pub type NebulaConfig = Vec<u8>;
|
||||||
|
|
||||||
|
/// A type alias to abstract DH private keys
|
||||||
|
pub type DHPrivateKeyPEM = Vec<u8>;
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
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<Self, Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("parsing public keys");
|
||||||
|
|
||||||
|
|
||||||
|
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 fn check_for_update(&self, creds: &Credentials) -> Result<bool, Box<dyn Error>> {
|
||||||
|
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
|
||||||
|
/// # 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<dyn Error>> {
|
||||||
|
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("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 {
|
||||||
|
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 fn post_dnclient(&self, req_type: &str, value: &[u8], host_id: &str, counter: u32, ed_privkey: &SigningKey) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
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 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: 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() {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
//! 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_many, serialize_ed25519_public};
|
||||||
|
use trifid_pki::ed25519_dalek::{SigningKey, VerifyingKey};
|
||||||
|
use serde::{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
|
||||||
|
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<VerifyingKey>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an array of `VerifyingKey`s to a singular bundle of PEM-encoded keys
|
||||||
|
pub fn ed25519_public_keys_to_pem(keys: &[VerifyingKey]) -> Vec<u8> {
|
||||||
|
let mut res = vec![];
|
||||||
|
|
||||||
|
for key in keys {
|
||||||
|
res.append(&mut serialize_ed25519_public(&key.to_bytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<Vec<VerifyingKey>, Box<dyn Error>> {
|
||||||
|
let pems = deserialize_ed25519_public_many(pem)?;
|
||||||
|
let mut keys = vec![];
|
||||||
|
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
for pem in pems {
|
||||||
|
keys.push(VerifyingKey::from_bytes(&pem.try_into().unwrap_or_else(|_| unreachable!()))?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(keys)
|
||||||
|
}
|
|
@ -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<u8>, Vec<u8>, 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<u8>, Vec<u8>) {
|
||||||
|
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(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()
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
//! # 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_blocking;
|
||||||
|
pub mod client_async;
|
||||||
|
|
||||||
|
pub mod credentials;
|
||||||
|
pub mod crypto;
|
|
@ -0,0 +1,201 @@
|
||||||
|
//! Models for interacting with the Defined Networking API.
|
||||||
|
|
||||||
|
use base64_serde::base64_serde_type;
|
||||||
|
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)]
|
||||||
|
/// `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,
|
||||||
|
/// 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<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<u8>,
|
||||||
|
/// The timestamp of when this message was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or:
|
||||||
|
/// <4-digit year>-<two-digit-month>-<two-digit-day>T<two-digit-hour, 24-hour>:<two-digit-minute>:<two-digit-second>.<nanoseconds, zero-padded><offset with semicolon>
|
||||||
|
/// 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<u8>,
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
/// The ed25519 signature over the `message`
|
||||||
|
pub signature: Vec<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<u8>,
|
||||||
|
#[serde(rename = "dhPubkeyPEM")]
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
/// The new ECDH public key that the Nebula certificate should be signed for
|
||||||
|
pub dh_pubkey_pem: Vec<u8>,
|
||||||
|
#[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<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<u8>,
|
||||||
|
/// 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<u8>,
|
||||||
|
#[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<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The REST enrollment endpoint
|
||||||
|
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
|
||||||
|
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<u8>,
|
||||||
|
#[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<u8>,
|
||||||
|
/// The timestamp of when this request was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or:
|
||||||
|
/// <4-digit year>-<two-digit-month>-<two-digit-day>T<two-digit-hour, 24-hour>:<two-digit-minute>:<two-digit-second>.<nanoseconds, zero-padded><offset with semicolon>
|
||||||
|
/// 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<APIError>`
|
||||||
|
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<u8>,
|
||||||
|
#[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<u8>,
|
||||||
|
/// 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<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type alias to a array of `APIErrors`. Just for parity with dnapi.
|
||||||
|
pub type APIErrors = Vec<APIError>;
|
|
@ -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!
|
|
@ -1,8 +1,41 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tfclient"
|
name = "tfclient"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.1.10", features = ["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"
|
||||||
|
sha2 = "0.10.6"
|
||||||
|
hex = "0.4.3"
|
||||||
|
url = "2.3.1"
|
||||||
|
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"
|
||||||
|
ipnet = "2.7.1"
|
||||||
|
base64-serde = "0.7.0"
|
||||||
|
dnapi-rs = { version = "0.1.7", path = "../dnapi-rs" }
|
||||||
|
serde_yaml = "0.9.19"
|
||||||
|
|
||||||
|
[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"
|
|
@ -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).
|
|
@ -0,0 +1,178 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
struct GithubRelease {
|
||||||
|
name: String,
|
||||||
|
assets: Vec<GithubReleaseAsset>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
struct GithubUser {}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
struct GithubReleaseAsset {
|
||||||
|
browser_download_url: String,
|
||||||
|
name: String,
|
||||||
|
size: i64
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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");
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<Vec<&str>>()[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
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::sync::mpsc::{Receiver, TryRecvError};
|
||||||
|
|
||||||
|
|
||||||
|
use log::{error, info, warn};
|
||||||
|
use url::Url;
|
||||||
|
use dnapi_rs::client_blocking::Client;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
Enroll { code: String },
|
||||||
|
Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apiworker_main(_config: TFClientConfig, instance: String, url: String, tx: ThreadMessageSender, rx: Receiver<APIWorkerMessage>) {
|
||||||
|
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) => {
|
||||||
|
match msg {
|
||||||
|
APIWorkerMessage::Shutdown => {
|
||||||
|
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.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);
|
||||||
|
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.creds.is_some() {
|
||||||
|
warn!("enrollment failed: already enrolled");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (config, dh_privkey, creds, meta) = match client.enroll(&code) {
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(e) => {
|
||||||
|
error!("error with enrollment request: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error saving cdata: {}", e);
|
||||||
|
error!("APIWorker exiting with error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
TryRecvError::Empty => {}
|
||||||
|
TryRecvError::Disconnected => {
|
||||||
|
error!("apiworker command socket disconnected, shutting down to prevent orphaning");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,534 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs;
|
||||||
|
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||||
|
use ipnet::{IpNet, Ipv4Net};
|
||||||
|
|
||||||
|
|
||||||
|
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};
|
||||||
|
|
||||||
|
pub const DEFAULT_PORT: u16 = 8157;
|
||||||
|
fn default_port() -> u16 { DEFAULT_PORT }
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct TFClientConfig {
|
||||||
|
#[serde(default = "default_port")]
|
||||||
|
pub listen_port: u16
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct TFClientData {
|
||||||
|
pub dh_privkey: Option<Vec<u8>>,
|
||||||
|
pub creds: Option<Credentials>,
|
||||||
|
pub meta: Option<EnrollMeta>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_config(instance: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
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<TFClientConfig, Box<dyn Error>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_cdata(instance: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
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 { 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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_cdata(instance: &str) -> Result<TFClientData, Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfig {
|
||||||
|
pub pki: NebulaConfigPki,
|
||||||
|
#[serde(default = "empty_hashmap")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_hashmap")]
|
||||||
|
pub static_host_map: HashMap<Ipv4Addr, Vec<SocketAddrV4>>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub lighthouse: Option<NebulaConfigLighthouse>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub listen: Option<NebulaConfigListen>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub punchy: Option<NebulaConfigPunchy>,
|
||||||
|
#[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<IpNet>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub relay: Option<NebulaConfigRelay>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub tun: Option<NebulaConfigTun>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub logging: Option<NebulaConfigLogging>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub sshd: Option<NebulaConfigSshd>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub firewall: Option<NebulaConfigFirewall>,
|
||||||
|
|
||||||
|
#[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<NebulaConfigStats>,
|
||||||
|
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub local_range: Option<Ipv4Net>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigPki {
|
||||||
|
pub ca: String,
|
||||||
|
pub cert: String,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub key: Option<String>,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub blocklist: Vec<String>,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub disconnect_invalid: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
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<NebulaConfigLighthouseDns>,
|
||||||
|
#[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<Ipv4Addr>,
|
||||||
|
#[serde(default = "empty_hashmap")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_hashmap")]
|
||||||
|
pub remote_allow_list: HashMap<Ipv4Net, bool>,
|
||||||
|
#[serde(default = "empty_hashmap")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_hashmap")]
|
||||||
|
pub local_allow_list: HashMap<Ipv4Net, bool>, // `interfaces` is not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigLighthouseDns {
|
||||||
|
#[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
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigListen {
|
||||||
|
#[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,
|
||||||
|
#[serde(default = "u32_64")]
|
||||||
|
#[serde(skip_serializing_if = "is_u32_64")]
|
||||||
|
pub batch: u32,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub read_buffer: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub write_buffer: Option<u32>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
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, Clone, Debug)]
|
||||||
|
pub enum NebulaConfigCipher {
|
||||||
|
#[serde(rename = "aes")]
|
||||||
|
Aes,
|
||||||
|
#[serde(rename = "chachapoly")]
|
||||||
|
ChaChaPoly
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigRelay {
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub relays: Vec<Ipv4Addr>,
|
||||||
|
#[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, Clone, Debug)]
|
||||||
|
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<String>,
|
||||||
|
#[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<NebulaConfigTunRouteOverride>,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub unsafe_routes: Vec<NebulaConfigTunUnsafeRoute>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigTunRouteOverride {
|
||||||
|
pub mtu: u64,
|
||||||
|
pub route: Ipv4Net
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
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, Clone, Debug)]
|
||||||
|
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, Clone, Debug)]
|
||||||
|
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, Clone, Debug)]
|
||||||
|
pub enum NebulaConfigLoggingFormat {
|
||||||
|
#[serde(rename = "json")]
|
||||||
|
Json,
|
||||||
|
#[serde(rename = "text")]
|
||||||
|
Text
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
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<NebulaConfigSshdAuthorizedUser>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigSshdAuthorizedUser {
|
||||||
|
pub user: String,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub keys: Vec<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum NebulaConfigStats {
|
||||||
|
#[serde(rename = "graphite")]
|
||||||
|
Graphite(NebulaConfigStatsGraphite),
|
||||||
|
#[serde(rename = "prometheus")]
|
||||||
|
Prometheus(NebulaConfigStatsPrometheus)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
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, Clone, Debug)]
|
||||||
|
pub enum NebulaConfigStatsGraphiteProtocol {
|
||||||
|
#[serde(rename = "tcp")]
|
||||||
|
Tcp,
|
||||||
|
#[serde(rename = "udp")]
|
||||||
|
Udp
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigFirewall {
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub conntrack: Option<NebulaConfigFirewallConntrack>,
|
||||||
|
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub inbound: Option<Vec<NebulaConfigFirewallRule>>,
|
||||||
|
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub outbound: Option<Vec<NebulaConfigFirewallRule>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
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, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigFirewallRule {
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub port: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub proto: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub ca_name: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub ca_sha: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub host: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub group: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub groups: Option<Vec<String>>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub cidr: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<T>() -> Vec<T> { vec![] }
|
||||||
|
fn is_empty_vec<T>(v: &Vec<T>) -> bool { v.is_empty() }
|
||||||
|
|
||||||
|
fn empty_hashmap<A, B>() -> HashMap<A, B> { HashMap::new() }
|
||||||
|
fn is_empty_hashmap<A, B>(h: &HashMap<A, B>) -> 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 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 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 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) }
|
||||||
|
|
||||||
|
fn none<T>() -> Option<T> { None }
|
||||||
|
fn is_none<T>(o: &Option<T>) -> bool { o.is_none() }
|
|
@ -0,0 +1,158 @@
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
use std::thread;
|
||||||
|
use log::{error, info};
|
||||||
|
|
||||||
|
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::timerworker::{timer_main, TimerWorkerMessage};
|
||||||
|
use crate::util::check_server_url;
|
||||||
|
|
||||||
|
pub fn daemon_main(name: String, server: String) {
|
||||||
|
// Validate the `server`
|
||||||
|
check_server_url(&server);
|
||||||
|
|
||||||
|
info!("Loading config...");
|
||||||
|
let config = match load_config(&name) {
|
||||||
|
Ok(cfg) => cfg,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error loading configuration: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Creating transmitter");
|
||||||
|
|
||||||
|
let (tx_api, rx_api) = mpsc::channel::<APIWorkerMessage>();
|
||||||
|
let (tx_socket, rx_socket) = mpsc::channel::<SocketWorkerMessage>();
|
||||||
|
let (tx_nebula, rx_nebula) = mpsc::channel::<NebulaWorkerMessage>();
|
||||||
|
let (tx_timer, rx_timer) = mpsc::channel::<TimerWorkerMessage>();
|
||||||
|
|
||||||
|
let transmitter = ThreadMessageSender {
|
||||||
|
socket_thread: tx_socket,
|
||||||
|
api_thread: tx_api,
|
||||||
|
nebula_thread: tx_nebula,
|
||||||
|
timer_thread: tx_timer,
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match mainthread_transmitter.timer_thread.send(TimerWorkerMessage::Shutdown) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error sending shutdown message to timer 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 name_api = name.clone();
|
||||||
|
let server_api = server.clone();
|
||||||
|
let api_thread = thread::spawn(move || {
|
||||||
|
apiworker_main(config_api, name_api, server_api,transmitter_api, rx_api);
|
||||||
|
});
|
||||||
|
|
||||||
|
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, name_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 name_socket = name.clone();
|
||||||
|
let socket_thread = thread::spawn(move || {
|
||||||
|
socketworker_main(config, name_socket, 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!("Socket thread exited");
|
||||||
|
|
||||||
|
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!("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(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
error!("Error waiting for nebula thread to exit.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("Nebula thread exited");
|
||||||
|
|
||||||
|
info!("All threads exited");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ThreadMessageSender {
|
||||||
|
pub socket_thread: Sender<SocketWorkerMessage>,
|
||||||
|
pub api_thread: Sender<APIWorkerMessage>,
|
||||||
|
pub nebula_thread: Sender<NebulaWorkerMessage>,
|
||||||
|
pub timer_thread: Sender<TimerWorkerMessage>
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub fn get_data_dir() -> Option<PathBuf> {
|
||||||
|
dirs::data_dir().map(|f| f.join("tfclient/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_dir(instance: &str) -> Option<PathBuf> {
|
||||||
|
dirs::config_dir().map(|f| f.join("tfclient/").join(format!("{}/", instance)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_file(instance: &str) -> Option<PathBuf> {
|
||||||
|
get_config_dir(instance).map(|f| f.join("tfclient.toml"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cdata_dir(instance: &str) -> Option<PathBuf> {
|
||||||
|
dirs::config_dir().map(|f| f.join("tfclient_data/").join(format!("{}/", instance)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cdata_file(instance: &str) -> Option<PathBuf> {
|
||||||
|
get_cdata_dir(instance).map(|f| f.join("tfclient.toml"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_nebulaconfig_file(instance: &str) -> Option<PathBuf> {
|
||||||
|
get_cdata_dir(instance).map(|f| f.join("nebula.sk_embedded.yml"))
|
||||||
|
}
|
|
@ -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};
|
||||||
|
use log::debug;
|
||||||
|
use crate::dirs::get_data_dir;
|
||||||
|
use crate::util::sha256;
|
||||||
|
|
||||||
|
pub fn extract_embedded_nebula() -> Result<PathBuf, Box<dyn Error>> {
|
||||||
|
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<PathBuf, Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_embedded_nebula(args: &[String]) -> Result<Child, Box<dyn Error>> {
|
||||||
|
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<Child, Box<dyn Error>> {
|
||||||
|
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()?)
|
||||||
|
}
|
|
@ -14,6 +14,243 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
fn main() {
|
pub mod embedded_nebula;
|
||||||
println!("Hello, world!");
|
pub mod dirs;
|
||||||
|
pub mod util;
|
||||||
|
pub mod nebulaworker;
|
||||||
|
pub mod daemon;
|
||||||
|
pub mod config;
|
||||||
|
pub mod service;
|
||||||
|
pub mod apiworker;
|
||||||
|
pub mod socketworker;
|
||||||
|
pub mod socketclient;
|
||||||
|
pub mod timerworker;
|
||||||
|
|
||||||
|
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 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};
|
||||||
|
|
||||||
|
#[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)]
|
||||||
|
/// 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)]
|
||||||
|
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<String>
|
||||||
|
},
|
||||||
|
/// 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<String>
|
||||||
|
},
|
||||||
|
/// Clear any cached data that tfclient may have added
|
||||||
|
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() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_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);
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
// Code to handle the nebula worker
|
||||||
|
|
||||||
|
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, 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<dyn Error>> {
|
||||||
|
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 = Some(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<NebulaWorkerMessage>) {
|
||||||
|
let _cdata = match load_cdata(&instance) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(e) => {
|
||||||
|
error!("unable to load cdata: {}", e);
|
||||||
|
error!("nebula thread exiting with error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 - 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
last_restart_time = SystemTime::now();
|
||||||
|
debug!("nebula process restarted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
TryRecvError::Empty => {}
|
||||||
|
TryRecvError::Disconnected => {
|
||||||
|
error!("nebulaworker command socket disconnected, shutting down to prevent orphaning");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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, server: &str) -> Result<(), Box<dyn Error>>;
|
||||||
|
fn delete_service_files(name: &str) -> Result<(), Box<dyn Error>>;
|
||||||
|
}
|
|
@ -0,0 +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, server: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Box<dyn ServiceManager>> {
|
||||||
|
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<Box<dyn ServiceManager>> {
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
use std::env::current_exe;
|
||||||
|
use log::{error, info};
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uninstall(&self, _name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self, _name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self, _name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
use std::error::Error;
|
||||||
|
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(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box<dyn Error>>;
|
||||||
|
fn uninstall(&self, name: &str) -> Result<(), Box<dyn Error>>;
|
||||||
|
fn start(&self, name: &str) -> Result<(), Box<dyn Error>>;
|
||||||
|
fn stop(&self, name: &str) -> Result<(), Box<dyn Error>>;
|
||||||
|
}
|
|
@ -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<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uninstall(&self, _name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self, _name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self, _name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +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(&self, bin_path: PathBuf, name: &str, server_url: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
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(&self, name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
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(&self, name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
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(&self, name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uninstall(&self, _name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self, _name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self, _name: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io::{BufRead, BufReader, 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<dyn Error>> {
|
||||||
|
info!("Connecting to local command socket...");
|
||||||
|
let mut stream = TcpStream::connect(SocketAddr::new(IpAddr::from([127, 0, 0, 1]), config.listen_port))?;
|
||||||
|
let 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<JsonMessage, Box<dyn Error>> {
|
||||||
|
let mut str = String::new();
|
||||||
|
reader.read_line(&mut str)?;
|
||||||
|
let msg: JsonMessage = serde_json::from_str(&str)?;
|
||||||
|
Ok(msg)
|
||||||
|
}
|
|
@ -0,0 +1,305 @@
|
||||||
|
// Code to handle the nebula worker
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::{io, thread};
|
||||||
|
use std::io::{BufRead, BufReader, BufWriter, 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::{load_cdata, TFClientConfig};
|
||||||
|
use crate::daemon::ThreadMessageSender;
|
||||||
|
use crate::nebulaworker::NebulaWorkerMessage;
|
||||||
|
use crate::timerworker::TimerWorkerMessage;
|
||||||
|
|
||||||
|
pub enum SocketWorkerMessage {
|
||||||
|
Shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn socketworker_main(config: TFClientConfig, instance: String, transmitter: ThreadMessageSender, rx: Receiver<SocketWorkerMessage>) {
|
||||||
|
info!("socketworker_main called, entering realmain");
|
||||||
|
match _main(config, instance, transmitter, rx) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error in socket thread: {}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _main(config: TFClientConfig, instance: String, transmitter: ThreadMessageSender, rx: Receiver<SocketWorkerMessage>) -> Result<(), Box<dyn Error>> {
|
||||||
|
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();
|
||||||
|
let config_clone = config.clone();
|
||||||
|
let instance_clone = instance.clone();
|
||||||
|
thread::spawn(|| {
|
||||||
|
match handle_stream(stream, transmitter_clone, config_clone, instance_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, config: TFClientConfig, instance: String) -> Result<(), io::Error> {
|
||||||
|
info!("Incoming client");
|
||||||
|
match handle_client(stream.0, transmitter, config, instance) {
|
||||||
|
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, 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,
|
||||||
|
config,
|
||||||
|
instance,
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
config: TFClientConfig,
|
||||||
|
instance: String
|
||||||
|
}
|
||||||
|
|
||||||
|
fn waithello_handle(client: &mut Client, _transmitter: &ThreadMessageSender, command: JsonMessage) -> Result<bool, io::Error> {
|
||||||
|
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<bool, io::Error> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match transmitter.timer_thread.send(TimerWorkerMessage::Shutdown) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error sending shutdown message to timer 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.creds.is_some(),
|
||||||
|
id: data.creds.map(|c| c.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
debug!("message type unexpected in SentHello state");
|
||||||
|
should_disconnect = true;
|
||||||
|
client.stream.write_all(&ctob(JsonMessage::Goodbye {
|
||||||
|
reason: DisconnectReason::UnexpectedMessageType,
|
||||||
|
}))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(should_disconnect)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ctob(command: JsonMessage) -> Vec<u8> {
|
||||||
|
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")]
|
||||||
|
pub enum JsonMessage {
|
||||||
|
#[serde(rename = "hello")]
|
||||||
|
Hello {
|
||||||
|
version: i32
|
||||||
|
},
|
||||||
|
#[serde(rename = "goodbye")]
|
||||||
|
Goodbye {
|
||||||
|
reason: DisconnectReason
|
||||||
|
},
|
||||||
|
#[serde(rename = "shutdown")]
|
||||||
|
Shutdown {},
|
||||||
|
#[serde(rename = "get_host_id")]
|
||||||
|
GetHostID {},
|
||||||
|
#[serde(rename = "host_id")]
|
||||||
|
HostID {
|
||||||
|
has_id: bool,
|
||||||
|
id: Option<String>
|
||||||
|
},
|
||||||
|
#[serde(rename = "enroll")]
|
||||||
|
Enroll {
|
||||||
|
code: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum DisconnectReason {
|
||||||
|
#[serde(rename = "unsupported_version")]
|
||||||
|
UnsupportedVersion { expected: i32, got: i32 },
|
||||||
|
#[serde(rename = "unexpected_message_type")]
|
||||||
|
UnexpectedMessageType,
|
||||||
|
#[serde(rename = "done")]
|
||||||
|
Done
|
||||||
|
}
|
|
@ -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<TimerWorkerMessage>) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "trifid-pki"
|
name = "trifid-pki"
|
||||||
version = "0.1.4"
|
version = "0.1.9"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A rust implementation of the Nebula PKI system"
|
description = "A rust implementation of the Nebula PKI system"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
@ -19,4 +19,9 @@ quick-protobuf = "0.8.1"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
sha2 = "0.10.6"
|
sha2 = "0.10.6"
|
||||||
rand_core = "0.6.4"
|
rand_core = "0.6.4"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
serde_derive = ["serde", "ipnet/serde", "x25519-dalek/serde", "ed25519-dalek/serde"]
|
|
@ -7,9 +7,13 @@ use std::time::SystemTime;
|
||||||
use ed25519_dalek::VerifyingKey;
|
use ed25519_dalek::VerifyingKey;
|
||||||
use crate::cert::{deserialize_nebula_certificate_from_pem, NebulaCertificate};
|
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.
|
/// 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.
|
/// 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 {
|
pub struct NebulaCAPool {
|
||||||
/// The list of CA root certificates that should be trusted.
|
/// The list of CA root certificates that should be trusted.
|
||||||
pub cas: HashMap<String, NebulaCertificate>,
|
pub cas: HashMap<String, NebulaCertificate>,
|
||||||
|
@ -102,8 +106,9 @@ impl NebulaCAPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// A list of errors that can happen when working with a CA Pool
|
/// 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 {
|
pub enum CaPoolError {
|
||||||
/// Tried to add a non-CA cert to the CA pool
|
/// Tried to add a non-CA cert to the CA pool
|
||||||
NotACA,
|
NotACA,
|
||||||
|
|
|
@ -15,6 +15,9 @@ use crate::ca::NebulaCAPool;
|
||||||
use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};
|
use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
|
|
||||||
|
#[cfg(feature = "serde_derive")]
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
/// The length, in bytes, of public keys
|
/// The length, in bytes, of public keys
|
||||||
pub const PUBLIC_KEY_LENGTH: i32 = 32;
|
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
|
/// A Nebula PKI certificate
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
||||||
pub struct NebulaCertificate {
|
pub struct NebulaCertificate {
|
||||||
/// The signed data of this certificate
|
/// The signed data of this certificate
|
||||||
pub details: NebulaCertificateDetails,
|
pub details: NebulaCertificateDetails,
|
||||||
|
@ -40,6 +44,7 @@ pub struct NebulaCertificate {
|
||||||
|
|
||||||
/// The signed details contained in a Nebula PKI certificate
|
/// The signed details contained in a Nebula PKI certificate
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
||||||
pub struct NebulaCertificateDetails {
|
pub struct NebulaCertificateDetails {
|
||||||
/// The name of the identity this certificate was issued for
|
/// The name of the identity this certificate was issued for
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -63,6 +68,7 @@ pub struct NebulaCertificateDetails {
|
||||||
|
|
||||||
/// A list of errors that can occur parsing certificates
|
/// A list of errors that can occur parsing certificates
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
||||||
pub enum CertificateError {
|
pub enum CertificateError {
|
||||||
/// Attempted to deserialize a certificate from an empty byte array
|
/// Attempted to deserialize a certificate from an empty byte array
|
||||||
EmptyByteArray,
|
EmptyByteArray,
|
||||||
|
@ -186,6 +192,7 @@ pub fn deserialize_nebula_certificate(bytes: &[u8]) -> Result<NebulaCertificate,
|
||||||
|
|
||||||
/// A list of errors that can occur parsing keys
|
/// A list of errors that can occur parsing keys
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
||||||
pub enum KeyError {
|
pub enum KeyError {
|
||||||
/// Keys should have their associated PEM tags but this had the wrong one
|
/// Keys should have their associated PEM tags but this had the wrong one
|
||||||
WrongPemTag,
|
WrongPemTag,
|
||||||
|
@ -300,12 +307,32 @@ pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error
|
||||||
if pem.tag != ED25519_PUBLIC_KEY_BANNER {
|
if pem.tag != ED25519_PUBLIC_KEY_BANNER {
|
||||||
return Err(KeyError::WrongPemTag.into())
|
return Err(KeyError::WrongPemTag.into())
|
||||||
}
|
}
|
||||||
if pem.contents.len() != 64 {
|
if pem.contents.len() != 32 {
|
||||||
return Err(KeyError::Not64Bytes.into())
|
return Err(KeyError::Not32Bytes.into())
|
||||||
}
|
}
|
||||||
Ok(pem.contents)
|
Ok(pem.contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to deserialize multiple PEM encoded Ed25519 public keys
|
||||||
|
/// # Errors
|
||||||
|
/// This function will return an error if the PEM data is invalid or has the wrong tag
|
||||||
|
pub fn deserialize_ed25519_public_many(bytes: &[u8]) -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
|
||||||
|
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() != 32 {
|
||||||
|
return Err(KeyError::Not32Bytes.into())
|
||||||
|
}
|
||||||
|
keys.push(pem.contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(keys)
|
||||||
|
}
|
||||||
|
|
||||||
impl NebulaCertificate {
|
impl NebulaCertificate {
|
||||||
/// Sign a nebula certificate with the provided private key
|
/// Sign a nebula certificate with the provided private key
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
@ -520,6 +547,7 @@ impl NebulaCertificate {
|
||||||
|
|
||||||
/// A list of possible errors that can happen validating a certificate
|
/// A list of possible errors that can happen validating a certificate
|
||||||
#[derive(Eq, PartialEq, Debug)]
|
#[derive(Eq, PartialEq, Debug)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
||||||
pub enum CertificateValidity {
|
pub enum CertificateValidity {
|
||||||
/// There are no issues with this certificate
|
/// There are no issues with this certificate
|
||||||
Ok,
|
Ok,
|
||||||
|
|
|
@ -57,4 +57,7 @@ pub mod cert;
|
||||||
pub(crate) mod cert_codec;
|
pub(crate) mod cert_codec;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
|
||||||
|
/// Get the compiled version of trifid-pki.
|
||||||
|
pub const TRIFID_PKI_VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
@ -6,7 +6,7 @@ use std::net::Ipv4Addr;
|
||||||
use std::ops::{Add, Sub};
|
use std::ops::{Add, Sub};
|
||||||
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
|
||||||
use ipnet::Ipv4Net;
|
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 std::str::FromStr;
|
||||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||||
use quick_protobuf::{MessageWrite, Writer};
|
use quick_protobuf::{MessageWrite, Writer};
|
||||||
|
@ -296,10 +296,21 @@ fn x25519_serialization() {
|
||||||
#[test]
|
#[test]
|
||||||
fn ed25519_serialization() {
|
fn ed25519_serialization() {
|
||||||
let bytes = [0u8; 64];
|
let bytes = [0u8; 64];
|
||||||
|
let bytes2 = [0u8; 32];
|
||||||
assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes);
|
assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes);
|
||||||
assert!(deserialize_ed25519_private(&[0u8; 32]).is_err());
|
assert!(deserialize_ed25519_private(&[0u8; 32]).is_err());
|
||||||
assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes);
|
assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes2)).unwrap(), bytes2);
|
||||||
assert!(deserialize_ed25519_public(&[0u8; 32]).is_err());
|
assert!(deserialize_ed25519_public(&[0u8; 64]).is_err());
|
||||||
|
|
||||||
|
let mut bytes = vec![];
|
||||||
|
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; 32]);
|
||||||
|
assert_eq!(deser[1], [1u8; 32]);
|
||||||
|
|
||||||
|
bytes.append(&mut serialize_ed25519_public(&[1u8; 33]));
|
||||||
|
deserialize_ed25519_public_many(&bytes).unwrap_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -637,11 +648,11 @@ bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_ed25519_public() {
|
fn test_deserialize_ed25519_public() {
|
||||||
let priv_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
|
let pub_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||||
-----END NEBULA ED25519 PUBLIC KEY-----";
|
-----END NEBULA ED25519 PUBLIC KEY-----";
|
||||||
let short_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
|
let short_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||||
-----END NEBULA ED25519 PUBLIC KEY-----";
|
-----END NEBULA ED25519 PUBLIC KEY-----";
|
||||||
let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY-----
|
let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY-----
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||||
|
@ -650,7 +661,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||||
-END NEBULA ED25519 PUBLIC KEY-----";
|
-END NEBULA ED25519 PUBLIC KEY-----";
|
||||||
|
|
||||||
deserialize_ed25519_public(priv_key).unwrap();
|
deserialize_ed25519_public(pub_key).unwrap();
|
||||||
|
|
||||||
deserialize_ed25519_public(short_key).unwrap_err();
|
deserialize_ed25519_public(short_key).unwrap_err();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue