diff --git a/Cargo.lock b/Cargo.lock
index 7608e40..aba578a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -14,7 +14,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"bytes",
"futures-core",
"futures-sink",
@@ -52,7 +52,7 @@ dependencies = [
"actix-utils",
"ahash 0.8.3",
"base64 0.21.0",
- "bitflags 1.3.2",
+ "bitflags",
"brotli",
"bytes",
"bytestring",
@@ -609,41 +609,12 @@ dependencies = [
"num-traits",
]
-[[package]]
-name = "bindgen"
-version = "0.66.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
-dependencies = [
- "bitflags 2.3.2",
- "cexpr",
- "clang-sys",
- "lazy_static",
- "lazycell",
- "log",
- "peeking_take_while",
- "prettyplease",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash",
- "shlex",
- "syn 2.0.16",
- "which",
-]
-
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-[[package]]
-name = "bitflags"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded"
-
[[package]]
name = "bitvec"
version = "1.0.1"
@@ -804,15 +775,6 @@ dependencies = [
"jobserver",
]
-[[package]]
-name = "cexpr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
-dependencies = [
- "nom",
-]
-
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -851,24 +813,13 @@ dependencies = [
"inout",
]
-[[package]]
-name = "clang-sys"
-version = "1.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
-dependencies = [
- "glob",
- "libc",
- "libloading",
-]
-
[[package]]
name = "clap"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"clap_derive 3.2.25",
"clap_lex 0.2.4",
"indexmap",
@@ -895,7 +846,7 @@ checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
dependencies = [
"anstream",
"anstyle",
- "bitflags 1.3.2",
+ "bitflags",
"clap_lex 0.4.1",
"strsim",
]
@@ -1054,7 +1005,7 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"crossterm_winapi",
"libc",
"mio",
@@ -1560,12 +1511,6 @@ dependencies = [
"polyval",
]
-[[package]]
-name = "glob"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
-
[[package]]
name = "gloo-timers"
version = "0.2.6"
@@ -1578,15 +1523,6 @@ dependencies = [
"wasm-bindgen",
]
-[[package]]
-name = "gobuild"
-version = "0.1.0-alpha.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71e156a4ddbf3deb5e8116946c111413bd9a5679bdc1536c78a60618a7a9ac9e"
-dependencies = [
- "cc",
-]
-
[[package]]
name = "h2"
version = "0.3.19"
@@ -1924,28 +1860,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-[[package]]
-name = "lazycell"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
-
[[package]]
name = "libc"
version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
-[[package]]
-name = "libloading"
-version = "0.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
-dependencies = [
- "cfg-if",
- "winapi",
-]
-
[[package]]
name = "libm"
version = "0.1.4"
@@ -2071,21 +1991,13 @@ dependencies = [
"tempfile",
]
-[[package]]
-name = "nebula-ffi"
-version = "0.1.0"
-dependencies = [
- "bindgen",
- "gobuild",
-]
-
[[package]]
name = "nix"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"cfg-if",
"libc",
"static_assertions",
@@ -2168,7 +2080,7 @@ version = "0.10.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"cfg-if",
"foreign-types",
"libc",
@@ -2321,12 +2233,6 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
-[[package]]
-name = "peeking_take_while"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
-
[[package]]
name = "pem"
version = "1.1.1"
@@ -2383,7 +2289,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
- "bitflags 1.3.2",
+ "bitflags",
"cfg-if",
"concurrent-queue",
"libc",
@@ -2410,16 +2316,6 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
-[[package]]
-name = "prettyplease"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282"
-dependencies = [
- "proc-macro2",
- "syn 2.0.16",
-]
-
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
@@ -2561,7 +2457,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
]
[[package]]
@@ -2570,7 +2466,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
]
[[package]]
@@ -2723,12 +2619,6 @@ dependencies = [
"serde_json",
]
-[[package]]
-name = "rustc-hash"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
-
[[package]]
name = "rustc_version"
version = "0.4.0"
@@ -2744,7 +2634,7 @@ version = "0.37.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"errno",
"io-lifetimes",
"libc",
@@ -2985,7 +2875,7 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2855b3715770894e67cbfa3df957790aa0c9edc3bf06efa1a84d77fa0839d1"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -3132,12 +3022,6 @@ dependencies = [
"lazy_static",
]
-[[package]]
-name = "shlex"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
-
[[package]]
name = "signal-hook"
version = "0.3.15"
@@ -3265,7 +3149,7 @@ dependencies = [
"atoi",
"base64 0.13.1",
"bigdecimal",
- "bitflags 1.3.2",
+ "bitflags",
"byteorder",
"bytes",
"chrono",
@@ -3431,7 +3315,7 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "tfcli"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"clap 4.2.7",
"dirs 5.0.1",
@@ -3446,7 +3330,7 @@ dependencies = [
[[package]]
name = "tfclient"
-version = "0.2.0"
+version = "0.1.9"
dependencies = [
"base64 0.21.0",
"base64-serde",
@@ -3459,7 +3343,6 @@ dependencies = [
"hex",
"ipnet",
"log",
- "nebula-ffi",
"openssl-sys",
"reqwest",
"serde",
@@ -3750,7 +3633,7 @@ dependencies = [
[[package]]
name = "trifid-api"
-version = "0.1.0"
+version = "0.1.3"
dependencies = [
"actix-cors",
"actix-request-identifier",
@@ -4073,17 +3956,6 @@ dependencies = [
"webpki",
]
-[[package]]
-name = "which"
-version = "4.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
-dependencies = [
- "either",
- "libc",
- "once_cell",
-]
-
[[package]]
name = "whoami"
version = "1.4.0"
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..8a4b58f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,9 @@
+FROM rust:latest
+
+COPY trifid-pki /trifid-pki
+COPY dnapi-rs /dnapi-rs
+COPY trifid-api /trifid-api
+
+RUN cd /trifid-api && cargo build --release && cp target/release/trifid-api /bin/trifid-api
+
+CMD ["/bin/trifid-api"]
diff --git a/README.md b/README.md
index 2fbff0d..581a97f 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,9 @@ trifid-api feature table:
| Feature | trifid-api | api.defined.net |
|---------------------------------------------|--------------|-----------------|
| Enroll in sites with dnclient | Yes | Yes |
-| Automatic config update polling by dnclient | Not yet | Yes |
-| Group-based firewalling | Not yet | Yes |
+| Automatic config update polling by dnclient | Yes | Yes |
+| Group-based firewalling | Yes | Yes |
+| All config features | Yes | Not officially |
| SSO authentication | Not yet | Yes |
| Open-source server | Yes | No |
@@ -33,3 +34,6 @@ tfclient feature table:
| Poll the API server for config updates | Yes | Yes |
| Secure Ed25519 signing for API communication | Yes | Yes |
+# Documentation
+
+Documentation work is underway. You can find a link to all documentation on the main project page [here](https://hub.e3t.cc/~core/trifid).
diff --git a/docs/tfclient/index.md b/docs/tfclient/index.md
new file mode 100644
index 0000000..4777063
--- /dev/null
+++ b/docs/tfclient/index.md
@@ -0,0 +1,3 @@
+# tfclient - a Rust DNClient alternative
+
+TODO
\ No newline at end of file
diff --git a/docs/tfclient/untested_os.md b/docs/tfclient/untested_os.md
new file mode 100644
index 0000000..8685001
--- /dev/null
+++ b/docs/tfclient/untested_os.md
@@ -0,0 +1,13 @@
+# Help us out - test tfclient on new platforms
+
+There are [lots](index.md#where-does-trifid-work) of operating systems in which `tfclient` *should* work, but due to a lack of devices, it cannot be tested. If you have a system running one of these devices and want to help out the project, please test tfclient for functionality on your device!
+
+
+ Notice: While tfclient is almost always perfectly safe, there are always risks to running untested software on production machines. Be careful out there!
+
+
+Any findings, positive or negative, can be posted straight to [trifid-devel](https://lists.e3t.cc/~core/trifid-devel).
+
+Thanks!
+
+[Return home](index.md)
\ No newline at end of file
diff --git a/docs/tfclient/why_not_this_os.md b/docs/tfclient/why_not_this_os.md
new file mode 100644
index 0000000..d34e562
--- /dev/null
+++ b/docs/tfclient/why_not_this_os.md
@@ -0,0 +1,9 @@
+# Why cant tfclient support XXXXX?
+
+There are limits to what operating systems and architectures tfclient can support. As you may know, tfclient is based upon the [Nebula](https://github.com/slackhq/nebula) project, maintained by Slack. tfclient can only support architectures and operating systems that [Nebula itself supports](https://github.com/slackhq/nebula/releases/).
+
+In addition, tfclient, being written in Rust, can only function in environments [where Rust compiles well](https://doc.rust-lang.org/nightly/rustc/platform-support.html). For example, this rules out MIPS - `rustc` currently does not function correctly on that architecture, and thus tfclient cannot, and likely never will support that architecture.
+
+Did Nebula add a new architecture, and we missed it? Rust supports a new architecture in a new release? [Let us know!](https://lists.e3t.cc/~core/trifid-devel) We love adding support for new systems whenever we can.
+
+Did we miss an architecture? If your architecture is supported by BOTH Nebula and Rust, but you still get `This architecture is not supported yet :(` when trying to build tfclient, we might have missed you - [get in touch](https://lists.e3t.cc/~core/trifid-devel) and we will work with you to get your system supported :D
\ No newline at end of file
diff --git a/index.md b/index.md
index c1da8bc..5460e67 100644
--- a/index.md
+++ b/index.md
@@ -15,15 +15,42 @@ The API implementation is tested with the official dnclient implementation, and
- [tfcli documentation](./docs/tfcli/index.md)
- [tfclient documentation](./docs/tfclient/index.md)
- [trifid-api documentation](./docs/trifid-api/index.md)
+
+
- [dnapi-rs documentation](https://docs.rs/dnapi-rs)
- [dnapi-rs on crates.io](https://crates.io/crates/dnapi-rs)
+
+
- [trifid-pki documentation](https://docs.rs/trifid-pki)
- [trifid-pki on crates.io](https://crates.io/crates/trifid-pki)
+
+
- [trifid git repository](https://git.e3t.cc/~core/trifid)
+
+
- [trifid announcements mailing list](https://lists.e3t.cc/~core/trifid-announce)
- [trifid patch mailing list](https://lists.e3t.cc/~core/trifid-devel)
- [trifid discussion mailing list](https://lists.e3t.cc/~core/trifid-discuss)
+# Where does trifid work?
+
+| Operating System | trifid-api? |
+|-------------------------|-----------------------------------------------------------------------------------------|
+| Windows amd64 | [Yes](docs/tfclient/index.md) [(untested - help wanted!)](docs/tfclient/untested_os.md) |
+| Windows arm64 | [Yes](docs/tfclient/index.md) [(untested - help wanted!)](docs/tfclient/untested_os.md) |
+| Darwin (macOS) | [Yes](docs/tfclient/index.md) [(untested - help wanted!)](docs/tfclient/untested_os.md) |
+| FreeBSD amd64 | [Yes](docs/tfclient/index.md) [(untested - help wanted!)](docs/tfclient/untested_os.md) |
+| Linux i386 | [Yes](docs/tfclient/index.md) |
+| Linux amd64 | [Yes](docs/tfclient/index.md) |
+| Linux armv5 | [Yes](docs/tfclient/index.md) |
+| Linux armv6 | [Yes](docs/tfclient/index.md) |
+| Linux armv7 | [Yes](docs/tfclient/index.md) |
+| Linux aarch64 | [Yes](docs/tfclient/index.md) |
+| Android | Almost [(help wanted!)](docs/trifid_mobile/help.md) |
+| iOS | Almost [(help wanted!)](docs/trifid_mobile/help.md) |
+| Other operating systems | No, and [likely never will be](docs/tfclient/why_not_this_os.md) |
+
+
# Get in touch
Have a question about trifid? Check out the [discussion mailing list](https://lists.e3t.cc/~core/trifid-discuss)! `trifid-discuss` is a mailing list for end-user discussion and questions related to the trifid project.
diff --git a/tfcli/Cargo.toml b/tfcli/Cargo.toml
index 673a357..fbebddd 100644
--- a/tfcli/Cargo.toml
+++ b/tfcli/Cargo.toml
@@ -1,7 +1,12 @@
[package]
name = "tfcli"
-version = "0.1.0"
+version = "0.1.1"
edition = "2021"
+description = "Command-line client for managing trifid-api"
+license = "GPL-3.0-or-later"
+documentation = "https://git.e3t.cc/~core/trifid"
+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
@@ -14,4 +19,4 @@ tokio = { version = "1", features = ["full"] }
dirs = "5.0.1"
qr2term = "0.3.1"
ipnet = "2.7.2"
-serde_json = "1.0.96"
\ No newline at end of file
+serde_json = "1.0.96"
diff --git a/tfcli/src/host.rs b/tfcli/src/host.rs
index 8e611ba..d550f80 100644
--- a/tfcli/src/host.rs
+++ b/tfcli/src/host.rs
@@ -4,7 +4,7 @@ use std::net::{Ipv4Addr, SocketAddrV4};
use serde::{Deserialize, Serialize};
use url::{Url};
use crate::api::APIErrorResponse;
-use crate::{HostCommands};
+use crate::{HostCommands, HostOverrideCommands};
pub async fn host_main(command: HostCommands, server: Url) -> Result<(), Box> {
match command {
@@ -14,7 +14,12 @@ pub async fn host_main(command: HostCommands, server: Url) -> Result<(), Box delete_host(id, server).await,
HostCommands::Update { id, listen_port, static_address, name, ip, role } => update_host(id, listen_port, static_address, name, ip, role, server).await,
HostCommands::Block { id } => block_host(id, server).await,
- HostCommands::Enroll { id } => enroll_host(id, server).await
+ HostCommands::Enroll { id } => enroll_host(id, server).await,
+ HostCommands::Overrides { command } => match command {
+ HostOverrideCommands::List { id } => list_overrides(id, server).await,
+ HostOverrideCommands::Set { id, key, boolean, string, numeric } => set_override(id, key, boolean, numeric, string, server).await,
+ HostOverrideCommands::Unset { id, key } => unset_override(id, key, server).await
+ }
}
}
@@ -370,7 +375,7 @@ pub async fn enroll_host(id: String, server: Url) -> Result<(), Box>
let token = format!("{} {}", session_token, auth_token);
- let res = client.post(server.join(&format!("/v1/hosts/{}/enrollment-code", id))?).bearer_auth(token).send().await?;
+ let res = client.post(server.join(&format!("/v1/hosts/{}/enrollment-code", id))?).header("content-length", 0).bearer_auth(token).send().await?;
if res.status().is_success() {
let resp: EnrollmentResponse = res.json().await?;
@@ -413,4 +418,223 @@ pub async fn block_host(id: String, server: Url) -> Result<(), Box> {
}
Ok(())
-}
\ No newline at end of file
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct HostConfigOverrideResponse {
+ pub data: HostConfigOverrideData
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct HostConfigOverrideData {
+ pub overrides: Vec
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct HostConfigOverrideDataOverride {
+ pub key: String,
+ pub value: HostConfigOverrideDataOverrideValue
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum HostConfigOverrideDataOverrideValue {
+ Boolean(bool),
+ Numeric(i64),
+ Other(String)
+}
+
+pub async fn list_overrides(id: String, server: Url) -> Result<(), Box> {
+ let client = reqwest::Client::new();
+
+ // load session token
+ let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
+ let session_token = fs::read_to_string(&sess_token_store)?;
+ let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
+ let auth_token = fs::read_to_string(&auth_token_store)?;
+
+ let token = format!("{} {}", session_token, auth_token);
+
+ let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token).send().await?;
+
+ if res.status().is_success() {
+ let resp: HostConfigOverrideResponse = res.json().await?;
+
+ for c_override in &resp.data.overrides {
+ println!(" Key: {}", c_override.key);
+ println!("Value: {}", match &c_override.value {
+ HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v),
+ HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v),
+ HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v)
+ });
+ }
+
+ if resp.data.overrides.is_empty() {
+ println!("No overrides found");
+ }
+ } else {
+ let resp: APIErrorResponse = res.json().await?;
+
+ eprintln!("[error] Error looking up config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
+
+ std::process::exit(1);
+ }
+
+ Ok(())
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct SetOverrideRequest {
+ pub overrides: Vec
+}
+
+pub async fn set_override(id: String, key: String, boolean: Option, numeric: Option, other: Option, server: Url) -> Result<(), Box> {
+ if boolean.is_none() && numeric.is_none() && other.is_none() {
+ eprintln!("[error] no value provided: you must provide at least --boolean, --numeric, or --string");
+ std::process::exit(1);
+ } else if boolean.is_some() && numeric.is_some() || boolean.is_some() && other.is_some() || numeric.is_some() && other.is_some() {
+ eprintln!("[error] multiple values provided: you must provide only one of --boolean, --numeric, or --string");
+ std::process::exit(1);
+ }
+
+ let val;
+
+ if let Some(v) = boolean {
+ val = HostConfigOverrideDataOverrideValue::Boolean(v);
+ } else if let Some(v) = numeric {
+ val = HostConfigOverrideDataOverrideValue::Numeric(v);
+ } else if let Some(v) = other {
+ val = HostConfigOverrideDataOverrideValue::Other(v);
+ } else {
+ unreachable!();
+ }
+
+ let client = reqwest::Client::new();
+
+ // load session token
+ let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
+ let session_token = fs::read_to_string(&sess_token_store)?;
+ let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
+ let auth_token = fs::read_to_string(&auth_token_store)?;
+
+ let token = format!("{} {}", session_token, auth_token);
+
+ let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).send().await?;
+
+ if res.status().is_success() {
+ let resp: HostConfigOverrideResponse = res.json().await?;
+
+ let mut others: Vec = vec![];
+
+ for c_override in resp.data.overrides {
+ if c_override.key != key {
+ others.push(c_override);
+ }
+ }
+
+ others.push(HostConfigOverrideDataOverride {
+ key,
+ value: val,
+ });
+
+ let res = client.put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).json(&SetOverrideRequest {
+ overrides: others,
+ }).send().await?;
+
+ if res.status().is_success() {
+ let resp: HostConfigOverrideResponse = res.json().await?;
+
+ for c_override in &resp.data.overrides {
+ println!(" Key: {}", c_override.key);
+ println!("Value: {}", match &c_override.value {
+ HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v),
+ HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v),
+ HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v)
+ });
+ }
+
+ if resp.data.overrides.is_empty() {
+ println!("No overrides found");
+ }
+
+ println!("Override set successfully");
+ } else {
+ let resp: APIErrorResponse = res.json().await?;
+
+ eprintln!("[error] Error setting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
+
+ std::process::exit(1);
+ }
+ } else {
+ let resp: APIErrorResponse = res.json().await?;
+
+ eprintln!("[error] Error setting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
+
+ std::process::exit(1);
+ }
+
+ Ok(())
+}
+
+pub async fn unset_override(id: String, key: String, server: Url) -> Result<(), Box> {
+ let client = reqwest::Client::new();
+
+ // load session token
+ let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
+ let session_token = fs::read_to_string(&sess_token_store)?;
+ let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
+ let auth_token = fs::read_to_string(&auth_token_store)?;
+
+ let token = format!("{} {}", session_token, auth_token);
+
+ let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).send().await?;
+
+ if res.status().is_success() {
+ let resp: HostConfigOverrideResponse = res.json().await?;
+
+ let mut others: Vec = vec![];
+
+ for c_override in resp.data.overrides {
+ if c_override.key != key {
+ others.push(c_override);
+ }
+ }
+
+ let res = client.put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).json(&SetOverrideRequest {
+ overrides: others,
+ }).send().await?;
+
+ if res.status().is_success() {
+ let resp: HostConfigOverrideResponse = res.json().await?;
+
+ for c_override in &resp.data.overrides {
+ println!(" Key: {}", c_override.key);
+ println!("Value: {}", match &c_override.value {
+ HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v),
+ HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v),
+ HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v)
+ });
+ }
+
+ if resp.data.overrides.is_empty() {
+ println!("No overrides found");
+ }
+
+ println!("Override unset successfully");
+ } else {
+ let resp: APIErrorResponse = res.json().await?;
+
+ eprintln!("[error] Error unsetting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
+
+ std::process::exit(1);
+ }
+ } else {
+ let resp: APIErrorResponse = res.json().await?;
+
+ eprintln!("[error] Error unsetting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
+
+ std::process::exit(1);
+ }
+
+ Ok(())
+}
diff --git a/tfcli/src/main.rs b/tfcli/src/main.rs
index 2c834ab..adb6529 100644
--- a/tfcli/src/main.rs
+++ b/tfcli/src/main.rs
@@ -203,6 +203,40 @@ pub enum HostCommands {
Enroll {
#[clap(short, long)]
id: String
+ },
+ /// Manage config overrides set on the host
+ Overrides {
+ #[command(subcommand)]
+ command: HostOverrideCommands
+ }
+}
+
+#[derive(Subcommand, Debug)]
+pub enum HostOverrideCommands {
+ /// List the config overrides set on the host
+ List {
+ #[clap(short, long)]
+ id: String
+ },
+ /// Set a config override on the host
+ Set {
+ #[clap(short, long)]
+ id: String,
+ #[clap(short, long)]
+ key: String,
+ #[clap(short, long)]
+ boolean: Option,
+ #[clap(short, long)]
+ numeric: Option,
+ #[clap(short, long)]
+ string: Option
+ },
+ /// Unset a config override on the host
+ Unset {
+ #[clap(short, long)]
+ id: String,
+ #[clap(short, long)]
+ key: String
}
}
diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml
index 30749f8..aeb67c6 100644
--- a/tfclient/Cargo.toml
+++ b/tfclient/Cargo.toml
@@ -40,4 +40,4 @@ flate2 = "1.0.25"
tar = "0.4.38"
hex = "0.4.3"
tempfile = "3.4.0"
-sha2 = "0.10.6"
\ No newline at end of file
+sha2 = "0.10.6"
diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml
index 711bce2..48723ea 100644
--- a/trifid-api/Cargo.toml
+++ b/trifid-api/Cargo.toml
@@ -1,7 +1,12 @@
[package]
name = "trifid-api"
-version = "0.1.0"
+version = "0.1.4"
edition = "2021"
+description = "Pure-rust Defined Networking compatible management server"
+license = "GPL-3.0-or-later"
+documentation = "https://git.e3t.cc/~core/trifid"
+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
diff --git a/trifid-api/src/codegen/mod.rs b/trifid-api/src/codegen/mod.rs
index 101a046..1b8df99 100644
--- a/trifid-api/src/codegen/mod.rs
+++ b/trifid-api/src/codegen/mod.rs
@@ -17,7 +17,8 @@ use crate::AppState;
use ed25519_dalek::SigningKey;
use ipnet::Ipv4Net;
use log::{debug, error};
-use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
+use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter};
+use serde_yaml::{Mapping, Value};
use trifid_api_entities::entity::{
firewall_rule, host, host_config_override, host_static_address, network, organization,
signing_ca,
@@ -156,7 +157,7 @@ pub async fn generate_config(
punchy: Some(NebulaConfigPunchy {
punch: true,
respond: true,
- delay: "".to_string(),
+ delay: "1s".to_string(),
}),
cipher: NebulaConfigCipher::Aes,
preferred_ranges: vec![],
@@ -191,7 +192,7 @@ pub async fn generate_config(
cidr: None,
}]),
}),
- routines: 0,
+ routines: 1,
stats: None,
local_range: None,
};
@@ -203,15 +204,15 @@ pub async fn generate_config(
for (key, kv_value) in &info.config_overrides {
// split up the key
// a.b.c.d = ['a']['b']['c']['d'] = value
- let key_split = key.split('.');
+ let key_split = key.split('.').collect::>();
let mut current_val = &mut value;
- for key_iter in key_split {
- current_val = current_val.get_mut(key_iter).ok_or("Invalid key-value override")?;
+ for key_iter in &key_split[..key_split.len()-1] {
+ current_val = current_val.as_mapping_mut().unwrap().entry(Value::String(key_iter.to_string())).or_insert(Value::Mapping(Mapping::new()));
}
- *current_val = serde_yaml::from_str(kv_value)?;
+ current_val.as_mapping_mut().unwrap().insert(Value::String(key_split[key_split.len()-1].to_string()), serde_yaml::from_str(kv_value)?);
}
let config_str_merged = serde_yaml::to_string(&value)?;
@@ -237,12 +238,12 @@ pub async fn collect_info<'a>(
};
let host_config_overrides = trifid_api_entities::entity::host_config_override::Entity::find()
- .filter(host_config_override::Column::Id.eq(&host.id))
+ .filter(host_config_override::Column::Host.eq(&host.id))
.all(&db.conn)
.await?;
let _host_static_addresses = trifid_api_entities::entity::host_static_address::Entity::find()
- .filter(host_static_address::Column::Id.eq(&host.id))
+ .filter(host_static_address::Column::Host.eq(&host.id))
.all(&db.conn)
.await?;
@@ -267,8 +268,7 @@ pub async fn collect_info<'a>(
let hosts = trifid_api_entities::entity::host::Entity::find()
.filter(host::Column::Network.eq(&network.id))
- .filter(host::Column::IsRelay.eq(true))
- .filter(host::Column::IsLighthouse.eq(true))
+ .filter(Condition::any().add(host::Column::IsRelay.eq(true)).add(host::Column::IsLighthouse.eq(true)))
.all(&db.conn)
.await?;
@@ -356,7 +356,7 @@ pub async fn collect_info<'a>(
} else {
format!("{}-{}", u.port_range_from, u.port_range_to)
}),
- proto: Some(u.protocol.clone()),
+ proto: Some(u.protocol.clone().to_lowercase()),
ca_name: None,
ca_sha: None,
host: if u.allowed_role_id.is_some() {
diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs
index 263b90d..583c122 100644
--- a/trifid-api/src/main.rs
+++ b/trifid-api/src/main.rs
@@ -119,6 +119,8 @@ async fn main() -> Result<(), Box> {
.service(routes::v2::enroll::enroll)
.service(routes::v1::dnclient::dnclient)
.service(routes::v2::whoami::whoami)
+ .service(routes::v1::hosts::get_host_overrides)
+ .service(routes::v1::hosts::update_host_overrides)
})
.bind(CONFIG.server.bind)?
.run()
diff --git a/trifid-api/src/routes/v1/dnclient.rs b/trifid-api/src/routes/v1/dnclient.rs
index 07f9b5e..87cea0b 100644
--- a/trifid-api/src/routes/v1/dnclient.rs
+++ b/trifid-api/src/routes/v1/dnclient.rs
@@ -266,7 +266,7 @@ pub async fn dnclient(
ks.signing_keys.push(KSSigningKey {
id: ks.current_signing_key + 1,
- key: SigningKey::generate(&mut OsRng),
+ key: ks.signing_keys[0].key.clone(),
});
ks.current_signing_key += 1;
@@ -322,7 +322,7 @@ pub async fn dnclient(
let signing_key = host_in_ks
.signing_keys
.iter()
- .find(|u| u.id == (req.counter as u64) - 1)
+ .find(|u| u.id == (req.counter as u64))
.unwrap();
let msg = DoUpdateResponse {
diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs
index 38568fb..f6fd78e 100644
--- a/trifid-api/src/routes/v1/hosts.rs
+++ b/trifid-api/src/routes/v1/hosts.rs
@@ -76,7 +76,8 @@ use serde::{Deserialize, Serialize};
use std::net::{Ipv4Addr, SocketAddrV4};
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
-use trifid_api_entities::entity::{host, host_static_address, network, organization};
+use trifid_api_entities::entity::{host, host_config_override, host_static_address, network, organization};
+use trifid_api_entities::entity::prelude::HostConfigOverride;
#[derive(Serialize, Deserialize)]
pub struct ListHostsRequestOpts {
@@ -2328,3 +2329,495 @@ pub async fn create_host_and_enrollment_code(
metadata: CreateHostAndCodeResponseMetadata {},
})
}
+
+#[derive(Serialize, Deserialize)]
+pub struct HostConfigOverrideResponse {
+ pub data: HostConfigOverrideData
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct HostConfigOverrideData {
+ pub overrides: Vec
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct HostConfigOverrideDataOverride {
+ pub key: String,
+ pub value: HostConfigOverrideDataOverrideValue
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum HostConfigOverrideDataOverrideValue {
+ Boolean(bool),
+ Numeric(i64),
+ Other(String)
+}
+
+#[get("/v1/hosts/{host_id}/config-overrides")]
+pub async fn get_host_overrides(id: Path, req_info: HttpRequest, db: Data) -> HttpResponse {
+ // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:read
+ let session_info = enforce_2fa(&req_info, &db.conn)
+ .await
+ .unwrap_or(TokenInfo::NotPresent);
+ let api_token_info = enforce_api_token(&req_info, &["hosts:read"], &db.conn)
+ .await
+ .unwrap_or(TokenInfo::NotPresent);
+
+ // If neither are present, throw an error
+ if matches!(session_info, TokenInfo::NotPresent)
+ && matches!(api_token_info, TokenInfo::NotPresent)
+ {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_UNAUTHORIZED".to_string(),
+ message: "This endpoint requires either a fully authenticated user or a token with the hosts:read scope".to_string(),
+ path: None,
+ }
+ ],
+ });
+ }
+
+ // If both are present, throw an error
+ if matches!(session_info, TokenInfo::AuthToken(_))
+ && matches!(api_token_info, TokenInfo::ApiToken(_))
+ {
+ return HttpResponse::BadRequest().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(),
+ message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(),
+ path: None
+ }
+ ],
+ });
+ }
+
+ let org_id = match api_token_info {
+ TokenInfo::ApiToken(tkn) => tkn.organization,
+ _ => {
+ // we have a session token, which means we have to do a db request to get the organization that this user owns
+ let user = match session_info {
+ TokenInfo::AuthToken(tkn) => tkn.session_info.user,
+ _ => unreachable!(),
+ };
+
+ let org = match organization::Entity::find()
+ .filter(organization::Column::Owner.eq(user.id))
+ .one(&db.conn)
+ .await
+ {
+ Ok(r) => r,
+ Err(e) => {
+ error!("database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error performing the database request, please try again later.".to_string(),
+ path: None,
+ }
+ ],
+ });
+ }
+ };
+
+ if let Some(org) = org {
+ org.id
+ } else {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_NO_ORG".to_string(),
+ message: "This user does not own any organizations. Try using an API token instead.".to_string(),
+ path: None
+ }
+ ],
+ });
+ }
+ }
+ };
+
+ let net_id;
+
+ let net = match network::Entity::find()
+ .filter(network::Column::Organization.eq(&org_id))
+ .one(&db.conn)
+ .await
+ {
+ Ok(r) => r,
+ Err(e) => {
+ error!("database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error performing the database request, please try again later.".to_string(),
+ path: None,
+ }
+ ],
+ });
+ }
+ };
+
+ if let Some(net) = net {
+ net_id = net.id;
+ } else {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_NO_NET".to_string(),
+ message: "This user does not own any networks. Try using an API token instead."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+
+ let host = match host::Entity::find()
+ .filter(host::Column::Id.eq(id.into_inner()))
+ .one(&db.conn)
+ .await
+ {
+ Ok(h) => h,
+ Err(e) => {
+ error!("Database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error with the database query. Please try again later."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+ };
+
+ let host = match host {
+ Some(h) => h,
+ None => {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_UNAUTHORIZED".to_string(),
+ message:
+ "This resource does not exist or you do not have permission to access it."
+ .to_string(),
+ path: None,
+ }],
+ })
+ }
+ };
+
+ if host.network != net_id {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_UNAUTHORIZED".to_string(),
+ message: "This resource does not exist or you do not have permission to access it."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+
+ let config_overrides = match trifid_api_entities::entity::host_config_override::Entity::find().filter(host_config_override::Column::Host.eq(host.id)).all(&db.conn).await {
+ Ok(h) => h,
+ Err(e) => {
+ error!("Database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error with the database query. Please try again later."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+ };
+
+ let overrides: Vec = config_overrides.iter().map(|u| {
+ let val;
+ if u.value == "true" || u.value == "false" {
+ val = HostConfigOverrideDataOverrideValue::Boolean(u.value == "true");
+ } else if u.value.chars().all(|c| c.is_numeric()) {
+ val = HostConfigOverrideDataOverrideValue::Numeric(u.value.parse().unwrap());
+ } else {
+ val = HostConfigOverrideDataOverrideValue::Other(u.value.clone());
+ }
+ HostConfigOverrideDataOverride {
+ key: u.key.clone(),
+ value: val,
+ }
+ }).collect();
+
+ HttpResponse::Ok().json(HostConfigOverrideResponse {
+ data: HostConfigOverrideData {
+ overrides,
+ },
+ })
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct UpdateOverridesRequest {
+ pub overrides: Vec
+}
+
+#[put("/v1/hosts/{host_id}/config-overrides")]
+pub async fn update_host_overrides(id: Path, req: Json, req_info: HttpRequest, db: Data) -> HttpResponse {
+ // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:read
+ let session_info = enforce_2fa(&req_info, &db.conn)
+ .await
+ .unwrap_or(TokenInfo::NotPresent);
+ let api_token_info = enforce_api_token(&req_info, &["hosts:read"], &db.conn)
+ .await
+ .unwrap_or(TokenInfo::NotPresent);
+
+ // If neither are present, throw an error
+ if matches!(session_info, TokenInfo::NotPresent)
+ && matches!(api_token_info, TokenInfo::NotPresent)
+ {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_UNAUTHORIZED".to_string(),
+ message: "This endpoint requires either a fully authenticated user or a token with the hosts:read scope".to_string(),
+ path: None,
+ }
+ ],
+ });
+ }
+
+ // If both are present, throw an error
+ if matches!(session_info, TokenInfo::AuthToken(_))
+ && matches!(api_token_info, TokenInfo::ApiToken(_))
+ {
+ return HttpResponse::BadRequest().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(),
+ message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(),
+ path: None
+ }
+ ],
+ });
+ }
+
+ let org_id = match api_token_info {
+ TokenInfo::ApiToken(tkn) => tkn.organization,
+ _ => {
+ // we have a session token, which means we have to do a db request to get the organization that this user owns
+ let user = match session_info {
+ TokenInfo::AuthToken(tkn) => tkn.session_info.user,
+ _ => unreachable!(),
+ };
+
+ let org = match organization::Entity::find()
+ .filter(organization::Column::Owner.eq(user.id))
+ .one(&db.conn)
+ .await
+ {
+ Ok(r) => r,
+ Err(e) => {
+ error!("database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error performing the database request, please try again later.".to_string(),
+ path: None,
+ }
+ ],
+ });
+ }
+ };
+
+ if let Some(org) = org {
+ org.id
+ } else {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_NO_ORG".to_string(),
+ message: "This user does not own any organizations. Try using an API token instead.".to_string(),
+ path: None
+ }
+ ],
+ });
+ }
+ }
+ };
+
+ let net_id;
+
+ let net = match network::Entity::find()
+ .filter(network::Column::Organization.eq(&org_id))
+ .one(&db.conn)
+ .await
+ {
+ Ok(r) => r,
+ Err(e) => {
+ error!("database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![
+ APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error performing the database request, please try again later.".to_string(),
+ path: None,
+ }
+ ],
+ });
+ }
+ };
+
+ if let Some(net) = net {
+ net_id = net.id;
+ } else {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_NO_NET".to_string(),
+ message: "This user does not own any networks. Try using an API token instead."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+
+ let host = match host::Entity::find()
+ .filter(host::Column::Id.eq(id.into_inner()))
+ .one(&db.conn)
+ .await
+ {
+ Ok(h) => h,
+ Err(e) => {
+ error!("Database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error with the database query. Please try again later."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+ };
+
+ let host = match host {
+ Some(h) => h,
+ None => {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_UNAUTHORIZED".to_string(),
+ message:
+ "This resource does not exist or you do not have permission to access it."
+ .to_string(),
+ path: None,
+ }],
+ })
+ }
+ };
+
+ if host.network != net_id {
+ return HttpResponse::Unauthorized().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_UNAUTHORIZED".to_string(),
+ message: "This resource does not exist or you do not have permission to access it."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+
+ let config_overrides = match trifid_api_entities::entity::host_config_override::Entity::find().filter(host_config_override::Column::Host.eq(&host.id)).all(&db.conn).await {
+ Ok(h) => h,
+ Err(e) => {
+ error!("Database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error with the database query. Please try again later."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+ };
+
+ for c_override in config_overrides {
+ match c_override.delete(&db.conn).await {
+ Ok(_) => (),
+ Err(e) => {
+ error!("Database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error with the database query. Please try again later."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+ }
+ }
+
+ for c_override in &req.overrides {
+ let db_override = host_config_override::Model {
+ id: random_id("override"),
+ key: c_override.key.clone(),
+ value: match &c_override.value {
+ HostConfigOverrideDataOverrideValue::Boolean(v) => v.to_string(),
+ HostConfigOverrideDataOverrideValue::Numeric(v) => v.to_string(),
+ HostConfigOverrideDataOverrideValue::Other(v) => v.clone(),
+ },
+ host: host.id.clone(),
+ };
+ match db_override.into_active_model().insert(&db.conn).await {
+ Ok(_) => (),
+ Err(e) => {
+ error!("Database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error with the database query. Please try again later."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+ }
+ }
+
+ let config_overrides = match trifid_api_entities::entity::host_config_override::Entity::find().filter(host_config_override::Column::Host.eq(&host.id)).all(&db.conn).await {
+ Ok(h) => h,
+ Err(e) => {
+ error!("Database error: {}", e);
+ return HttpResponse::InternalServerError().json(APIErrorsResponse {
+ errors: vec![APIError {
+ code: "ERR_DB_ERROR".to_string(),
+ message: "There was an error with the database query. Please try again later."
+ .to_string(),
+ path: None,
+ }],
+ });
+ }
+ };
+
+ let overrides: Vec = config_overrides.iter().map(|u| {
+ let val;
+ if u.value == "true" || u.value == "false" {
+ val = HostConfigOverrideDataOverrideValue::Boolean(u.value == "true");
+ } else if u.value.chars().all(|c| c.is_numeric()) || u.value.starts_with('-') && u.value.chars().collect::>()[1..].iter().all(|c| c.is_numeric()) {
+ val = HostConfigOverrideDataOverrideValue::Numeric(u.value.parse().unwrap());
+ } else {
+ val = HostConfigOverrideDataOverrideValue::Other(u.value.clone());
+ }
+ HostConfigOverrideDataOverride {
+ key: u.key.clone(),
+ value: val,
+ }
+ }).collect();
+
+ HttpResponse::Ok().json(HostConfigOverrideResponse {
+ data: HostConfigOverrideData {
+ overrides,
+ },
+ })
+}
\ No newline at end of file
diff --git a/trifid-api/trifid_api_entities/Cargo.toml b/trifid-api/trifid_api_entities/Cargo.toml
index 73b20f4..141303c 100644
--- a/trifid-api/trifid_api_entities/Cargo.toml
+++ b/trifid-api/trifid_api_entities/Cargo.toml
@@ -2,6 +2,11 @@
name = "trifid_api_entities"
version = "0.1.0"
edition = "2021"
+description = "Database entities for trifid-api"
+license = "GPL-3.0-or-later"
+documentation = "https://git.e3t.cc/~core/trifid"
+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
diff --git a/trifid-api/trifid_api_migration/Cargo.toml b/trifid-api/trifid_api_migration/Cargo.toml
index f090efb..b760105 100644
--- a/trifid-api/trifid_api_migration/Cargo.toml
+++ b/trifid-api/trifid_api_migration/Cargo.toml
@@ -2,7 +2,11 @@
name = "trifid_api_migration"
version = "0.1.0"
edition = "2021"
-publish = false
+description = "Database migrations for trifid-api"
+license = "GPL-3.0-or-later"
+documentation = "https://git.e3t.cc/~core/trifid"
+homepage = "https://git.e3t.cc/~core/trifid"
+repository = "https://git.e3t.cc/~core/trifid"
[lib]
name = "trifid_api_migration"
diff --git a/trifid-api/trifid_data/tfks.toml b/trifid-api/trifid_data/tfks.toml
deleted file mode 100644
index e527be7..0000000
--- a/trifid-api/trifid_data/tfks.toml
+++ /dev/null
@@ -1,361 +0,0 @@
-[[hosts]]
-id = "host-IPNHZ2XBXJDY2WYOYG7709CBJ8"
-current_signing_key = 1
-current_client_key = 2
-current_config = 2
-current_cert = 2
-
-[[hosts.certs]]
-id = 1
-
-[hosts.certs.cert]
-signature = [112, 198, 103, 65, 58, 33, 254, 185, 255, 1, 204, 111, 236, 234, 55, 143, 24, 27, 104, 53, 89, 106, 209, 53, 201, 35, 248, 55, 109, 120, 219, 26, 171, 234, 181, 70, 174, 177, 12, 121, 190, 67, 73, 104, 218, 2, 139, 120, 116, 174, 106, 120, 56, 162, 143, 162, 143, 199, 237, 151, 215, 129, 245, 8]
-
-[hosts.certs.cert.details]
-name = "asd"
-ips = ["10.17.2.3/15"]
-subnets = []
-groups = ["role:role-A4YTNBOMCFJNK5OAKHQCUUVIL8"]
-public_key = [10, 175, 118, 186, 191, 43, 172, 0, 152, 238, 83, 31, 38, 79, 189, 76, 149, 38, 157, 84, 200, 210, 0, 95, 37, 169, 196, 77, 214, 209, 91, 10]
-is_ca = false
-issuer = "9a4dd7cb5c3a086b0173f126bbf20b85ac7886a2129d2f8573acc2e20f09ec1f"
-
-[hosts.certs.cert.details.not_before]
-secs_since_epoch = 1684171628
-nanos_since_epoch = 68795993
-
-[hosts.certs.cert.details.not_after]
-secs_since_epoch = 1716312428
-nanos_since_epoch = 68796023
-
-[[hosts.certs]]
-id = 2
-
-[hosts.certs.cert]
-signature = [134, 249, 92, 208, 133, 181, 164, 230, 242, 79, 132, 140, 164, 28, 159, 165, 55, 176, 140, 73, 208, 50, 53, 184, 178, 242, 62, 90, 55, 187, 245, 231, 22, 89, 161, 9, 181, 56, 135, 163, 93, 102, 69, 34, 51, 139, 158, 181, 5, 207, 2, 87, 100, 236, 215, 116, 109, 43, 186, 148, 200, 235, 99, 7]
-
-[hosts.certs.cert.details]
-name = "addsd"
-ips = ["10.17.2.3/15"]
-subnets = []
-groups = ["role:role-A4YTNBOMCFJNK5OAKHQCUUVIL8"]
-public_key = [78, 139, 195, 146, 198, 211, 251, 196, 238, 154, 134, 158, 111, 25, 198, 228, 195, 108, 242, 146, 16, 45, 98, 155, 152, 116, 114, 218, 226, 137, 182, 11]
-is_ca = false
-issuer = "9a4dd7cb5c3a086b0173f126bbf20b85ac7886a2129d2f8573acc2e20f09ec1f"
-
-[hosts.certs.cert.details.not_before]
-secs_since_epoch = 1684171718
-nanos_since_epoch = 140841799
-
-[hosts.certs.cert.details.not_after]
-secs_since_epoch = 1716312518
-nanos_since_epoch = 140841859
-
-[[hosts.config]]
-id = 1
-
-[hosts.config.config]
-routines = 0
-
-[hosts.config.config.pki]
-ca = """
------BEGIN NEBULA CERTIFICATE-----\r
-Cl0KK2NvcmVAY29yZWRvZXMuZGV2J3MgT3JnYW5pemF0aW9uIFNpZ25pbmcgQ0Eo\r
-y7iEowYwy+2S0AY6II2RV3kVBopKoTe3j+aT1LbZuWTR/5oQGra185GB5W63QAES\r
-QGRgfmRuJOzhtWwwU4BGMo47uoncMGV41sz1NYcvwmruwhJDaYYJ51DLz3v5bYZV\r
-LCxfFB661cvoq1OZ7G5ZcgY=\r
------END NEBULA CERTIFICATE-----\r
-"""
-cert = """
------BEGIN NEBULA CERTIFICATE-----\r
-CoYBCgNhc2QSCYOExFCAgPj/DyIkcm9sZTpyb2xlLUE0WVROQk9NQ0ZKTks1T0FL\r
-SFFDVVVWSUw4KOzWiaMGMOyys7IGOiAKr3a6vyusAJjuUx8mT71MlSadVMjSAF8l\r
-qcRN1tFbCkogmk3Xy1w6CGsBc/Emu/ILhax4hqISnS+Fc6zC4g8J7B8SQHDGZ0E6\r
-If65/wHMb+zqN48YG2g1WWrRNckj+DdteNsaq+q1Rq6xDHm+Q0lo2gKLeHSuang4\r
-oo+ij8ftl9eB9Qg=\r
------END NEBULA CERTIFICATE-----\r
-"""
-disconnect_invalid = true
-
-[hosts.config.config.lighthouse]
-interval = 60
-
-[hosts.config.config.listen]
-host = "[::]"
-read_buffer = 10485760
-write_buffer = 10485760
-
-[hosts.config.config.punchy]
-punch = true
-respond = true
-delay = ""
-
-[hosts.config.config.relay]
-
-[hosts.config.config.tun]
-dev = "trifid1"
-drop_local_broadcast = true
-drop_multicast = true
-
-[hosts.config.config.firewall]
-inbound = []
-
-[[hosts.config.config.firewall.outbound]]
-port = "any"
-proto = "any"
-host = "any"
-
-[[hosts.config]]
-id = 2
-
-[hosts.config.config]
-routines = 0
-
-[hosts.config.config.pki]
-ca = """
------BEGIN NEBULA CERTIFICATE-----\r
-Cl0KK2NvcmVAY29yZWRvZXMuZGV2J3MgT3JnYW5pemF0aW9uIFNpZ25pbmcgQ0Eo\r
-y7iEowYwy+2S0AY6II2RV3kVBopKoTe3j+aT1LbZuWTR/5oQGra185GB5W63QAES\r
-QGRgfmRuJOzhtWwwU4BGMo47uoncMGV41sz1NYcvwmruwhJDaYYJ51DLz3v5bYZV\r
-LCxfFB661cvoq1OZ7G5ZcgY=\r
------END NEBULA CERTIFICATE-----\r
-"""
-cert = """
------BEGIN NEBULA CERTIFICATE-----\r
-CogBCgVhZGRzZBIJg4TEUICA+P8PIiRyb2xlOnJvbGUtQTRZVE5CT01DRkpOSzVP\r
-QUtIUUNVVVZJTDgoxteJowYwxrOzsgY6IE6Lw5LG0/vE7pqGnm8ZxuTDbPKSEC1i\r
-m5h0ctriibYLSiCaTdfLXDoIawFz8Sa78guFrHiGohKdL4VzrMLiDwnsHxJAhvlc\r
-0IW1pObyT4SMpByfpTewjEnQMjW4svI+Wje79ecWWaEJtTiHo11mRSIzi561Bc8C\r
-V2Ts13RtK7qUyOtjBw==\r
------END NEBULA CERTIFICATE-----\r
-"""
-disconnect_invalid = true
-
-[hosts.config.config.lighthouse]
-interval = 60
-
-[hosts.config.config.listen]
-host = "[::]"
-read_buffer = 10485760
-write_buffer = 10485760
-
-[hosts.config.config.punchy]
-punch = true
-respond = true
-delay = ""
-
-[hosts.config.config.relay]
-
-[hosts.config.config.tun]
-dev = "trifid1"
-drop_local_broadcast = true
-drop_multicast = true
-
-[hosts.config.config.firewall]
-inbound = []
-
-[[hosts.config.config.firewall.outbound]]
-port = "any"
-proto = "any"
-host = "any"
-
-[[hosts.signing_keys]]
-id = 0
-key = [108, 174, 65, 117, 166, 239, 62, 150, 81, 111, 185, 79, 158, 206, 104, 43, 163, 224, 206, 219, 147, 71, 158, 88, 103, 149, 113, 152, 123, 41, 78, 255]
-
-[[hosts.signing_keys]]
-id = 1
-key = [119, 226, 183, 227, 53, 121, 14, 141, 125, 165, 249, 103, 28, 60, 102, 111, 242, 63, 26, 52, 87, 29, 29, 114, 11, 62, 138, 121, 213, 245, 193, 212]
-
-[[hosts.client_keys]]
-id = 1
-dh_pub = [10, 175, 118, 186, 191, 43, 172, 0, 152, 238, 83, 31, 38, 79, 189, 76, 149, 38, 157, 84, 200, 210, 0, 95, 37, 169, 196, 77, 214, 209, 91, 10]
-ed_pub = [135, 237, 110, 71, 189, 155, 246, 66, 50, 229, 80, 254, 93, 99, 35, 29, 87, 138, 132, 193, 118, 216, 218, 60, 142, 178, 42, 126, 182, 25, 31, 103]
-
-[[hosts.client_keys]]
-id = 2
-dh_pub = [78, 139, 195, 146, 198, 211, 251, 196, 238, 154, 134, 158, 111, 25, 198, 228, 195, 108, 242, 146, 16, 45, 98, 155, 152, 116, 114, 218, 226, 137, 182, 11]
-ed_pub = [178, 77, 253, 159, 81, 137, 20, 14, 184, 230, 73, 111, 130, 129, 15, 184, 114, 90, 133, 147, 178, 252, 197, 75, 82, 33, 21, 5, 38, 238, 57, 84]
-
-[[hosts]]
-id = "host-2PXIOHLPQA3CQL8O7XD6CXMMRM"
-current_signing_key = 1
-current_client_key = 2
-current_config = 2
-current_cert = 2
-
-[[hosts.certs]]
-id = 1
-
-[hosts.certs.cert]
-signature = [160, 205, 80, 112, 16, 205, 155, 249, 221, 26, 47, 128, 2, 59, 15, 102, 153, 174, 61, 35, 207, 233, 42, 242, 212, 28, 133, 40, 189, 1, 234, 67, 24, 109, 152, 248, 130, 96, 48, 104, 69, 0, 178, 30, 103, 76, 33, 179, 216, 92, 191, 89, 6, 236, 136, 216, 9, 208, 189, 16, 140, 132, 209, 2]
-
-[hosts.certs.cert.details]
-name = "testhost4"
-ips = ["10.17.4.2/15"]
-subnets = []
-groups = ["role:role-A4YTNBOMCFJNK5OAKHQCUUVIL8"]
-public_key = [40, 175, 28, 13, 183, 102, 108, 21, 53, 79, 113, 191, 101, 74, 77, 151, 66, 146, 250, 155, 196, 38, 178, 44, 41, 186, 71, 1, 152, 237, 245, 93]
-is_ca = false
-issuer = "9a4dd7cb5c3a086b0173f126bbf20b85ac7886a2129d2f8573acc2e20f09ec1f"
-
-[hosts.certs.cert.details.not_before]
-secs_since_epoch = 1684172253
-nanos_since_epoch = 219759539
-
-[hosts.certs.cert.details.not_after]
-secs_since_epoch = 1716313053
-nanos_since_epoch = 219759579
-
-[[hosts.certs]]
-id = 2
-
-[hosts.certs.cert]
-signature = [54, 210, 5, 3, 189, 187, 221, 142, 238, 142, 175, 248, 12, 128, 6, 58, 99, 44, 248, 198, 51, 3, 152, 118, 113, 46, 41, 191, 138, 15, 120, 103, 170, 24, 229, 27, 241, 182, 236, 220, 51, 117, 224, 118, 191, 25, 84, 111, 100, 15, 53, 234, 132, 214, 213, 66, 95, 8, 44, 162, 212, 60, 151, 13]
-
-[hosts.certs.cert.details]
-name = "testhost4"
-ips = ["10.17.4.2/15"]
-subnets = []
-groups = ["role:role-A4YTNBOMCFJNK5OAKHQCUUVIL8"]
-public_key = [4, 249, 63, 6, 25, 145, 63, 132, 106, 48, 243, 192, 249, 159, 185, 160, 196, 146, 24, 7, 241, 160, 121, 122, 212, 249, 19, 213, 158, 105, 142, 86]
-is_ca = false
-issuer = "9a4dd7cb5c3a086b0173f126bbf20b85ac7886a2129d2f8573acc2e20f09ec1f"
-
-[hosts.certs.cert.details.not_before]
-secs_since_epoch = 1684172313
-nanos_since_epoch = 739770378
-
-[hosts.certs.cert.details.not_after]
-secs_since_epoch = 1716313113
-nanos_since_epoch = 739770429
-
-[[hosts.config]]
-id = 1
-
-[hosts.config.config]
-routines = 0
-
-[hosts.config.config.pki]
-ca = """
------BEGIN NEBULA CERTIFICATE-----\r
-Cl0KK2NvcmVAY29yZWRvZXMuZGV2J3MgT3JnYW5pemF0aW9uIFNpZ25pbmcgQ0Eo\r
-y7iEowYwy+2S0AY6II2RV3kVBopKoTe3j+aT1LbZuWTR/5oQGra185GB5W63QAES\r
-QGRgfmRuJOzhtWwwU4BGMo47uoncMGV41sz1NYcvwmruwhJDaYYJ51DLz3v5bYZV\r
-LCxfFB661cvoq1OZ7G5ZcgY=\r
------END NEBULA CERTIFICATE-----\r
-"""
-cert = """
------BEGIN NEBULA CERTIFICATE-----\r
-CowBCgl0ZXN0aG9zdDQSCYKIxFCAgPj/DyIkcm9sZTpyb2xlLUE0WVROQk9NQ0ZK\r
-Tks1T0FLSFFDVVVWSUw4KN3biaMGMN23s7IGOiAorxwNt2ZsFTVPcb9lSk2XQpL6\r
-m8QmsiwpukcBmO31XUogmk3Xy1w6CGsBc/Emu/ILhax4hqISnS+Fc6zC4g8J7B8S\r
-QKDNUHAQzZv53RovgAI7D2aZrj0jz+kq8tQchSi9AepDGG2Y+IJgMGhFALIeZ0wh\r
-s9hcv1kG7IjYCdC9EIyE0QI=\r
------END NEBULA CERTIFICATE-----\r
-"""
-disconnect_invalid = true
-
-[hosts.config.config.lighthouse]
-am_lighthouse = true
-interval = 60
-
-[hosts.config.config.listen]
-host = "[::]"
-port = 5679
-read_buffer = 10485760
-write_buffer = 10485760
-
-[hosts.config.config.punchy]
-punch = true
-respond = true
-delay = ""
-
-[hosts.config.config.relay]
-
-[hosts.config.config.tun]
-dev = "trifid1"
-drop_local_broadcast = true
-drop_multicast = true
-
-[hosts.config.config.firewall]
-inbound = []
-
-[[hosts.config.config.firewall.outbound]]
-port = "any"
-proto = "any"
-host = "any"
-
-[[hosts.config]]
-id = 2
-
-[hosts.config.config]
-routines = 0
-
-[hosts.config.config.pki]
-ca = """
------BEGIN NEBULA CERTIFICATE-----\r
-Cl0KK2NvcmVAY29yZWRvZXMuZGV2J3MgT3JnYW5pemF0aW9uIFNpZ25pbmcgQ0Eo\r
-y7iEowYwy+2S0AY6II2RV3kVBopKoTe3j+aT1LbZuWTR/5oQGra185GB5W63QAES\r
-QGRgfmRuJOzhtWwwU4BGMo47uoncMGV41sz1NYcvwmruwhJDaYYJ51DLz3v5bYZV\r
-LCxfFB661cvoq1OZ7G5ZcgY=\r
------END NEBULA CERTIFICATE-----\r
-"""
-cert = """
------BEGIN NEBULA CERTIFICATE-----\r
-CowBCgl0ZXN0aG9zdDQSCYKIxFCAgPj/DyIkcm9sZTpyb2xlLUE0WVROQk9NQ0ZK\r
-Tks1T0FLSFFDVVVWSUw4KJnciaMGMJm4s7IGOiAE+T8GGZE/hGow88D5n7mgxJIY\r
-B/GgeXrU+RPVnmmOVkogmk3Xy1w6CGsBc/Emu/ILhax4hqISnS+Fc6zC4g8J7B8S\r
-QDbSBQO9u92O7o6v+AyABjpjLPjGMwOYdnEuKb+KD3hnqhjlG/G27NwzdeB2vxlU\r
-b2QPNeqE1tVCXwgsotQ8lw0=\r
------END NEBULA CERTIFICATE-----\r
-"""
-disconnect_invalid = true
-
-[hosts.config.config.lighthouse]
-am_lighthouse = true
-interval = 60
-
-[hosts.config.config.listen]
-host = "[::]"
-port = 5677
-read_buffer = 10485760
-write_buffer = 10485760
-
-[hosts.config.config.punchy]
-punch = true
-respond = true
-delay = ""
-
-[hosts.config.config.relay]
-
-[hosts.config.config.tun]
-dev = "trifid1"
-drop_local_broadcast = true
-drop_multicast = true
-
-[hosts.config.config.firewall]
-inbound = []
-
-[[hosts.config.config.firewall.outbound]]
-port = "any"
-proto = "any"
-host = "any"
-
-[[hosts.signing_keys]]
-id = 0
-key = [255, 84, 221, 121, 87, 225, 7, 12, 236, 8, 209, 175, 98, 20, 119, 146, 92, 177, 79, 121, 24, 243, 247, 113, 106, 212, 183, 155, 208, 55, 219, 135]
-
-[[hosts.signing_keys]]
-id = 1
-key = [98, 159, 193, 58, 183, 156, 75, 17, 70, 103, 112, 6, 71, 197, 167, 152, 99, 210, 199, 40, 49, 13, 101, 72, 57, 34, 221, 237, 142, 29, 144, 175]
-
-[[hosts.client_keys]]
-id = 1
-dh_pub = [40, 175, 28, 13, 183, 102, 108, 21, 53, 79, 113, 191, 101, 74, 77, 151, 66, 146, 250, 155, 196, 38, 178, 44, 41, 186, 71, 1, 152, 237, 245, 93]
-ed_pub = [247, 172, 97, 223, 43, 24, 248, 133, 118, 219, 227, 72, 95, 25, 167, 179, 115, 225, 73, 211, 161, 216, 95, 140, 151, 59, 118, 39, 122, 136, 144, 245]
-
-[[hosts.client_keys]]
-id = 2
-dh_pub = [4, 249, 63, 6, 25, 145, 63, 132, 106, 48, 243, 192, 249, 159, 185, 160, 196, 146, 24, 7, 241, 160, 121, 122, 212, 249, 19, 213, 158, 105, 142, 86]
-ed_pub = [55, 82, 153, 75, 220, 207, 87, 221, 50, 200, 77, 9, 242, 136, 64, 91, 60, 96, 31, 100, 58, 162, 150, 147, 109, 109, 117, 188, 164, 217, 248, 140]
diff --git a/trifid-api/trifiddata/tfks.toml b/trifid-api/trifiddata/tfks.toml
new file mode 100644
index 0000000..05aaf7a
--- /dev/null
+++ b/trifid-api/trifiddata/tfks.toml
@@ -0,0 +1,98 @@
+[[hosts]]
+id = "host-CAJDSM36900G2MFGFNIODZUG2G"
+current_signing_key = 0
+current_client_key = 1
+current_config = 1
+current_cert = 1
+
+[[hosts.certs]]
+id = 1
+
+[hosts.certs.cert]
+signature = [254, 145, 36, 202, 32, 234, 248, 2, 147, 188, 207, 151, 147, 246, 100, 22, 114, 174, 221, 91, 62, 0, 48, 23, 106, 196, 75, 27, 116, 203, 68, 41, 110, 118, 85, 76, 230, 19, 128, 163, 134, 96, 121, 9, 227, 100, 174, 173, 144, 145, 149, 79, 189, 252, 126, 4, 113, 231, 141, 69, 77, 108, 48, 4]
+
+[hosts.certs.cert.details]
+name = "Test Host"
+ips = ["10.16.1.1/15"]
+subnets = []
+groups = ["role:role-IRN57I1D5L3EOI3CDDU2TNBS5W"]
+public_key = [43, 106, 145, 119, 55, 49, 4, 83, 171, 182, 60, 167, 213, 135, 126, 129, 148, 82, 15, 219, 155, 146, 132, 112, 141, 147, 46, 131, 207, 175, 199, 15]
+is_ca = false
+issuer = "90e0d7de2b241f3cdd7005e392f5c9e45277e2735edf90602424cbec786b6f32"
+
+[hosts.certs.cert.details.not_before]
+secs_since_epoch = 1687446533
+nanos_since_epoch = 957200472
+
+[hosts.certs.cert.details.not_after]
+secs_since_epoch = 1719587333
+nanos_since_epoch = 957200502
+
+[[hosts.config]]
+id = 1
+
+[hosts.config.config.pki]
+ca = """
+-----BEGIN NEBULA CERTIFICATE-----\r
+Cl0KK2NvcmVAY29yZWRvZXMuZGV2J3MgT3JnYW5pemF0aW9uIFNpZ25pbmcgQ0Eo\r
+sN2upAYwsJK90QY6IHetWlUyvE9ka3Q4OFRaJGeOLH98I5uqEwO0temq88RJQAES\r
+QG7FI2jb0M83FUYpqX70a4DlQA4EvsyjuOiDp2gm0jn5lgaPe3rZbuYuJ114zSO4\r
+9QZKdFiTpeGoZkaikJJz0ws=\r
+-----END NEBULA CERTIFICATE-----\r
+"""
+cert = """
+-----BEGIN NEBULA CERTIFICATE-----\r
+CowBCglUZXN0IEhvc3QSCYGCwFCAgPj/DyIkcm9sZTpyb2xlLUlSTjU3STFENUwz\r
+RU9JM0NERFUyVE5CUzVXKIXI0aQGMIWk+7MGOiArapF3NzEEU6u2PKfVh36BlFIP\r
+25uShHCNky6Dz6/HD0ogkODX3iskHzzdcAXjkvXJ5FJ34nNe35BgJCTL7HhrbzIS\r
+QP6RJMog6vgCk7zPl5P2ZBZyrt1bPgAwF2rESxt0y0QpbnZVTOYTgKOGYHkJ42Su\r
+rZCRlU+9/H4EceeNRU1sMAQ=\r
+-----END NEBULA CERTIFICATE-----\r
+"""
+disconnect_invalid = true
+
+[hosts.config.config.lighthouse]
+interval = 60
+
+[hosts.config.config.listen]
+host = "[::]"
+read_buffer = 10485760
+write_buffer = 10485760
+
+[hosts.config.config.punchy]
+punch = true
+respond = true
+
+[hosts.config.config.relay]
+
+[hosts.config.config.tun]
+dev = "trifid1"
+drop_local_broadcast = true
+drop_multicast = true
+
+[[hosts.config.config.firewall.inbound]]
+port = "any"
+proto = "icmp"
+host = "any"
+
+[[hosts.config.config.firewall.outbound]]
+port = "any"
+proto = "any"
+host = "any"
+
+[hosts.config.config.stats]
+type = "prometheus"
+listen = "0.0.0.0:8788"
+path = "/metrics"
+interval = "10s"
+message_metrics = true
+lighthouse_metrics = true
+
+[[hosts.signing_keys]]
+id = 0
+key = [23, 88, 206, 16, 216, 58, 12, 80, 3, 178, 254, 16, 93, 137, 109, 69, 27, 111, 30, 32, 27, 194, 171, 200, 109, 69, 29, 45, 199, 174, 119, 46]
+
+[[hosts.client_keys]]
+id = 1
+dh_pub = [43, 106, 145, 119, 55, 49, 4, 83, 171, 182, 60, 167, 213, 135, 126, 129, 148, 82, 15, 219, 155, 146, 132, 112, 141, 147, 46, 131, 207, 175, 199, 15]
+ed_pub = [62, 181, 68, 68, 230, 78, 207, 233, 92, 252, 148, 118, 38, 84, 233, 54, 98, 220, 174, 146, 240, 37, 197, 19, 254, 137, 181, 241, 240, 83, 14, 74]