mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-27 19:33:08 -05:00
write key pem files by default when not otherwise provided.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::{fs::read_to_string, path::PathBuf, sync::OnceLock};
|
||||
use std::{path::PathBuf, sync::OnceLock};
|
||||
|
||||
use anyhow::Context;
|
||||
use colored::Colorize;
|
||||
@@ -16,24 +16,17 @@ use komodo_client::entities::{
|
||||
},
|
||||
logger::LogConfig,
|
||||
};
|
||||
use noise::key::{EncodedKeyPair, SpkiPublicKey};
|
||||
use noise::key::{SpkiPublicKey, load_maybe_generate_private_key};
|
||||
|
||||
/// Should call in startup to ensure Core errors without valid private key.
|
||||
pub fn core_private_key() -> &'static String {
|
||||
static CORE_PRIVATE_KEY: OnceLock<String> = OnceLock::new();
|
||||
CORE_PRIVATE_KEY.get_or_init(|| {
|
||||
let config = core_config();
|
||||
let Some(private_key) = config.private_key.as_ref() else {
|
||||
return EncodedKeyPair::generate().unwrap().private;
|
||||
};
|
||||
if let Some(path) = private_key.strip_prefix("file:") {
|
||||
read_to_string(path)
|
||||
.with_context(|| {
|
||||
format!("Failed to read private key at {path:?}")
|
||||
})
|
||||
.unwrap()
|
||||
if let Some(path) = config.private_key.strip_prefix("file:") {
|
||||
load_maybe_generate_private_key(path).unwrap()
|
||||
} else {
|
||||
private_key.clone()
|
||||
config.private_key.clone()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -62,7 +55,7 @@ pub fn periphery_public_keys() -> Option<&'static [SpkiPublicKey]> {
|
||||
let maybe_pem = if let Some(path) =
|
||||
public_key.strip_prefix("file:")
|
||||
{
|
||||
read_to_string(path)
|
||||
std::fs::read_to_string(path)
|
||||
.with_context(|| {
|
||||
format!("Failed to read public key at {path:?}")
|
||||
})
|
||||
@@ -167,7 +160,7 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
CoreConfig {
|
||||
// Secret things overridden with file
|
||||
private_key: maybe_read_item_from_file(env.komodo_private_key_file, env.komodo_private_key)
|
||||
.or(config.private_key),
|
||||
.unwrap_or(config.private_key),
|
||||
passkey: maybe_read_item_from_file(env.komodo_passkey_file, env.komodo_passkey)
|
||||
.or(config.passkey),
|
||||
jwt_secret: maybe_read_item_from_file(env.komodo_jwt_secret_file, env.komodo_jwt_secret).unwrap_or(config.jwt_secret),
|
||||
|
||||
@@ -11,22 +11,19 @@ use komodo_client::entities::{
|
||||
config::periphery::{CliArgs, Env, PeripheryConfig},
|
||||
logger::{LogConfig, LogLevel},
|
||||
};
|
||||
use noise::key::{EncodedKeyPair, SpkiPublicKey};
|
||||
use noise::key::{SpkiPublicKey, load_maybe_generate_private_key};
|
||||
|
||||
/// Should call in startup to ensure Periphery errors without valid private key.
|
||||
pub fn periphery_private_key() -> &'static String {
|
||||
static PERIPHERY_PRIVATE_KEY: OnceLock<String> = OnceLock::new();
|
||||
PERIPHERY_PRIVATE_KEY.get_or_init(|| {
|
||||
let config = periphery_config();
|
||||
let Some(private_key) = config.private_key.as_ref() else {
|
||||
return EncodedKeyPair::generate().unwrap().private;
|
||||
};
|
||||
let private_key = config.private_key.clone().unwrap_or(format!(
|
||||
"file:{}/keys/periphery.key",
|
||||
config.root_directory.display()
|
||||
));
|
||||
if let Some(path) = private_key.strip_prefix("file:") {
|
||||
read_to_string(path)
|
||||
.with_context(|| {
|
||||
format!("Failed to read private key at {path:?}")
|
||||
})
|
||||
.unwrap()
|
||||
load_maybe_generate_private_key(path).unwrap()
|
||||
} else {
|
||||
private_key.clone()
|
||||
}
|
||||
|
||||
@@ -327,15 +327,19 @@ pub struct CoreConfig {
|
||||
#[serde(default)]
|
||||
pub internet_interface: String,
|
||||
|
||||
/// Default private key to use with Noise handshake to authenticate with Periphery agents.
|
||||
/// If not provided, will use randomly generated one on startup.
|
||||
/// Private key to use with Noise handshake to authenticate with Periphery agents.
|
||||
///
|
||||
/// Supports openssl generated pem file, `openssl genpkey -algorithm X25519 -out private.key`.
|
||||
/// To load from file, use `private_key = "file:/path/to/private.key"`
|
||||
/// To load from file, use `private_key = "file:/path/to/private.key"`.
|
||||
///
|
||||
/// Note. The private key used can be overridden for individual Servers / Builders
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub private_key: Option<String>,
|
||||
/// If a file is specified and does not exist, will try to generate one at the path
|
||||
/// and use it going forward.
|
||||
///
|
||||
/// Note. The private key used can be overridden for individual Servers / Builders.
|
||||
///
|
||||
/// Default: file:/config/keys/core.key
|
||||
#[serde(default = "default_private_key")]
|
||||
pub private_key: String,
|
||||
|
||||
/// Default accepted public keys to allow Periphery to connect.
|
||||
/// Core gains knowledge of the Periphery public key through the noise handshake.
|
||||
@@ -679,6 +683,10 @@ fn default_core_bind_ip() -> String {
|
||||
"[::]".to_string()
|
||||
}
|
||||
|
||||
fn default_private_key() -> String {
|
||||
String::from("file:/config/keys/core.key")
|
||||
}
|
||||
|
||||
fn default_frontend_path() -> String {
|
||||
"/app/frontend".to_string()
|
||||
}
|
||||
@@ -798,13 +806,11 @@ impl CoreConfig {
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
bind_ip: config.bind_ip,
|
||||
private_key: self.private_key.as_ref().map(|private_key| {
|
||||
if private_key.starts_with("file:") {
|
||||
private_key.clone()
|
||||
} else {
|
||||
empty_or_redacted(private_key)
|
||||
}
|
||||
}),
|
||||
private_key: if self.private_key.starts_with("file:") {
|
||||
self.private_key.clone()
|
||||
} else {
|
||||
empty_or_redacted(&self.private_key)
|
||||
},
|
||||
periphery_public_keys: config.periphery_public_keys,
|
||||
passkey: config.passkey.as_deref().map(empty_or_redacted),
|
||||
timezone: config.timezone,
|
||||
|
||||
@@ -212,10 +212,14 @@ pub struct Env {
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PeripheryConfig {
|
||||
/// The private key used with noise handshake.
|
||||
/// If not provided, uses randomly generated one on startup.
|
||||
///
|
||||
/// Supports openssl generated pem file, `openssl genpkey -algorithm X25519 -out private.key`.
|
||||
/// To load from file, use `private_key = "file:/path/to/private.key"`
|
||||
///
|
||||
/// If a file is specified and does not exist, will try to generate one at the path
|
||||
/// and use it going forward.
|
||||
///
|
||||
/// Default: ${root_directory}/keys/periphery.key
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub private_key: Option<String>,
|
||||
/// Optionally pin a specific Core public key for additional trust.
|
||||
|
||||
@@ -13,15 +13,19 @@ pub async fn handle(command: &KeyCommand) -> anyhow::Result<()> {
|
||||
.context("Failed to generate key pair")?;
|
||||
match format {
|
||||
KeyOutputFormat::Standard => {
|
||||
println!("\nPrivate Key: {}", keys.private.red().bold());
|
||||
println!("Public Key: {}", keys.public.bold());
|
||||
println!(
|
||||
"\nPrivate Key: {}",
|
||||
keys.private.as_str().red().bold()
|
||||
);
|
||||
println!("Public Key: {}", keys.public.as_str().bold());
|
||||
}
|
||||
KeyOutputFormat::Json => {
|
||||
print_json(&keys.private, &keys.public)?
|
||||
}
|
||||
KeyOutputFormat::JsonPretty => {
|
||||
print_json_pretty(&keys.private, &keys.public)?
|
||||
print_json(keys.private.as_str(), keys.public.as_str())?
|
||||
}
|
||||
KeyOutputFormat::JsonPretty => print_json_pretty(
|
||||
keys.private.as_str(),
|
||||
keys.public.as_str(),
|
||||
)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use der::AnyRef;
|
||||
|
||||
@@ -21,9 +23,9 @@ fn algorithm() -> spki::AlgorithmIdentifier<AnyRef<'static>> {
|
||||
|
||||
pub struct EncodedKeyPair {
|
||||
/// pkcs8 encoded private key
|
||||
pub private: String,
|
||||
pub private: Pkcs8PrivateKey,
|
||||
/// spki encoded public key
|
||||
pub public: String,
|
||||
pub public: SpkiPublicKey,
|
||||
}
|
||||
|
||||
impl EncodedKeyPair {
|
||||
@@ -32,10 +34,31 @@ impl EncodedKeyPair {
|
||||
let keypair = builder
|
||||
.generate_keypair()
|
||||
.context("Failed to generate keypair")?;
|
||||
let private =
|
||||
Pkcs8PrivateKey::from_raw_bytes(&keypair.private)?.into_inner();
|
||||
let public =
|
||||
SpkiPublicKey::from_raw_bytes(&keypair.public)?.into_inner();
|
||||
let private = Pkcs8PrivateKey::from_raw_bytes(&keypair.private)?;
|
||||
let public = SpkiPublicKey::from_raw_bytes(&keypair.public)?;
|
||||
Ok(EncodedKeyPair { private, public })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_maybe_generate_private_key(
|
||||
path: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
let path = path.as_ref();
|
||||
let path = PathBuf::from_str(path)
|
||||
.with_context(|| format!("Invalid private key path: {path:?}"))?;
|
||||
if path
|
||||
.try_exists()
|
||||
.with_context(|| format!("Invalid private key path: {path:?}"))?
|
||||
{
|
||||
// Already exists, load it
|
||||
std::fs::read_to_string(&path).with_context(|| {
|
||||
format!("Failed to read private key at {path:?}")
|
||||
})
|
||||
} else {
|
||||
// Generate and write pems to path
|
||||
let keys = EncodedKeyPair::generate()?;
|
||||
keys.private.write_pem(&path)?;
|
||||
keys.public.write_pem(path.with_extension("pub"))?;
|
||||
Ok(keys.private.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use base64::{Engine as _, prelude::BASE64_STANDARD};
|
||||
use der::{Decode as _, Encode as _, asn1::OctetStringRef};
|
||||
@@ -26,6 +28,26 @@ impl Pkcs8PrivateKey {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn as_pem(&self) -> String {
|
||||
let private_key = &self.0;
|
||||
format!(
|
||||
r#"-----BEGIN PRIVATE KEY-----
|
||||
{private_key}
|
||||
-----END PRIVATE KEY-----
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_pem<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
std::fs::write(path, self.as_pem()).with_context(|| {
|
||||
format!("Failed to write private key pem to {path:?}")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_raw_bytes(private_key: &[u8]) -> anyhow::Result<Self> {
|
||||
if private_key.len() > 32 {
|
||||
return Err(anyhow!(
|
||||
@@ -65,6 +87,8 @@ impl Pkcs8PrivateKey {
|
||||
Self::raw_bytes(self.0.as_bytes())
|
||||
}
|
||||
|
||||
/// Converts pkcs8 base64 bytes
|
||||
/// to raw private key
|
||||
pub fn raw_bytes(
|
||||
pkcs8_private_key: &[u8],
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
@@ -74,21 +98,9 @@ impl Pkcs8PrivateKey {
|
||||
Self::raw_bytes_after_decode(&decoded)
|
||||
}
|
||||
|
||||
fn raw_bytes_after_decode(
|
||||
decoded: &[u8],
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
let pki = pkcs8::PrivateKeyInfo::from_der(decoded)
|
||||
.map_err(anyhow::Error::msg)
|
||||
.context("Failed to parse pki from der")?;
|
||||
if pki.algorithm.oid != super::OID_X25519 {
|
||||
return Err(anyhow!("Private key is not X25519"));
|
||||
}
|
||||
let octet = OctetStringRef::from_der(pki.private_key)
|
||||
.map_err(anyhow::Error::msg)
|
||||
.context("Failed to get octet string ref from private key")?;
|
||||
Ok(octet.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
/// - For raw bytes: clones and returns
|
||||
/// - For pkcs8 base64: converts and returns
|
||||
/// - For pkcs8 base64 pem: unwraps and returns
|
||||
pub fn maybe_raw_bytes(
|
||||
maybe_pkcs8_private_key: &str,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
@@ -113,6 +125,21 @@ impl Pkcs8PrivateKey {
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_bytes_after_decode(
|
||||
decoded: &[u8],
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
let pki = pkcs8::PrivateKeyInfo::from_der(decoded)
|
||||
.map_err(anyhow::Error::msg)
|
||||
.context("Failed to parse pki from der")?;
|
||||
if pki.algorithm.oid != super::OID_X25519 {
|
||||
return Err(anyhow!("Private key is not X25519"));
|
||||
}
|
||||
let octet = OctetStringRef::from_der(pki.private_key)
|
||||
.map_err(anyhow::Error::msg)
|
||||
.context("Failed to get octet string ref from private key")?;
|
||||
Ok(octet.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
pub fn compute_public_key(
|
||||
&self,
|
||||
) -> anyhow::Result<super::public::SpkiPublicKey> {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use base64::{Engine as _, prelude::BASE64_STANDARD};
|
||||
use der::{Decode as _, Encode as _, asn1::BitStringRef};
|
||||
@@ -27,6 +29,26 @@ impl SpkiPublicKey {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn as_pem(&self) -> String {
|
||||
let public_key = &self.0;
|
||||
format!(
|
||||
r#"-----BEGIN PUBLIC KEY-----
|
||||
{public_key}
|
||||
-----END PUBLIC KEY-----
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_pem<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
std::fs::write(path, self.as_pem()).with_context(|| {
|
||||
format!("Failed to write private key pem to {path:?}")
|
||||
})
|
||||
}
|
||||
|
||||
/// Accepts pem rfc7468 (openssl) or base64 der (second line of rfc7468 pem).
|
||||
pub fn from_maybe_pem(
|
||||
public_key_maybe_pem: &str,
|
||||
|
||||
Reference in New Issue
Block a user