From 52453d1320a74319e69bf105b9bc52cc3d5d7f9c Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Sun, 21 Sep 2025 15:26:18 -0700 Subject: [PATCH] set default allowed periphery public key --- Cargo.lock | 1 + bin/cli/src/command/key.rs | 2 +- bin/core/src/api/write/build.rs | 3 ++- bin/core/src/config.rs | 1 + bin/core/src/helpers/builder.rs | 6 +++--- bin/core/src/main.rs | 2 +- bin/core/src/ws/periphery.rs | 1 + bin/periphery/Cargo.toml | 1 + bin/periphery/src/config.rs | 9 +++++++++ bin/periphery/src/connection/server.rs | 1 - bin/periphery/src/main.rs | 4 ++++ client/core/rs/src/entities/config/core.rs | 17 +++++++++++++++-- client/core/rs/src/entities/config/periphery.rs | 9 +++++++++ client/periphery/rs/src/connection/client.rs | 8 ++++---- client/periphery/rs/src/connection/mod.rs | 13 ++++++++----- client/periphery/rs/src/connection/server.rs | 11 +++++++++-- client/periphery/rs/src/lib.rs | 2 +- client/periphery/rs/src/terminal.rs | 10 ++++++---- .../src/components/resources/builder/config.tsx | 12 ++++++------ .../src/components/resources/server/config.tsx | 6 +++--- 20 files changed, 85 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f027339e..6a658e299 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2971,6 +2971,7 @@ dependencies = [ "interpolate", "komodo_client", "logger", + "noise", "periphery_client", "portable-pty", "resolver_api", diff --git a/bin/cli/src/command/key.rs b/bin/cli/src/command/key.rs index b4555c0ce..00fb02cfd 100644 --- a/bin/cli/src/command/key.rs +++ b/bin/cli/src/command/key.rs @@ -16,7 +16,7 @@ pub async fn handle(command: &KeyCommand) -> anyhow::Result<()> { Ok(()) } KeyCommand::Compute { private_key } => { - let public_key = noise::compute_public_key(&private_key) + let public_key = noise::compute_public_key(private_key) .context("Failed to compute public key")?; println!("\nPublic Key: {}", public_key.green().bold()); Ok(()) diff --git a/bin/core/src/api/write/build.rs b/bin/core/src/api/write/build.rs index d8129ac08..ea19bc155 100644 --- a/bin/core/src/api/write/build.rs +++ b/bin/core/src/api/write/build.rs @@ -7,6 +7,7 @@ use database::{ mongo_indexed::doc, mungos::mongodb::bson::oid::ObjectId, }; use formatting::format_serror; +use komodo_client::entities::optional_string; use komodo_client::{ api::write::*, entities::{ @@ -442,7 +443,7 @@ async fn get_on_host_periphery( } else { config.private_key }, - config.public_key, + optional_string(config.public_key), ) .await?; // Poll for connection to be estalished diff --git a/bin/core/src/config.rs b/bin/core/src/config.rs index 5fe8e4cc1..1277316bb 100644 --- a/bin/core/src/config.rs +++ b/bin/core/src/config.rs @@ -180,6 +180,7 @@ pub fn core_config() -> &'static CoreConfig { }, // Non secrets + periphery_public_key: env.komodo_periphery_public_key.or(config.periphery_public_key), title: env.komodo_title.unwrap_or(config.title), host: env.komodo_host.unwrap_or(config.host), port: env.komodo_port.unwrap_or(config.port), diff --git a/bin/core/src/helpers/builder.rs b/bin/core/src/helpers/builder.rs index bdb48a45e..9f13fa6f4 100644 --- a/bin/core/src/helpers/builder.rs +++ b/bin/core/src/helpers/builder.rs @@ -6,7 +6,7 @@ use formatting::muted; use komodo_client::entities::{ Version, builder::{AwsBuilderConfig, Builder, BuilderConfig}, - komodo_timestamp, + komodo_timestamp, optional_string, server::Server, update::{Log, Update}, }; @@ -59,7 +59,7 @@ pub async fn get_builder_periphery( } else { config.private_key }, - config.public_key, + optional_string(config.public_key), ) .await?; periphery @@ -125,7 +125,7 @@ async fn get_aws_builder( } else { config.private_key }, - config.public_key, + optional_string(config.public_key), ) .await?; diff --git a/bin/core/src/main.rs b/bin/core/src/main.rs index 58b59edba..424a39470 100644 --- a/bin/core/src/main.rs +++ b/bin/core/src/main.rs @@ -48,7 +48,7 @@ async fn app() -> anyhow::Result<()> { info!("Komodo Core version: v{}", env!("CARGO_PKG_VERSION")); // Init public key to crash on failure - info!("Public Key: {}", core_public_key()); + info!("Core Public Key: {}", core_public_key()); match ( config.pretty_startup_config, diff --git a/bin/core/src/ws/periphery.rs b/bin/core/src/ws/periphery.rs index 48de3b492..39219591f 100644 --- a/bin/core/src/ws/periphery.rs +++ b/bin/core/src/ws/periphery.rs @@ -20,6 +20,7 @@ pub async fn handler( periphery_client::connection::server::handler( server, core_config().private_key.clone(), + core_config().periphery_public_key.clone(), headers, query, ws, diff --git a/bin/periphery/Cargo.toml b/bin/periphery/Cargo.toml index 174ad406f..ec0bd060e 100644 --- a/bin/periphery/Cargo.toml +++ b/bin/periphery/Cargo.toml @@ -27,6 +27,7 @@ command.workspace = true config.workspace = true logger.workspace = true cache.workspace = true +noise.workspace = true git.workspace = true # mogh serror = { workspace = true, features = ["axum"] } diff --git a/bin/periphery/src/config.rs b/bin/periphery/src/config.rs index 4fe14d6aa..f7126b014 100644 --- a/bin/periphery/src/config.rs +++ b/bin/periphery/src/config.rs @@ -11,6 +11,15 @@ use komodo_client::entities::{ logger::{LogConfig, LogLevel}, }; +/// Should call in startup to ensure Periphery errors without valid private key. +pub fn periphery_public_key() -> &'static String { + static PERIPHERY_PUBLIC_KEY: OnceLock = OnceLock::new(); + PERIPHERY_PUBLIC_KEY.get_or_init(|| { + noise::compute_public_key(&periphery_config().private_key) + .unwrap() + }) +} + pub fn periphery_config() -> &'static PeripheryConfig { static PERIPHERY_CONFIG: OnceLock = OnceLock::new(); diff --git a/bin/periphery/src/connection/server.rs b/bin/periphery/src/connection/server.rs index 1f95a3b25..1e18f8f4b 100644 --- a/bin/periphery/src/connection/server.rs +++ b/bin/periphery/src/connection/server.rs @@ -81,7 +81,6 @@ async fn handler( }; if let Err(e) = handler.handle::().await { warn!("Core failed to login to connection | {e:#}"); - return; } })) } diff --git a/bin/periphery/src/main.rs b/bin/periphery/src/main.rs index 920fccc4c..b7931acdd 100644 --- a/bin/periphery/src/main.rs +++ b/bin/periphery/src/main.rs @@ -1,5 +1,7 @@ use anyhow::anyhow; +use crate::config::periphery_public_key; + #[macro_use] extern crate tracing; @@ -17,6 +19,8 @@ async fn app() -> anyhow::Result<()> { logger::init(&config.logging)?; info!("Komodo Periphery version: v{}", env!("CARGO_PKG_VERSION")); + // Init public key to crash on failure + info!("Periphery Public Key: {}", periphery_public_key()); if config.pretty_startup_config { info!("{:#?}", config.sanitized()); diff --git a/client/core/rs/src/entities/config/core.rs b/client/core/rs/src/entities/config/core.rs index e349c35dc..4a011d053 100644 --- a/client/core/rs/src/entities/config/core.rs +++ b/client/core/rs/src/entities/config/core.rs @@ -75,6 +75,8 @@ pub struct Env { pub komodo_private_key: Option, /// Override `private_key` with file pub komodo_private_key_file: Option, + /// Override `periphery_public_key` + pub komodo_periphery_public_key: Option, /// Override `timezone` #[serde(alias = "tz", alias = "TZ")] pub komodo_timezone: Option, @@ -317,11 +319,20 @@ pub struct CoreConfig { #[serde(default)] pub internet_interface: String, - /// Default private key to use with Noise handshake - /// with Periphery agents. + /// Default private key to use with Noise handshake to authenticate with Periphery agents. + /// If not provided, will use random default. + /// Note. The private key can be overridden on individual Server / Builds #[serde(default = "default_private_key")] pub private_key: String, + /// Default accepted public key to allow Periphery to connect. + /// Core gains knowledge of the Periphery public key through the noise handshake. + /// If not provided, Periphery -> Core connected Servers must + /// configure accepted public keys individually. + /// Note: If used, the public key can still be overridden on individual Server / Builds + #[serde(skip_serializing_if = "Option::is_none")] + pub periphery_public_key: Option, + /// A TZ Identifier. If not provided, will use Core local timezone. /// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. /// This will be populated by TZ env variable in addition to KOMODO_TIMEZONE. @@ -703,6 +714,7 @@ impl Default for CoreConfig { bind_ip: default_core_bind_ip(), internet_interface: Default::default(), private_key: default_private_key(), + periphery_public_key: Default::default(), timezone: Default::default(), ui_write_disabled: Default::default(), disable_confirm_dialog: Default::default(), @@ -765,6 +777,7 @@ impl CoreConfig { port: config.port, bind_ip: config.bind_ip, private_key: empty_or_redacted(&config.private_key), + periphery_public_key: config.periphery_public_key, timezone: config.timezone, first_server: config.first_server, first_server_name: config.first_server_name, diff --git a/client/core/rs/src/entities/config/periphery.rs b/client/core/rs/src/entities/config/periphery.rs index 8fc1df855..1db2e8db6 100644 --- a/client/core/rs/src/entities/config/periphery.rs +++ b/client/core/rs/src/entities/config/periphery.rs @@ -195,20 +195,24 @@ pub struct Env { #[derive(Debug, Clone, Deserialize)] pub struct PeripheryConfig { /// The private key used with noise handshake. + /// If not provided, will generate random default. #[serde(default = "default_private_key")] pub private_key: String, /// Optionally pin a specific Core public key /// for additional trust. + #[serde(skip_serializing_if = "Option::is_none")] pub core_public_key: Option, // ============================ // = OUTBOUND CONNECTION MODE = // ============================ /// Address of Komodo Core when connecting outbound + #[serde(skip_serializing_if = "Option::is_none")] pub core_host: Option, /// Server name / id to connect as /// TODO: explore using device identifier like MAC + #[serde(skip_serializing_if = "Option::is_none")] pub connect_as: Option, // =========================== @@ -245,10 +249,12 @@ pub struct PeripheryConfig { /// Path to the ssl key. /// Default: `${root_directory}/ssl/key.pem`. + #[serde(skip_serializing_if = "Option::is_none")] pub ssl_key_file: Option, /// Path to the ssl cert. /// Default: `${root_directory}/ssl/cert.pem`. + #[serde(skip_serializing_if = "Option::is_none")] pub ssl_cert_file: Option, // ================== @@ -270,16 +276,19 @@ pub struct PeripheryConfig { /// The system directory where Komodo managed repos will be cloned. /// If not provided, will default to `${root_directory}/repos`. /// Default: empty + #[serde(skip_serializing_if = "Option::is_none")] pub repo_dir: Option, /// The system directory where stacks will managed. /// If not provided, will default to `${root_directory}/stacks`. /// Default: empty + #[serde(skip_serializing_if = "Option::is_none")] pub stack_dir: Option, /// The system directory where builds will managed. /// If not provided, will default to `${root_directory}/builds`. /// Default: empty + #[serde(skip_serializing_if = "Option::is_none")] pub build_dir: Option, /// Whether to disable the create terminal diff --git a/client/periphery/rs/src/connection/client.rs b/client/periphery/rs/src/connection/client.rs index 068d91b44..81a1f5df6 100644 --- a/client/periphery/rs/src/connection/client.rs +++ b/client/periphery/rs/src/connection/client.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; use anyhow::Context; use axum::http::HeaderValue; -use komodo_client::entities::server::Server; +use komodo_client::entities::{optional_string, server::Server}; use rustls::{ClientConfig, client::danger::ServerCertVerifier}; use tokio_tungstenite::Connector; use tracing::{info, warn}; @@ -102,7 +102,7 @@ pub async fn manage_client_connections( } else { private_key.clone() }, - expected_public_key.clone(), + optional_string(expected_public_key), ) .await { @@ -118,7 +118,7 @@ pub async fn spawn_client_connection( server_id: String, address: String, private_key: String, - expected_public_key: String, + expected_public_key: Option, ) -> anyhow::Result<()> { let url = ::url::Url::parse(&address) .context("Failed to parse server address")?; @@ -168,7 +168,7 @@ pub async fn spawn_client_connection( query: &[], }, private_key: &private_key, - expected_public_key: &expected_public_key, + expected_public_key: expected_public_key.as_deref(), write_receiver: &mut write_receiver, connection: &connection, handler: &handler, diff --git a/client/periphery/rs/src/connection/mod.rs b/client/periphery/rs/src/connection/mod.rs index d2dbaad28..4d796a1a8 100644 --- a/client/periphery/rs/src/connection/mod.rs +++ b/client/periphery/rs/src/connection/mod.rs @@ -33,7 +33,7 @@ pub struct WebsocketHandler<'a, W> { pub socket: W, pub connection_identifiers: ConnectionIdentifiers<'a>, pub private_key: &'a str, - pub expected_public_key: &'a str, + pub expected_public_key: Option<&'a str>, pub write_receiver: &'a mut BufferedReceiver, pub connection: &'a PeripheryConnection, pub handler: &'a MessageHandler, @@ -133,14 +133,17 @@ impl WebsocketHandler<'_, W> { } pub struct PeripheryPublicKeyValidator<'a> { - pub expected: &'a str, + /// If None, ignore public key. + pub expected: Option<&'a str>, } impl PublicKeyValidator for PeripheryPublicKeyValidator<'_> { fn validate(&self, public_key: String) -> anyhow::Result<()> { - if self.expected.is_empty() || self.expected == public_key { - Ok(()) - } else { + if let Some(expected) = self.expected + && public_key != expected + { Err(anyhow!("Public key does not match expected")) + } else { + Ok(()) } } } diff --git a/client/periphery/rs/src/connection/server.rs b/client/periphery/rs/src/connection/server.rs index 4d4fec495..4e7348ed7 100644 --- a/client/periphery/rs/src/connection/server.rs +++ b/client/periphery/rs/src/connection/server.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use axum::{ extract::WebSocketUpgrade, http::{HeaderMap, StatusCode}, @@ -18,6 +19,7 @@ use crate::connection::{ pub async fn handler( server: Server, default_private_key: String, + default_public_key: Option, mut headers: HeaderMap, query: String, ws: WebSocketUpgrade, @@ -25,6 +27,12 @@ pub async fn handler( let identifiers = ServerHeaderIdentifiers::extract(&mut headers) .status_code(StatusCode::UNAUTHORIZED)?; + let expected_public_key = if server.config.public_key.is_empty() { + default_public_key.context("Must either configure Server 'Periphery Public Key' or set KOMODO_PERIPHERY_PUBLIC_KEY")? + } else { + server.config.public_key + }; + let handler = MessageHandler::new(&server.id).await; let (connection, mut write_receiver) = @@ -47,7 +55,7 @@ pub async fn handler( } else { &server.config.private_key }, - expected_public_key: &server.config.public_key, + expected_public_key: Some(&expected_public_key), write_receiver: &mut write_receiver, connection: &connection, handler: &handler, @@ -59,7 +67,6 @@ pub async fn handler( server.name ); connection.set_error(e).await; - return; } })) } diff --git a/client/periphery/rs/src/lib.rs b/client/periphery/rs/src/lib.rs index 481a4f873..2a96f9140 100644 --- a/client/periphery/rs/src/lib.rs +++ b/client/periphery/rs/src/lib.rs @@ -51,7 +51,7 @@ impl PeripheryClient { server_id: String, address: &str, private_key: String, - expected_public_key: String, + expected_public_key: Option, ) -> anyhow::Result { if address.is_empty() { return Err(anyhow!("Server address cannot be empty")); diff --git a/client/periphery/rs/src/terminal.rs b/client/periphery/rs/src/terminal.rs index fe87f746b..25cbf66c9 100644 --- a/client/periphery/rs/src/terminal.rs +++ b/client/periphery/rs/src/terminal.rs @@ -187,10 +187,12 @@ impl Stream for ReceiverStream { mut self: Pin<&mut Self>, cx: &mut task::Context<'_>, ) -> Poll> { - match self.receiver.poll_recv(cx).map(|bytes| { - bytes.map(|bytes| data_from_transport_bytes(bytes)) - }) { - Poll::Ready(Some(Ok(bytes))) if &bytes == END_OF_OUTPUT => { + match self + .receiver + .poll_recv(cx) + .map(|bytes| bytes.map(data_from_transport_bytes)) + { + Poll::Ready(Some(Ok(bytes))) if bytes == END_OF_OUTPUT => { self.cleanup(); Poll::Ready(None) } diff --git a/frontend/src/components/resources/builder/config.tsx b/frontend/src/components/resources/builder/config.tsx index f2e7df315..a8336dc90 100644 --- a/frontend/src/components/resources/builder/config.tsx +++ b/frontend/src/components/resources/builder/config.tsx @@ -136,13 +136,13 @@ const AwsBuilderConfig = ({ id }: { id: string }) => { labelHidden: true, components: { public_key: { - label: "Inbound Public Key", + label: "Periphery Public Key", description: - "If provided, the associated private key must be set as Periphery 'private_key'. For Periphery -> Core connection, this is required for Periphery to be able to connect.", + "If provided, the associated private key must be set as Periphery 'private_key'. For Periphery -> Core connection, either this or using 'periphery_public_key' in Core config is required for Periphery to be able to connect.", placeholder: "custom-public-key", }, private_key: { - label: "Outbound Private Key", + label: "Core Private Key", description: "Optional. A custom private key used to authenticate Periphery connection. The associated public key must match Periphery 'core_public_key'. If not provided, will use 'private_key' in Core config. Max length of 32 characters.", placeholder: "custom-private-key", @@ -354,13 +354,13 @@ const UrlBuilderConfig = ({ id }: { id: string }) => { labelHidden: true, components: { public_key: { - label: "Inbound Public Key", + label: "Periphery Public Key", description: - "If provided, the associated private key must be set as Periphery 'private_key'. For Periphery -> Core connection, this is required for Periphery to be able to connect.", + "If provided, the associated private key must be set as Periphery 'private_key'. For Periphery -> Core connection, either this or using 'periphery_public_key' in Core config is required for Periphery to be able to connect.", placeholder: "custom-public-key", }, private_key: { - label: "Outbound Private Key", + label: "Core Private Key", description: "Optional. A custom private key used to authenticate Periphery connection. The associated public key must match Periphery 'core_public_key'. If not provided, will use 'private_key' in Core config. Max length of 32 characters.", placeholder: "custom-private-key", diff --git a/frontend/src/components/resources/server/config.tsx b/frontend/src/components/resources/server/config.tsx index 65b198c86..6f0272911 100644 --- a/frontend/src/components/resources/server/config.tsx +++ b/frontend/src/components/resources/server/config.tsx @@ -64,13 +64,13 @@ export const ServerConfig = ({ labelHidden: true, components: { public_key: { - label: "Inbound Public Key", + label: "Periphery Public Key", description: - "If provided, the associated private key must be set as Periphery 'private_key'. For Periphery -> Core connection, this is required for Periphery to be able to connect.", + "If provided, the associated private key must be set as Periphery 'private_key'. For Periphery -> Core connection, either this or using 'periphery_public_key' in Core config is required for Periphery to be able to connect.", placeholder: "custom-public-key", }, private_key: { - label: "Outbound Private Key", + label: "Core Private Key", description: "Optional. A custom private key used to authenticate Periphery connection. The associated public key must match Periphery 'core_public_key'. If not provided, will use 'private_key' in Core config. Max length of 32 characters.", placeholder: "custom-private-key",