diff --git a/bin/cli/src/config.rs b/bin/cli/src/config.rs index cc5dc859c..12eb0e3aa 100644 --- a/bin/cli/src/config.rs +++ b/bin/cli/src/config.rs @@ -28,7 +28,7 @@ pub fn cli_env() -> &'static Env { { Ok(env) => env, Err(e) => { - panic!("{e:?}"); + panic!("{e:?}") } } }) diff --git a/bin/periphery/src/connection/mod.rs b/bin/periphery/src/connection/mod.rs index 35094bf99..b5131c2f8 100644 --- a/bin/periphery/src/connection/mod.rs +++ b/bin/periphery/src/connection/mod.rs @@ -32,10 +32,7 @@ pub mod server; impl PublicKeyValidator for &CorePublicKeys { type ValidationResult = (); async fn validate(&self, public_key: String) -> anyhow::Result<()> { - let keys = self.load(); - if keys.is_empty() - || keys.iter().any(|pk| pk.as_str() == public_key) - { + if self.is_valid(&public_key).await { Ok(()) } else { Err( diff --git a/bin/periphery/src/main.rs b/bin/periphery/src/main.rs index c13863f05..62d5b5d95 100644 --- a/bin/periphery/src/main.rs +++ b/bin/periphery/src/main.rs @@ -1,7 +1,10 @@ use futures::{StreamExt, stream::FuturesUnordered}; use komodo_client::entities::config::periphery::Command; -use crate::{config::periphery_args, state::periphery_keys}; +use crate::{ + config::periphery_args, + state::{core_public_keys, periphery_keys}, +}; #[macro_use] extern crate tracing; @@ -30,6 +33,9 @@ async fn app() -> anyhow::Result<()> { // Init + log public key. Will crash if invalid private key here. info!("Public Key: {}", periphery_keys().load().public); + // Init core public keys. Will crash if invalid core public keys here. + core_public_keys(); + rustls::crypto::aws_lc_rs::default_provider() .install_default() .expect("Failed to install default crypto provider"); diff --git a/bin/periphery/src/state.rs b/bin/periphery/src/state.rs index 0d3a41901..6a58e8ee8 100644 --- a/bin/periphery/src/state.rs +++ b/bin/periphery/src/state.rs @@ -1,5 +1,7 @@ use std::{ collections::HashMap, + path::PathBuf, + str::FromStr, sync::{Arc, OnceLock}, }; @@ -47,11 +49,20 @@ pub fn core_public_keys() -> &'static CorePublicKeys { CORE_PUBLIC_KEYS.get_or_init(CorePublicKeys::default) } -pub struct CorePublicKeys(ArcSwap>); +pub struct CorePublicKeys { + keys: ArcSwap>, + /// If any keys fail to write, store them here. + /// For Periphery -> Core connection, Periphery will + /// write the Core pub keys to these files as they connect. + to_write: ArcSwap>, +} impl Default for CorePublicKeys { fn default() -> Self { - let keys = CorePublicKeys(Default::default()); + let keys = CorePublicKeys { + keys: Default::default(), + to_write: Default::default(), + }; keys.refresh(); keys } @@ -59,45 +70,83 @@ impl Default for CorePublicKeys { impl CorePublicKeys { pub fn load(&self) -> arc_swap::Guard>> { - self.0.load() + self.keys.load() } - pub fn is_valid(&self, public_key: &str) -> bool { - let keys = self.0.load(); + pub async fn is_valid(&self, public_key: &str) -> bool { + // For Periphery -> Core connection, maybe init + // Core public key file if it doesn't exist. + self.maybe_write(public_key).await; + let keys = self.keys.load(); keys.is_empty() || keys.iter().any(|pk| pk.as_str() == public_key) } + async fn maybe_write(&self, public_key: &str) { + let to_write = self.to_write.load(); + match to_write.as_slice() { + // Do nothing if empty + [] => { + return; + } + [path, _rest @ ..] => { + let public_key = + match SpkiPublicKey::from_maybe_pem(public_key) { + Ok(public_key) => public_key, + Err(e) => { + error!("Invalid incoming public key | {e:#}"); + return; + } + }; + if let Err(e) = public_key.write_pem_async(path).await { + warn!("Failed to pin incoming public key | {e:#}"); + return; + } + self.refresh(); + } + }; + } + pub fn refresh(&self) { let config = periphery_config(); let Some(core_public_keys) = config.core_public_keys.as_ref() else { return; }; + let mut to_write = Vec::new(); let core_public_keys = core_public_keys .iter() .flat_map(|public_key| { - let res = if let Some(path) = public_key.strip_prefix("file:") + if let Some(path) = public_key.strip_prefix("file:") { - SpkiPublicKey::from_file(path) + match (SpkiPublicKey::from_file(path), config.server_enabled) { + (Ok(public_key), _) => Some(public_key), + (Err(e), false) => { + // If only outbound connections, only warn. + // It will be written when Core public key received. + warn!("{e:#}"); + let Ok(path) = PathBuf::from_str(path); + to_write.push(path); + None + } + (Err(e), true) => { + // This is too dangerous to allow if server_enabled. + error!("{e:#}"); + std::process::exit(1) + } + } } else { SpkiPublicKey::from_maybe_pem(public_key) - }; - match (res, config.server_enabled) { - (Ok(public_key), _) => Some(public_key), - (Err(e), false) => { - // If only outbound connections, only warn. - // It will be written the next time `RotateCoreKeys` is executed. - warn!("{e:#}"); - None - } - (Err(e), true) => { - // This is too dangerous to allow if server_enabled. - panic!("{e:#}"); - } + .context("Invalid hardcoded public key. If this is supposed to point to file, add 'file:' prefix.") + .inspect_err(|e| { + error!("{e:#}"); + std::process::exit(1) + }) + .ok() } }) .collect::>(); - self.0.store(Arc::new(core_public_keys)); + self.keys.store(Arc::new(core_public_keys)); + self.to_write.store(Arc::new(to_write)); } }