mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
WIP: IP, pubkey, system time, system uptime, ca fingerprint (#2091)
* closes #923, #2063, #2012, #1153 * add ca fingerprint * add `server.time` * add `ip-info` to `server-info` * add ssh pubkey * support multiple IPs * rename key * add `ca-fingerprint` and `system-start-time` * fix off-by-one * update compat cargo lock Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
committed by
Aiden McClelland
parent
673e5af030
commit
06cf83b901
244
backend/Cargo.lock
generated
244
backend/Cargo.lock
generated
@@ -212,6 +212,12 @@ dependencies = [
|
|||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base16ct"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base32"
|
name = "base32"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -636,9 +642,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.16.1"
|
version = "0.16.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917"
|
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"time 0.3.17",
|
"time 0.3.17",
|
||||||
@@ -661,6 +667,22 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie_store"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bca9b3c618262fc0c85ecbc814c144e04be9c6eec08b315e7cd1cfbe0bb6ca84"
|
||||||
|
dependencies = [
|
||||||
|
"cookie",
|
||||||
|
"idna 0.3.0",
|
||||||
|
"log",
|
||||||
|
"publicsuffix",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"time 0.3.17",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
@@ -735,6 +757,18 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-bigint"
|
||||||
|
version = "0.4.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@@ -1088,6 +1122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
|
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer 0.10.3",
|
"block-buffer 0.10.3",
|
||||||
|
"const-oid",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
@@ -1160,6 +1195,18 @@ dependencies = [
|
|||||||
"text_lines",
|
"text_lines",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ecdsa"
|
||||||
|
version = "0.14.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
|
||||||
|
dependencies = [
|
||||||
|
"der",
|
||||||
|
"elliptic-curve",
|
||||||
|
"rfc6979",
|
||||||
|
"signature",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519"
|
name = "ed25519"
|
||||||
version = "1.5.2"
|
version = "1.5.2"
|
||||||
@@ -1195,6 +1242,25 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "elliptic-curve"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
|
||||||
|
dependencies = [
|
||||||
|
"base16ct",
|
||||||
|
"crypto-bigint",
|
||||||
|
"der",
|
||||||
|
"digest 0.10.5",
|
||||||
|
"ff",
|
||||||
|
"generic-array",
|
||||||
|
"group",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"sec1",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embassy-os"
|
name = "embassy-os"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -1213,7 +1279,8 @@ dependencies = [
|
|||||||
"ciborium",
|
"ciborium",
|
||||||
"clap 3.2.23",
|
"clap 3.2.23",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"cookie_store",
|
"cookie",
|
||||||
|
"cookie_store 0.19.0",
|
||||||
"current_platform",
|
"current_platform",
|
||||||
"digest 0.10.5",
|
"digest 0.10.5",
|
||||||
"digest 0.9.0",
|
"digest 0.9.0",
|
||||||
@@ -1273,6 +1340,7 @@ dependencies = [
|
|||||||
"sha2 0.9.9",
|
"sha2 0.9.9",
|
||||||
"simple-logging",
|
"simple-logging",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
"ssh-key",
|
||||||
"stderrlog",
|
"stderrlog",
|
||||||
"tar",
|
"tar",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@@ -1462,6 +1530,16 @@ dependencies = [
|
|||||||
"nix 0.24.2",
|
"nix 0.24.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ff"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.18"
|
version = "0.2.18"
|
||||||
@@ -1723,6 +1801,17 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "group"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7"
|
||||||
|
dependencies = [
|
||||||
|
"ff",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
@@ -2296,6 +2385,9 @@ name = "lazy_static"
|
|||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
dependencies = [
|
||||||
|
"spin",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazycell"
|
name = "lazycell"
|
||||||
@@ -2392,6 +2484,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "link-cplusplus"
|
name = "link-cplusplus"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -2538,6 +2636,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
"ssh-key",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"torut",
|
"torut",
|
||||||
@@ -2672,6 +2771,23 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint-dig"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"lazy_static",
|
||||||
|
"libm",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-traits",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"smallvec",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-complex"
|
name = "num-complex"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -2721,6 +2837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
|
"libm",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2861,6 +2978,28 @@ version = "3.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "p256"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594"
|
||||||
|
dependencies = [
|
||||||
|
"ecdsa",
|
||||||
|
"elliptic-curve",
|
||||||
|
"sha2 0.10.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "p384"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa"
|
||||||
|
dependencies = [
|
||||||
|
"ecdsa",
|
||||||
|
"elliptic-curve",
|
||||||
|
"sha2 0.10.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
@@ -3092,6 +3231,18 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkcs1"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719"
|
||||||
|
dependencies = [
|
||||||
|
"der",
|
||||||
|
"pkcs8",
|
||||||
|
"spki",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkcs8"
|
name = "pkcs8"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -3447,7 +3598,7 @@ dependencies = [
|
|||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cookie",
|
"cookie",
|
||||||
"cookie_store",
|
"cookie_store 0.16.1",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -3482,17 +3633,28 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_cookie_store"
|
name = "reqwest_cookie_store"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0568e27f107b933735a07b3f8cb985ecfe3d3ce2f2225f82f10b3750f5981263"
|
checksum = "06b407c05de7a0f7e4cc2a56af5e9bd6468e509124e81078ce1f8bc2ed3536bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"cookie",
|
"cookie",
|
||||||
"cookie_store",
|
"cookie_store 0.19.0",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rfc6979"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-bigint",
|
||||||
|
"hmac 0.12.1",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.20"
|
version = "0.16.20"
|
||||||
@@ -3562,6 +3724,27 @@ dependencies = [
|
|||||||
"syn 1.0.103",
|
"syn 1.0.103",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rsa"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"digest 0.10.5",
|
||||||
|
"num-bigint-dig",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-traits",
|
||||||
|
"pkcs1",
|
||||||
|
"pkcs8",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"signature",
|
||||||
|
"smallvec",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-argon2"
|
name = "rust-argon2"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -3701,6 +3884,20 @@ dependencies = [
|
|||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sec1"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
|
||||||
|
dependencies = [
|
||||||
|
"base16ct",
|
||||||
|
"der",
|
||||||
|
"generic-array",
|
||||||
|
"pkcs8",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
@@ -3987,6 +4184,10 @@ name = "signature"
|
|||||||
version = "1.6.4"
|
version = "1.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
|
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
|
||||||
|
dependencies = [
|
||||||
|
"digest 0.10.5",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simple-logging"
|
name = "simple-logging"
|
||||||
@@ -4180,6 +4381,35 @@ dependencies = [
|
|||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ssh-encoding"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19cfdc32e0199062113edf41f344fbf784b8205a94600233c84eb838f45191e1"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"pem-rfc7468",
|
||||||
|
"sha2 0.10.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ssh-key"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "288d8f5562af5a3be4bda308dd374b2c807b940ac370b5efa1c99311da91d9a1"
|
||||||
|
dependencies = [
|
||||||
|
"ed25519-dalek",
|
||||||
|
"p256",
|
||||||
|
"p384",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"rsa",
|
||||||
|
"sec1",
|
||||||
|
"sha2 0.10.6",
|
||||||
|
"signature",
|
||||||
|
"ssh-encoding",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ bytes = "1"
|
|||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
clap = "3.2.8"
|
clap = "3.2.8"
|
||||||
color-eyre = "0.6.1"
|
color-eyre = "0.6.1"
|
||||||
cookie_store = "0.16.1"
|
cookie = "0.16.2"
|
||||||
|
cookie_store = "0.19.0"
|
||||||
current_platform = "0.2.0"
|
current_platform = "0.2.0"
|
||||||
digest = "0.10.3"
|
digest = "0.10.3"
|
||||||
digest-old = { package = "digest", version = "0.9.0" }
|
digest-old = { package = "digest", version = "0.9.0" }
|
||||||
@@ -113,7 +114,7 @@ rand = { version = "0.8.5", features = ["std"] }
|
|||||||
rand-old = { package = "rand", version = "0.7.3" }
|
rand-old = { package = "rand", version = "0.7.3" }
|
||||||
regex = "1.6.0"
|
regex = "1.6.0"
|
||||||
reqwest = { version = "0.11.11", features = ["stream", "json", "socks"] }
|
reqwest = { version = "0.11.11", features = ["stream", "json", "socks"] }
|
||||||
reqwest_cookie_store = "0.4.0"
|
reqwest_cookie_store = "0.5.0"
|
||||||
rpassword = "7.0.0"
|
rpassword = "7.0.0"
|
||||||
rpc-toolkit = "0.2.2"
|
rpc-toolkit = "0.2.2"
|
||||||
rust-argon2 = "1.0.0"
|
rust-argon2 = "1.0.0"
|
||||||
@@ -133,6 +134,7 @@ sqlx = { version = "0.6.0", features = [
|
|||||||
"runtime-tokio-rustls",
|
"runtime-tokio-rustls",
|
||||||
"postgres",
|
"postgres",
|
||||||
] }
|
] }
|
||||||
|
ssh-key = { version = "0.5.1", features = ["ed25519"] }
|
||||||
stderrlog = "0.5.3"
|
stderrlog = "0.5.3"
|
||||||
tar = "0.4.38"
|
tar = "0.4.38"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
|
|||||||
21
backend/migrations/20230109181507_AccountSshKey.sql
Normal file
21
backend/migrations/20230109181507_AccountSshKey.sql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
CREATE EXTENSION pgcrypto;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
account
|
||||||
|
ADD
|
||||||
|
COLUMN ssh_key BYTEA CHECK (length(ssh_key) = 32);
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
account
|
||||||
|
SET
|
||||||
|
ssh_key = gen_random_bytes(32)
|
||||||
|
WHERE
|
||||||
|
id = 0;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
account
|
||||||
|
ALTER COLUMN
|
||||||
|
ssh_key
|
||||||
|
SET
|
||||||
|
NOT NULL;
|
||||||
@@ -102,6 +102,21 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT hostname, path, username, password FROM cifs_shares WHERE id = $1"
|
"query": "SELECT hostname, path, username, password FROM cifs_shares WHERE id = $1"
|
||||||
},
|
},
|
||||||
|
"2f615764532e975c964f1d0e063a02110d781644b0eaae1ff85a7d6ed903bfe5": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int4",
|
||||||
|
"Text",
|
||||||
|
"Bytea",
|
||||||
|
"Bytea"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO account (id, password, tor_key, ssh_key) VALUES ($1, $2, $3, $4) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3, ssh_key = $4"
|
||||||
|
},
|
||||||
"3502e58f2ab48fb4566d21c920c096f81acfa3ff0d02f970626a4dcd67bac71d": {
|
"3502e58f2ab48fb4566d21c920c096f81acfa3ff0d02f970626a4dcd67bac71d": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -256,6 +271,24 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT setval('certificates_id_seq', GREATEST(MAX(id) + 1, nextval('certificates_id_seq') - 1)) FROM certificates"
|
"query": "SELECT setval('certificates_id_seq', GREATEST(MAX(id) + 1, nextval('certificates_id_seq') - 1)) FROM certificates"
|
||||||
},
|
},
|
||||||
|
"5c0ea94081695dba827e525ecc0c555757b43ea513c2c93f9c7f7f8c174d36bf": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "ssh_key",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Bytea"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT ssh_key FROM account"
|
||||||
|
},
|
||||||
"629be61c3c341c131ddbbff0293a83dbc6afd07cae69d246987f62cf0cc35c2a": {
|
"629be61c3c341c131ddbbff0293a83dbc6afd07cae69d246987f62cf0cc35c2a": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -536,33 +569,6 @@
|
|||||||
},
|
},
|
||||||
"query": "DELETE FROM cifs_shares WHERE id = $1"
|
"query": "DELETE FROM cifs_shares WHERE id = $1"
|
||||||
},
|
},
|
||||||
"a645d636be810a4ba61dcadf22e90de6e9baf3614aa9e97f053ff480cb3118a2": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text",
|
|
||||||
"Bytea"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "INSERT INTO tor (package, interface, key) VALUES ($1, 'main', $2) ON CONFLICT (package, interface) DO UPDATE SET key = $2"
|
|
||||||
},
|
|
||||||
"a6645d91f76b3d5fac2191ea3bec5dab7d7d124715fde02e6a816fa5dbc7acf2": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int4",
|
|
||||||
"Text",
|
|
||||||
"Bytea"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "INSERT INTO account (id, password, tor_key) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3"
|
|
||||||
},
|
|
||||||
"a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e": {
|
"a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ use helpers::AtomicFile;
|
|||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use patch_db::{DbHandle, LockType, PatchDbHandle};
|
use patch_db::{DbHandle, LockType, PatchDbHandle};
|
||||||
|
use rand::random;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use ssh_key::private::Ed25519PrivateKey;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use torut::onion::TorSecretKeyV3;
|
use torut::onion::TorSecretKeyV3;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
@@ -35,6 +37,7 @@ use crate::{Error, ErrorKind, ResultExt};
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct OsBackup {
|
pub struct OsBackup {
|
||||||
pub tor_key: TorSecretKeyV3,
|
pub tor_key: TorSecretKeyV3,
|
||||||
|
pub ssh_key: Ed25519PrivateKey,
|
||||||
pub root_ca_key: PKey<Private>,
|
pub root_ca_key: PKey<Private>,
|
||||||
pub root_ca_cert: X509,
|
pub root_ca_cert: X509,
|
||||||
pub ui: Value,
|
pub ui: Value,
|
||||||
@@ -48,32 +51,52 @@ impl<'de> Deserialize<'de> for OsBackup {
|
|||||||
#[serde(rename = "kebab-case")]
|
#[serde(rename = "kebab-case")]
|
||||||
struct OsBackupDe {
|
struct OsBackupDe {
|
||||||
tor_key: String,
|
tor_key: String,
|
||||||
|
ssh_key: Option<String>,
|
||||||
root_ca_key: String,
|
root_ca_key: String,
|
||||||
root_ca_cert: String,
|
root_ca_cert: String,
|
||||||
ui: Value,
|
ui: Value,
|
||||||
}
|
}
|
||||||
let int = OsBackupDe::deserialize(deserializer)?;
|
fn vec_from_base32<E: serde::de::Error>(base32: &str, len: usize) -> Result<Vec<u8>, E> {
|
||||||
let key_vec = base32::decode(base32::Alphabet::RFC4648 { padding: true }, &int.tor_key)
|
let key_vec = base32::decode(base32::Alphabet::RFC4648 { padding: true }, base32)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
serde::de::Error::invalid_value(
|
serde::de::Error::invalid_value(
|
||||||
serde::de::Unexpected::Str(&int.tor_key),
|
serde::de::Unexpected::Str(base32),
|
||||||
&"an RFC4648 encoded string",
|
&"an RFC4648 encoded string",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if key_vec.len() != 64 {
|
if key_vec.len() != 64 {
|
||||||
return Err(serde::de::Error::invalid_value(
|
return Err(serde::de::Error::invalid_value(
|
||||||
serde::de::Unexpected::Str(&int.tor_key),
|
serde::de::Unexpected::Str(base32),
|
||||||
&"a 64 byte value encoded as an RFC4648 string",
|
&"a 64 byte value encoded as an RFC4648 string",
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
Ok(key_vec)
|
||||||
}
|
}
|
||||||
let mut key_slice = [0; 64];
|
let int = OsBackupDe::deserialize(deserializer)?;
|
||||||
key_slice.clone_from_slice(&key_vec);
|
let tor_key = {
|
||||||
|
let mut key_slice = [0; 64];
|
||||||
|
key_slice.clone_from_slice(&vec_from_base32(&int.tor_key, 64)?);
|
||||||
|
TorSecretKeyV3::from(key_slice)
|
||||||
|
};
|
||||||
|
let ssh_key = int
|
||||||
|
.ssh_key
|
||||||
|
.as_ref()
|
||||||
|
.map(|ssh_key| {
|
||||||
|
let mut key_slice = [0; 32];
|
||||||
|
key_slice.clone_from_slice(&vec_from_base32(ssh_key, 32)?);
|
||||||
|
Ok(Ed25519PrivateKey::from_bytes(&key_slice))
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or_else(|| Ed25519PrivateKey::from_bytes(&random()));
|
||||||
|
let root_ca_key = PKey::<Private>::private_key_from_pem(int.root_ca_key.as_bytes())
|
||||||
|
.map_err(serde::de::Error::custom)?;
|
||||||
|
let root_ca_cert =
|
||||||
|
X509::from_pem(int.root_ca_cert.as_bytes()).map_err(serde::de::Error::custom)?;
|
||||||
Ok(OsBackup {
|
Ok(OsBackup {
|
||||||
tor_key: TorSecretKeyV3::from(key_slice),
|
tor_key,
|
||||||
root_ca_key: PKey::<Private>::private_key_from_pem(int.root_ca_key.as_bytes())
|
ssh_key,
|
||||||
.map_err(serde::de::Error::custom)?,
|
root_ca_key,
|
||||||
root_ca_cert: X509::from_pem(int.root_ca_cert.as_bytes())
|
root_ca_cert,
|
||||||
.map_err(serde::de::Error::custom)?,
|
|
||||||
ui: int.ui,
|
ui: int.ui,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -437,6 +460,7 @@ async fn perform_backup<Db: DbHandle>(
|
|||||||
.write_all(
|
.write_all(
|
||||||
&IoFormat::Cbor.to_vec(&OsBackup {
|
&IoFormat::Cbor.to_vec(&OsBackup {
|
||||||
tor_key: ctx.net_controller.tor.embassyd_tor_key().await,
|
tor_key: ctx.net_controller.tor.embassyd_tor_key().await,
|
||||||
|
ssh_key: crate::ssh::os_key(&mut ctx.secret_store.acquire().await?).await?,
|
||||||
root_ca_key,
|
root_ca_key,
|
||||||
root_ca_cert,
|
root_ca_cert,
|
||||||
ui: crate::db::DatabaseModel::new()
|
ui: crate::db::DatabaseModel::new()
|
||||||
|
|||||||
@@ -198,13 +198,15 @@ pub async fn recover_full_embassy(
|
|||||||
&argon2::Config::default(),
|
&argon2::Config::default(),
|
||||||
)
|
)
|
||||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
|
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
|
||||||
let key_vec = os_backup.tor_key.as_bytes().to_vec();
|
let tor_key_bytes = os_backup.tor_key.as_bytes().to_vec();
|
||||||
|
let ssh_key_bytes = os_backup.ssh_key.to_bytes().to_vec();
|
||||||
let secret_store = ctx.secret_store().await?;
|
let secret_store = ctx.secret_store().await?;
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO account (id, password, tor_key) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3",
|
"INSERT INTO account (id, password, tor_key, ssh_key) VALUES ($1, $2, $3, $4) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3, ssh_key = $4",
|
||||||
0,
|
0,
|
||||||
password,
|
password,
|
||||||
key_vec,
|
tor_key_bytes,
|
||||||
|
ssh_key_bytes,
|
||||||
)
|
)
|
||||||
.execute(&mut secret_store.acquire().await?)
|
.execute(&mut secret_store.acquire().await?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
|
use cookie::Cookie;
|
||||||
use cookie_store::CookieStore;
|
use cookie_store::CookieStore;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use reqwest::Proxy;
|
use reqwest::Proxy;
|
||||||
@@ -16,6 +17,7 @@ use rpc_toolkit::Context;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||||
use crate::util::config::{load_config_from_paths, local_config_path};
|
use crate::util::config::{load_config_from_paths, local_config_path};
|
||||||
use crate::ResultExt;
|
use crate::ResultExt;
|
||||||
|
|
||||||
@@ -83,7 +85,7 @@ impl CliContext {
|
|||||||
} else if let Some(host) = base.host {
|
} else if let Some(host) = base.host {
|
||||||
host
|
host
|
||||||
} else {
|
} else {
|
||||||
format!("http://localhost").parse()?
|
"http://localhost".parse()?
|
||||||
};
|
};
|
||||||
let proxy = if let Some(proxy) = matches.value_of("proxy") {
|
let proxy = if let Some(proxy) = matches.value_of("proxy") {
|
||||||
Some(proxy.parse()?)
|
Some(proxy.parse()?)
|
||||||
@@ -100,9 +102,15 @@ impl CliContext {
|
|||||||
.join(".cookies.json")
|
.join(".cookies.json")
|
||||||
});
|
});
|
||||||
let cookie_store = Arc::new(CookieStoreMutex::new(if cookie_path.exists() {
|
let cookie_store = Arc::new(CookieStoreMutex::new(if cookie_path.exists() {
|
||||||
CookieStore::load_json(BufReader::new(File::open(&cookie_path)?))
|
let mut store = CookieStore::load_json(BufReader::new(File::open(&cookie_path)?))
|
||||||
.map_err(|e| eyre!("{}", e))
|
.map_err(|e| eyre!("{}", e))
|
||||||
.with_kind(crate::ErrorKind::Deserialization)?
|
.with_kind(crate::ErrorKind::Deserialization)?;
|
||||||
|
if let Ok(local) = std::fs::read_to_string(LOCAL_AUTH_COOKIE_PATH) {
|
||||||
|
store
|
||||||
|
.insert_raw(&Cookie::new("local", local), &"http://localhost".parse()?)
|
||||||
|
.with_kind(crate::ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
store
|
||||||
} else {
|
} else {
|
||||||
CookieStore::default()
|
CookieStore::default()
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use serde::Deserialize;
|
|||||||
use tokio::sync::broadcast::Sender;
|
use tokio::sync::broadcast::Sender;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::os_install::find_eth_iface;
|
use crate::net::net_utils::find_eth_iface;
|
||||||
use crate::util::config::load_config_from_paths;
|
use crate::util::config::load_config_from_paths;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use crate::install::cleanup::{cleanup_failed, uninstall, CleanupFailedReceipts};
|
|||||||
use crate::manager::ManagerMap;
|
use crate::manager::ManagerMap;
|
||||||
use crate::middleware::auth::HashSessionToken;
|
use crate::middleware::auth::HashSessionToken;
|
||||||
use crate::net::net_controller::NetController;
|
use crate::net::net_controller::NetController;
|
||||||
use crate::net::tor::os_key;
|
use crate::net::ssl::SslManager;
|
||||||
use crate::net::wifi::WpaCli;
|
use crate::net::wifi::WpaCli;
|
||||||
use crate::notifications::NotificationManager;
|
use crate::notifications::NotificationManager;
|
||||||
use crate::setup::password_hash;
|
use crate::setup::password_hash;
|
||||||
@@ -85,8 +85,14 @@ impl RpcContextConfig {
|
|||||||
db.put(
|
db.put(
|
||||||
&<JsonPointer>::default(),
|
&<JsonPointer>::default(),
|
||||||
&Database::init(
|
&Database::init(
|
||||||
&os_key(&mut secret_store.acquire().await?).await?,
|
&crate::net::tor::os_key(&mut secret_store.acquire().await?).await?,
|
||||||
password_hash(&mut secret_store.acquire().await?).await?,
|
password_hash(&mut secret_store.acquire().await?).await?,
|
||||||
|
&crate::ssh::os_key(&mut secret_store.acquire().await?).await?,
|
||||||
|
&SslManager::init(secret_store.clone(), &mut db.handle())
|
||||||
|
.await?
|
||||||
|
.export_root_ca()
|
||||||
|
.await?
|
||||||
|
.1,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use tracing::instrument;
|
|||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::init::{init_postgres, pgloader};
|
use crate::init::{init_postgres, pgloader};
|
||||||
use crate::net::tor::os_key;
|
use crate::net::ssl::SslManager;
|
||||||
use crate::setup::{password_hash, SetupStatus};
|
use crate::setup::{password_hash, SetupStatus};
|
||||||
use crate::util::config::load_config_from_paths;
|
use crate::util::config::load_config_from_paths;
|
||||||
use crate::{Error, ResultExt};
|
use crate::{Error, ResultExt};
|
||||||
@@ -120,8 +120,14 @@ impl SetupContext {
|
|||||||
db.put(
|
db.put(
|
||||||
&<JsonPointer>::default(),
|
&<JsonPointer>::default(),
|
||||||
&Database::init(
|
&Database::init(
|
||||||
&os_key(&mut secret_store.acquire().await?).await?,
|
&crate::net::tor::os_key(&mut secret_store.acquire().await?).await?,
|
||||||
password_hash(&mut secret_store.acquire().await?).await?,
|
password_hash(&mut secret_store.acquire().await?).await?,
|
||||||
|
&crate::ssh::os_key(&mut secret_store.acquire().await?).await?,
|
||||||
|
&SslManager::init(secret_store.clone(), &mut db.handle())
|
||||||
|
.await?
|
||||||
|
.export_root_ca()
|
||||||
|
.await?
|
||||||
|
.1,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ pub async fn subscribe(ctx: RpcContext, req: Request<Body>) -> Result<Response<B
|
|||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
let session = match async {
|
let session = match async {
|
||||||
let token = HashSessionToken::from_request_parts(&parts)?;
|
let token = HashSessionToken::from_request_parts(&parts)?;
|
||||||
let session = HasValidSession::from_session(&token, &ctx).await?;
|
let session = HasValidSession::from_request_parts(&parts, &ctx).await?;
|
||||||
Ok::<_, Error>((session, token))
|
Ok::<_, Error>((session, token))
|
||||||
}
|
}
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,25 +1,33 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use emver::VersionRange;
|
use emver::VersionRange;
|
||||||
use isocountry::CountryCode;
|
use isocountry::CountryCode;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use openssl::hash::MessageDigest;
|
||||||
|
use openssl::x509::X509;
|
||||||
use patch_db::json_ptr::JsonPointer;
|
use patch_db::json_ptr::JsonPointer;
|
||||||
use patch_db::{HasModel, Map, MapModel, OptionModel};
|
use patch_db::{HasModel, Map, MapModel, OptionModel};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use ssh_key::private::Ed25519PrivateKey;
|
||||||
|
use ssh_key::public::Ed25519PublicKey;
|
||||||
use torut::onion::TorSecretKeyV3;
|
use torut::onion::TorSecretKeyV3;
|
||||||
|
|
||||||
use crate::config::spec::{PackagePointerSpec, SystemPointerSpec};
|
use crate::config::spec::{PackagePointerSpec, SystemPointerSpec};
|
||||||
use crate::hostname::{generate_hostname, generate_id};
|
use crate::hostname::{generate_hostname, generate_id};
|
||||||
use crate::install::progress::InstallProgress;
|
use crate::install::progress::InstallProgress;
|
||||||
use crate::net::interface::InterfaceId;
|
use crate::net::interface::InterfaceId;
|
||||||
|
use crate::net::net_utils::{get_iface_ipv4_addr, get_iface_ipv6_addr};
|
||||||
use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId};
|
use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId};
|
||||||
use crate::status::health_check::HealthCheckId;
|
use crate::status::health_check::HealthCheckId;
|
||||||
use crate::status::Status;
|
use crate::status::Status;
|
||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
use crate::version::{Current, VersionT};
|
use crate::version::{Current, VersionT};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
@@ -31,7 +39,12 @@ pub struct Database {
|
|||||||
pub ui: Value,
|
pub ui: Value,
|
||||||
}
|
}
|
||||||
impl Database {
|
impl Database {
|
||||||
pub fn init(tor_key: &TorSecretKeyV3, password_hash: String) -> Self {
|
pub fn init(
|
||||||
|
tor_key: &TorSecretKeyV3,
|
||||||
|
password_hash: String,
|
||||||
|
ssh_key: &Ed25519PrivateKey,
|
||||||
|
cert: &X509,
|
||||||
|
) -> Self {
|
||||||
let id = generate_id();
|
let id = generate_id();
|
||||||
let my_hostname = generate_hostname();
|
let my_hostname = generate_hostname();
|
||||||
let lan_address = my_hostname.lan_address().parse().unwrap();
|
let lan_address = my_hostname.lan_address().parse().unwrap();
|
||||||
@@ -48,6 +61,7 @@ impl Database {
|
|||||||
tor_address: format!("http://{}", tor_key.public().get_onion_address())
|
tor_address: format!("http://{}", tor_key.public().get_onion_address())
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
ip_info: BTreeMap::new(),
|
||||||
status_info: ServerStatus {
|
status_info: ServerStatus {
|
||||||
backup_progress: None,
|
backup_progress: None,
|
||||||
updated: false,
|
updated: false,
|
||||||
@@ -64,6 +78,16 @@ impl Database {
|
|||||||
clearnet: Vec::new(),
|
clearnet: Vec::new(),
|
||||||
},
|
},
|
||||||
password_hash,
|
password_hash,
|
||||||
|
pubkey: ssh_key::PublicKey::from(Ed25519PublicKey::from(ssh_key))
|
||||||
|
.to_openssh()
|
||||||
|
.unwrap(),
|
||||||
|
ca_fingerprint: cert
|
||||||
|
.digest(MessageDigest::sha256())
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|x| format!("{x:X}"))
|
||||||
|
.join(":"),
|
||||||
|
system_start_time: Utc::now().to_rfc3339(),
|
||||||
},
|
},
|
||||||
package_data: AllPackageData::default(),
|
package_data: AllPackageData::default(),
|
||||||
ui: serde_json::from_str(include_str!("../../../frontend/patchdb-ui-seed.json"))
|
ui: serde_json::from_str(include_str!("../../../frontend/patchdb-ui-seed.json"))
|
||||||
@@ -90,12 +114,32 @@ pub struct ServerInfo {
|
|||||||
pub lan_address: Url,
|
pub lan_address: Url,
|
||||||
pub tor_address: Url,
|
pub tor_address: Url,
|
||||||
#[model]
|
#[model]
|
||||||
|
pub ip_info: BTreeMap<String, IpInfo>,
|
||||||
|
#[model]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub status_info: ServerStatus,
|
pub status_info: ServerStatus,
|
||||||
pub wifi: WifiInfo,
|
pub wifi: WifiInfo,
|
||||||
pub unread_notification_count: u64,
|
pub unread_notification_count: u64,
|
||||||
pub connection_addresses: ConnectionAddresses,
|
pub connection_addresses: ConnectionAddresses,
|
||||||
pub password_hash: String,
|
pub password_hash: String,
|
||||||
|
pub pubkey: String,
|
||||||
|
pub ca_fingerprint: String,
|
||||||
|
pub system_start_time: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct IpInfo {
|
||||||
|
ipv4: Option<Ipv4Addr>,
|
||||||
|
ipv6: Option<Ipv6Addr>,
|
||||||
|
}
|
||||||
|
impl IpInfo {
|
||||||
|
pub async fn for_interface(iface: &str) -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
ipv4: get_iface_ipv4_addr(iface).await?,
|
||||||
|
ipv6: get_iface_ipv6_addr(iface).await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
use std::fs::Permissions;
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
|
use models::ResultExt;
|
||||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||||
|
use rand::random;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::context::rpc::RpcContextConfig;
|
use crate::context::rpc::RpcContextConfig;
|
||||||
use crate::db::model::ServerStatus;
|
use crate::db::model::{IpInfo, ServerStatus};
|
||||||
use crate::install::PKG_ARCHIVE_DIR;
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
|
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||||
use crate::sound::BEP;
|
use crate::sound::BEP;
|
||||||
|
use crate::system::time;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
@@ -37,6 +43,8 @@ pub struct InitReceipts {
|
|||||||
pub version_range: LockReceipt<emver::VersionRange, ()>,
|
pub version_range: LockReceipt<emver::VersionRange, ()>,
|
||||||
pub last_wifi_region: LockReceipt<Option<isocountry::CountryCode>, ()>,
|
pub last_wifi_region: LockReceipt<Option<isocountry::CountryCode>, ()>,
|
||||||
pub status_info: LockReceipt<ServerStatus, ()>,
|
pub status_info: LockReceipt<ServerStatus, ()>,
|
||||||
|
pub ip_info: LockReceipt<BTreeMap<String, IpInfo>, ()>,
|
||||||
|
pub system_start_time: LockReceipt<String, ()>,
|
||||||
}
|
}
|
||||||
impl InitReceipts {
|
impl InitReceipts {
|
||||||
pub async fn new(db: &mut impl DbHandle) -> Result<Self, Error> {
|
pub async fn new(db: &mut impl DbHandle) -> Result<Self, Error> {
|
||||||
@@ -57,19 +65,31 @@ impl InitReceipts {
|
|||||||
.last_wifi_region()
|
.last_wifi_region()
|
||||||
.make_locker(LockType::Write)
|
.make_locker(LockType::Write)
|
||||||
.add_to_keys(&mut locks);
|
.add_to_keys(&mut locks);
|
||||||
|
let ip_info = crate::db::DatabaseModel::new()
|
||||||
|
.server_info()
|
||||||
|
.ip_info()
|
||||||
|
.make_locker(LockType::Write)
|
||||||
|
.add_to_keys(&mut locks);
|
||||||
let status_info = crate::db::DatabaseModel::new()
|
let status_info = crate::db::DatabaseModel::new()
|
||||||
.server_info()
|
.server_info()
|
||||||
.status_info()
|
.status_info()
|
||||||
.into_model()
|
.into_model()
|
||||||
.make_locker(LockType::Write)
|
.make_locker(LockType::Write)
|
||||||
.add_to_keys(&mut locks);
|
.add_to_keys(&mut locks);
|
||||||
|
let system_start_time = crate::db::DatabaseModel::new()
|
||||||
|
.server_info()
|
||||||
|
.system_start_time()
|
||||||
|
.make_locker(LockType::Write)
|
||||||
|
.add_to_keys(&mut locks);
|
||||||
|
|
||||||
let skeleton_key = db.lock_all(locks).await?;
|
let skeleton_key = db.lock_all(locks).await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
server_version: server_version.verify(&skeleton_key)?,
|
server_version: server_version.verify(&skeleton_key)?,
|
||||||
version_range: version_range.verify(&skeleton_key)?,
|
version_range: version_range.verify(&skeleton_key)?,
|
||||||
|
ip_info: ip_info.verify(&skeleton_key)?,
|
||||||
status_info: status_info.verify(&skeleton_key)?,
|
status_info: status_info.verify(&skeleton_key)?,
|
||||||
last_wifi_region: last_wifi_region.verify(&skeleton_key)?,
|
last_wifi_region: last_wifi_region.verify(&skeleton_key)?,
|
||||||
|
system_start_time: system_start_time.verify(&skeleton_key)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,6 +216,24 @@ pub struct InitResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||||
|
tokio::fs::create_dir_all("/run/embassy")
|
||||||
|
.await
|
||||||
|
.with_ctx(|_| (crate::ErrorKind::Filesystem, "mkdir -p /run/embassy"))?;
|
||||||
|
if tokio::fs::metadata(LOCAL_AUTH_COOKIE_PATH).await.is_err() {
|
||||||
|
tokio::fs::write(
|
||||||
|
LOCAL_AUTH_COOKIE_PATH,
|
||||||
|
base64::encode(random::<[u8; 32]>()).as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_ctx(|_| {
|
||||||
|
(
|
||||||
|
crate::ErrorKind::Filesystem,
|
||||||
|
format!("write {}", LOCAL_AUTH_COOKIE_PATH),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
tokio::fs::set_permissions(LOCAL_AUTH_COOKIE_PATH, Permissions::from_mode(046)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
let secret_store = cfg.secret_store().await?;
|
let secret_store = cfg.secret_store().await?;
|
||||||
tracing::info!("Opened Postgres");
|
tracing::info!("Opened Postgres");
|
||||||
|
|
||||||
@@ -334,6 +372,10 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
.await?;
|
.await?;
|
||||||
tracing::info!("Enabled Docker QEMU Emulation");
|
tracing::info!("Enabled Docker QEMU Emulation");
|
||||||
|
|
||||||
|
receipts
|
||||||
|
.ip_info
|
||||||
|
.set(&mut handle, crate::net::dhcp::init_ips().await?)
|
||||||
|
.await?;
|
||||||
receipts
|
receipts
|
||||||
.status_info
|
.status_info
|
||||||
.set(
|
.set(
|
||||||
@@ -345,20 +387,10 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
receipts
|
||||||
let mut warn_time_not_synced = true;
|
.system_start_time
|
||||||
for _ in 0..60 {
|
.set(&mut handle, time().await?)
|
||||||
if check_time_is_synchronized().await? {
|
.await?;
|
||||||
warn_time_not_synced = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
if warn_time_not_synced {
|
|
||||||
tracing::warn!("Timed out waiting for system time to synchronize");
|
|
||||||
} else {
|
|
||||||
tracing::info!("Syncronized system clock");
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::version::init(&mut handle, &receipts).await?;
|
crate::version::init(&mut handle, &receipts).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ pub fn main_api() -> Result<(), RpcError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[command(subcommands(
|
#[command(subcommands(
|
||||||
|
system::time,
|
||||||
system::logs,
|
system::logs,
|
||||||
system::kernel_logs,
|
system::kernel_logs,
|
||||||
system::metrics,
|
system::metrics,
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ use tokio::sync::Mutex;
|
|||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::{Error, ResultExt};
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
|
pub const LOCAL_AUTH_COOKIE_PATH: &str = "/run/embassy/rpc.authcookie";
|
||||||
|
|
||||||
pub trait AsLogoutSessionId {
|
pub trait AsLogoutSessionId {
|
||||||
fn as_logout_session_id(self) -> String;
|
fn as_logout_session_id(self) -> String;
|
||||||
}
|
}
|
||||||
@@ -63,7 +66,29 @@ impl HasValidSession {
|
|||||||
request_parts: &RequestParts,
|
request_parts: &RequestParts,
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
Self::from_session(&HashSessionToken::from_request_parts(request_parts)?, ctx).await
|
if let Some(cookie_header) = request_parts.headers.get(COOKIE) {
|
||||||
|
let cookies = Cookie::parse(
|
||||||
|
cookie_header
|
||||||
|
.to_str()
|
||||||
|
.with_kind(crate::ErrorKind::Authorization)?,
|
||||||
|
)
|
||||||
|
.with_kind(crate::ErrorKind::Authorization)?;
|
||||||
|
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") {
|
||||||
|
if let Ok(s) = Self::from_local(cookie).await {
|
||||||
|
return Ok(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "session") {
|
||||||
|
if let Ok(s) = Self::from_session(&HashSessionToken::from_cookie(cookie), ctx).await
|
||||||
|
{
|
||||||
|
return Ok(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("UNAUTHORIZED"),
|
||||||
|
crate::ErrorKind::Authorization,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_session(session: &HashSessionToken, ctx: &RpcContext) -> Result<Self, Error> {
|
pub async fn from_session(session: &HashSessionToken, ctx: &RpcContext) -> Result<Self, Error> {
|
||||||
@@ -79,6 +104,18 @@ impl HasValidSession {
|
|||||||
}
|
}
|
||||||
Ok(Self(()))
|
Ok(Self(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn from_local(local: &Cookie<'_>) -> Result<Self, Error> {
|
||||||
|
let token = tokio::fs::read_to_string("/run/embassy/rpc.authcookie").await?;
|
||||||
|
if local.get_value() == &*token {
|
||||||
|
Ok(Self(()))
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("UNAUTHORIZED"),
|
||||||
|
crate::ErrorKind::Authorization,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When we have a need to create a new session,
|
/// When we have a need to create a new session,
|
||||||
|
|||||||
43
backend/src/net/dhcp.rs
Normal file
43
backend/src/net/dhcp.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
use rpc_toolkit::command;
|
||||||
|
|
||||||
|
use crate::context::RpcContext;
|
||||||
|
use crate::db::model::IpInfo;
|
||||||
|
use crate::net::net_utils::{iface_is_physical, list_interfaces};
|
||||||
|
use crate::util::display_none;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
pub async fn init_ips() -> Result<BTreeMap<String, IpInfo>, Error> {
|
||||||
|
let mut res = BTreeMap::new();
|
||||||
|
let mut ifaces = list_interfaces();
|
||||||
|
while let Some(iface) = ifaces.try_next().await? {
|
||||||
|
if iface_is_physical(&iface).await {
|
||||||
|
let ip_info = IpInfo::for_interface(&iface).await?;
|
||||||
|
res.insert(iface, ip_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(subcommands(update))]
|
||||||
|
pub async fn dhcp() -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(display(display_none))]
|
||||||
|
pub async fn update(#[context] ctx: RpcContext, #[arg] interface: String) -> Result<(), Error> {
|
||||||
|
if iface_is_physical(&interface).await {
|
||||||
|
crate::db::DatabaseModel::new()
|
||||||
|
.server_info()
|
||||||
|
.ip_info()
|
||||||
|
.idx_model(&interface)
|
||||||
|
.put(
|
||||||
|
&mut ctx.db.handle(),
|
||||||
|
&IpInfo::for_interface(&interface).await?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ use crate::util::serde::Port;
|
|||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
pub mod cert_resolver;
|
pub mod cert_resolver;
|
||||||
|
pub mod dhcp;
|
||||||
pub mod dns;
|
pub mod dns;
|
||||||
pub mod embassy_service_http_server;
|
pub mod embassy_service_http_server;
|
||||||
pub mod interface;
|
pub mod interface;
|
||||||
@@ -28,7 +29,7 @@ pub mod wifi;
|
|||||||
|
|
||||||
const PACKAGE_CERT_PATH: &str = "/var/lib/embassy/ssl";
|
const PACKAGE_CERT_PATH: &str = "/var/lib/embassy/ssl";
|
||||||
|
|
||||||
#[command(subcommands(tor::tor))]
|
#[command(subcommands(tor::tor, dhcp::dhcp))]
|
||||||
pub fn net() -> Result<(), Error> {
|
pub fn net() -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,115 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::net::IpAddr;
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||||
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use async_stream::try_stream;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
|
use futures::stream::BoxStream;
|
||||||
|
use futures::{StreamExt, TryStreamExt};
|
||||||
use http::{Request, Uri};
|
use http::{Request, Uri};
|
||||||
use hyper::Body;
|
use hyper::Body;
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::util::Invoke;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
|
fn parse_iface_ip(output: &str) -> Result<Option<&str>, Error> {
|
||||||
|
let output = output.trim();
|
||||||
|
if output.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if let Some(ip) = output
|
||||||
|
.split_ascii_whitespace()
|
||||||
|
.nth(3)
|
||||||
|
.and_then(|range| range.split("/").next())
|
||||||
|
{
|
||||||
|
Ok(Some(ip))
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("malformed output from `ip`"),
|
||||||
|
crate::ErrorKind::Network,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_iface_ipv4_addr(iface: &str) -> Result<Option<Ipv4Addr>, Error> {
|
||||||
|
Ok(parse_iface_ip(&String::from_utf8(
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("-4")
|
||||||
|
.arg("-o")
|
||||||
|
.arg("addr")
|
||||||
|
.arg("show")
|
||||||
|
.arg(iface)
|
||||||
|
.invoke(crate::ErrorKind::Network)
|
||||||
|
.await?,
|
||||||
|
)?)?
|
||||||
|
.map(|s| s.parse())
|
||||||
|
.transpose()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_iface_ipv6_addr(iface: &str) -> Result<Option<Ipv6Addr>, Error> {
|
||||||
|
Ok(parse_iface_ip(&String::from_utf8(
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("-6")
|
||||||
|
.arg("-o")
|
||||||
|
.arg("addr")
|
||||||
|
.arg("show")
|
||||||
|
.arg(iface)
|
||||||
|
.invoke(crate::ErrorKind::Network)
|
||||||
|
.await?,
|
||||||
|
)?)?
|
||||||
|
.map(|s| s.parse())
|
||||||
|
.transpose()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn iface_is_physical(iface: &str) -> bool {
|
||||||
|
tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("device"))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn iface_is_wireless(iface: &str) -> bool {
|
||||||
|
tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("wireless"))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_interfaces() -> BoxStream<'static, Result<String, Error>> {
|
||||||
|
try_stream! {
|
||||||
|
let mut ifaces = tokio::fs::read_dir("/sys/class/net").await?;
|
||||||
|
while let Some(iface) = ifaces.next_entry().await? {
|
||||||
|
if let Some(iface) = iface.file_name().into_string().ok() {
|
||||||
|
yield iface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_wifi_iface() -> Result<Option<String>, Error> {
|
||||||
|
let mut ifaces = list_interfaces();
|
||||||
|
while let Some(iface) = ifaces.try_next().await? {
|
||||||
|
if iface_is_wireless(&iface).await {
|
||||||
|
return Ok(Some(iface));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_eth_iface() -> Result<String, Error> {
|
||||||
|
let mut ifaces = list_interfaces();
|
||||||
|
while let Some(iface) = ifaces.try_next().await? {
|
||||||
|
if iface_is_physical(&iface).await && !iface_is_wireless(&iface).await {
|
||||||
|
return Ok(iface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("Could not detect ethernet interface"),
|
||||||
|
crate::ErrorKind::Network,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn host_addr_fqdn(req: &Request<Body>) -> Result<ResourceFqdn, Error> {
|
pub fn host_addr_fqdn(req: &Request<Body>) -> Result<ResourceFqdn, Error> {
|
||||||
let host = req.headers().get(http::header::HOST);
|
let host = req.headers().get(http::header::HOST);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ use crate::context::{DiagnosticContext, InstallContext, RpcContext, SetupContext
|
|||||||
use crate::core::rpc_continuations::RequestGuid;
|
use crate::core::rpc_continuations::RequestGuid;
|
||||||
use crate::db::subscribe;
|
use crate::db::subscribe;
|
||||||
use crate::install::PKG_PUBLIC_DIR;
|
use crate::install::PKG_PUBLIC_DIR;
|
||||||
use crate::middleware::auth::HasValidSession;
|
use crate::middleware::auth::{auth as auth_middleware, HasValidSession};
|
||||||
|
use crate::middleware::cors::cors;
|
||||||
|
use crate::middleware::db::db as db_middleware;
|
||||||
|
use crate::middleware::diagnostic::diagnostic as diagnostic_middleware;
|
||||||
use crate::net::HttpHandler;
|
use crate::net::HttpHandler;
|
||||||
use crate::{diagnostic_api, install_api, main_api, setup_api, Error, ErrorKind, ResultExt};
|
use crate::{diagnostic_api, install_api, main_api, setup_api, Error, ErrorKind, ResultExt};
|
||||||
|
|
||||||
@@ -48,8 +51,14 @@ pub async fn setup_ui_file_router(ctx: SetupContext) -> Result<HttpHandler, Erro
|
|||||||
async move {
|
async move {
|
||||||
let res = match req.uri().path() {
|
let res = match req.uri().path() {
|
||||||
path if path.starts_with("/rpc/") => {
|
path if path.starts_with("/rpc/") => {
|
||||||
let rpc_handler =
|
let rpc_handler = rpc_handler!({
|
||||||
rpc_handler!({command: setup_api, context: ctx, status: status_fn});
|
command: setup_api,
|
||||||
|
context: ctx,
|
||||||
|
status: status_fn,
|
||||||
|
middleware: [
|
||||||
|
cors,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
rpc_handler(req)
|
rpc_handler(req)
|
||||||
.await
|
.await
|
||||||
@@ -76,8 +85,15 @@ pub async fn diag_ui_file_router(ctx: DiagnosticContext) -> Result<HttpHandler,
|
|||||||
async move {
|
async move {
|
||||||
let res = match req.uri().path() {
|
let res = match req.uri().path() {
|
||||||
path if path.starts_with("/rpc/") => {
|
path if path.starts_with("/rpc/") => {
|
||||||
let rpc_handler =
|
let rpc_handler = rpc_handler!({
|
||||||
rpc_handler!({command: diagnostic_api, context: ctx, status: status_fn});
|
command: diagnostic_api,
|
||||||
|
context: ctx,
|
||||||
|
status: status_fn,
|
||||||
|
middleware: [
|
||||||
|
cors,
|
||||||
|
diagnostic_middleware,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
rpc_handler(req)
|
rpc_handler(req)
|
||||||
.await
|
.await
|
||||||
@@ -104,8 +120,14 @@ pub async fn install_ui_file_router(ctx: InstallContext) -> Result<HttpHandler,
|
|||||||
async move {
|
async move {
|
||||||
let res = match req.uri().path() {
|
let res = match req.uri().path() {
|
||||||
path if path.starts_with("/rpc/") => {
|
path if path.starts_with("/rpc/") => {
|
||||||
let rpc_handler =
|
let rpc_handler = rpc_handler!({
|
||||||
rpc_handler!({command: install_api, context: ctx, status: status_fn});
|
command: install_api,
|
||||||
|
context: ctx,
|
||||||
|
status: status_fn,
|
||||||
|
middleware: [
|
||||||
|
cors,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
rpc_handler(req)
|
rpc_handler(req)
|
||||||
.await
|
.await
|
||||||
@@ -132,8 +154,18 @@ pub async fn main_ui_server_router(ctx: RpcContext) -> Result<HttpHandler, Error
|
|||||||
async move {
|
async move {
|
||||||
let res = match req.uri().path() {
|
let res = match req.uri().path() {
|
||||||
path if path.starts_with("/rpc/") => {
|
path if path.starts_with("/rpc/") => {
|
||||||
let rpc_handler =
|
let auth_middleware = auth_middleware(ctx.clone());
|
||||||
rpc_handler!({command: main_api, context: ctx, status: status_fn});
|
let db_middleware = db_middleware(ctx.clone());
|
||||||
|
let rpc_handler = rpc_handler!({
|
||||||
|
command: main_api,
|
||||||
|
context: ctx,
|
||||||
|
status: status_fn,
|
||||||
|
middleware: [
|
||||||
|
cors,
|
||||||
|
auth_middleware,
|
||||||
|
db_middleware,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
rpc_handler(req)
|
rpc_handler(req)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use crate::disk::mount::filesystem::ReadWrite;
|
|||||||
use crate::disk::mount::guard::{MountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{MountGuard, TmpMountGuard};
|
||||||
use crate::disk::util::DiskInfo;
|
use crate::disk::util::DiskInfo;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
|
use crate::net::net_utils::{find_eth_iface, find_wifi_iface};
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
use crate::util::{display_none, Invoke};
|
use crate::util::{display_none, Invoke};
|
||||||
|
|
||||||
@@ -69,43 +70,6 @@ pub async fn list() -> Result<Vec<DiskInfo>, Error> {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_wifi_iface() -> Result<Option<String>, Error> {
|
|
||||||
let mut ifaces = tokio::fs::read_dir("/sys/class/net").await?;
|
|
||||||
while let Some(iface) = ifaces.next_entry().await? {
|
|
||||||
if tokio::fs::metadata(iface.path().join("wireless"))
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
if let Some(iface) = iface.file_name().into_string().ok() {
|
|
||||||
return Ok(Some(iface));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn find_eth_iface() -> Result<String, Error> {
|
|
||||||
let mut ifaces = tokio::fs::read_dir("/sys/class/net").await?;
|
|
||||||
while let Some(iface) = ifaces.next_entry().await? {
|
|
||||||
if tokio::fs::metadata(iface.path().join("wireless"))
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
&& tokio::fs::metadata(iface.path().join("device"))
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
if let Some(iface) = iface.file_name().into_string().ok() {
|
|
||||||
return Ok(iface);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::new(
|
|
||||||
eyre!("Could not detect ethernet interface"),
|
|
||||||
crate::ErrorKind::Network,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn partition_for(disk: impl AsRef<Path>, idx: usize) -> PathBuf {
|
pub fn partition_for(disk: impl AsRef<Path>, idx: usize) -> PathBuf {
|
||||||
let disk_path = disk.as_ref();
|
let disk_path = disk.as_ref();
|
||||||
let (root, leaf) = if let (Some(root), Some(leaf)) = (
|
let (root, leaf) = if let (Some(root), Some(leaf)) = (
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ use helpers::{Rsync, RsyncOptions};
|
|||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use patch_db::DbHandle;
|
use patch_db::DbHandle;
|
||||||
|
use rand::random;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{Connection, Executor, Postgres};
|
use sqlx::{Connection, Executor, Postgres};
|
||||||
|
use ssh_key::private::Ed25519PrivateKey;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
||||||
@@ -390,13 +392,16 @@ async fn fresh_setup(
|
|||||||
)
|
)
|
||||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
|
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
|
||||||
let tor_key = TorSecretKeyV3::generate();
|
let tor_key = TorSecretKeyV3::generate();
|
||||||
let key_vec = tor_key.as_bytes().to_vec();
|
let tor_key_bytes = tor_key.as_bytes().to_vec();
|
||||||
|
let ssh_key = Ed25519PrivateKey::from_bytes(&random());
|
||||||
|
let ssh_key_bytes = ssh_key.to_bytes().to_vec();
|
||||||
let sqlite_pool = ctx.secret_store().await?;
|
let sqlite_pool = ctx.secret_store().await?;
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO account (id, password, tor_key) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3",
|
"INSERT INTO account (id, password, tor_key, ssh_key) VALUES ($1, $2, $3, $4) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3, ssh_key = $4",
|
||||||
0,
|
0,
|
||||||
password,
|
password,
|
||||||
key_vec,
|
tor_key_bytes,
|
||||||
|
ssh_key_bytes,
|
||||||
)
|
)
|
||||||
.execute(&mut sqlite_pool.acquire().await?)
|
.execute(&mut sqlite_pool.acquire().await?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ use chrono::Utc;
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Executor, Pool, Postgres};
|
||||||
|
use ssh_key::private::Ed25519PrivateKey;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
@@ -14,6 +15,25 @@ use crate::{Error, ErrorKind};
|
|||||||
|
|
||||||
static SSH_AUTHORIZED_KEYS_FILE: &str = "/home/start9/.ssh/authorized_keys";
|
static SSH_AUTHORIZED_KEYS_FILE: &str = "/home/start9/.ssh/authorized_keys";
|
||||||
|
|
||||||
|
#[instrument(skip(secrets))]
|
||||||
|
pub async fn os_key<Ex>(secrets: &mut Ex) -> Result<Ed25519PrivateKey, Error>
|
||||||
|
where
|
||||||
|
for<'a> &'a mut Ex: Executor<'a, Database = Postgres>,
|
||||||
|
{
|
||||||
|
let key = sqlx::query!("SELECT ssh_key FROM account")
|
||||||
|
.fetch_one(secrets)
|
||||||
|
.await?
|
||||||
|
.ssh_key;
|
||||||
|
|
||||||
|
let mut buf = [0; 32];
|
||||||
|
buf.clone_from_slice(
|
||||||
|
key.get(0..64).ok_or_else(|| {
|
||||||
|
Error::new(eyre!("Invalid Ssh Key Length"), crate::ErrorKind::Database)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
Ok(Ed25519PrivateKey::from_bytes(&buf))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct PubKey(
|
pub struct PubKey(
|
||||||
#[serde(serialize_with = "crate::util::serde::serialize_display")]
|
#[serde(serialize_with = "crate::util::serde::serialize_display")]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
@@ -22,6 +23,11 @@ use crate::{Error, ErrorKind, ResultExt};
|
|||||||
|
|
||||||
pub const SYSTEMD_UNIT: &'static str = "embassyd";
|
pub const SYSTEMD_UNIT: &'static str = "embassyd";
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
pub async fn time() -> Result<String, Error> {
|
||||||
|
Ok(Utc::now().to_rfc3339())
|
||||||
|
}
|
||||||
|
|
||||||
#[command(
|
#[command(
|
||||||
custom_cli(cli_logs(async, context(CliContext))),
|
custom_cli(cli_logs(async, context(CliContext))),
|
||||||
subcommands(self(logs_nofollow(async)), logs_follow),
|
subcommands(self(logs_nofollow(async)), logs_follow),
|
||||||
|
|||||||
1
build/lib/scripts/dhclient-exit-hook
Executable file
1
build/lib/scripts/dhclient-exit-hook
Executable file
@@ -0,0 +1 @@
|
|||||||
|
embassy-cli net dhcp update $interface
|
||||||
@@ -102,6 +102,10 @@ update-locale LANGUAGE
|
|||||||
rm "/etc/locale.gen"
|
rm "/etc/locale.gen"
|
||||||
dpkg-reconfigure --frontend noninteractive locales
|
dpkg-reconfigure --frontend noninteractive locales
|
||||||
|
|
||||||
|
groupadd embassy
|
||||||
|
|
||||||
|
ln -s /usr/lib/embassy/scripts/dhclient-exit-hook /etc/dhcp/dhclient-exit-hooks.d/embassy
|
||||||
|
|
||||||
rm -f /etc/motd
|
rm -f /etc/motd
|
||||||
ln -sf /usr/lib/embassy/motd /etc/update-motd.d/00-embassy
|
ln -sf /usr/lib/embassy/motd /etc/update-motd.d/00-embassy
|
||||||
chmod -x /etc/update-motd.d/*
|
chmod -x /etc/update-motd.d/*
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ set -e
|
|||||||
# introduce start9 username and embassy as default password
|
# introduce start9 username and embassy as default password
|
||||||
if ! awk -F: '{ print $1 }' /etc/passwd | grep start9
|
if ! awk -F: '{ print $1 }' /etc/passwd | grep start9
|
||||||
then
|
then
|
||||||
usermod -l start9 -d /home/start9 -m pi
|
usermod -l start9 -d /home/start9 -aG embassy -m pi
|
||||||
groupmod --new-name start9 pi
|
groupmod --new-name start9 pi
|
||||||
echo start9:embassy | chpasswd
|
echo start9:embassy | chpasswd
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -15,6 +15,32 @@
|
|||||||
|
|
||||||
<div id="metricSection">
|
<div id="metricSection">
|
||||||
<ng-container *ngIf="!loading">
|
<ng-container *ngIf="!loading">
|
||||||
|
<ion-item-group>
|
||||||
|
<ion-item-divider>Time</ion-item-divider>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>System Time</ion-label>
|
||||||
|
<ion-note slot="end" class="metric-note">
|
||||||
|
<ion-text style="color: white"
|
||||||
|
>{{ systemTime$ | async | date:'MMMM d, y, h:mm a z':'UTC'
|
||||||
|
}}</ion-text
|
||||||
|
>
|
||||||
|
</ion-note>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>System Uptime</ion-label>
|
||||||
|
<ion-note
|
||||||
|
*ngIf="systemUptime$ | async as uptime"
|
||||||
|
slot="end"
|
||||||
|
class="metric-note"
|
||||||
|
>
|
||||||
|
<ion-text style="color: white">
|
||||||
|
<b>{{ uptime.days }}</b> Days, <b>{{ uptime.hours }}</b> Hours,
|
||||||
|
<b>{{ uptime.minutes }}</b> Minutes
|
||||||
|
</ion-text>
|
||||||
|
</ion-note>
|
||||||
|
</ion-item>
|
||||||
|
</ion-item-group>
|
||||||
|
|
||||||
<ion-item-group
|
<ion-item-group
|
||||||
*ngFor="let metricGroup of metrics | keyvalue : asIsOrder"
|
*ngFor="let metricGroup of metrics | keyvalue : asIsOrder"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { Metrics } from 'src/app/services/api/api.types'
|
import { Metrics } from 'src/app/services/api/api.types'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
|
import { TimeService } from 'src/app/services/time-service'
|
||||||
import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -13,9 +14,13 @@ export class ServerMetricsPage {
|
|||||||
going = false
|
going = false
|
||||||
metrics: Metrics = {}
|
metrics: Metrics = {}
|
||||||
|
|
||||||
|
readonly systemTime$ = this.timeService.systemTime$
|
||||||
|
readonly systemUptime$ = this.timeService.systemUptime$
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
|
private readonly timeService: TimeService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -8,28 +8,28 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-item-divider>Basic</ion-item-divider>
|
|
||||||
|
|
||||||
<ion-item-group *ngIf="server$ | async as server">
|
<ion-item-group *ngIf="server$ | async as server">
|
||||||
|
<ion-item-divider>embassyOS Info</ion-item-divider>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>embassyOS Version</h2>
|
<h2>Version</h2>
|
||||||
<p>{{ server.version | displayEmver }}</p>
|
<p>{{ server.version | displayEmver }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Git Hash</h2>
|
<h2>Git Hash</h2>
|
||||||
<p>{{ gitHash }}</p>
|
<p>{{ gitHash }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
<ion-button slot="end" fill="clear" (click)="copy(gitHash)">
|
||||||
|
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item-divider>Addresses</ion-item-divider>
|
<ion-item-divider>Web Addresses</ion-item-divider>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label class="break-all">
|
<ion-label class="break-all">
|
||||||
<h2>Tor Address</h2>
|
<h2>Tor</h2>
|
||||||
<p>{{ server['tor-address'] }}</p>
|
<p>{{ server['tor-address'] }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button slot="end" fill="clear" (click)="copy(server['tor-address'])">
|
<ion-button slot="end" fill="clear" (click)="copy(server['tor-address'])">
|
||||||
@@ -38,12 +38,49 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label class="break-all">
|
<ion-label class="break-all">
|
||||||
<h2>LAN Address</h2>
|
<h2>LAN</h2>
|
||||||
<p>{{ server['lan-address'] }}</p>
|
<p>{{ server['lan-address'] }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button slot="end" fill="clear" (click)="copy(server['lan-address'])">
|
<ion-button slot="end" fill="clear" (click)="copy(server['lan-address'])">
|
||||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<ng-container *ngFor="let ip of server['ip-info'] | keyvalue">
|
||||||
|
<ng-container *ngFor="let entry of ip.value | keyvalue">
|
||||||
|
<ion-item *ngIf="entry.value as address">
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ ip.key }} ({{ entry.key }})</h2>
|
||||||
|
<p>{{ address }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-button slot="end" fill="clear" (click)="copy(address)">
|
||||||
|
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ion-item-divider>Device Credentials</ion-item-divider>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>
|
||||||
|
<h2>Pubkey</h2>
|
||||||
|
<p>{{ server['pubkey'] }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-button slot="end" fill="clear" (click)="copy(server['pubkey'])">
|
||||||
|
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>
|
||||||
|
<h2>CA fingerprint</h2>
|
||||||
|
<p>{{ server['ca-fingerprint'] }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-button
|
||||||
|
slot="end"
|
||||||
|
fill="clear"
|
||||||
|
(click)="copy(server['ca-fingerprint'])"
|
||||||
|
>
|
||||||
|
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ export module RR {
|
|||||||
export type EchoReq = { message: string } // server.echo
|
export type EchoReq = { message: string } // server.echo
|
||||||
export type EchoRes = string
|
export type EchoRes = string
|
||||||
|
|
||||||
|
export type GetSystemTimeReq = {} // server.time
|
||||||
|
export type GetSystemTimeRes = string
|
||||||
|
|
||||||
export type GetServerLogsReq = ServerLogsReq // server.logs & server.kernel-logs
|
export type GetServerLogsReq = ServerLogsReq // server.logs & server.kernel-logs
|
||||||
export type GetServerLogsRes = LogsRes
|
export type GetServerLogsRes = LogsRes
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ export abstract class ApiService {
|
|||||||
config: WebSocketSubjectConfig<Log>,
|
config: WebSocketSubjectConfig<Log>,
|
||||||
): Observable<Log>
|
): Observable<Log>
|
||||||
|
|
||||||
|
abstract getSystemTime(
|
||||||
|
params: RR.GetSystemTimeReq,
|
||||||
|
): Promise<RR.GetSystemTimeRes>
|
||||||
|
|
||||||
abstract getServerLogs(
|
abstract getServerLogs(
|
||||||
params: RR.GetServerLogsReq,
|
params: RR.GetServerLogsReq,
|
||||||
): Promise<RR.GetServerLogsRes>
|
): Promise<RR.GetServerLogsRes>
|
||||||
|
|||||||
@@ -117,6 +117,12 @@ export class LiveApiService extends ApiService {
|
|||||||
return this.openWebsocket(config)
|
return this.openWebsocket(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSystemTime(
|
||||||
|
params: RR.GetSystemTimeReq,
|
||||||
|
): Promise<RR.GetSystemTimeRes> {
|
||||||
|
return this.rpcRequest({ method: 'server.time', params })
|
||||||
|
}
|
||||||
|
|
||||||
async getServerLogs(
|
async getServerLogs(
|
||||||
params: RR.GetServerLogsReq,
|
params: RR.GetServerLogsReq,
|
||||||
): Promise<RR.GetServerLogsRes> {
|
): Promise<RR.GetServerLogsRes> {
|
||||||
|
|||||||
@@ -177,6 +177,13 @@ export class MockApiService extends ApiService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSystemTime(
|
||||||
|
params: RR.GetSystemTimeReq,
|
||||||
|
): Promise<RR.GetSystemTimeRes> {
|
||||||
|
await pauseFor(2000)
|
||||||
|
return new Date().toUTCString()
|
||||||
|
}
|
||||||
|
|
||||||
async getServerLogs(
|
async getServerLogs(
|
||||||
params: RR.GetServerLogsReq,
|
params: RR.GetServerLogsReq,
|
||||||
): Promise<RR.GetServerLogsRes> {
|
): Promise<RR.GetServerLogsRes> {
|
||||||
|
|||||||
@@ -39,6 +39,16 @@ export const mockPatchData: DataModel = {
|
|||||||
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
|
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
|
||||||
'lan-address': 'https://embassy-abcdefgh.local',
|
'lan-address': 'https://embassy-abcdefgh.local',
|
||||||
'tor-address': 'http://myveryownspecialtoraddress.onion',
|
'tor-address': 'http://myveryownspecialtoraddress.onion',
|
||||||
|
'ip-info': {
|
||||||
|
eth0: {
|
||||||
|
ipv4: '10.0.0.1',
|
||||||
|
ipv6: null,
|
||||||
|
},
|
||||||
|
wlan0: {
|
||||||
|
ipv4: '10.0.90.12',
|
||||||
|
ipv6: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD',
|
||||||
|
},
|
||||||
|
},
|
||||||
'last-wifi-region': null,
|
'last-wifi-region': null,
|
||||||
'unread-notification-count': 4,
|
'unread-notification-count': 4,
|
||||||
// password is asdfasdf
|
// password is asdfasdf
|
||||||
@@ -51,6 +61,9 @@ export const mockPatchData: DataModel = {
|
|||||||
'update-progress': null,
|
'update-progress': null,
|
||||||
},
|
},
|
||||||
hostname: 'random-words',
|
hostname: 'random-words',
|
||||||
|
pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m',
|
||||||
|
'ca-fingerprint': 'SHA-256: 63 2B 11 99 44 40 17 DF 37 FC C3 DF 0F 3D 15',
|
||||||
|
'system-start-time': new Date(new Date().valueOf() - 360042).toUTCString(),
|
||||||
},
|
},
|
||||||
'package-data': {
|
'package-data': {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
|
|||||||
@@ -52,12 +52,23 @@ export interface ServerInfo {
|
|||||||
'last-backup': string | null
|
'last-backup': string | null
|
||||||
'lan-address': Url
|
'lan-address': Url
|
||||||
'tor-address': Url
|
'tor-address': Url
|
||||||
|
'ip-info': IpInfo
|
||||||
'last-wifi-region': string | null
|
'last-wifi-region': string | null
|
||||||
'unread-notification-count': number
|
'unread-notification-count': number
|
||||||
'status-info': ServerStatusInfo
|
'status-info': ServerStatusInfo
|
||||||
'eos-version-compat': string
|
'eos-version-compat': string
|
||||||
'password-hash': string
|
'password-hash': string
|
||||||
hostname: string
|
hostname: string
|
||||||
|
pubkey: string
|
||||||
|
'ca-fingerprint': string
|
||||||
|
'system-start-time': string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IpInfo {
|
||||||
|
[iface: string]: {
|
||||||
|
ipv4: string | null
|
||||||
|
ipv6: string | null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerStatusInfo {
|
export interface ServerStatusInfo {
|
||||||
|
|||||||
63
frontend/projects/ui/src/app/services/time-service.ts
Normal file
63
frontend/projects/ui/src/app/services/time-service.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import {
|
||||||
|
map,
|
||||||
|
shareReplay,
|
||||||
|
startWith,
|
||||||
|
switchMap,
|
||||||
|
take,
|
||||||
|
tap,
|
||||||
|
} from 'rxjs/operators'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import { DataModel } from './patch-db/data-model'
|
||||||
|
import { ApiService } from './api/embassy-api.service'
|
||||||
|
import { combineLatest, from, timer } from 'rxjs'
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class TimeService {
|
||||||
|
private readonly startTimeMs$ = this.patch
|
||||||
|
.watch$('server-info', 'system-start-time')
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
map(startTime => new Date(startTime).valueOf()),
|
||||||
|
shareReplay(),
|
||||||
|
)
|
||||||
|
|
||||||
|
readonly systemTime$ = from(this.apiService.getSystemTime({})).pipe(
|
||||||
|
switchMap(utcStr => {
|
||||||
|
const dateObj = new Date(utcStr)
|
||||||
|
const msRemaining = (60 - dateObj.getSeconds()) * 1000
|
||||||
|
dateObj.setSeconds(0)
|
||||||
|
const current = dateObj.valueOf()
|
||||||
|
return timer(msRemaining, 60000).pipe(
|
||||||
|
map(index => {
|
||||||
|
const incremented = index + 1
|
||||||
|
const msToAdd = 60000 * incremented
|
||||||
|
return current + msToAdd
|
||||||
|
}),
|
||||||
|
startWith(current),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
readonly systemUptime$ = combineLatest([
|
||||||
|
this.startTimeMs$,
|
||||||
|
this.systemTime$,
|
||||||
|
]).pipe(
|
||||||
|
map(([startTime, currentTime]) => {
|
||||||
|
const ms = currentTime - startTime
|
||||||
|
const days = Math.floor(ms / (24 * 60 * 60 * 1000))
|
||||||
|
const daysms = ms % (24 * 60 * 60 * 1000)
|
||||||
|
const hours = Math.floor(daysms / (60 * 60 * 1000))
|
||||||
|
const hoursms = ms % (60 * 60 * 1000)
|
||||||
|
const minutes = Math.floor(hoursms / (60 * 1000))
|
||||||
|
return { days, hours, minutes }
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly patch: PatchDB<DataModel>,
|
||||||
|
private readonly apiService: ApiService,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
776
libs/Cargo.lock
generated
776
libs/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@ sqlx = { version = "0.6.0", features = [
|
|||||||
"runtime-tokio-rustls",
|
"runtime-tokio-rustls",
|
||||||
"postgres",
|
"postgres",
|
||||||
] }
|
] }
|
||||||
|
ssh-key = "0.5.1"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
torut = "0.2.1"
|
torut = "0.2.1"
|
||||||
|
|||||||
@@ -261,6 +261,11 @@ impl From<InvalidUri> for Error {
|
|||||||
Error::new(eyre!("{}", e), ErrorKind::ParseUrl)
|
Error::new(eyre!("{}", e), ErrorKind::ParseUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<ssh_key::Error> for Error {
|
||||||
|
fn from(e: ssh_key::Error) -> Self {
|
||||||
|
Error::new(e, ErrorKind::OpenSsh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Error> for RpcError {
|
impl From<Error> for RpcError {
|
||||||
fn from(e: Error) -> Self {
|
fn from(e: Error) -> Self {
|
||||||
|
|||||||
1321
system-images/compat/Cargo.lock
generated
1321
system-images/compat/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user