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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
|
||||
|
||||
[[package]]
|
||||
name = "base32"
|
||||
version = "0.4.0"
|
||||
@@ -636,9 +642,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.1"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time 0.3.17",
|
||||
@@ -661,6 +667,22 @@ dependencies = [
|
||||
"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]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
@@ -735,6 +757,18 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@@ -1088,6 +1122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.3",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
@@ -1160,6 +1195,18 @@ dependencies = [
|
||||
"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]]
|
||||
name = "ed25519"
|
||||
version = "1.5.2"
|
||||
@@ -1195,6 +1242,25 @@ dependencies = [
|
||||
"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]]
|
||||
name = "embassy-os"
|
||||
version = "0.3.3"
|
||||
@@ -1213,7 +1279,8 @@ dependencies = [
|
||||
"ciborium",
|
||||
"clap 3.2.23",
|
||||
"color-eyre",
|
||||
"cookie_store",
|
||||
"cookie",
|
||||
"cookie_store 0.19.0",
|
||||
"current_platform",
|
||||
"digest 0.10.5",
|
||||
"digest 0.9.0",
|
||||
@@ -1273,6 +1340,7 @@ dependencies = [
|
||||
"sha2 0.9.9",
|
||||
"simple-logging",
|
||||
"sqlx",
|
||||
"ssh-key",
|
||||
"stderrlog",
|
||||
"tar",
|
||||
"thiserror",
|
||||
@@ -1462,6 +1530,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "filetime"
|
||||
version = "0.2.18"
|
||||
@@ -1723,6 +1801,17 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
@@ -2296,6 +2385,9 @@ name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
@@ -2392,6 +2484,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.7"
|
||||
@@ -2538,6 +2636,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"ssh-key",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"torut",
|
||||
@@ -2672,6 +2771,23 @@ dependencies = [
|
||||
"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]]
|
||||
name = "num-complex"
|
||||
version = "0.4.2"
|
||||
@@ -2721,6 +2837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2861,6 +2978,28 @@ version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
@@ -3092,6 +3231,18 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "pkcs8"
|
||||
version = "0.9.0"
|
||||
@@ -3447,7 +3598,7 @@ dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"cookie_store",
|
||||
"cookie_store 0.16.1",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -3482,17 +3633,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest_cookie_store"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0568e27f107b933735a07b3f8cb985ecfe3d3ce2f2225f82f10b3750f5981263"
|
||||
checksum = "06b407c05de7a0f7e4cc2a56af5e9bd6468e509124e81078ce1f8bc2ed3536bf"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cookie",
|
||||
"cookie_store",
|
||||
"cookie_store 0.19.0",
|
||||
"reqwest",
|
||||
"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]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
@@ -3562,6 +3724,27 @@ dependencies = [
|
||||
"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]]
|
||||
name = "rust-argon2"
|
||||
version = "1.0.0"
|
||||
@@ -3701,6 +3884,20 @@ dependencies = [
|
||||
"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]]
|
||||
name = "security-framework"
|
||||
version = "2.7.0"
|
||||
@@ -3987,6 +4184,10 @@ name = "signature"
|
||||
version = "1.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
|
||||
dependencies = [
|
||||
"digest 0.10.5",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-logging"
|
||||
@@ -4180,6 +4381,35 @@ dependencies = [
|
||||
"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]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
||||
@@ -62,7 +62,8 @@ bytes = "1"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
clap = "3.2.8"
|
||||
color-eyre = "0.6.1"
|
||||
cookie_store = "0.16.1"
|
||||
cookie = "0.16.2"
|
||||
cookie_store = "0.19.0"
|
||||
current_platform = "0.2.0"
|
||||
digest = "0.10.3"
|
||||
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" }
|
||||
regex = "1.6.0"
|
||||
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"
|
||||
rpc-toolkit = "0.2.2"
|
||||
rust-argon2 = "1.0.0"
|
||||
@@ -133,6 +134,7 @@ sqlx = { version = "0.6.0", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"postgres",
|
||||
] }
|
||||
ssh-key = { version = "0.5.1", features = ["ed25519"] }
|
||||
stderrlog = "0.5.3"
|
||||
tar = "0.4.38"
|
||||
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"
|
||||
},
|
||||
"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": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -256,6 +271,24 @@
|
||||
},
|
||||
"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": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -536,33 +569,6 @@
|
||||
},
|
||||
"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": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
||||
@@ -8,9 +8,11 @@ use helpers::AtomicFile;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::X509;
|
||||
use patch_db::{DbHandle, LockType, PatchDbHandle};
|
||||
use rand::random;
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use ssh_key::private::Ed25519PrivateKey;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
use tracing::instrument;
|
||||
@@ -35,6 +37,7 @@ use crate::{Error, ErrorKind, ResultExt};
|
||||
#[derive(Debug)]
|
||||
pub struct OsBackup {
|
||||
pub tor_key: TorSecretKeyV3,
|
||||
pub ssh_key: Ed25519PrivateKey,
|
||||
pub root_ca_key: PKey<Private>,
|
||||
pub root_ca_cert: X509,
|
||||
pub ui: Value,
|
||||
@@ -48,32 +51,52 @@ impl<'de> Deserialize<'de> for OsBackup {
|
||||
#[serde(rename = "kebab-case")]
|
||||
struct OsBackupDe {
|
||||
tor_key: String,
|
||||
ssh_key: Option<String>,
|
||||
root_ca_key: String,
|
||||
root_ca_cert: String,
|
||||
ui: Value,
|
||||
}
|
||||
let int = OsBackupDe::deserialize(deserializer)?;
|
||||
let key_vec = base32::decode(base32::Alphabet::RFC4648 { padding: true }, &int.tor_key)
|
||||
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 }, base32)
|
||||
.ok_or_else(|| {
|
||||
serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Str(&int.tor_key),
|
||||
serde::de::Unexpected::Str(base32),
|
||||
&"an RFC4648 encoded string",
|
||||
)
|
||||
})?;
|
||||
if key_vec.len() != 64 {
|
||||
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",
|
||||
));
|
||||
}
|
||||
Ok(key_vec)
|
||||
}
|
||||
let int = OsBackupDe::deserialize(deserializer)?;
|
||||
let tor_key = {
|
||||
let mut key_slice = [0; 64];
|
||||
key_slice.clone_from_slice(&key_vec);
|
||||
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 {
|
||||
tor_key: TorSecretKeyV3::from(key_slice),
|
||||
root_ca_key: PKey::<Private>::private_key_from_pem(int.root_ca_key.as_bytes())
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
root_ca_cert: X509::from_pem(int.root_ca_cert.as_bytes())
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
tor_key,
|
||||
ssh_key,
|
||||
root_ca_key,
|
||||
root_ca_cert,
|
||||
ui: int.ui,
|
||||
})
|
||||
}
|
||||
@@ -437,6 +460,7 @@ async fn perform_backup<Db: DbHandle>(
|
||||
.write_all(
|
||||
&IoFormat::Cbor.to_vec(&OsBackup {
|
||||
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_cert,
|
||||
ui: crate::db::DatabaseModel::new()
|
||||
|
||||
@@ -198,13 +198,15 @@ pub async fn recover_full_embassy(
|
||||
&argon2::Config::default(),
|
||||
)
|
||||
.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?;
|
||||
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,
|
||||
password,
|
||||
key_vec,
|
||||
tor_key_bytes,
|
||||
ssh_key_bytes,
|
||||
)
|
||||
.execute(&mut secret_store.acquire().await?)
|
||||
.await?;
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::sync::Arc;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use cookie::Cookie;
|
||||
use cookie_store::CookieStore;
|
||||
use josekit::jwk::Jwk;
|
||||
use reqwest::Proxy;
|
||||
@@ -16,6 +17,7 @@ use rpc_toolkit::Context;
|
||||
use serde::Deserialize;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||
use crate::util::config::{load_config_from_paths, local_config_path};
|
||||
use crate::ResultExt;
|
||||
|
||||
@@ -83,7 +85,7 @@ impl CliContext {
|
||||
} else if let Some(host) = base.host {
|
||||
host
|
||||
} else {
|
||||
format!("http://localhost").parse()?
|
||||
"http://localhost".parse()?
|
||||
};
|
||||
let proxy = if let Some(proxy) = matches.value_of("proxy") {
|
||||
Some(proxy.parse()?)
|
||||
@@ -100,9 +102,15 @@ impl CliContext {
|
||||
.join(".cookies.json")
|
||||
});
|
||||
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))
|
||||
.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 {
|
||||
CookieStore::default()
|
||||
}));
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde::Deserialize;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
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::Error;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::install::cleanup::{cleanup_failed, uninstall, CleanupFailedReceipts};
|
||||
use crate::manager::ManagerMap;
|
||||
use crate::middleware::auth::HashSessionToken;
|
||||
use crate::net::net_controller::NetController;
|
||||
use crate::net::tor::os_key;
|
||||
use crate::net::ssl::SslManager;
|
||||
use crate::net::wifi::WpaCli;
|
||||
use crate::notifications::NotificationManager;
|
||||
use crate::setup::password_hash;
|
||||
@@ -85,8 +85,14 @@ impl RpcContextConfig {
|
||||
db.put(
|
||||
&<JsonPointer>::default(),
|
||||
&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?,
|
||||
&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?;
|
||||
|
||||
@@ -17,7 +17,7 @@ use tracing::instrument;
|
||||
use crate::db::model::Database;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
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::util::config::load_config_from_paths;
|
||||
use crate::{Error, ResultExt};
|
||||
@@ -120,8 +120,14 @@ impl SetupContext {
|
||||
db.put(
|
||||
&<JsonPointer>::default(),
|
||||
&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?,
|
||||
&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?;
|
||||
|
||||
@@ -132,7 +132,7 @@ pub async fn subscribe(ctx: RpcContext, req: Request<Body>) -> Result<Response<B
|
||||
let (parts, body) = req.into_parts();
|
||||
let session = match async {
|
||||
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))
|
||||
}
|
||||
.await
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use emver::VersionRange;
|
||||
use isocountry::CountryCode;
|
||||
use itertools::Itertools;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::x509::X509;
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{HasModel, Map, MapModel, OptionModel};
|
||||
use reqwest::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use ssh_key::private::Ed25519PrivateKey;
|
||||
use ssh_key::public::Ed25519PublicKey;
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
use crate::config::spec::{PackagePointerSpec, SystemPointerSpec};
|
||||
use crate::hostname::{generate_hostname, generate_id};
|
||||
use crate::install::progress::InstallProgress;
|
||||
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::status::health_check::HealthCheckId;
|
||||
use crate::status::Status;
|
||||
use crate::util::Version;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
@@ -31,7 +39,12 @@ pub struct Database {
|
||||
pub ui: Value,
|
||||
}
|
||||
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 my_hostname = generate_hostname();
|
||||
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())
|
||||
.parse()
|
||||
.unwrap(),
|
||||
ip_info: BTreeMap::new(),
|
||||
status_info: ServerStatus {
|
||||
backup_progress: None,
|
||||
updated: false,
|
||||
@@ -64,6 +78,16 @@ impl Database {
|
||||
clearnet: Vec::new(),
|
||||
},
|
||||
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(),
|
||||
ui: serde_json::from_str(include_str!("../../../frontend/patchdb-ui-seed.json"))
|
||||
@@ -90,12 +114,32 @@ pub struct ServerInfo {
|
||||
pub lan_address: Url,
|
||||
pub tor_address: Url,
|
||||
#[model]
|
||||
pub ip_info: BTreeMap<String, IpInfo>,
|
||||
#[model]
|
||||
#[serde(default)]
|
||||
pub status_info: ServerStatus,
|
||||
pub wifi: WifiInfo,
|
||||
pub unread_notification_count: u64,
|
||||
pub connection_addresses: ConnectionAddresses,
|
||||
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)]
|
||||
|
||||
@@ -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::process::Stdio;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use models::ResultExt;
|
||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||
use rand::random;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::context::rpc::RpcContextConfig;
|
||||
use crate::db::model::ServerStatus;
|
||||
use crate::db::model::{IpInfo, ServerStatus};
|
||||
use crate::install::PKG_ARCHIVE_DIR;
|
||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||
use crate::sound::BEP;
|
||||
use crate::system::time;
|
||||
use crate::util::Invoke;
|
||||
use crate::Error;
|
||||
|
||||
@@ -37,6 +43,8 @@ pub struct InitReceipts {
|
||||
pub version_range: LockReceipt<emver::VersionRange, ()>,
|
||||
pub last_wifi_region: LockReceipt<Option<isocountry::CountryCode>, ()>,
|
||||
pub status_info: LockReceipt<ServerStatus, ()>,
|
||||
pub ip_info: LockReceipt<BTreeMap<String, IpInfo>, ()>,
|
||||
pub system_start_time: LockReceipt<String, ()>,
|
||||
}
|
||||
impl InitReceipts {
|
||||
pub async fn new(db: &mut impl DbHandle) -> Result<Self, Error> {
|
||||
@@ -57,19 +65,31 @@ impl InitReceipts {
|
||||
.last_wifi_region()
|
||||
.make_locker(LockType::Write)
|
||||
.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()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.into_model()
|
||||
.make_locker(LockType::Write)
|
||||
.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?;
|
||||
Ok(Self {
|
||||
server_version: server_version.verify(&skeleton_key)?,
|
||||
version_range: version_range.verify(&skeleton_key)?,
|
||||
ip_info: ip_info.verify(&skeleton_key)?,
|
||||
status_info: status_info.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> {
|
||||
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?;
|
||||
tracing::info!("Opened Postgres");
|
||||
|
||||
@@ -334,6 +372,10 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
.await?;
|
||||
tracing::info!("Enabled Docker QEMU Emulation");
|
||||
|
||||
receipts
|
||||
.ip_info
|
||||
.set(&mut handle, crate::net::dhcp::init_ips().await?)
|
||||
.await?;
|
||||
receipts
|
||||
.status_info
|
||||
.set(
|
||||
@@ -345,20 +387,10 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut warn_time_not_synced = true;
|
||||
for _ in 0..60 {
|
||||
if check_time_is_synchronized().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");
|
||||
}
|
||||
receipts
|
||||
.system_start_time
|
||||
.set(&mut handle, time().await?)
|
||||
.await?;
|
||||
|
||||
crate::version::init(&mut handle, &receipts).await?;
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ pub fn main_api() -> Result<(), RpcError> {
|
||||
}
|
||||
|
||||
#[command(subcommands(
|
||||
system::time,
|
||||
system::logs,
|
||||
system::kernel_logs,
|
||||
system::metrics,
|
||||
|
||||
@@ -23,6 +23,9 @@ use tokio::sync::Mutex;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
pub const LOCAL_AUTH_COOKIE_PATH: &str = "/run/embassy/rpc.authcookie";
|
||||
|
||||
pub trait AsLogoutSessionId {
|
||||
fn as_logout_session_id(self) -> String;
|
||||
}
|
||||
@@ -63,7 +66,29 @@ impl HasValidSession {
|
||||
request_parts: &RequestParts,
|
||||
ctx: &RpcContext,
|
||||
) -> 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> {
|
||||
@@ -79,6 +104,18 @@ impl HasValidSession {
|
||||
}
|
||||
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,
|
||||
|
||||
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;
|
||||
|
||||
pub mod cert_resolver;
|
||||
pub mod dhcp;
|
||||
pub mod dns;
|
||||
pub mod embassy_service_http_server;
|
||||
pub mod interface;
|
||||
@@ -28,7 +29,7 @@ pub mod wifi;
|
||||
|
||||
const PACKAGE_CERT_PATH: &str = "/var/lib/embassy/ssl";
|
||||
|
||||
#[command(subcommands(tor::tor))]
|
||||
#[command(subcommands(tor::tor, dhcp::dhcp))]
|
||||
pub fn net() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,13 +1,115 @@
|
||||
use std::fmt;
|
||||
use std::net::IpAddr;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use async_stream::try_stream;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::stream::BoxStream;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use http::{Request, Uri};
|
||||
use hyper::Body;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::util::Invoke;
|
||||
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> {
|
||||
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::db::subscribe;
|
||||
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::{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 {
|
||||
let res = match req.uri().path() {
|
||||
path if path.starts_with("/rpc/") => {
|
||||
let rpc_handler =
|
||||
rpc_handler!({command: setup_api, context: ctx, status: status_fn});
|
||||
let rpc_handler = rpc_handler!({
|
||||
command: setup_api,
|
||||
context: ctx,
|
||||
status: status_fn,
|
||||
middleware: [
|
||||
cors,
|
||||
]
|
||||
});
|
||||
|
||||
rpc_handler(req)
|
||||
.await
|
||||
@@ -76,8 +85,15 @@ pub async fn diag_ui_file_router(ctx: DiagnosticContext) -> Result<HttpHandler,
|
||||
async move {
|
||||
let res = match req.uri().path() {
|
||||
path if path.starts_with("/rpc/") => {
|
||||
let rpc_handler =
|
||||
rpc_handler!({command: diagnostic_api, context: ctx, status: status_fn});
|
||||
let rpc_handler = rpc_handler!({
|
||||
command: diagnostic_api,
|
||||
context: ctx,
|
||||
status: status_fn,
|
||||
middleware: [
|
||||
cors,
|
||||
diagnostic_middleware,
|
||||
]
|
||||
});
|
||||
|
||||
rpc_handler(req)
|
||||
.await
|
||||
@@ -104,8 +120,14 @@ pub async fn install_ui_file_router(ctx: InstallContext) -> Result<HttpHandler,
|
||||
async move {
|
||||
let res = match req.uri().path() {
|
||||
path if path.starts_with("/rpc/") => {
|
||||
let rpc_handler =
|
||||
rpc_handler!({command: install_api, context: ctx, status: status_fn});
|
||||
let rpc_handler = rpc_handler!({
|
||||
command: install_api,
|
||||
context: ctx,
|
||||
status: status_fn,
|
||||
middleware: [
|
||||
cors,
|
||||
]
|
||||
});
|
||||
|
||||
rpc_handler(req)
|
||||
.await
|
||||
@@ -132,8 +154,18 @@ pub async fn main_ui_server_router(ctx: RpcContext) -> Result<HttpHandler, Error
|
||||
async move {
|
||||
let res = match req.uri().path() {
|
||||
path if path.starts_with("/rpc/") => {
|
||||
let rpc_handler =
|
||||
rpc_handler!({command: main_api, context: ctx, status: status_fn});
|
||||
let auth_middleware = auth_middleware(ctx.clone());
|
||||
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)
|
||||
.await
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::guard::{MountGuard, TmpMountGuard};
|
||||
use crate::disk::util::DiskInfo;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::net::net_utils::{find_eth_iface, find_wifi_iface};
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::{display_none, Invoke};
|
||||
|
||||
@@ -69,43 +70,6 @@ pub async fn list() -> Result<Vec<DiskInfo>, Error> {
|
||||
.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 {
|
||||
let disk_path = disk.as_ref();
|
||||
let (root, leaf) = if let (Some(root), Some(leaf)) = (
|
||||
|
||||
@@ -7,10 +7,12 @@ use helpers::{Rsync, RsyncOptions};
|
||||
use josekit::jwk::Jwk;
|
||||
use openssl::x509::X509;
|
||||
use patch_db::DbHandle;
|
||||
use rand::random;
|
||||
use rpc_toolkit::command;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Connection, Executor, Postgres};
|
||||
use ssh_key::private::Ed25519PrivateKey;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
||||
@@ -390,13 +392,16 @@ async fn fresh_setup(
|
||||
)
|
||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
|
||||
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?;
|
||||
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,
|
||||
password,
|
||||
key_vec,
|
||||
tor_key_bytes,
|
||||
ssh_key_bytes,
|
||||
)
|
||||
.execute(&mut sqlite_pool.acquire().await?)
|
||||
.await?;
|
||||
|
||||
@@ -4,7 +4,8 @@ use chrono::Utc;
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use rpc_toolkit::command;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use sqlx::{Executor, Pool, Postgres};
|
||||
use ssh_key::private::Ed25519PrivateKey;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
@@ -14,6 +15,25 @@ use crate::{Error, ErrorKind};
|
||||
|
||||
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)]
|
||||
pub struct PubKey(
|
||||
#[serde(serialize_with = "crate::util::serde::serialize_display")]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::fmt;
|
||||
|
||||
use chrono::Utc;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::FutureExt;
|
||||
use rpc_toolkit::command;
|
||||
@@ -22,6 +23,11 @@ use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
pub const SYSTEMD_UNIT: &'static str = "embassyd";
|
||||
|
||||
#[command]
|
||||
pub async fn time() -> Result<String, Error> {
|
||||
Ok(Utc::now().to_rfc3339())
|
||||
}
|
||||
|
||||
#[command(
|
||||
custom_cli(cli_logs(async, context(CliContext))),
|
||||
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"
|
||||
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
|
||||
ln -sf /usr/lib/embassy/motd /etc/update-motd.d/00-embassy
|
||||
chmod -x /etc/update-motd.d/*
|
||||
|
||||
@@ -4,7 +4,7 @@ set -e
|
||||
# introduce start9 username and embassy as default password
|
||||
if ! awk -F: '{ print $1 }' /etc/passwd | grep start9
|
||||
then
|
||||
usermod -l start9 -d /home/start9 -m pi
|
||||
usermod -l start9 -d /home/start9 -aG embassy -m pi
|
||||
groupmod --new-name start9 pi
|
||||
echo start9:embassy | chpasswd
|
||||
fi
|
||||
|
||||
@@ -15,6 +15,32 @@
|
||||
|
||||
<div id="metricSection">
|
||||
<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
|
||||
*ngFor="let metricGroup of metrics | keyvalue : asIsOrder"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { Metrics } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { TimeService } from 'src/app/services/time-service'
|
||||
import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
@@ -13,9 +14,13 @@ export class ServerMetricsPage {
|
||||
going = false
|
||||
metrics: Metrics = {}
|
||||
|
||||
readonly systemTime$ = this.timeService.systemTime$
|
||||
readonly systemUptime$ = this.timeService.systemUptime$
|
||||
|
||||
constructor(
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly timeService: TimeService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
|
||||
@@ -8,28 +8,28 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-item-divider>Basic</ion-item-divider>
|
||||
|
||||
<ion-item-group *ngIf="server$ | async as server">
|
||||
<ion-item-divider>embassyOS Info</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>embassyOS Version</h2>
|
||||
<h2>Version</h2>
|
||||
<p>{{ server.version | displayEmver }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>Git Hash</h2>
|
||||
<p>{{ gitHash }}</p>
|
||||
</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-divider>Addresses</ion-item-divider>
|
||||
|
||||
<ion-item-divider>Web Addresses</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-label class="break-all">
|
||||
<h2>Tor Address</h2>
|
||||
<h2>Tor</h2>
|
||||
<p>{{ server['tor-address'] }}</p>
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" (click)="copy(server['tor-address'])">
|
||||
@@ -38,12 +38,49 @@
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label class="break-all">
|
||||
<h2>LAN Address</h2>
|
||||
<h2>LAN</h2>
|
||||
<p>{{ server['lan-address'] }}</p>
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" (click)="copy(server['lan-address'])">
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</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-content>
|
||||
|
||||
@@ -35,6 +35,9 @@ export module RR {
|
||||
export type EchoReq = { message: string } // server.echo
|
||||
export type EchoRes = string
|
||||
|
||||
export type GetSystemTimeReq = {} // server.time
|
||||
export type GetSystemTimeRes = string
|
||||
|
||||
export type GetServerLogsReq = ServerLogsReq // server.logs & server.kernel-logs
|
||||
export type GetServerLogsRes = LogsRes
|
||||
|
||||
|
||||
@@ -57,6 +57,10 @@ export abstract class ApiService {
|
||||
config: WebSocketSubjectConfig<Log>,
|
||||
): Observable<Log>
|
||||
|
||||
abstract getSystemTime(
|
||||
params: RR.GetSystemTimeReq,
|
||||
): Promise<RR.GetSystemTimeRes>
|
||||
|
||||
abstract getServerLogs(
|
||||
params: RR.GetServerLogsReq,
|
||||
): Promise<RR.GetServerLogsRes>
|
||||
|
||||
@@ -117,6 +117,12 @@ export class LiveApiService extends ApiService {
|
||||
return this.openWebsocket(config)
|
||||
}
|
||||
|
||||
async getSystemTime(
|
||||
params: RR.GetSystemTimeReq,
|
||||
): Promise<RR.GetSystemTimeRes> {
|
||||
return this.rpcRequest({ method: 'server.time', params })
|
||||
}
|
||||
|
||||
async getServerLogs(
|
||||
params: RR.GetServerLogsReq,
|
||||
): 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(
|
||||
params: RR.GetServerLogsReq,
|
||||
): Promise<RR.GetServerLogsRes> {
|
||||
|
||||
@@ -39,6 +39,16 @@ export const mockPatchData: DataModel = {
|
||||
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
|
||||
'lan-address': 'https://embassy-abcdefgh.local',
|
||||
'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,
|
||||
'unread-notification-count': 4,
|
||||
// password is asdfasdf
|
||||
@@ -51,6 +61,9 @@ export const mockPatchData: DataModel = {
|
||||
'update-progress': null,
|
||||
},
|
||||
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': {
|
||||
bitcoind: {
|
||||
|
||||
@@ -52,12 +52,23 @@ export interface ServerInfo {
|
||||
'last-backup': string | null
|
||||
'lan-address': Url
|
||||
'tor-address': Url
|
||||
'ip-info': IpInfo
|
||||
'last-wifi-region': string | null
|
||||
'unread-notification-count': number
|
||||
'status-info': ServerStatusInfo
|
||||
'eos-version-compat': string
|
||||
'password-hash': 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 {
|
||||
|
||||
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",
|
||||
"postgres",
|
||||
] }
|
||||
ssh-key = "0.5.1"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
torut = "0.2.1"
|
||||
|
||||
@@ -261,6 +261,11 @@ impl From<InvalidUri> for Error {
|
||||
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 {
|
||||
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