generated from Patagia/template-nix
WIP: Registration #10
13 changed files with 535 additions and 7 deletions
189
Cargo.lock
generated
189
Cargo.lock
generated
|
@ -305,8 +305,10 @@ checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
|||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
|
@ -465,6 +467,69 @@ dependencies = [
|
|||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug-ignore"
|
||||
version = "1.0.5"
|
||||
|
@ -489,6 +554,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -612,6 +678,31 @@ version = "1.0.19"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
|
@ -674,6 +765,12 @@ version = "2.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.1"
|
||||
|
@ -1268,6 +1365,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.0.3"
|
||||
|
@ -1297,6 +1400,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
|||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1851,6 +1955,7 @@ name = "patagia-agent"
|
|||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"futures",
|
||||
"instrumentation",
|
||||
|
@ -1868,12 +1973,21 @@ name = "patagia-controller"
|
|||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"dropshot",
|
||||
"ed25519-dalek",
|
||||
"futures",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"http",
|
||||
"instrumentation",
|
||||
"rand 0.8.5",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha2",
|
||||
"slog",
|
||||
"slog-async",
|
||||
"sqlx",
|
||||
|
@ -1882,6 +1996,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-slog",
|
||||
"uuid",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2201,9 +2316,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.11"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
|
||||
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
@ -2371,6 +2486,15 @@ version = "2.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.7"
|
||||
|
@ -2492,6 +2616,7 @@ version = "0.8.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
|
@ -2649,6 +2774,36 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.9.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
|
@ -4217,9 +4372,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.8"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b"
|
||||
checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -4245,6 +4400,18 @@ version = "0.5.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.2.0"
|
||||
|
@ -4325,6 +4492,20 @@ name = "zeroize"
|
|||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -24,6 +24,7 @@ name = "patagia-run"
|
|||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.98"
|
||||
chrono = "0.4.39"
|
||||
clap = { version = "4.5.37", features = [
|
||||
"derive",
|
||||
"deprecated",
|
||||
|
@ -32,19 +33,27 @@ clap = { version = "4.5.37", features = [
|
|||
"string",
|
||||
] }
|
||||
dropshot = "0.16.0"
|
||||
ed25519-dalek = { version = "2.1.1", features = ["pem", "rand_core"] }
|
||||
futures = "0.3"
|
||||
hex = "0.4.3"
|
||||
hkdf = "0.12.4"
|
||||
http = "1.3.1"
|
||||
once_cell = "1.21.3"
|
||||
progenitor = "0.9"
|
||||
rand = "0.8.5"
|
||||
reqwest = { version = "0.12.15", features = ["json", "stream", "rustls-tls"] }
|
||||
schemars = "0.8.22"
|
||||
schemars = { version = "0.8.22", features = ["bytes", "chrono", "derive"] }
|
||||
semver = "1.0.26"
|
||||
serde_json = "1.0.138"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_with = { version = "3.12.0", features = ["base64", "hex", "macros", "std"] }
|
||||
sha2 = "0.10.8"
|
||||
slog = "2.7.0"
|
||||
slog-async = "2.8.0"
|
||||
tokio = { version = "1.44.2", features = ["full"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-core = "0.1.33"
|
||||
tracing-chrome = "0.7.2"
|
||||
tracing-core = "0.1.33"
|
||||
tracing-slog = { git = "https://github.com/oxidecomputer/tracing-slog", default-features = false }
|
||||
uuid = { version = "1", features = [ "serde", "v4" ] }
|
||||
x25519-dalek = { version = "2.0.1", features = ["serde", "static_secrets"] }
|
||||
|
|
|
@ -6,6 +6,7 @@ version.workspace = true
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
clap.workspace = true
|
||||
futures.workspace = true
|
||||
instrumentation = { path = "../instrumentation" }
|
||||
|
|
65
api.json
65
api.json
|
@ -5,6 +5,43 @@
|
|||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/nodes/register": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"node"
|
||||
],
|
||||
"summary": "Get registration info",
|
||||
"operationId": "get_registration_info",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "key",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RegistrationInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"4XX": {
|
||||
"$ref": "#/components/responses/Error"
|
||||
},
|
||||
"5XX": {
|
||||
"$ref": "#/components/responses/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
@ -141,6 +178,31 @@
|
|||
"request_id"
|
||||
]
|
||||
},
|
||||
"RegistrationInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"node_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"nonce": {
|
||||
"type": "string"
|
||||
},
|
||||
"server_dh_pub": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"node_id",
|
||||
"nonce",
|
||||
"server_dh_pub",
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
"User": {
|
||||
"description": "User",
|
||||
"type": "object",
|
||||
|
@ -210,6 +272,9 @@
|
|||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "node"
|
||||
},
|
||||
{
|
||||
"name": "user"
|
||||
}
|
||||
|
|
|
@ -7,12 +7,21 @@ version.workspace = true
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
clap.workspace = true
|
||||
dropshot.workspace = true
|
||||
ed25519-dalek.workspace = true
|
||||
futures.workspace = true
|
||||
hex.workspace = true
|
||||
hkdf.workspace = true
|
||||
http.workspace = true
|
||||
instrumentation = { path = "../instrumentation" }
|
||||
rand.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_with.workspace = true
|
||||
sha2.workspace = true
|
||||
slog-async.workspace = true
|
||||
slog.workspace = true
|
||||
sqlx = { version = "0.8.5", default-features = false, features = [
|
||||
|
@ -23,6 +32,7 @@ trace-request = { path = "../trace-request" }
|
|||
tracing-slog.workspace = true
|
||||
tracing.workspace = true
|
||||
uuid.workspace = true
|
||||
x25519-dalek.workspace = true
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["http"]
|
||||
|
|
|
@ -4,6 +4,7 @@ use dropshot::ApiDescription;
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::context::ControllerContext;
|
||||
use crate::node;
|
||||
use crate::user;
|
||||
use crate::version;
|
||||
|
||||
|
@ -11,6 +12,7 @@ type ControllerApiDescription = ApiDescription<Arc<ControllerContext>>;
|
|||
|
||||
pub fn api() -> Result<ControllerApiDescription> {
|
||||
let mut api = ControllerApiDescription::new();
|
||||
node::register_api(&mut api)?;
|
||||
user::register_api(&mut api)?;
|
||||
api.register(version::version)?;
|
||||
Ok(api)
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
use sqlx::postgres::PgPool;
|
||||
|
||||
use crate::node::NodeController;
|
||||
|
||||
pub struct ControllerContext {
|
||||
pub pg_pool: PgPool,
|
||||
// pub node_controller: Mutex<NodeController>,
|
||||
pub node_controller: NodeController,
|
||||
}
|
||||
|
||||
impl ControllerContext {
|
||||
pub fn new(pg_pool: PgPool) -> ControllerContext {
|
||||
ControllerContext { pg_pool }
|
||||
// let node_controller = Mutex::new(NodeController::new(pg_pool.to_owned()));
|
||||
let node_controller = NodeController::new(pg_pool.to_owned());
|
||||
ControllerContext {
|
||||
pg_pool,
|
||||
node_controller,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pub mod api;
|
||||
pub mod context;
|
||||
|
||||
mod node;
|
||||
mod user;
|
||||
mod version;
|
||||
|
|
43
controller/src/node/api.rs
Normal file
43
controller/src/node/api.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use dropshot::{endpoint, HttpError, HttpResponseOk, Query, RequestContext};
|
||||
use dropshot::{ApiDescription, ApiDescriptionRegisterError};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
use trace_request::trace_request;
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::context::ControllerContext;
|
||||
use crate::node::controller::RegistrationInfo;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Default, Deserialize, JsonSchema, Serialize)]
|
||||
pub struct RegistrationParams {
|
||||
#[serde_as(as = "serde_with::base64::Base64<serde_with::base64::UrlSafe>")]
|
||||
#[schemars(with = "String")]
|
||||
key: [u8; 32],
|
||||
}
|
||||
|
||||
pub fn register_api(
|
||||
api: &mut ApiDescription<Arc<ControllerContext>>,
|
||||
) -> Result<(), ApiDescriptionRegisterError> {
|
||||
api.register(get_registration_info)
|
||||
}
|
||||
|
||||
/// Get registration info
|
||||
#[endpoint {
|
||||
method = GET,
|
||||
path = "/nodes/register",
|
||||
tags = [ "node" ],
|
||||
}]
|
||||
#[trace_request]
|
||||
async fn get_registration_info(
|
||||
rqctx: RequestContext<Arc<ControllerContext>>,
|
||||
params: Query<RegistrationParams>,
|
||||
) -> Result<HttpResponseOk<RegistrationInfo>, HttpError> {
|
||||
let key = PublicKey::from(params.into_inner().key);
|
||||
tracing::debug!("Registration info for public key: {:?}", key);
|
||||
let info = rqctx.context().node_controller.new_registration(&key);
|
||||
Ok(HttpResponseOk(info))
|
||||
}
|
179
controller/src/node/controller.rs
Normal file
179
controller/src/node/controller.rs
Normal file
|
@ -0,0 +1,179 @@
|
|||
use chrono::DateTime;
|
||||
use ed25519_dalek::ed25519::signature::SignerMut;
|
||||
use hkdf::Hkdf;
|
||||
use rand::Rng;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
use sha2::Sha256;
|
||||
use sqlx::postgres::PgPool;
|
||||
use uuid::Uuid;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize, JsonSchema, Serialize)]
|
||||
pub struct RegistrationInfo {
|
||||
node_id: Uuid,
|
||||
challenge: Challenge,
|
||||
|
||||
#[serde_as(as = "serde_with::base64::Base64")]
|
||||
#[schemars(with = "String")]
|
||||
challenge_signature: [u8; 64],
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize, JsonSchema, Serialize)]
|
||||
pub struct Challenge {
|
||||
timestamp: DateTime<chrono::Utc>,
|
||||
|
||||
#[serde_as(as = "serde_with::base64::Base64")]
|
||||
#[schemars(with = "String")]
|
||||
nonce: [u8; 32],
|
||||
|
||||
#[serde_as(as = "serde_with::base64::Base64")]
|
||||
#[schemars(with = "String")]
|
||||
server_dh_pub: [u8; 32],
|
||||
|
||||
params: ArgonParams,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize, JsonSchema, Serialize)]
|
||||
struct ArgonParams {
|
||||
iterations: u32,
|
||||
memory_kb: u32,
|
||||
threads: u32,
|
||||
}
|
||||
|
||||
pub struct NodeController {
|
||||
_pg_pool: PgPool,
|
||||
signing_key: RwLock<Option<Arc<SecretKey>>>,
|
||||
}
|
||||
|
||||
type SecretKey = [u8; 32];
|
||||
type Nonce256 = [u8; 32];
|
||||
|
||||
trait SecretKeyExt {
|
||||
fn derive_dh_secret(&self, nonce: &Nonce256) -> StaticSecret;
|
||||
}
|
||||
|
||||
impl SecretKeyExt for SecretKey {
|
||||
fn derive_dh_secret(&self, nonce: &Nonce256) -> StaticSecret {
|
||||
let hk = Hkdf::<Sha256>::new(Some(nonce), self);
|
||||
let mut out = [0u8; 32];
|
||||
hk.expand(b"x25519-secret", &mut out).unwrap();
|
||||
StaticSecret::from(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistrationInfo {
|
||||
pub fn from(server_secret: &SecretKey, client_dh_public_key: &PublicKey) -> RegistrationInfo {
|
||||
let mut rng = rand::thread_rng();
|
||||
let nonce = rng.gen();
|
||||
|
||||
let server_dh_secret = server_secret.derive_dh_secret(&nonce);
|
||||
let _shared_secret = server_dh_secret.diffie_hellman(client_dh_public_key);
|
||||
let server_dh_public_key = PublicKey::from(&server_dh_secret);
|
||||
|
||||
let timestamp = chrono::Utc::now();
|
||||
|
||||
let params = ArgonParams {
|
||||
iterations: 10,
|
||||
memory_kb: 220 * 1024, // 220 MiB
|
||||
threads: 4,
|
||||
};
|
||||
|
||||
let challenge = Challenge {
|
||||
timestamp,
|
||||
nonce,
|
||||
server_dh_pub: server_dh_public_key.to_bytes(),
|
||||
params,
|
||||
};
|
||||
|
||||
let challenge_json = serde_json::to_vec(&challenge).unwrap();
|
||||
|
||||
let mut sk = ed25519_dalek::SigningKey::from_bytes(server_secret);
|
||||
let sig = sk.sign(challenge_json.as_slice());
|
||||
|
||||
let info = RegistrationInfo {
|
||||
node_id: Uuid::new_v4(),
|
||||
challenge,
|
||||
challenge_signature: sig.to_bytes(),
|
||||
};
|
||||
|
||||
tracing::debug!("Info: {:?}", info);
|
||||
info
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeController {
|
||||
pub fn new(pg_pool: PgPool) -> NodeController {
|
||||
NodeController {
|
||||
_pg_pool: pg_pool,
|
||||
signing_key: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registration(&self, client_dh_public_key: &PublicKey) -> RegistrationInfo {
|
||||
let server_secret = self.master_key();
|
||||
RegistrationInfo::from(&server_secret, client_dh_public_key)
|
||||
}
|
||||
|
||||
fn master_key(&self) -> Arc<SecretKey> {
|
||||
self.signing_key
|
||||
.write()
|
||||
.unwrap()
|
||||
.get_or_insert_with(|| {
|
||||
tracing::debug!("Generating key");
|
||||
Arc::new(rand::rngs::OsRng.gen())
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_registration_info() {
|
||||
let client_key: SecretKey = rand::rngs::OsRng.gen();
|
||||
let server_key = rand::rngs::OsRng.gen();
|
||||
let client_public_key = PublicKey::from(client_key);
|
||||
|
||||
let info = RegistrationInfo::from(&server_key, &client_public_key);
|
||||
let info2 = RegistrationInfo::from(&server_key, &client_public_key);
|
||||
|
||||
// Randomness
|
||||
assert_ne!(info.node_id, info2.node_id, "Node ID should be unique");
|
||||
assert_ne!(
|
||||
info.challenge.nonce, info2.challenge.nonce,
|
||||
"Nonce should be unique"
|
||||
);
|
||||
assert_ne!(
|
||||
info.challenge.server_dh_pub, info2.challenge.server_dh_pub,
|
||||
"Server public key derived from nonce should be unique"
|
||||
);
|
||||
|
||||
// Timestamp
|
||||
let now = chrono::Utc::now();
|
||||
assert!(
|
||||
info.challenge.timestamp <= now,
|
||||
"Timestamp is in the future"
|
||||
);
|
||||
assert!(
|
||||
info.challenge.timestamp >= now - chrono::Duration::milliseconds(5),
|
||||
"Timestamp is too old"
|
||||
);
|
||||
|
||||
// Public key
|
||||
let server_dh_secret = server_key.derive_dh_secret(&info.challenge.nonce);
|
||||
let server_dh_public_key = PublicKey::from(&server_dh_secret).to_bytes();
|
||||
assert_eq!(
|
||||
info.challenge.server_dh_pub, server_dh_public_key,
|
||||
"Server public key should be derived from nonce"
|
||||
);
|
||||
}
|
||||
}
|
5
controller/src/node/mod.rs
Normal file
5
controller/src/node/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod api;
|
||||
mod controller;
|
||||
|
||||
pub use self::api::register_api;
|
||||
pub(crate) use self::controller::NodeController;
|
|
@ -167,6 +167,7 @@
|
|||
rust-dev-toolchain
|
||||
sqls
|
||||
sqlx-cli
|
||||
tpm2-tools
|
||||
watchexec
|
||||
]
|
||||
++ commonArgs.buildInputs;
|
||||
|
|
22
node-registration/Cargo.toml
Normal file
22
node-registration/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "node-registration"
|
||||
edition = "2023"
|
||||
license = "MPL-2.0"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
clap.workspace = true
|
||||
futures.workspace = true
|
||||
instrumentation = { path = "../instrumentation" }
|
||||
progenitor.workspace = true
|
||||
reqwest.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["reqwest", "serde"]
|
Loading…
Add table
Add a link
Reference in a new issue