mirror of
https://github.com/moghtech/komodo.git
synced 2026-03-11 17:44:19 -05:00
set default allowed periphery public key
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2971,6 +2971,7 @@ dependencies = [
|
||||
"interpolate",
|
||||
"komodo_client",
|
||||
"logger",
|
||||
"noise",
|
||||
"periphery_client",
|
||||
"portable-pty",
|
||||
"resolver_api",
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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<String> = 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<PeripheryConfig> =
|
||||
OnceLock::new();
|
||||
|
||||
@@ -81,7 +81,6 @@ async fn handler(
|
||||
};
|
||||
if let Err(e) = handler.handle::<ServerLoginFlow>().await {
|
||||
warn!("Core failed to login to connection | {e:#}");
|
||||
return;
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -75,6 +75,8 @@ pub struct Env {
|
||||
pub komodo_private_key: Option<String>,
|
||||
/// Override `private_key` with file
|
||||
pub komodo_private_key_file: Option<PathBuf>,
|
||||
/// Override `periphery_public_key`
|
||||
pub komodo_periphery_public_key: Option<String>,
|
||||
/// Override `timezone`
|
||||
#[serde(alias = "tz", alias = "TZ")]
|
||||
pub komodo_timezone: Option<String>,
|
||||
@@ -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<String>,
|
||||
|
||||
/// 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,
|
||||
|
||||
@@ -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<String>,
|
||||
|
||||
// ============================
|
||||
// = OUTBOUND CONNECTION MODE =
|
||||
// ============================
|
||||
/// Address of Komodo Core when connecting outbound
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub core_host: Option<String>,
|
||||
|
||||
/// Server name / id to connect as
|
||||
/// TODO: explore using device identifier like MAC
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub connect_as: Option<String>,
|
||||
|
||||
// ===========================
|
||||
@@ -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<PathBuf>,
|
||||
|
||||
/// Path to the ssl cert.
|
||||
/// Default: `${root_directory}/ssl/cert.pem`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ssl_cert_file: Option<PathBuf>,
|
||||
|
||||
// ==================
|
||||
@@ -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<PathBuf>,
|
||||
|
||||
/// 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<PathBuf>,
|
||||
|
||||
/// 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<PathBuf>,
|
||||
|
||||
/// Whether to disable the create terminal
|
||||
|
||||
@@ -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<String>,
|
||||
) -> 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,
|
||||
|
||||
@@ -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<Bytes>,
|
||||
pub connection: &'a PeripheryConnection,
|
||||
pub handler: &'a MessageHandler,
|
||||
@@ -133,14 +133,17 @@ impl<W: Websocket> 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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
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;
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ impl PeripheryClient {
|
||||
server_id: String,
|
||||
address: &str,
|
||||
private_key: String,
|
||||
expected_public_key: String,
|
||||
expected_public_key: Option<String>,
|
||||
) -> anyhow::Result<PeripheryClient> {
|
||||
if address.is_empty() {
|
||||
return Err(anyhow!("Server address cannot be empty"));
|
||||
|
||||
@@ -187,10 +187,12 @@ impl Stream for ReceiverStream {
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user