diff --git a/build/lib/scripts/dhclient-exit-hook b/build/lib/scripts/dhclient-exit-hook index 5580a00b5..8c4a97746 100755 --- a/build/lib/scripts/dhclient-exit-hook +++ b/build/lib/scripts/dhclient-exit-hook @@ -1 +1 @@ -embassy-cli net dhcp update $interface \ No newline at end of file +start-cli net dhcp update $interface \ No newline at end of file diff --git a/core/models/src/id/interface.rs b/core/models/src/id/interface.rs index d062a3648..b9b32dd4a 100644 --- a/core/models/src/id/interface.rs +++ b/core/models/src/id/interface.rs @@ -1,11 +1,18 @@ use std::path::Path; +use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize}; -use crate::Id; +use crate::{Id, InvalidId}; #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] pub struct InterfaceId(Id); +impl FromStr for InterfaceId { + type Err = InvalidId; + fn from_str(s: &str) -> Result { + Ok(Self(Id::try_from(s.to_owned())?)) + } +} impl From for InterfaceId { fn from(id: Id) -> Self { Self(id) diff --git a/core/startos/.sqlx/query-350ab82048fb4a049042e4fdbe1b8c606ca400e43e31b9a05d2937217e0f6962.json b/core/startos/.sqlx/query-350ab82048fb4a049042e4fdbe1b8c606ca400e43e31b9a05d2937217e0f6962.json new file mode 100644 index 000000000..c451ce9f3 --- /dev/null +++ b/core/startos/.sqlx/query-350ab82048fb4a049042e4fdbe1b8c606ca400e43e31b9a05d2937217e0f6962.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM tor WHERE package = $1 AND interface = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "350ab82048fb4a049042e4fdbe1b8c606ca400e43e31b9a05d2937217e0f6962" +} diff --git a/core/startos/.sqlx/query-b81592b3a74940ab56d41537484090d45cfa4c85168a587b1a41dc5393cccea1.json b/core/startos/.sqlx/query-b81592b3a74940ab56d41537484090d45cfa4c85168a587b1a41dc5393cccea1.json new file mode 100644 index 000000000..e2e8a1620 --- /dev/null +++ b/core/startos/.sqlx/query-b81592b3a74940ab56d41537484090d45cfa4c85168a587b1a41dc5393cccea1.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE account SET tor_key = NULL, network_key = gen_random_bytes(32)", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "b81592b3a74940ab56d41537484090d45cfa4c85168a587b1a41dc5393cccea1" +} diff --git a/core/startos/.sqlx/query-dfc23b7e966c3853284753a7e934351ba0cae3825988b3e0ecd3b6781bcff524.json b/core/startos/.sqlx/query-dfc23b7e966c3853284753a7e934351ba0cae3825988b3e0ecd3b6781bcff524.json new file mode 100644 index 000000000..2fc8ad1ba --- /dev/null +++ b/core/startos/.sqlx/query-dfc23b7e966c3853284753a7e934351ba0cae3825988b3e0ecd3b6781bcff524.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM network_keys WHERE package = $1 AND interface = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "dfc23b7e966c3853284753a7e934351ba0cae3825988b3e0ecd3b6781bcff524" +} diff --git a/core/startos/src/net/keys.rs b/core/startos/src/net/keys.rs index 624f3bcc4..a64e3a187 100644 --- a/core/startos/src/net/keys.rs +++ b/core/startos/src/net/keys.rs @@ -1,14 +1,22 @@ +use std::collections::BTreeMap; + +use clap::ArgMatches; use color_eyre::eyre::eyre; use models::{Id, InterfaceId, PackageId}; use openssl::pkey::{PKey, Private}; use openssl::sha::Sha256; use openssl::x509::X509; use p256::elliptic_curve::pkcs8::EncodePrivateKey; -use sqlx::PgExecutor; +use rpc_toolkit::command; +use sqlx::{Acquire, PgExecutor}; use ssh_key::private::Ed25519PrivateKey; use torut::onion::{OnionAddressV3, TorSecretKeyV3}; use zeroize::Zeroize; +use crate::config::{configure, ConfigureContext}; +use crate::context::RpcContext; +use crate::control::restart; +use crate::disk::fsck::RequiresReboot; use crate::net::ssl::CertPair; use crate::prelude::*; use crate::util::crypto::ed25519_expand_key; @@ -271,3 +279,107 @@ pub fn test_keygen() { key.tor_key(); key.openssl_key_nistp256(); } + +fn display_requires_reboot(arg: RequiresReboot, matches: &ArgMatches) { + if arg.0 { + println!("Server must be restarted for changes to take effect"); + } +} + +#[command(rename = "rotate-key", display(display_requires_reboot))] +pub async fn rotate_key( + #[context] ctx: RpcContext, + #[arg] package: Option, + #[arg] interface: Option, +) -> Result { + let mut pgcon = ctx.secret_store.acquire().await?; + let mut tx = pgcon.begin().await?; + if let Some(package) = package { + let Some(interface) = interface else { + return Err(Error::new( + eyre!("Must specify interface"), + ErrorKind::InvalidRequest, + )); + }; + sqlx::query!( + "DELETE FROM tor WHERE package = $1 AND interface = $2", + &package, + &interface, + ) + .execute(&mut *tx) + .await?; + sqlx::query!( + "DELETE FROM network_keys WHERE package = $1 AND interface = $2", + &package, + &interface, + ) + .execute(&mut *tx) + .await?; + let new_key = + Key::for_interface(&mut *tx, Some((package.clone(), interface.clone()))).await?; + let needs_config = ctx + .db + .mutate(|v| { + let installed = v + .as_package_data_mut() + .as_idx_mut(&package) + .or_not_found(&package)? + .as_installed_mut() + .or_not_found("installed")?; + let addrs = installed + .as_interface_addresses_mut() + .as_idx_mut(&interface) + .or_not_found(&interface)?; + if let Some(lan) = addrs.as_lan_address_mut().transpose_mut() { + lan.ser(&new_key.local_address())?; + } + if let Some(lan) = addrs.as_tor_address_mut().transpose_mut() { + lan.ser(&new_key.tor_address().to_string())?; + } + + if installed + .as_manifest() + .as_config() + .transpose_ref() + .is_some() + { + installed + .as_status_mut() + .as_configured_mut() + .replace(&false) + } else { + Ok(false) + } + }) + .await?; + tx.commit().await?; + if needs_config { + configure( + &ctx, + &package, + ConfigureContext { + breakages: BTreeMap::new(), + timeout: None, + config: None, + overrides: BTreeMap::new(), + dry_run: false, + }, + ) + .await?; + } else { + restart(ctx, package).await?; + } + Ok(RequiresReboot(false)) + } else { + sqlx::query!("UPDATE account SET tor_key = NULL, network_key = gen_random_bytes(32)") + .execute(&mut *tx) + .await?; + let new_key = Key::for_interface(&mut *tx, None).await?; + let url = format!("https://{}", new_key.tor_address()).parse()?; + ctx.db + .mutate(|v| v.as_server_info_mut().as_tor_address_mut().ser(&url)) + .await?; + tx.commit().await?; + Ok(RequiresReboot(true)) + } +} diff --git a/core/startos/src/net/mod.rs b/core/startos/src/net/mod.rs index 0b98c439a..50935fb18 100644 --- a/core/startos/src/net/mod.rs +++ b/core/startos/src/net/mod.rs @@ -22,7 +22,7 @@ pub mod wifi; pub const PACKAGE_CERT_PATH: &str = "/var/lib/embassy/ssl"; -#[command(subcommands(tor::tor, dhcp::dhcp, ssl::ssl))] +#[command(subcommands(tor::tor, dhcp::dhcp, ssl::ssl, keys::rotate_key))] pub fn net() -> Result<(), Error> { Ok(()) } diff --git a/core/startos/src/net/tor.rs b/core/startos/src/net/tor.rs index a00ab9c54..1bf4c5f44 100644 --- a/core/startos/src/net/tor.rs +++ b/core/startos/src/net/tor.rs @@ -53,11 +53,6 @@ lazy_static! { static ref PROGRESS_REGEX: Regex = Regex::new("PROGRESS=([0-9]+)").unwrap(); } -#[test] -fn random_key() { - println!("x'{}'", hex::encode(rand::random::<[u8; 32]>())); -} - #[command(subcommands(list_services, logs, reset))] pub fn tor() -> Result<(), Error> { Ok(())