mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-26 23:27:14 +03:00
Merge branch 'develop' into dr/settings-page
This commit is contained in:
commit
08eb7d2297
@ -1,11 +1,12 @@
|
||||
use crate::http::server_types::*;
|
||||
use crate::http::utils::*;
|
||||
use crate::{keygen, register};
|
||||
use crate::keygen;
|
||||
use anyhow::Result;
|
||||
use base64::{engine::general_purpose::STANDARD as base64_standard, Engine};
|
||||
use dashmap::DashMap;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use http::uri::Authority;
|
||||
use lib::types::core::*;
|
||||
use route_recognizer::Router;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashMap;
|
||||
@ -16,8 +17,6 @@ use warp::http::{header::HeaderValue, StatusCode};
|
||||
use warp::ws::{WebSocket, Ws};
|
||||
use warp::{Filter, Reply};
|
||||
|
||||
use lib::types::core::*;
|
||||
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
const HTTP_SELF_IMPOSED_TIMEOUT: u64 = 15;
|
||||
#[cfg(feature = "simulation-mode")]
|
||||
@ -332,7 +331,7 @@ async fn login_handler(
|
||||
|
||||
match keygen::decode_keyfile(&encoded_keyfile, &info.password_hash) {
|
||||
Ok(keyfile) => {
|
||||
let token = match register::generate_jwt(&keyfile.jwt_secret_bytes, our.as_ref()) {
|
||||
let token = match keygen::generate_jwt(&keyfile.jwt_secret_bytes, our.as_ref()) {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Ok(warp::reply::with_status(
|
||||
|
@ -2,14 +2,19 @@ use aes_gcm::{
|
||||
aead::{Aead, AeadCore, KeyInit, OsRng},
|
||||
Aes256Gcm, Key,
|
||||
};
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
use alloy_primitives::keccak256;
|
||||
use anyhow::Result;
|
||||
use digest::generic_array::GenericArray;
|
||||
use hmac::Hmac;
|
||||
use jwt::SignWithKey;
|
||||
use lib::types::core::Keyfile;
|
||||
use ring::pbkdf2;
|
||||
use ring::pkcs8::Document;
|
||||
use ring::rand::SystemRandom;
|
||||
use ring::signature::{self, KeyPair};
|
||||
use ring::{digest as ring_digest, rand::SecureRandom};
|
||||
use sha2::Sha256;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
type DiskKey = [u8; CREDENTIAL_LEN];
|
||||
@ -108,6 +113,24 @@ pub fn decode_keyfile(keyfile: &[u8], password: &str) -> Result<Keyfile, &'stati
|
||||
})
|
||||
}
|
||||
|
||||
pub fn generate_jwt(jwt_secret_bytes: &[u8], username: &str) -> Option<String> {
|
||||
let jwt_secret: Hmac<Sha256> = match Hmac::new_from_slice(jwt_secret_bytes) {
|
||||
Ok(secret) => secret,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let claims = crate::http::server_types::JwtClaims {
|
||||
username: username.to_string(),
|
||||
expiration: 0,
|
||||
};
|
||||
|
||||
match claims.sign_with_key(&jwt_secret) {
|
||||
Ok(token) => Some(token),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
pub fn get_username_and_routers(keyfile: &[u8]) -> Result<(String, Vec<String>), &'static str> {
|
||||
let (username, routers, _salt, _key_enc, _jwt_enc) =
|
||||
bincode::deserialize::<(String, Vec<String>, Vec<u8>, Vec<u8>, Vec<u8>)>(keyfile)
|
||||
@ -116,6 +139,7 @@ pub fn get_username_and_routers(keyfile: &[u8]) -> Result<(String, Vec<String>),
|
||||
Ok((username, routers))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
pub fn namehash(name: &str) -> Vec<u8> {
|
||||
let mut node = vec![0u8; 32];
|
||||
if name.is_empty() {
|
||||
|
@ -1,6 +1,5 @@
|
||||
#![feature(async_closure)]
|
||||
#![feature(btree_extract_if)]
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{arg, value_parser, Command};
|
||||
use lib::types::core::*;
|
||||
@ -8,8 +7,7 @@ use lib::types::core::*;
|
||||
use ring::{rand::SystemRandom, signature, signature::KeyPair};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::{fs, time::timeout};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
mod eth;
|
||||
mod http;
|
||||
@ -17,6 +15,7 @@ mod kernel;
|
||||
mod keygen;
|
||||
mod kv;
|
||||
mod net;
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
mod register;
|
||||
mod sqlite;
|
||||
mod state;
|
||||
@ -35,113 +34,33 @@ const VFS_CHANNEL_CAPACITY: usize = 1_000;
|
||||
const CAP_CHANNEL_CAPACITY: usize = 1_000;
|
||||
const KV_CHANNEL_CAPACITY: usize = 1_000;
|
||||
const SQLITE_CHANNEL_CAPACITY: usize = 1_000;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// default routers as a eth-provider fallback
|
||||
const DEFAULT_PROVIDERS_MAINNET: &str = include_str!("eth/default_providers_mainnet.json");
|
||||
|
||||
async fn serve_register_fe(
|
||||
home_directory_path: &str,
|
||||
our_ip: String,
|
||||
ws_networking: (tokio::net::TcpListener, bool),
|
||||
http_server_port: u16,
|
||||
maybe_rpc: Option<String>,
|
||||
) -> (Identity, Vec<u8>, Keyfile) {
|
||||
// check if we have keys saved on disk, encrypted
|
||||
// if so, prompt user for "password" to decrypt with
|
||||
|
||||
// once password is received, use to decrypt local keys file,
|
||||
// and pass the keys into boot process as is done in registration.
|
||||
|
||||
// NOTE: when we log in, we MUST check the PKI to make sure our
|
||||
// information matches what we think it should be. this includes
|
||||
// username, networking key, and routing info.
|
||||
// if any do not match, we should prompt user to create a "transaction"
|
||||
// that updates their PKI info on-chain.
|
||||
let (kill_tx, kill_rx) = oneshot::channel::<bool>();
|
||||
|
||||
let disk_keyfile: Option<Vec<u8>> = fs::read(format!("{}/.keys", home_directory_path))
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let (tx, mut rx) = mpsc::channel::<(Identity, Keyfile, Vec<u8>)>(1);
|
||||
let (our, decoded_keyfile, encoded_keyfile) = tokio::select! {
|
||||
_ = register::register(
|
||||
tx,
|
||||
kill_rx,
|
||||
our_ip,
|
||||
ws_networking,
|
||||
http_server_port,
|
||||
disk_keyfile,
|
||||
maybe_rpc) => {
|
||||
panic!("registration failed")
|
||||
}
|
||||
Some((our, decoded_keyfile, encoded_keyfile)) = rx.recv() => {
|
||||
(our, decoded_keyfile, encoded_keyfile)
|
||||
}
|
||||
};
|
||||
|
||||
fs::write(
|
||||
format!("{}/.keys", home_directory_path),
|
||||
encoded_keyfile.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _ = kill_tx.send(true);
|
||||
|
||||
(our, encoded_keyfile, decoded_keyfile)
|
||||
}
|
||||
const DEFAULT_ETH_PROVIDERS: &str = include_str!("eth/default_providers_mainnet.json");
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
const CHAIN_ID: u64 = 10;
|
||||
#[cfg(feature = "simulation-mode")]
|
||||
const CHAIN_ID: u64 = 31337;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let app = Command::new("kinode")
|
||||
.version(VERSION)
|
||||
.author("Kinode DAO: https://github.com/kinode-dao")
|
||||
.about("A General Purpose Sovereign Cloud Computing Platform")
|
||||
.arg(arg!([home] "Path to home directory").required(true))
|
||||
.arg(
|
||||
arg!(--port <PORT> "Port to bind [default: first unbound at or above 8080]")
|
||||
.value_parser(value_parser!(u16)),
|
||||
)
|
||||
.arg(
|
||||
arg!(--"ws-port" <PORT> "Kinode internal WebSockets protocol port [default: first unbound at or above 9000]")
|
||||
.alias("network-router-port")
|
||||
.value_parser(value_parser!(u16)),
|
||||
)
|
||||
.arg(
|
||||
arg!(--verbosity <VERBOSITY> "Verbosity level: higher is more verbose")
|
||||
.default_value("0")
|
||||
.value_parser(value_parser!(u8)),
|
||||
)
|
||||
.arg(
|
||||
arg!(--"reveal-ip" "If set to false, as an indirect node, always use routers to connect to other nodes.")
|
||||
.default_value("true")
|
||||
.value_parser(value_parser!(bool)),
|
||||
)
|
||||
.arg(arg!(--rpc <RPC> "Add a WebSockets RPC URL at boot"));
|
||||
|
||||
#[cfg(feature = "simulation-mode")]
|
||||
let app = app
|
||||
.arg(arg!(--password <PASSWORD> "Networking password"))
|
||||
.arg(arg!(--"fake-node-name" <NAME> "Name of fake node to boot"))
|
||||
.arg(
|
||||
arg!(--detached <IS_DETACHED> "Run in detached mode (don't accept input)")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
);
|
||||
// add arg for fakechain bootup w/ kit?
|
||||
let fakenode = cfg!(feature = "simulation-mode");
|
||||
let app = build_command();
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
||||
let home_directory_path = matches.get_one::<String>("home").unwrap();
|
||||
|
||||
let http_port = matches.get_one::<u16>("port");
|
||||
let home_directory_path = matches
|
||||
.get_one::<String>("home")
|
||||
.expect("home directory required");
|
||||
create_home_directory(&home_directory_path).await;
|
||||
let http_server_port = set_http_server_port(matches.get_one::<u16>("port")).await;
|
||||
let ws_networking_port = matches.get_one::<u16>("ws-port");
|
||||
let verbose_mode = *matches
|
||||
.get_one::<u8>("verbosity")
|
||||
.expect("verbosity required");
|
||||
|
||||
// if we are in sim-mode, detached determines whether terminal is interactive
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
let is_detached = false;
|
||||
|
||||
#[cfg(feature = "simulation-mode")]
|
||||
let (password, fake_node_name, is_detached) = (
|
||||
matches.get_one::<String>("password"),
|
||||
@ -149,36 +68,18 @@ async fn main() {
|
||||
*matches.get_one::<bool>("detached").unwrap(),
|
||||
);
|
||||
|
||||
let verbose_mode = *matches.get_one::<u8>("verbosity").unwrap();
|
||||
|
||||
// check .testnet file for true/false in order to enforce testnet mode on subsequent boots of this node
|
||||
match fs::read(format!("{}/.testnet", home_directory_path)).await {
|
||||
Ok(contents) => {
|
||||
if contents == b"true" {
|
||||
println!("\x1b[38;5;196mfatal: this is a deprecated testnet node, either boot a fakenode or a real one. exiting.\x1b[0m");
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Err(e) = fs::create_dir_all(home_directory_path).await {
|
||||
panic!("failed to create home directory: {:?}", e);
|
||||
}
|
||||
println!("home at {}\r", home_directory_path);
|
||||
|
||||
// default eth providers/routers
|
||||
let mut eth_provider_config: lib::eth::SavedConfigs =
|
||||
match fs::read_to_string(format!("{}/.eth_providers", home_directory_path)).await {
|
||||
match tokio::fs::read_to_string(format!("{}/.eth_providers", home_directory_path)).await {
|
||||
Ok(contents) => {
|
||||
println!("loaded saved eth providers\r");
|
||||
serde_json::from_str(&contents).unwrap()
|
||||
}
|
||||
Err(_) => serde_json::from_str(DEFAULT_PROVIDERS_MAINNET).unwrap(),
|
||||
Err(_) => serde_json::from_str(DEFAULT_ETH_PROVIDERS).unwrap(),
|
||||
};
|
||||
if let Some(rpc) = matches.get_one::<String>("rpc") {
|
||||
eth_provider_config.push(lib::eth::ProviderConfig {
|
||||
chain_id: if fakenode { 31337 } else { 10 },
|
||||
chain_id: CHAIN_ID,
|
||||
trusted: true,
|
||||
provider: lib::eth::NodeOrRpcUrl::RpcUrl(rpc.to_string()),
|
||||
});
|
||||
@ -227,115 +128,90 @@ async fn main() {
|
||||
let (print_sender, print_receiver): (PrintSender, PrintReceiver) =
|
||||
mpsc::channel(TERMINAL_CHANNEL_CAPACITY);
|
||||
|
||||
println!("finding public IP address...");
|
||||
let our_ip: std::net::Ipv4Addr = {
|
||||
if let Ok(Some(ip)) = timeout(std::time::Duration::from_secs(5), public_ip::addr_v4()).await
|
||||
{
|
||||
ip
|
||||
let our;
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
let our_ip: std::net::Ipv4Addr;
|
||||
let encoded_keyfile;
|
||||
let decoded_keyfile;
|
||||
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
{
|
||||
println!("finding public IP address...");
|
||||
our_ip = {
|
||||
if let Ok(Some(ip)) =
|
||||
tokio::time::timeout(std::time::Duration::from_secs(5), public_ip::addr_v4()).await
|
||||
{
|
||||
ip
|
||||
} else {
|
||||
println!("failed to find public IPv4 address: booting as a routed node");
|
||||
std::net::Ipv4Addr::LOCALHOST
|
||||
}
|
||||
};
|
||||
// if the --ws-port flag is used, bind to that port right away.
|
||||
// if the flag is not used, find the first available port between 9000 and 65535.
|
||||
// NOTE: if the node has a different port specified in its onchain (direct) id,
|
||||
// booting will fail if the flag was used to select a different port.
|
||||
// if the flag was not used, the bound port will be dropped in favor of the onchain port.
|
||||
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
let (ws_tcp_handle, flag_used) = if let Some(port) = ws_networking_port {
|
||||
(
|
||||
http::utils::find_open_port(*port, port + 1)
|
||||
.await
|
||||
.expect("ws-port selected with flag could not be bound"),
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
println!("failed to find public IPv4 address: booting as a routed node");
|
||||
std::net::Ipv4Addr::LOCALHOST
|
||||
}
|
||||
};
|
||||
(
|
||||
http::utils::find_open_port(9000, 65535)
|
||||
.await
|
||||
.expect("no ports found in range 9000-65535 for websocket server"),
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
let http_server_port = if let Some(port) = http_port {
|
||||
match http::utils::find_open_port(*port, port + 1).await {
|
||||
Some(bound) => bound.local_addr().unwrap().port(),
|
||||
None => {
|
||||
println!(
|
||||
"error: couldn't bind {}; first available port found was {}. \
|
||||
Set an available port with `--port` and try again.",
|
||||
port,
|
||||
http::utils::find_open_port(*port, port + 1000)
|
||||
.await
|
||||
.expect("no ports found in range")
|
||||
.local_addr()
|
||||
.unwrap()
|
||||
.port(),
|
||||
);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match http::utils::find_open_port(8080, 8999).await {
|
||||
Some(bound) => bound.local_addr().unwrap().port(),
|
||||
None => {
|
||||
println!(
|
||||
"error: couldn't bind any ports between 8080 and 8999. \
|
||||
Set an available port with `--port` and try again."
|
||||
);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
};
|
||||
println!(
|
||||
"login or register at http://localhost:{}\r",
|
||||
http_server_port
|
||||
);
|
||||
|
||||
// if the --ws-port flag is used, bind to that port right away.
|
||||
// if the flag is not used, find the first available port between 9000 and 65535.
|
||||
// NOTE: if the node has a different port specified in its onchain (direct) id,
|
||||
// booting will fail if the flag was used to select a different port.
|
||||
// if the flag was not used, the bound port will be dropped in favor of the onchain port.
|
||||
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
let (ws_tcp_handle, flag_used) = if let Some(port) = ws_networking_port {
|
||||
(
|
||||
http::utils::find_open_port(*port, port + 1)
|
||||
.await
|
||||
.expect("ws-port selected with flag could not be bound"),
|
||||
true,
|
||||
(our, encoded_keyfile, decoded_keyfile) = serve_register_fe(
|
||||
home_directory_path,
|
||||
our_ip.to_string(),
|
||||
(ws_tcp_handle, flag_used),
|
||||
http_server_port,
|
||||
matches.get_one::<String>("rpc").cloned(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
http::utils::find_open_port(9000, 65535)
|
||||
.await
|
||||
.expect("no ports found in range 9000-65535 for websocket server"),
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
println!(
|
||||
"login or register at http://localhost:{}\r",
|
||||
http_server_port
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
let (our, encoded_keyfile, decoded_keyfile) = serve_register_fe(
|
||||
home_directory_path,
|
||||
our_ip.to_string(),
|
||||
(ws_tcp_handle, flag_used),
|
||||
http_server_port,
|
||||
matches.get_one::<String>("rpc").cloned(),
|
||||
)
|
||||
.await;
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "simulation-mode")]
|
||||
let (our, encoded_keyfile, decoded_keyfile) = match fake_node_name {
|
||||
match fake_node_name {
|
||||
None => {
|
||||
match password {
|
||||
None => {
|
||||
panic!("Fake node must be booted with either a --fake-node-name, --password, or both.");
|
||||
}
|
||||
Some(password) => {
|
||||
match fs::read(format!("{}/.keys", home_directory_path)).await {
|
||||
match tokio::fs::read(format!("{}/.keys", home_directory_path)).await {
|
||||
Err(e) => panic!("could not read keyfile: {}", e),
|
||||
Ok(keyfile) => {
|
||||
match keygen::decode_keyfile(&keyfile, &password) {
|
||||
Err(e) => panic!("could not decode keyfile: {}", e),
|
||||
Ok(decoded_keyfile) => {
|
||||
let our = Identity {
|
||||
name: decoded_keyfile.username.clone(),
|
||||
Ok(decoded) => {
|
||||
our = Identity {
|
||||
name: decoded.username.clone(),
|
||||
networking_key: format!(
|
||||
"0x{}",
|
||||
hex::encode(
|
||||
decoded_keyfile
|
||||
.networking_keypair
|
||||
.public_key()
|
||||
.as_ref()
|
||||
decoded.networking_keypair.public_key().as_ref()
|
||||
)
|
||||
),
|
||||
ws_routing: None, // TODO
|
||||
allowed_routers: decoded_keyfile.routers.clone(),
|
||||
allowed_routers: decoded.routers.clone(),
|
||||
};
|
||||
(our, keyfile, decoded_keyfile)
|
||||
decoded_keyfile = decoded;
|
||||
encoded_keyfile = keyfile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -354,14 +230,14 @@ async fn main() {
|
||||
let mut jwt_secret = [0u8, 32];
|
||||
ring::rand::SecureRandom::fill(&seed, &mut jwt_secret).unwrap();
|
||||
|
||||
let our = Identity {
|
||||
our = Identity {
|
||||
name: name.clone(),
|
||||
networking_key: pubkey,
|
||||
ws_routing: None,
|
||||
allowed_routers: vec![],
|
||||
};
|
||||
|
||||
let decoded_keyfile = Keyfile {
|
||||
decoded_keyfile = Keyfile {
|
||||
username: name.clone(),
|
||||
routers: vec![],
|
||||
networking_keypair: signature::Ed25519KeyPair::from_pkcs8(
|
||||
@ -372,7 +248,7 @@ async fn main() {
|
||||
file_key: keygen::generate_file_key(),
|
||||
};
|
||||
|
||||
let encoded_keyfile = keygen::encode_keyfile(
|
||||
encoded_keyfile = keygen::encode_keyfile(
|
||||
password_hash,
|
||||
name.clone(),
|
||||
decoded_keyfile.routers.clone(),
|
||||
@ -381,14 +257,12 @@ async fn main() {
|
||||
&decoded_keyfile.file_key,
|
||||
);
|
||||
|
||||
fs::write(
|
||||
tokio::fs::write(
|
||||
format!("{}/.keys", home_directory_path),
|
||||
encoded_keyfile.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
(our, encoded_keyfile, decoded_keyfile)
|
||||
}
|
||||
};
|
||||
|
||||
@ -495,7 +369,7 @@ async fn main() {
|
||||
.collect(),
|
||||
));
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
tasks.spawn(net::networking(
|
||||
tasks.spawn(net::ws::networking(
|
||||
our.clone(),
|
||||
our_ip.to_string(),
|
||||
networking_keypair_arc.clone(),
|
||||
@ -508,7 +382,7 @@ async fn main() {
|
||||
*matches.get_one::<bool>("reveal-ip").unwrap_or(&true),
|
||||
));
|
||||
#[cfg(feature = "simulation-mode")]
|
||||
tasks.spawn(net::mock_client(
|
||||
tasks.spawn(net::mock::mock_client(
|
||||
*ws_networking_port.unwrap_or(&9000),
|
||||
our.name.clone(),
|
||||
kernel_message_sender.clone(),
|
||||
@ -579,10 +453,11 @@ async fn main() {
|
||||
caps_oracle_sender.clone(),
|
||||
home_directory_path.clone(),
|
||||
));
|
||||
|
||||
// if a runtime task exits, try to recover it,
|
||||
// unless it was terminal signaling a quit
|
||||
// or a SIG* was intercepted
|
||||
let quit_msg: String = tokio::select! {
|
||||
let mut quit_msg: String = tokio::select! {
|
||||
Some(Ok(res)) = tasks.join_next() => {
|
||||
format!(
|
||||
"uh oh, a kernel process crashed -- this should never happen: {:?}",
|
||||
@ -608,7 +483,7 @@ async fn main() {
|
||||
};
|
||||
|
||||
// gracefully abort all running processes in kernel
|
||||
let _ = kernel_message_sender
|
||||
if let Err(_) = kernel_message_sender
|
||||
.send(KernelMessage {
|
||||
id: rand::random(),
|
||||
source: Address {
|
||||
@ -629,18 +504,157 @@ async fn main() {
|
||||
}),
|
||||
lazy_load_blob: None,
|
||||
})
|
||||
.await;
|
||||
.await
|
||||
{
|
||||
quit_msg = "failed to gracefully shut down kernel".into();
|
||||
}
|
||||
|
||||
// abort all remaining tasks
|
||||
tasks.shutdown().await;
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let _ = crossterm::execute!(
|
||||
crossterm::execute!(
|
||||
stdout,
|
||||
crossterm::event::DisableBracketedPaste,
|
||||
crossterm::terminal::SetTitle(""),
|
||||
crossterm::style::SetForegroundColor(crossterm::style::Color::Red),
|
||||
crossterm::style::Print(format!("\r\n{quit_msg}\r\n")),
|
||||
crossterm::style::ResetColor,
|
||||
);
|
||||
)
|
||||
.expect("failed to clean up terminal visual state! your terminal window might be funky now");
|
||||
}
|
||||
|
||||
async fn set_http_server_port(set_port: Option<&u16>) -> u16 {
|
||||
if let Some(port) = set_port {
|
||||
match http::utils::find_open_port(*port, port + 1).await {
|
||||
Some(bound) => bound.local_addr().unwrap().port(),
|
||||
None => {
|
||||
println!(
|
||||
"error: couldn't bind {}; first available port found was {}. \
|
||||
Set an available port with `--port` and try again.",
|
||||
port,
|
||||
http::utils::find_open_port(*port, port + 1000)
|
||||
.await
|
||||
.expect("no ports found in range")
|
||||
.local_addr()
|
||||
.unwrap()
|
||||
.port(),
|
||||
);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match http::utils::find_open_port(8080, 8999).await {
|
||||
Some(bound) => bound.local_addr().unwrap().port(),
|
||||
None => {
|
||||
println!(
|
||||
"error: couldn't bind any ports between 8080 and 8999. \
|
||||
Set an available port with `--port` and try again."
|
||||
);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_home_directory(home_directory_path: &str) {
|
||||
if let Err(e) = tokio::fs::create_dir_all(home_directory_path).await {
|
||||
panic!("failed to create home directory: {:?}", e);
|
||||
}
|
||||
println!("home at {}\r", home_directory_path);
|
||||
}
|
||||
|
||||
/// build the command line interface for kinode
|
||||
///
|
||||
/// TODO: add arg for fakechain bootup w/ kit?
|
||||
fn build_command() -> Command {
|
||||
let app = Command::new("kinode")
|
||||
.version(VERSION)
|
||||
.author("Kinode DAO: https://github.com/kinode-dao")
|
||||
.about("A General Purpose Sovereign Cloud Computing Platform")
|
||||
.arg(arg!([home] "Path to home directory").required(true))
|
||||
.arg(
|
||||
arg!(--port <PORT> "Port to bind [default: first unbound at or above 8080]")
|
||||
.value_parser(value_parser!(u16)),
|
||||
)
|
||||
.arg(
|
||||
arg!(--"ws-port" <PORT> "Kinode internal WebSockets protocol port [default: first unbound at or above 9000]")
|
||||
.alias("network-router-port")
|
||||
.value_parser(value_parser!(u16)),
|
||||
)
|
||||
.arg(
|
||||
arg!(--verbosity <VERBOSITY> "Verbosity level: higher is more verbose")
|
||||
.default_value("0")
|
||||
.value_parser(value_parser!(u8)),
|
||||
)
|
||||
.arg(
|
||||
arg!(--"reveal-ip" "If set to false, as an indirect node, always use routers to connect to other nodes.")
|
||||
.default_value("true")
|
||||
.value_parser(value_parser!(bool)),
|
||||
)
|
||||
.arg(arg!(--rpc <RPC> "Add a WebSockets RPC URL at boot"));
|
||||
|
||||
#[cfg(feature = "simulation-mode")]
|
||||
let app = app
|
||||
.arg(arg!(--password <PASSWORD> "Networking password"))
|
||||
.arg(arg!(--"fake-node-name" <NAME> "Name of fake node to boot"))
|
||||
.arg(
|
||||
arg!(--detached <IS_DETACHED> "Run in detached mode (don't accept input)")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
);
|
||||
app
|
||||
}
|
||||
|
||||
/// check if we have keys saved on disk, encrypted
|
||||
/// if so, prompt user for "password" to decrypt with
|
||||
///
|
||||
/// once password is received, use to decrypt local keys file,
|
||||
/// and pass the keys into boot process as is done in registration.
|
||||
///
|
||||
/// NOTE: when we log in, we MUST check the PKI to make sure our
|
||||
/// information matches what we think it should be. this includes
|
||||
/// username, networking key, and routing info.
|
||||
/// if any do not match, we should prompt user to create a "transaction"
|
||||
/// that updates their PKI info on-chain.
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
async fn serve_register_fe(
|
||||
home_directory_path: &str,
|
||||
our_ip: String,
|
||||
ws_networking: (tokio::net::TcpListener, bool),
|
||||
http_server_port: u16,
|
||||
maybe_rpc: Option<String>,
|
||||
) -> (Identity, Vec<u8>, Keyfile) {
|
||||
let (kill_tx, kill_rx) = tokio::sync::oneshot::channel::<bool>();
|
||||
|
||||
let disk_keyfile: Option<Vec<u8>> = tokio::fs::read(format!("{}/.keys", home_directory_path))
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let (tx, mut rx) = mpsc::channel::<(Identity, Keyfile, Vec<u8>)>(1);
|
||||
let (our, decoded_keyfile, encoded_keyfile) = tokio::select! {
|
||||
_ = register::register(
|
||||
tx,
|
||||
kill_rx,
|
||||
our_ip,
|
||||
ws_networking,
|
||||
http_server_port,
|
||||
disk_keyfile,
|
||||
maybe_rpc) => {
|
||||
panic!("registration failed")
|
||||
}
|
||||
Some((our, decoded_keyfile, encoded_keyfile)) = rx.recv() => {
|
||||
(our, decoded_keyfile, encoded_keyfile)
|
||||
}
|
||||
};
|
||||
|
||||
tokio::fs::write(
|
||||
format!("{}/.keys", home_directory_path),
|
||||
encoded_keyfile.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _ = kill_tx.send(true);
|
||||
|
||||
(our, encoded_keyfile, decoded_keyfile)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,8 @@
|
||||
use crate::net::{types::*, MESSAGE_MAX_SIZE, TIMEOUT};
|
||||
use crate::net::{types::*, ws::MESSAGE_MAX_SIZE, ws::TIMEOUT};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use lib::types::core::*;
|
||||
use ring::signature::{self, Ed25519KeyPair};
|
||||
use snow::params::NoiseParams;
|
||||
use tokio::net::TcpStream;
|
||||
@ -9,8 +10,6 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
|
||||
use tokio::time::timeout;
|
||||
use tokio_tungstenite::{connect_async, tungstenite, MaybeTlsStream, WebSocketStream};
|
||||
|
||||
use lib::types::core::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref PARAMS: NoiseParams = "Noise_XX_25519_ChaChaPoly_BLAKE2s"
|
||||
.parse()
|
||||
@ -264,7 +263,7 @@ pub fn validate_signature(from: &str, signature: &[u8], message: &[u8], pki: &On
|
||||
if let Some(peer_id) = pki.get(from) {
|
||||
let their_networking_key = signature::UnparsedPublicKey::new(
|
||||
&signature::ED25519,
|
||||
hex::decode(strip_0x(&peer_id.networking_key)).unwrap_or_default(),
|
||||
net_key_string_to_hex(&peer_id.networking_key),
|
||||
);
|
||||
their_networking_key.verify(message, signature).is_ok()
|
||||
} else {
|
||||
@ -283,7 +282,7 @@ pub fn validate_routing_request(
|
||||
.ok_or(anyhow!("unknown KNS name"))?;
|
||||
let their_networking_key = signature::UnparsedPublicKey::new(
|
||||
&signature::ED25519,
|
||||
hex::decode(strip_0x(&their_id.networking_key))?,
|
||||
net_key_string_to_hex(&their_id.networking_key),
|
||||
);
|
||||
their_networking_key
|
||||
.verify(
|
||||
@ -308,7 +307,7 @@ pub fn validate_handshake(
|
||||
// verify their signature of their static key
|
||||
let their_networking_key = signature::UnparsedPublicKey::new(
|
||||
&signature::ED25519,
|
||||
hex::decode(strip_0x(&their_id.networking_key))?,
|
||||
net_key_string_to_hex(&their_id.networking_key),
|
||||
);
|
||||
their_networking_key
|
||||
.verify(their_static_key, &handshake.signature)
|
||||
@ -342,10 +341,10 @@ pub async fn recv_protocol_message(conn: &mut PeerConnection) -> Result<KernelMe
|
||||
&ws_recv(&mut conn.read_stream, &mut conn.write_stream).await?,
|
||||
&mut conn.buf,
|
||||
)?;
|
||||
|
||||
if outer_len < 4 {
|
||||
return Err(anyhow!("protocol message too small!"));
|
||||
}
|
||||
|
||||
let length_bytes = [conn.buf[0], conn.buf[1], conn.buf[2], conn.buf[3]];
|
||||
let msg_len = u32::from_be_bytes(length_bytes);
|
||||
if msg_len > MESSAGE_MAX_SIZE {
|
||||
@ -444,7 +443,7 @@ pub fn build_initiator() -> (snow::HandshakeState, Vec<u8>) {
|
||||
builder
|
||||
.local_private_key(&keypair.private)
|
||||
.build_initiator()
|
||||
.expect("net: couldn't build responder?"),
|
||||
.expect("net: couldn't build initiator?"),
|
||||
keypair.public,
|
||||
)
|
||||
}
|
||||
@ -473,11 +472,8 @@ pub async fn error_offline(km: KernelMessage, network_error_tx: &NetworkErrorSen
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn strip_0x(s: &str) -> String {
|
||||
if let Some(stripped) = s.strip_prefix("0x") {
|
||||
return stripped.to_string();
|
||||
}
|
||||
s.to_string()
|
||||
fn net_key_string_to_hex(s: &str) -> Vec<u8> {
|
||||
hex::decode(s.strip_prefix("0x").unwrap_or(s)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn parse_hello_message(
|
||||
|
1112
kinode/src/net/ws.rs
Normal file
1112
kinode/src/net/ws.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,4 @@
|
||||
use crate::keygen;
|
||||
use aes_gcm::aead::KeyInit;
|
||||
use alloy_primitives::{Address as EthAddress, Bytes, FixedBytes, U256};
|
||||
use alloy_providers::provider::{Provider, TempProvider};
|
||||
use alloy_pubsub::PubSubFrontend;
|
||||
@ -10,13 +9,10 @@ use alloy_sol_macro::sol;
|
||||
use alloy_sol_types::{SolCall, SolValue};
|
||||
use alloy_transport_ws::WsConnect;
|
||||
use base64::{engine::general_purpose::STANDARD as base64_standard, Engine};
|
||||
use hmac::Hmac;
|
||||
use jwt::SignWithKey;
|
||||
use lib::types::core::*;
|
||||
use ring::rand::SystemRandom;
|
||||
use ring::signature;
|
||||
use ring::signature::KeyPair;
|
||||
use sha2::Sha256;
|
||||
use static_dir::static_dir;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
@ -96,23 +92,6 @@ fn _hex_string_to_u8_array(hex_str: &str) -> Result<[u8; 32], &'static str> {
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
pub fn generate_jwt(jwt_secret_bytes: &[u8], username: &str) -> Option<String> {
|
||||
let jwt_secret: Hmac<Sha256> = match Hmac::new_from_slice(jwt_secret_bytes) {
|
||||
Ok(secret) => secret,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let claims = crate::http::server_types::JwtClaims {
|
||||
username: username.to_string(),
|
||||
expiration: 0,
|
||||
};
|
||||
|
||||
match claims.sign_with_key(&jwt_secret) {
|
||||
Ok(token) => Some(token),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serve the registration page and receive POSTs and PUTs from it
|
||||
pub async fn register(
|
||||
tx: RegistrationSender,
|
||||
@ -766,7 +745,7 @@ async fn success_response(
|
||||
encoded_keyfile: Vec<u8>,
|
||||
) -> Result<warp::reply::Response, Rejection> {
|
||||
let encoded_keyfile_str = base64_standard.encode(&encoded_keyfile);
|
||||
let token = match generate_jwt(&decoded_keyfile.jwt_secret_bytes, &our.name) {
|
||||
let token = match keygen::generate_jwt(&decoded_keyfile.jwt_secret_bytes, &our.name) {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Ok(warp::reply::with_status(
|
||||
|
@ -391,11 +391,11 @@ async fn bootstrap(
|
||||
|
||||
for (package_metadata, mut package) in packages.clone() {
|
||||
let package_name = package_metadata.properties.package_name.as_str();
|
||||
// // special case tester: only load it in if in simulation mode
|
||||
// if package_name == "tester" {
|
||||
// #[cfg(not(feature = "simulation-mode"))]
|
||||
// continue;
|
||||
// }
|
||||
// special case tester: only load it in if in simulation mode
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
if package_name == "tester" {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("fs: handling package {package_name}...\r");
|
||||
let package_publisher = package_metadata.properties.publisher.as_str();
|
||||
@ -610,8 +610,8 @@ async fn bootstrap(
|
||||
for (package_metadata, mut package) in packages {
|
||||
let package_name = package_metadata.properties.package_name.as_str();
|
||||
// special case tester: only load it in if in simulation mode
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
if package_name == "tester" {
|
||||
#[cfg(not(feature = "simulation-mode"))]
|
||||
continue;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user