mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
Feature/remove postgres (#2570)
* wip: move postgres data to patchdb * wip * wip * wip * complete notifications and clean up warnings * fill in user agent * move os tor bindings to single call
This commit is contained in:
@@ -18,6 +18,7 @@ use trust_dns_server::proto::rr::{Name, Record, RecordType};
|
||||
use trust_dns_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
||||
use trust_dns_server::ServerFuture;
|
||||
|
||||
use crate::net::forward::START9_BRIDGE_IFACE;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
@@ -163,13 +164,13 @@ impl DnsController {
|
||||
|
||||
Command::new("resolvectl")
|
||||
.arg("dns")
|
||||
.arg("lxcbr0")
|
||||
.arg(START9_BRIDGE_IFACE)
|
||||
.arg("127.0.0.1")
|
||||
.invoke(ErrorKind::Network)
|
||||
.await?;
|
||||
Command::new("resolvectl")
|
||||
.arg("domain")
|
||||
.arg("lxcbr0")
|
||||
.arg(START9_BRIDGE_IFACE)
|
||||
.arg("embassy")
|
||||
.invoke(ErrorKind::Network)
|
||||
.await?;
|
||||
|
||||
177
core/startos/src/net/forward.rs
Normal file
177
core/startos/src/net/forward.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use id_pool::IdPool;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub const START9_BRIDGE_IFACE: &str = "lxcbr0";
|
||||
pub const FIRST_DYNAMIC_PRIVATE_PORT: u16 = 49152;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct AvailablePorts(IdPool);
|
||||
impl AvailablePorts {
|
||||
pub fn new() -> Self {
|
||||
Self(IdPool::new_ranged(FIRST_DYNAMIC_PRIVATE_PORT..u16::MAX))
|
||||
}
|
||||
pub fn alloc(&mut self) -> Result<u16, Error> {
|
||||
self.0.request_id().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("No more dynamic ports available!"),
|
||||
ErrorKind::Network,
|
||||
)
|
||||
})
|
||||
}
|
||||
pub fn free(&mut self, ports: impl IntoIterator<Item = u16>) {
|
||||
for port in ports {
|
||||
self.0.return_id(port).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LanPortForwardController {
|
||||
forwards: Mutex<BTreeMap<u16, BTreeMap<SocketAddr, Weak<()>>>>,
|
||||
}
|
||||
impl LanPortForwardController {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
forwards: Mutex::new(BTreeMap::new()),
|
||||
}
|
||||
}
|
||||
pub async fn add(&self, port: u16, addr: SocketAddr) -> Result<Arc<()>, Error> {
|
||||
let mut writable = self.forwards.lock().await;
|
||||
let (prev, mut forward) = if let Some(forward) = writable.remove(&port) {
|
||||
(
|
||||
forward.keys().next().cloned(),
|
||||
forward
|
||||
.into_iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
(None, BTreeMap::new())
|
||||
};
|
||||
let rc = Arc::new(());
|
||||
forward.insert(addr, Arc::downgrade(&rc));
|
||||
let next = forward.keys().next().cloned();
|
||||
if !forward.is_empty() {
|
||||
writable.insert(port, forward);
|
||||
}
|
||||
|
||||
update_forward(port, prev, next).await?;
|
||||
Ok(rc)
|
||||
}
|
||||
pub async fn gc(&self, external: u16) -> Result<(), Error> {
|
||||
let mut writable = self.forwards.lock().await;
|
||||
let (prev, forward) = if let Some(forward) = writable.remove(&external) {
|
||||
(
|
||||
forward.keys().next().cloned(),
|
||||
forward
|
||||
.into_iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
(None, BTreeMap::new())
|
||||
};
|
||||
let next = forward.keys().next().cloned();
|
||||
if !forward.is_empty() {
|
||||
writable.insert(external, forward);
|
||||
}
|
||||
|
||||
update_forward(external, prev, next).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_forward(
|
||||
external: u16,
|
||||
prev: Option<SocketAddr>,
|
||||
next: Option<SocketAddr>,
|
||||
) -> Result<(), Error> {
|
||||
if prev != next {
|
||||
if let Some(prev) = prev {
|
||||
unforward(START9_BRIDGE_IFACE, external, prev).await?;
|
||||
}
|
||||
if let Some(next) = next {
|
||||
forward(START9_BRIDGE_IFACE, external, next).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// iptables -I FORWARD -o br-start9 -p tcp -d 172.18.0.2 --dport 8333 -j ACCEPT
|
||||
// iptables -t nat -I PREROUTING -p tcp --dport 32768 -j DNAT --to 172.18.0.2:8333
|
||||
async fn forward(iface: &str, external: u16, addr: SocketAddr) -> Result<(), Error> {
|
||||
Command::new("iptables")
|
||||
.arg("-I")
|
||||
.arg("FORWARD")
|
||||
.arg("-o")
|
||||
.arg(iface)
|
||||
.arg("-p")
|
||||
.arg("tcp")
|
||||
.arg("-d")
|
||||
.arg(addr.ip().to_string())
|
||||
.arg("--dport")
|
||||
.arg(addr.port().to_string())
|
||||
.arg("-j")
|
||||
.arg("ACCEPT")
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Command::new("iptables")
|
||||
.arg("-t")
|
||||
.arg("nat")
|
||||
.arg("-I")
|
||||
.arg("PREROUTING")
|
||||
.arg("-p")
|
||||
.arg("tcp")
|
||||
.arg("--dport")
|
||||
.arg(external.to_string())
|
||||
.arg("-j")
|
||||
.arg("DNAT")
|
||||
.arg("--to")
|
||||
.arg(addr.to_string())
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// iptables -D FORWARD -o br-start9 -p tcp -d 172.18.0.2 --dport 8333 -j ACCEPT
|
||||
// iptables -t nat -D PREROUTING -p tcp --dport 32768 -j DNAT --to 172.18.0.2:8333
|
||||
async fn unforward(iface: &str, external: u16, addr: SocketAddr) -> Result<(), Error> {
|
||||
Command::new("iptables")
|
||||
.arg("-D")
|
||||
.arg("FORWARD")
|
||||
.arg("-o")
|
||||
.arg(iface)
|
||||
.arg("-p")
|
||||
.arg("tcp")
|
||||
.arg("-d")
|
||||
.arg(addr.ip().to_string())
|
||||
.arg("--dport")
|
||||
.arg(addr.port().to_string())
|
||||
.arg("-j")
|
||||
.arg("ACCEPT")
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Command::new("iptables")
|
||||
.arg("-t")
|
||||
.arg("nat")
|
||||
.arg("-D")
|
||||
.arg("PREROUTING")
|
||||
.arg("-p")
|
||||
.arg("tcp")
|
||||
.arg("--dport")
|
||||
.arg(external.to_string())
|
||||
.arg("-j")
|
||||
.arg("DNAT")
|
||||
.arg("--to")
|
||||
.arg(addr.to_string())
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
9
core/startos/src/net/host/address.rs
Normal file
9
core/startos/src/net/host/address.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use torut::onion::OnionAddressV3;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum HostAddress {
|
||||
Onion { address: OnionAddressV3 },
|
||||
}
|
||||
71
core/startos/src/net/host/binding.rs
Normal file
71
core/startos/src/net/host/binding.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use imbl_value::InternedString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::net::forward::AvailablePorts;
|
||||
use crate::net::vhost::AlpnInfo;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BindInfo {
|
||||
pub options: BindOptions,
|
||||
pub assigned_lan_port: Option<u16>,
|
||||
}
|
||||
impl BindInfo {
|
||||
pub fn new(available_ports: &mut AvailablePorts, options: BindOptions) -> Result<Self, Error> {
|
||||
let mut assigned_lan_port = None;
|
||||
if options.add_ssl.is_some() || options.secure {
|
||||
assigned_lan_port = Some(available_ports.alloc()?);
|
||||
}
|
||||
Ok(Self {
|
||||
options,
|
||||
assigned_lan_port,
|
||||
})
|
||||
}
|
||||
pub fn update(
|
||||
self,
|
||||
available_ports: &mut AvailablePorts,
|
||||
options: BindOptions,
|
||||
) -> Result<Self, Error> {
|
||||
let Self {
|
||||
mut assigned_lan_port,
|
||||
..
|
||||
} = self;
|
||||
if options.add_ssl.is_some() || options.secure {
|
||||
assigned_lan_port = if let Some(port) = assigned_lan_port.take() {
|
||||
Some(port)
|
||||
} else {
|
||||
Some(available_ports.alloc()?)
|
||||
};
|
||||
} else {
|
||||
if let Some(port) = assigned_lan_port.take() {
|
||||
available_ports.free([port]);
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
options,
|
||||
assigned_lan_port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BindOptions {
|
||||
pub scheme: InternedString,
|
||||
pub preferred_external_port: u16,
|
||||
pub add_ssl: Option<AddSslOptions>,
|
||||
pub secure: bool,
|
||||
pub ssl: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AddSslOptions {
|
||||
pub scheme: InternedString,
|
||||
pub preferred_external_port: u16,
|
||||
// #[serde(default)]
|
||||
// pub add_x_forwarded_headers: bool, // TODO
|
||||
#[serde(default)]
|
||||
pub alpn: AlpnInfo,
|
||||
}
|
||||
@@ -1,29 +1,84 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use models::HostId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::net::host::multi::MultiHost;
|
||||
use crate::net::forward::AvailablePorts;
|
||||
use crate::net::host::address::HostAddress;
|
||||
use crate::net::host::binding::{BindInfo, BindOptions};
|
||||
use crate::prelude::*;
|
||||
|
||||
pub mod multi;
|
||||
pub mod address;
|
||||
pub mod binding;
|
||||
|
||||
pub enum Host {
|
||||
Multi(MultiHost),
|
||||
// Single(SingleHost),
|
||||
// Static(StaticHost),
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct Host {
|
||||
pub kind: HostKind,
|
||||
pub bindings: BTreeMap<u16, BindInfo>,
|
||||
pub addresses: BTreeSet<HostAddress>,
|
||||
pub primary: Option<HostAddress>,
|
||||
}
|
||||
impl AsRef<Host> for Host {
|
||||
fn as_ref(&self) -> &Host {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl Host {
|
||||
pub fn new(kind: HostKind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
bindings: BTreeMap::new(),
|
||||
addresses: BTreeSet::new(),
|
||||
primary: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct BindOptions {
|
||||
scheme: InternedString,
|
||||
preferred_external_port: u16,
|
||||
add_ssl: Option<AddSslOptions>,
|
||||
secure: bool,
|
||||
ssl: bool,
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum HostKind {
|
||||
Multi,
|
||||
// Single,
|
||||
// Static,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AddSslOptions {
|
||||
scheme: InternedString,
|
||||
preferred_external_port: u16,
|
||||
#[serde(default)]
|
||||
add_x_forwarded_headers: bool,
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct HostInfo(BTreeMap<HostId, Host>);
|
||||
|
||||
impl Map for HostInfo {
|
||||
type Key = HostId;
|
||||
type Value = Host;
|
||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||
Ok(key)
|
||||
}
|
||||
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
||||
Ok(key.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Model<HostInfo> {
|
||||
pub fn add_binding(
|
||||
&mut self,
|
||||
available_ports: &mut AvailablePorts,
|
||||
kind: HostKind,
|
||||
id: &HostId,
|
||||
internal_port: u16,
|
||||
options: BindOptions,
|
||||
) -> Result<(), Error> {
|
||||
self.upsert(id, || Host::new(kind))?
|
||||
.as_bindings_mut()
|
||||
.mutate(|b| {
|
||||
let info = if let Some(info) = b.remove(&internal_port) {
|
||||
info.update(available_ports, options)?
|
||||
} else {
|
||||
BindInfo::new(available_ports, options)?
|
||||
};
|
||||
b.insert(internal_port, info);
|
||||
Ok(())
|
||||
}) // TODO: handle host kind change
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::net::host::BindOptions;
|
||||
use crate::net::keys::Key;
|
||||
|
||||
pub struct MultiHost {
|
||||
id: InternedString,
|
||||
key: Key,
|
||||
binds: BTreeMap<u16, BindOptions>,
|
||||
}
|
||||
@@ -1,393 +1,24 @@
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use models::{HostId, Id, PackageId};
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::sha::Sha256;
|
||||
use openssl::x509::X509;
|
||||
use p256::elliptic_curve::pkcs8::EncodePrivateKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Acquire, PgExecutor};
|
||||
use ssh_key::private::Ed25519PrivateKey;
|
||||
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::config::ConfigureContext;
|
||||
use crate::context::RpcContext;
|
||||
use crate::control::{restart, ControlParams};
|
||||
use crate::disk::fsck::RequiresReboot;
|
||||
use crate::net::ssl::CertPair;
|
||||
use crate::account::AccountInfo;
|
||||
use crate::net::ssl::CertStore;
|
||||
use crate::net::tor::OnionStore;
|
||||
use crate::prelude::*;
|
||||
use crate::util::crypto::ed25519_expand_key;
|
||||
|
||||
// TODO: delete once we may change tor addresses
|
||||
async fn compat(
|
||||
secrets: impl PgExecutor<'_>,
|
||||
host: &Option<(PackageId, HostId)>,
|
||||
) -> Result<Option<[u8; 64]>, Error> {
|
||||
if let Some((package, host)) = host {
|
||||
if let Some(r) = sqlx::query!(
|
||||
"SELECT key FROM tor WHERE package = $1 AND interface = $2",
|
||||
package,
|
||||
host
|
||||
)
|
||||
.fetch_optional(secrets)
|
||||
.await?
|
||||
{
|
||||
Ok(Some(<[u8; 64]>::try_from(r.key).map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("expected vec of len 64, got len {}", e.len()),
|
||||
ErrorKind::ParseDbField,
|
||||
)
|
||||
})?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else if let Some(key) = sqlx::query!("SELECT tor_key FROM account WHERE id = 0")
|
||||
.fetch_one(secrets)
|
||||
.await?
|
||||
.tor_key
|
||||
{
|
||||
Ok(Some(<[u8; 64]>::try_from(key).map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("expected vec of len 64, got len {}", e.len()),
|
||||
ErrorKind::ParseDbField,
|
||||
)
|
||||
})?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct KeyStore {
|
||||
pub onion: OnionStore,
|
||||
pub local_certs: CertStore,
|
||||
// pub letsencrypt_certs: BTreeMap<BTreeSet<InternedString>, CertData>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Key {
|
||||
host: Option<(PackageId, HostId)>,
|
||||
base: [u8; 32],
|
||||
tor_key: [u8; 64], // Does NOT necessarily match base
|
||||
}
|
||||
impl Key {
|
||||
pub fn host(&self) -> Option<(PackageId, HostId)> {
|
||||
self.host.clone()
|
||||
}
|
||||
pub fn as_bytes(&self) -> [u8; 32] {
|
||||
self.base
|
||||
}
|
||||
pub fn internal_address(&self) -> String {
|
||||
self.host
|
||||
.as_ref()
|
||||
.map(|(pkg_id, _)| format!("{}.embassy", pkg_id))
|
||||
.unwrap_or_else(|| "embassy".to_owned())
|
||||
}
|
||||
pub fn tor_key(&self) -> TorSecretKeyV3 {
|
||||
self.tor_key.into()
|
||||
}
|
||||
pub fn tor_address(&self) -> OnionAddressV3 {
|
||||
self.tor_key().public().get_onion_address()
|
||||
}
|
||||
pub fn base_address(&self) -> String {
|
||||
self.tor_key()
|
||||
.public()
|
||||
.get_onion_address()
|
||||
.get_address_without_dot_onion()
|
||||
}
|
||||
pub fn local_address(&self) -> String {
|
||||
self.base_address() + ".local"
|
||||
}
|
||||
pub fn openssl_key_ed25519(&self) -> PKey<Private> {
|
||||
PKey::private_key_from_raw_bytes(&self.base, openssl::pkey::Id::ED25519).unwrap()
|
||||
}
|
||||
pub fn openssl_key_nistp256(&self) -> PKey<Private> {
|
||||
let mut buf = self.base;
|
||||
loop {
|
||||
if let Ok(k) = p256::SecretKey::from_slice(&buf) {
|
||||
return PKey::private_key_from_pkcs8(&*k.to_pkcs8_der().unwrap().as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
let mut sha = Sha256::new();
|
||||
sha.update(&buf);
|
||||
buf = sha.finish();
|
||||
}
|
||||
}
|
||||
pub fn ssh_key(&self) -> Ed25519PrivateKey {
|
||||
Ed25519PrivateKey::from_bytes(&self.base)
|
||||
}
|
||||
pub(crate) fn from_pair(
|
||||
host: Option<(PackageId, HostId)>,
|
||||
bytes: [u8; 32],
|
||||
tor_key: [u8; 64],
|
||||
) -> Self {
|
||||
Self {
|
||||
host,
|
||||
tor_key,
|
||||
base: bytes,
|
||||
}
|
||||
}
|
||||
pub fn from_bytes(host: Option<(PackageId, HostId)>, bytes: [u8; 32]) -> Self {
|
||||
Self::from_pair(host, bytes, ed25519_expand_key(&bytes))
|
||||
}
|
||||
pub fn new(host: Option<(PackageId, HostId)>) -> Self {
|
||||
Self::from_bytes(host, rand::random())
|
||||
}
|
||||
pub(super) fn with_certs(self, certs: CertPair, int: X509, root: X509) -> KeyInfo {
|
||||
KeyInfo {
|
||||
key: self,
|
||||
certs,
|
||||
int,
|
||||
root,
|
||||
}
|
||||
}
|
||||
pub async fn for_package(
|
||||
secrets: impl PgExecutor<'_>,
|
||||
package: &PackageId,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
network_keys.package,
|
||||
network_keys.interface,
|
||||
network_keys.key,
|
||||
tor.key AS "tor_key?"
|
||||
FROM
|
||||
network_keys
|
||||
LEFT JOIN
|
||||
tor
|
||||
ON
|
||||
network_keys.package = tor.package
|
||||
AND
|
||||
network_keys.interface = tor.interface
|
||||
WHERE
|
||||
network_keys.package = $1
|
||||
"#,
|
||||
package
|
||||
)
|
||||
.fetch_all(secrets)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let host = Some((package.clone(), HostId::from(Id::try_from(row.interface)?)));
|
||||
let bytes = row.key.try_into().map_err(|e: Vec<u8>| {
|
||||
Error::new(
|
||||
eyre!("Invalid length for network key {} expected 32", e.len()),
|
||||
crate::ErrorKind::Database,
|
||||
)
|
||||
})?;
|
||||
Ok(match row.tor_key {
|
||||
Some(tor_key) => Key::from_pair(
|
||||
host,
|
||||
bytes,
|
||||
tor_key.try_into().map_err(|e: Vec<u8>| {
|
||||
Error::new(
|
||||
eyre!("Invalid length for tor key {} expected 64", e.len()),
|
||||
crate::ErrorKind::Database,
|
||||
)
|
||||
})?,
|
||||
),
|
||||
None => Key::from_bytes(host, bytes),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
pub async fn for_host<Ex>(
|
||||
secrets: &mut Ex,
|
||||
host: Option<(PackageId, HostId)>,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: PgExecutor<'a>,
|
||||
{
|
||||
let tentative = rand::random::<[u8; 32]>();
|
||||
let actual = if let Some((pkg, iface)) = &host {
|
||||
let k = tentative.as_slice();
|
||||
let actual = sqlx::query!(
|
||||
"INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET package = EXCLUDED.package RETURNING key",
|
||||
pkg,
|
||||
iface,
|
||||
k,
|
||||
)
|
||||
.fetch_one(&mut *secrets)
|
||||
.await?.key;
|
||||
let mut bytes = tentative;
|
||||
bytes.clone_from_slice(actual.get(0..32).ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Invalid key size returned from DB"),
|
||||
crate::ErrorKind::Database,
|
||||
)
|
||||
})?);
|
||||
bytes
|
||||
} else {
|
||||
let actual = sqlx::query!("SELECT network_key FROM account WHERE id = 0")
|
||||
.fetch_one(&mut *secrets)
|
||||
.await?
|
||||
.network_key;
|
||||
let mut bytes = tentative;
|
||||
bytes.clone_from_slice(actual.get(0..32).ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Invalid key size returned from DB"),
|
||||
crate::ErrorKind::Database,
|
||||
)
|
||||
})?);
|
||||
bytes
|
||||
impl KeyStore {
|
||||
pub fn new(account: &AccountInfo) -> Result<Self, Error> {
|
||||
let mut res = Self {
|
||||
onion: OnionStore::new(),
|
||||
local_certs: CertStore::new(account)?,
|
||||
};
|
||||
let mut res = Self::from_bytes(host, actual);
|
||||
if let Some(tor_key) = compat(secrets, &res.host).await? {
|
||||
res.tor_key = tor_key;
|
||||
}
|
||||
res.onion.insert(account.tor_key.clone());
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
impl Drop for Key {
|
||||
fn drop(&mut self) {
|
||||
self.base.zeroize();
|
||||
self.tor_key.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct KeyInfo {
|
||||
key: Key,
|
||||
certs: CertPair,
|
||||
int: X509,
|
||||
root: X509,
|
||||
}
|
||||
impl KeyInfo {
|
||||
pub fn key(&self) -> &Key {
|
||||
&self.key
|
||||
}
|
||||
pub fn certs(&self) -> &CertPair {
|
||||
&self.certs
|
||||
}
|
||||
pub fn int_ca(&self) -> &X509 {
|
||||
&self.int
|
||||
}
|
||||
pub fn root_ca(&self) -> &X509 {
|
||||
&self.root
|
||||
}
|
||||
pub fn fullchain_ed25519(&self) -> Vec<&X509> {
|
||||
vec![&self.certs.ed25519, &self.int, &self.root]
|
||||
}
|
||||
pub fn fullchain_nistp256(&self) -> Vec<&X509> {
|
||||
vec![&self.certs.nistp256, &self.int, &self.root]
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_keygen() {
|
||||
let key = Key::new(None);
|
||||
key.tor_key();
|
||||
key.openssl_key_nistp256();
|
||||
}
|
||||
|
||||
pub fn display_requires_reboot(_: RotateKeysParams, args: RequiresReboot) {
|
||||
if args.0 {
|
||||
println!("Server must be restarted for changes to take effect");
|
||||
}
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct RotateKeysParams {
|
||||
package: Option<PackageId>,
|
||||
host: Option<HostId>,
|
||||
}
|
||||
|
||||
// #[command(display(display_requires_reboot))]
|
||||
pub async fn rotate_key(
|
||||
ctx: RpcContext,
|
||||
RotateKeysParams { package, host }: RotateKeysParams,
|
||||
) -> Result<RequiresReboot, Error> {
|
||||
let mut pgcon = ctx.secret_store.acquire().await?;
|
||||
let mut tx = pgcon.begin().await?;
|
||||
if let Some(package) = package {
|
||||
let Some(host) = host else {
|
||||
return Err(Error::new(
|
||||
eyre!("Must specify host"),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
sqlx::query!(
|
||||
"DELETE FROM tor WHERE package = $1 AND interface = $2",
|
||||
&package,
|
||||
&host,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"DELETE FROM network_keys WHERE package = $1 AND interface = $2",
|
||||
&package,
|
||||
&host,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
let new_key = Key::for_host(&mut *tx, Some((package.clone(), host.clone()))).await?;
|
||||
let needs_config = ctx
|
||||
.db
|
||||
.mutate(|v| {
|
||||
let installed = v
|
||||
.as_public_mut()
|
||||
.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(&host)
|
||||
.or_not_found(&host)?;
|
||||
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())?;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// if installed
|
||||
// .as_manifest()
|
||||
// .as_config()
|
||||
// .transpose_ref()
|
||||
// .is_some()
|
||||
// {
|
||||
// installed
|
||||
// .as_status_mut()
|
||||
// .as_configured_mut()
|
||||
// .replace(&false)
|
||||
// } else {
|
||||
// Ok(false)
|
||||
// }
|
||||
Ok(false)
|
||||
})
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
if needs_config {
|
||||
ctx.services
|
||||
.get(&package)
|
||||
.await
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("There is no manager running for {package}"),
|
||||
ErrorKind::Unknown,
|
||||
)
|
||||
})?
|
||||
.configure(ConfigureContext::default())
|
||||
.await?;
|
||||
} else {
|
||||
restart(ctx, ControlParams { id: 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_host(&mut *tx, None).await?;
|
||||
let url = format!("https://{}", new_key.tor_address()).parse()?;
|
||||
ctx.db
|
||||
.mutate(|v| {
|
||||
v.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_tor_address_mut()
|
||||
.ser(&url)
|
||||
})
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(RequiresReboot(true))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::instrument;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
pub async fn resolve_mdns(hostname: &str) -> Result<Ipv4Addr, Error> {
|
||||
Ok(String::from_utf8(
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use rpc_toolkit::{from_fn_async, AnyContext, HandlerExt, ParentHandler};
|
||||
|
||||
use crate::context::CliContext;
|
||||
use rpc_toolkit::ParentHandler;
|
||||
|
||||
pub mod dhcp;
|
||||
pub mod dns;
|
||||
pub mod forward;
|
||||
pub mod host;
|
||||
pub mod keys;
|
||||
pub mod mdns;
|
||||
@@ -22,13 +21,4 @@ pub fn net() -> ParentHandler {
|
||||
ParentHandler::new()
|
||||
.subcommand("tor", tor::tor())
|
||||
.subcommand("dhcp", dhcp::dhcp())
|
||||
.subcommand("ssl", ssl::ssl())
|
||||
.subcommand(
|
||||
"rotate-key",
|
||||
from_fn_async(keys::rotate_key)
|
||||
.with_custom_display_fn::<AnyContext, _>(|handle, result| {
|
||||
Ok(keys::display_requires_reboot(handle.params, result))
|
||||
})
|
||||
.with_remote_cli::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,59 +1,72 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use models::{HostId, PackageId};
|
||||
use sqlx::PgExecutor;
|
||||
use imbl::OrdMap;
|
||||
use lazy_format::lazy_format;
|
||||
use models::{HostId, OptionExt, PackageId};
|
||||
use patch_db::PatchDb;
|
||||
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::db::prelude::PatchDbExt;
|
||||
use crate::error::ErrorCollection;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::net::dns::DnsController;
|
||||
use crate::net::keys::Key;
|
||||
use crate::net::ssl::{export_cert, export_key, SslManager};
|
||||
use crate::net::forward::LanPortForwardController;
|
||||
use crate::net::host::address::HostAddress;
|
||||
use crate::net::host::binding::{AddSslOptions, BindOptions};
|
||||
use crate::net::host::{Host, HostKind};
|
||||
use crate::net::tor::TorController;
|
||||
use crate::net::vhost::{AlpnInfo, VHostController};
|
||||
use crate::volume::cert_dir;
|
||||
use crate::util::serde::MaybeUtf8String;
|
||||
use crate::{Error, HOST_IP};
|
||||
|
||||
pub struct NetController {
|
||||
db: PatchDb,
|
||||
pub(super) tor: TorController,
|
||||
pub(super) vhost: VHostController,
|
||||
pub(super) dns: DnsController,
|
||||
pub(super) ssl: Arc<SslManager>,
|
||||
pub(super) forward: LanPortForwardController,
|
||||
pub(super) os_bindings: Vec<Arc<()>>,
|
||||
}
|
||||
|
||||
impl NetController {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn init(
|
||||
db: PatchDb,
|
||||
tor_control: SocketAddr,
|
||||
tor_socks: SocketAddr,
|
||||
dns_bind: &[SocketAddr],
|
||||
ssl: SslManager,
|
||||
hostname: &Hostname,
|
||||
os_key: &Key,
|
||||
os_tor_key: TorSecretKeyV3,
|
||||
) -> Result<Self, Error> {
|
||||
let ssl = Arc::new(ssl);
|
||||
let mut res = Self {
|
||||
db: db.clone(),
|
||||
tor: TorController::new(tor_control, tor_socks),
|
||||
vhost: VHostController::new(ssl.clone()),
|
||||
vhost: VHostController::new(db),
|
||||
dns: DnsController::init(dns_bind).await?,
|
||||
ssl,
|
||||
forward: LanPortForwardController::new(),
|
||||
os_bindings: Vec::new(),
|
||||
};
|
||||
res.add_os_bindings(hostname, os_key).await?;
|
||||
res.add_os_bindings(hostname, os_tor_key).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn add_os_bindings(&mut self, hostname: &Hostname, key: &Key) -> Result<(), Error> {
|
||||
let alpn = Err(AlpnInfo::Specified(vec!["http/1.1".into(), "h2".into()]));
|
||||
async fn add_os_bindings(
|
||||
&mut self,
|
||||
hostname: &Hostname,
|
||||
tor_key: TorSecretKeyV3,
|
||||
) -> Result<(), Error> {
|
||||
let alpn = Err(AlpnInfo::Specified(vec![
|
||||
MaybeUtf8String("http/1.1".into()),
|
||||
MaybeUtf8String("h2".into()),
|
||||
]));
|
||||
|
||||
// Internal DNS
|
||||
self.vhost
|
||||
.add(
|
||||
key.clone(),
|
||||
Some("embassy".into()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
@@ -66,13 +79,7 @@ impl NetController {
|
||||
// LAN IP
|
||||
self.os_bindings.push(
|
||||
self.vhost
|
||||
.add(
|
||||
key.clone(),
|
||||
None,
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
alpn.clone(),
|
||||
)
|
||||
.add(None, 443, ([127, 0, 0, 1], 80).into(), alpn.clone())
|
||||
.await?,
|
||||
);
|
||||
|
||||
@@ -80,7 +87,6 @@ impl NetController {
|
||||
self.os_bindings.push(
|
||||
self.vhost
|
||||
.add(
|
||||
key.clone(),
|
||||
Some("localhost".into()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
@@ -91,7 +97,6 @@ impl NetController {
|
||||
self.os_bindings.push(
|
||||
self.vhost
|
||||
.add(
|
||||
key.clone(),
|
||||
Some(hostname.no_dot_host_name()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
@@ -104,7 +109,6 @@ impl NetController {
|
||||
self.os_bindings.push(
|
||||
self.vhost
|
||||
.add(
|
||||
key.clone(),
|
||||
Some(hostname.local_domain_name()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
@@ -113,28 +117,26 @@ impl NetController {
|
||||
.await?,
|
||||
);
|
||||
|
||||
// Tor (http)
|
||||
self.os_bindings.push(
|
||||
self.tor
|
||||
.add(key.tor_key(), 80, ([127, 0, 0, 1], 80).into())
|
||||
.await?,
|
||||
);
|
||||
|
||||
// Tor (https)
|
||||
// Tor
|
||||
self.os_bindings.push(
|
||||
self.vhost
|
||||
.add(
|
||||
key.clone(),
|
||||
Some(key.tor_address().to_string()),
|
||||
Some(tor_key.public().get_onion_address().to_string()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
alpn.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
self.os_bindings.push(
|
||||
self.os_bindings.extend(
|
||||
self.tor
|
||||
.add(key.tor_key(), 443, ([127, 0, 0, 1], 443).into())
|
||||
.add(
|
||||
tor_key,
|
||||
vec![
|
||||
(80, ([127, 0, 0, 1], 80).into()), // http
|
||||
(443, ([127, 0, 0, 1], 443).into()), // https
|
||||
],
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
@@ -155,57 +157,15 @@ impl NetController {
|
||||
ip,
|
||||
dns,
|
||||
controller: Arc::downgrade(self),
|
||||
tor: BTreeMap::new(),
|
||||
lan: BTreeMap::new(),
|
||||
binds: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn add_tor(
|
||||
&self,
|
||||
key: &Key,
|
||||
external: u16,
|
||||
target: SocketAddr,
|
||||
) -> Result<Vec<Arc<()>>, Error> {
|
||||
let mut rcs = Vec::with_capacity(1);
|
||||
rcs.push(self.tor.add(key.tor_key(), external, target).await?);
|
||||
Ok(rcs)
|
||||
}
|
||||
|
||||
async fn remove_tor(&self, key: &Key, external: u16, rcs: Vec<Arc<()>>) -> Result<(), Error> {
|
||||
drop(rcs);
|
||||
self.tor.gc(Some(key.tor_key()), Some(external)).await
|
||||
}
|
||||
|
||||
async fn add_lan(
|
||||
&self,
|
||||
key: Key,
|
||||
external: u16,
|
||||
target: SocketAddr,
|
||||
connect_ssl: Result<(), AlpnInfo>,
|
||||
) -> Result<Vec<Arc<()>>, Error> {
|
||||
let mut rcs = Vec::with_capacity(2);
|
||||
rcs.push(
|
||||
self.vhost
|
||||
.add(
|
||||
key.clone(),
|
||||
Some(key.local_address()),
|
||||
external,
|
||||
target.into(),
|
||||
connect_ssl,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
// rcs.push(self.mdns.add(key.base_address()).await?);
|
||||
// TODO
|
||||
Ok(rcs)
|
||||
}
|
||||
|
||||
async fn remove_lan(&self, key: &Key, external: u16, rcs: Vec<Arc<()>>) -> Result<(), Error> {
|
||||
drop(rcs);
|
||||
// self.mdns.gc(key.base_address()).await?;
|
||||
// TODO
|
||||
self.vhost.gc(Some(key.local_address()), external).await
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct HostBinds {
|
||||
lan: BTreeMap<u16, (u16, Option<AddSslOptions>, Arc<()>)>,
|
||||
tor: BTreeMap<OnionAddressV3, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
|
||||
}
|
||||
|
||||
pub struct NetService {
|
||||
@@ -214,8 +174,7 @@ pub struct NetService {
|
||||
ip: Ipv4Addr,
|
||||
dns: Arc<()>,
|
||||
controller: Weak<NetController>,
|
||||
tor: BTreeMap<(HostId, u16), (Key, Vec<Arc<()>>)>,
|
||||
lan: BTreeMap<(HostId, u16), (Key, Vec<Arc<()>>)>,
|
||||
binds: BTreeMap<HostId, HostBinds>,
|
||||
}
|
||||
impl NetService {
|
||||
fn net_controller(&self) -> Result<Arc<NetController>, Error> {
|
||||
@@ -226,111 +185,196 @@ impl NetService {
|
||||
)
|
||||
})
|
||||
}
|
||||
pub async fn add_tor<Ex>(
|
||||
|
||||
pub async fn bind(
|
||||
&mut self,
|
||||
secrets: &mut Ex,
|
||||
kind: HostKind,
|
||||
id: HostId,
|
||||
external: u16,
|
||||
internal: u16,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: PgExecutor<'a>,
|
||||
{
|
||||
let key = Key::for_host(secrets, Some((self.id.clone(), id.clone()))).await?;
|
||||
let ctrl = self.net_controller()?;
|
||||
let tor_idx = (id, external);
|
||||
let mut tor = self
|
||||
.tor
|
||||
.remove(&tor_idx)
|
||||
.unwrap_or_else(|| (key.clone(), Vec::new()));
|
||||
tor.1.append(
|
||||
&mut ctrl
|
||||
.add_tor(&key, external, SocketAddr::new(self.ip.into(), internal))
|
||||
.await?,
|
||||
);
|
||||
self.tor.insert(tor_idx, tor);
|
||||
Ok(())
|
||||
internal_port: u16,
|
||||
options: BindOptions,
|
||||
) -> Result<(), Error> {
|
||||
let id_ref = &id;
|
||||
let pkg_id = &self.id;
|
||||
let host = self
|
||||
.net_controller()?
|
||||
.db
|
||||
.mutate(|d| {
|
||||
let mut ports = d.as_private().as_available_ports().de()?;
|
||||
let hosts = d
|
||||
.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(pkg_id)
|
||||
.or_not_found(pkg_id)?
|
||||
.as_installed_mut()
|
||||
.or_not_found(pkg_id)?
|
||||
.as_hosts_mut();
|
||||
hosts.add_binding(&mut ports, kind, &id, internal_port, options)?;
|
||||
let host = hosts
|
||||
.as_idx(&id)
|
||||
.or_not_found(lazy_format!("Host {id_ref} for {pkg_id}"))?
|
||||
.de()?;
|
||||
d.as_private_mut().as_available_ports_mut().ser(&ports)?;
|
||||
Ok(host)
|
||||
})
|
||||
.await?;
|
||||
self.update(id, host).await
|
||||
}
|
||||
pub async fn remove_tor(&mut self, id: HostId, external: u16) -> Result<(), Error> {
|
||||
|
||||
async fn update(&mut self, id: HostId, host: Host) -> Result<(), Error> {
|
||||
let ctrl = self.net_controller()?;
|
||||
if let Some((key, rcs)) = self.tor.remove(&(id, external)) {
|
||||
ctrl.remove_tor(&key, external, rcs).await?;
|
||||
let binds = {
|
||||
if !self.binds.contains_key(&id) {
|
||||
self.binds.insert(id.clone(), Default::default());
|
||||
}
|
||||
self.binds.get_mut(&id).unwrap()
|
||||
};
|
||||
if true
|
||||
// TODO: if should listen lan
|
||||
{
|
||||
for (port, bind) in &host.bindings {
|
||||
let old_lan_bind = binds.lan.remove(port);
|
||||
let old_lan_port = old_lan_bind.as_ref().map(|(external, _, _)| *external);
|
||||
let lan_bind = old_lan_bind.filter(|(external, ssl, _)| {
|
||||
ssl == &bind.options.add_ssl
|
||||
&& bind.assigned_lan_port.as_ref() == Some(external)
|
||||
}); // only keep existing binding if relevant details match
|
||||
if let Some(external) = bind.assigned_lan_port {
|
||||
let new_lan_bind = if let Some(b) = lan_bind {
|
||||
b
|
||||
} else {
|
||||
if let Some(ssl) = &bind.options.add_ssl {
|
||||
let rc = ctrl
|
||||
.vhost
|
||||
.add(
|
||||
None,
|
||||
external,
|
||||
(self.ip, *port).into(),
|
||||
if bind.options.ssl {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ssl.alpn.clone())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
(*port, Some(ssl.clone()), rc)
|
||||
} else {
|
||||
let rc = ctrl.forward.add(external, (self.ip, *port).into()).await?;
|
||||
(*port, None, rc)
|
||||
}
|
||||
};
|
||||
binds.lan.insert(*port, new_lan_bind);
|
||||
}
|
||||
if let Some(external) = old_lan_port {
|
||||
ctrl.vhost.gc(None, external).await?;
|
||||
ctrl.forward.gc(external).await?;
|
||||
}
|
||||
}
|
||||
let mut removed = BTreeSet::new();
|
||||
let mut removed_ssl = BTreeSet::new();
|
||||
binds.lan.retain(|internal, (external, ssl, _)| {
|
||||
if host.bindings.contains_key(internal) {
|
||||
true
|
||||
} else {
|
||||
if ssl.is_some() {
|
||||
removed_ssl.insert(*external);
|
||||
} else {
|
||||
removed.insert(*external);
|
||||
}
|
||||
false
|
||||
}
|
||||
});
|
||||
for external in removed {
|
||||
ctrl.forward.gc(external).await?;
|
||||
}
|
||||
for external in removed_ssl {
|
||||
ctrl.vhost.gc(None, external).await?;
|
||||
}
|
||||
}
|
||||
let tor_binds: OrdMap<u16, SocketAddr> = host
|
||||
.bindings
|
||||
.iter()
|
||||
.flat_map(|(internal, info)| {
|
||||
let non_ssl = (
|
||||
info.options.preferred_external_port,
|
||||
SocketAddr::from((self.ip, *internal)),
|
||||
);
|
||||
if let (Some(ssl), Some(ssl_internal)) =
|
||||
(&info.options.add_ssl, info.assigned_lan_port)
|
||||
{
|
||||
itertools::Either::Left(
|
||||
[
|
||||
(
|
||||
ssl.preferred_external_port,
|
||||
SocketAddr::from(([127, 0, 0, 1], ssl_internal)),
|
||||
),
|
||||
non_ssl,
|
||||
]
|
||||
.into_iter(),
|
||||
)
|
||||
} else {
|
||||
itertools::Either::Right([non_ssl].into_iter())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut keep_tor_addrs = BTreeSet::new();
|
||||
for addr in match host.kind {
|
||||
HostKind::Multi => {
|
||||
// itertools::Either::Left(
|
||||
host.addresses.iter()
|
||||
// )
|
||||
} // HostKind::Single | HostKind::Static => itertools::Either::Right(&host.primary),
|
||||
} {
|
||||
match addr {
|
||||
HostAddress::Onion { address } => {
|
||||
keep_tor_addrs.insert(address);
|
||||
let old_tor_bind = binds.tor.remove(address);
|
||||
let tor_bind = old_tor_bind.filter(|(ports, _)| ports == &tor_binds);
|
||||
let new_tor_bind = if let Some(tor_bind) = tor_bind {
|
||||
tor_bind
|
||||
} else {
|
||||
let key = ctrl
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.into_private()
|
||||
.into_key_store()
|
||||
.into_onion()
|
||||
.get_key(address)?;
|
||||
let rcs = ctrl
|
||||
.tor
|
||||
.add(key, tor_binds.clone().into_iter().collect())
|
||||
.await?;
|
||||
(tor_binds.clone(), rcs)
|
||||
};
|
||||
binds.tor.insert(address.clone(), new_tor_bind);
|
||||
}
|
||||
}
|
||||
}
|
||||
for addr in binds.tor.keys() {
|
||||
if !keep_tor_addrs.contains(addr) {
|
||||
ctrl.tor.gc(Some(addr.clone()), None).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn add_lan<Ex>(
|
||||
&mut self,
|
||||
secrets: &mut Ex,
|
||||
id: HostId,
|
||||
external: u16,
|
||||
internal: u16,
|
||||
connect_ssl: Result<(), AlpnInfo>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: PgExecutor<'a>,
|
||||
{
|
||||
let key = Key::for_host(secrets, Some((self.id.clone(), id.clone()))).await?;
|
||||
let ctrl = self.net_controller()?;
|
||||
let lan_idx = (id, external);
|
||||
let mut lan = self
|
||||
.lan
|
||||
.remove(&lan_idx)
|
||||
.unwrap_or_else(|| (key.clone(), Vec::new()));
|
||||
lan.1.append(
|
||||
&mut ctrl
|
||||
.add_lan(
|
||||
key,
|
||||
external,
|
||||
SocketAddr::new(self.ip.into(), internal),
|
||||
connect_ssl,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
self.lan.insert(lan_idx, lan);
|
||||
Ok(())
|
||||
}
|
||||
pub async fn remove_lan(&mut self, id: HostId, external: u16) -> Result<(), Error> {
|
||||
let ctrl = self.net_controller()?;
|
||||
if let Some((key, rcs)) = self.lan.remove(&(id, external)) {
|
||||
ctrl.remove_lan(&key, external, rcs).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn export_cert<Ex>(
|
||||
&self,
|
||||
secrets: &mut Ex,
|
||||
id: &HostId,
|
||||
ip: IpAddr,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: PgExecutor<'a>,
|
||||
{
|
||||
let key = Key::for_host(secrets, Some((self.id.clone(), id.clone()))).await?;
|
||||
let ctrl = self.net_controller()?;
|
||||
let cert = ctrl.ssl.with_certs(key, ip).await?;
|
||||
let cert_dir = cert_dir(&self.id, id);
|
||||
tokio::fs::create_dir_all(&cert_dir).await?;
|
||||
export_key(
|
||||
&cert.key().openssl_key_nistp256(),
|
||||
&cert_dir.join(format!("{id}.key.pem")),
|
||||
)
|
||||
.await?;
|
||||
export_cert(
|
||||
&cert.fullchain_nistp256(),
|
||||
&cert_dir.join(format!("{id}.cert.pem")),
|
||||
)
|
||||
.await?; // TODO: can upgrade to ed25519?
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_all(mut self) -> Result<(), Error> {
|
||||
self.shutdown = true;
|
||||
let mut errors = ErrorCollection::new();
|
||||
if let Some(ctrl) = Weak::upgrade(&self.controller) {
|
||||
for ((_, external), (key, rcs)) in std::mem::take(&mut self.lan) {
|
||||
errors.handle(ctrl.remove_lan(&key, external, rcs).await);
|
||||
}
|
||||
for ((_, external), (key, rcs)) in std::mem::take(&mut self.tor) {
|
||||
errors.handle(ctrl.remove_tor(&key, external, rcs).await);
|
||||
for (_, binds) in std::mem::take(&mut self.binds) {
|
||||
for (_, (external, ssl, rc)) in binds.lan {
|
||||
drop(rc);
|
||||
if ssl.is_some() {
|
||||
errors.handle(ctrl.vhost.gc(None, external).await);
|
||||
} else {
|
||||
errors.handle(ctrl.forward.gc(external).await);
|
||||
}
|
||||
}
|
||||
for (addr, (_, rcs)) in binds.tor {
|
||||
drop(rcs);
|
||||
errors.handle(ctrl.tor.gc(Some(addr), None).await);
|
||||
}
|
||||
}
|
||||
std::mem::take(&mut self.dns);
|
||||
errors.handle(ctrl.dns.gc(Some(self.id.clone()), self.ip).await);
|
||||
@@ -357,8 +401,7 @@ impl Drop for NetService {
|
||||
ip: Ipv4Addr::new(0, 0, 0, 0),
|
||||
dns: Default::default(),
|
||||
controller: Default::default(),
|
||||
tor: Default::default(),
|
||||
lan: Default::default(),
|
||||
binds: BTreeMap::new(),
|
||||
},
|
||||
);
|
||||
tokio::spawn(async move { svc.remove_all().await.unwrap() });
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use futures::FutureExt;
|
||||
use imbl_value::InternedString;
|
||||
use libc::time_t;
|
||||
use openssl::asn1::{Asn1Integer, Asn1Time};
|
||||
use openssl::bn::{BigNum, MsbOption};
|
||||
@@ -14,17 +15,137 @@ use openssl::nid::Nid;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509};
|
||||
use openssl::*;
|
||||
use rpc_toolkit::{from_fn_async, HandlerExt, ParentHandler};
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use patch_db::HasModel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::account::AccountInfo;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::hostname::Hostname;
|
||||
use crate::init::check_time_is_synchronized;
|
||||
use crate::net::dhcp::ips;
|
||||
use crate::net::keys::{Key, KeyInfo};
|
||||
use crate::{Error, ErrorKind, ResultExt, SOURCE_DATE};
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::Pem;
|
||||
use crate::SOURCE_DATE;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[model = "Model<Self>"]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct CertStore {
|
||||
pub root_key: Pem<PKey<Private>>,
|
||||
pub root_cert: Pem<X509>,
|
||||
pub int_key: Pem<PKey<Private>>,
|
||||
pub int_cert: Pem<X509>,
|
||||
pub leaves: BTreeMap<JsonKey<BTreeSet<InternedString>>, CertData>,
|
||||
}
|
||||
impl CertStore {
|
||||
pub fn new(account: &AccountInfo) -> Result<Self, Error> {
|
||||
let int_key = generate_key()?;
|
||||
let int_cert = make_int_cert((&account.root_ca_key, &account.root_ca_cert), &int_key)?;
|
||||
Ok(Self {
|
||||
root_key: Pem::new(account.root_ca_key.clone()),
|
||||
root_cert: Pem::new(account.root_ca_cert.clone()),
|
||||
int_key: Pem::new(int_key),
|
||||
int_cert: Pem::new(int_cert),
|
||||
leaves: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Model<CertStore> {
|
||||
/// This function will grant any cert for any domain. It is up to the *caller* to enusure that the calling service has permission to sign a cert for the requested domain
|
||||
pub fn cert_for(
|
||||
&mut self,
|
||||
hostnames: &BTreeSet<InternedString>,
|
||||
) -> Result<FullchainCertData, Error> {
|
||||
let keys = if let Some(cert_data) = self
|
||||
.as_leaves()
|
||||
.as_idx(JsonKey::new_ref(hostnames))
|
||||
.map(|m| m.de())
|
||||
.transpose()?
|
||||
{
|
||||
if cert_data
|
||||
.certs
|
||||
.ed25519
|
||||
.not_before()
|
||||
.compare(Asn1Time::days_from_now(0)?.as_ref())?
|
||||
== Ordering::Less
|
||||
&& cert_data
|
||||
.certs
|
||||
.ed25519
|
||||
.not_after()
|
||||
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
||||
== Ordering::Greater
|
||||
&& cert_data
|
||||
.certs
|
||||
.nistp256
|
||||
.not_before()
|
||||
.compare(Asn1Time::days_from_now(0)?.as_ref())?
|
||||
== Ordering::Less
|
||||
&& cert_data
|
||||
.certs
|
||||
.nistp256
|
||||
.not_after()
|
||||
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
||||
== Ordering::Greater
|
||||
{
|
||||
return Ok(FullchainCertData {
|
||||
root: self.as_root_cert().de()?.0,
|
||||
int: self.as_int_cert().de()?.0,
|
||||
leaf: cert_data,
|
||||
});
|
||||
}
|
||||
cert_data.keys
|
||||
} else {
|
||||
PKeyPair {
|
||||
ed25519: PKey::generate_ed25519()?,
|
||||
nistp256: PKey::from_ec_key(EcKey::generate(&*EcGroup::from_curve_name(
|
||||
Nid::X9_62_PRIME256V1,
|
||||
)?)?)?,
|
||||
}
|
||||
};
|
||||
let int_key = self.as_int_key().de()?.0;
|
||||
let int_cert = self.as_int_cert().de()?.0;
|
||||
let cert_data = CertData {
|
||||
certs: CertPair {
|
||||
ed25519: make_leaf_cert(
|
||||
(&int_key, &int_cert),
|
||||
(&keys.ed25519, &SANInfo::new(hostnames)),
|
||||
)?,
|
||||
nistp256: make_leaf_cert(
|
||||
(&int_key, &int_cert),
|
||||
(&keys.nistp256, &SANInfo::new(hostnames)),
|
||||
)?,
|
||||
},
|
||||
keys,
|
||||
};
|
||||
self.as_leaves_mut()
|
||||
.insert(JsonKey::new_ref(hostnames), &cert_data)?;
|
||||
Ok(FullchainCertData {
|
||||
root: self.as_root_cert().de()?.0,
|
||||
int: self.as_int_cert().de()?.0,
|
||||
leaf: cert_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CertData {
|
||||
pub keys: PKeyPair,
|
||||
pub certs: CertPair,
|
||||
}
|
||||
|
||||
pub struct FullchainCertData {
|
||||
pub root: X509,
|
||||
pub int: X509,
|
||||
pub leaf: CertData,
|
||||
}
|
||||
impl FullchainCertData {
|
||||
pub fn fullchain_ed25519(&self) -> Vec<&X509> {
|
||||
vec![&self.root, &self.int, &self.leaf.certs.ed25519]
|
||||
}
|
||||
pub fn fullchain_nistp256(&self) -> Vec<&X509> {
|
||||
vec![&self.root, &self.int, &self.leaf.certs.nistp256]
|
||||
}
|
||||
}
|
||||
|
||||
static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you.
|
||||
|
||||
@@ -35,62 +156,20 @@ fn unix_time(time: SystemTime) -> time_t {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct CertPair {
|
||||
pub ed25519: X509,
|
||||
pub nistp256: X509,
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PKeyPair {
|
||||
#[serde(with = "crate::util::serde::pem")]
|
||||
pub ed25519: PKey<Private>,
|
||||
#[serde(with = "crate::util::serde::pem")]
|
||||
pub nistp256: PKey<Private>,
|
||||
}
|
||||
impl CertPair {
|
||||
fn updated(
|
||||
pair: Option<&Self>,
|
||||
hostname: &Hostname,
|
||||
signer: (&PKey<Private>, &X509),
|
||||
applicant: &Key,
|
||||
ip: BTreeSet<IpAddr>,
|
||||
) -> Result<(Self, bool), Error> {
|
||||
let mut updated = false;
|
||||
let mut updated_cert = |cert: Option<&X509>, osk: PKey<Private>| -> Result<X509, Error> {
|
||||
let mut ips = BTreeSet::new();
|
||||
if let Some(cert) = cert {
|
||||
ips.extend(
|
||||
cert.subject_alt_names()
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter_map(|a| a.ipaddress())
|
||||
.filter_map(|a| match a.len() {
|
||||
4 => Some::<IpAddr>(<[u8; 4]>::try_from(a).unwrap().into()),
|
||||
16 => Some::<IpAddr>(<[u8; 16]>::try_from(a).unwrap().into()),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
if cert
|
||||
.not_before()
|
||||
.compare(Asn1Time::days_from_now(0)?.as_ref())?
|
||||
== Ordering::Less
|
||||
&& cert
|
||||
.not_after()
|
||||
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
||||
== Ordering::Greater
|
||||
&& ips.is_superset(&ip)
|
||||
{
|
||||
return Ok(cert.clone());
|
||||
}
|
||||
}
|
||||
ips.extend(ip.iter().copied());
|
||||
updated = true;
|
||||
make_leaf_cert(signer, (&osk, &SANInfo::new(&applicant, hostname, ips)))
|
||||
};
|
||||
Ok((
|
||||
Self {
|
||||
ed25519: updated_cert(pair.map(|c| &c.ed25519), applicant.openssl_key_ed25519())?,
|
||||
nistp256: updated_cert(
|
||||
pair.map(|c| &c.nistp256),
|
||||
applicant.openssl_key_nistp256(),
|
||||
)?,
|
||||
},
|
||||
updated,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
pub struct CertPair {
|
||||
#[serde(with = "crate::util::serde::pem")]
|
||||
pub ed25519: X509,
|
||||
#[serde(with = "crate::util::serde::pem")]
|
||||
pub nistp256: X509,
|
||||
}
|
||||
|
||||
pub async fn root_ca_start_time() -> Result<SystemTime, Error> {
|
||||
@@ -101,51 +180,6 @@ pub async fn root_ca_start_time() -> Result<SystemTime, Error> {
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SslManager {
|
||||
hostname: Hostname,
|
||||
root_cert: X509,
|
||||
int_key: PKey<Private>,
|
||||
int_cert: X509,
|
||||
cert_cache: RwLock<BTreeMap<Key, CertPair>>,
|
||||
}
|
||||
impl SslManager {
|
||||
pub fn new(account: &AccountInfo, start_time: SystemTime) -> Result<Self, Error> {
|
||||
let int_key = generate_key()?;
|
||||
let int_cert = make_int_cert(
|
||||
(&account.root_ca_key, &account.root_ca_cert),
|
||||
&int_key,
|
||||
start_time,
|
||||
)?;
|
||||
Ok(Self {
|
||||
hostname: account.hostname.clone(),
|
||||
root_cert: account.root_ca_cert.clone(),
|
||||
int_key,
|
||||
int_cert,
|
||||
cert_cache: RwLock::new(BTreeMap::new()),
|
||||
})
|
||||
}
|
||||
pub async fn with_certs(&self, key: Key, ip: IpAddr) -> Result<KeyInfo, Error> {
|
||||
let mut ips = ips().await?;
|
||||
ips.insert(ip);
|
||||
let (pair, updated) = CertPair::updated(
|
||||
self.cert_cache.read().await.get(&key),
|
||||
&self.hostname,
|
||||
(&self.int_key, &self.int_cert),
|
||||
&key,
|
||||
ips,
|
||||
)?;
|
||||
if updated {
|
||||
self.cert_cache
|
||||
.write()
|
||||
.await
|
||||
.insert(key.clone(), pair.clone());
|
||||
}
|
||||
|
||||
Ok(key.with_certs(pair, self.int_cert.clone(), self.root_cert.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
const EC_CURVE_NAME: nid::Nid = nid::Nid::X9_62_PRIME256V1;
|
||||
lazy_static::lazy_static! {
|
||||
static ref EC_GROUP: EcGroup = EcGroup::from_curve_name(EC_CURVE_NAME).unwrap();
|
||||
@@ -245,18 +279,13 @@ pub fn make_root_cert(
|
||||
pub fn make_int_cert(
|
||||
signer: (&PKey<Private>, &X509),
|
||||
applicant: &PKey<Private>,
|
||||
start_time: SystemTime,
|
||||
) -> Result<X509, Error> {
|
||||
let mut builder = X509Builder::new()?;
|
||||
builder.set_version(CERTIFICATE_VERSION)?;
|
||||
|
||||
let unix_start_time = unix_time(start_time);
|
||||
builder.set_not_before(signer.1.not_before())?;
|
||||
|
||||
let embargo = Asn1Time::from_unix(unix_start_time - 86400)?;
|
||||
builder.set_not_before(&embargo)?;
|
||||
|
||||
let expiration = Asn1Time::from_unix(unix_start_time + (10 * 364 * 86400))?;
|
||||
builder.set_not_after(&expiration)?;
|
||||
builder.set_not_after(signer.1.not_after())?;
|
||||
|
||||
builder.set_serial_number(&*rand_serial()?)?;
|
||||
|
||||
@@ -309,13 +338,13 @@ pub fn make_int_cert(
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum MaybeWildcard {
|
||||
WithWildcard(String),
|
||||
WithoutWildcard(String),
|
||||
WithoutWildcard(InternedString),
|
||||
}
|
||||
impl MaybeWildcard {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
MaybeWildcard::WithWildcard(s) => s.as_str(),
|
||||
MaybeWildcard::WithoutWildcard(s) => s.as_str(),
|
||||
MaybeWildcard::WithoutWildcard(s) => &**s,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,18 +363,16 @@ pub struct SANInfo {
|
||||
pub ips: BTreeSet<IpAddr>,
|
||||
}
|
||||
impl SANInfo {
|
||||
pub fn new(key: &Key, hostname: &Hostname, ips: BTreeSet<IpAddr>) -> Self {
|
||||
pub fn new(hostnames: &BTreeSet<InternedString>) -> Self {
|
||||
let mut dns = BTreeSet::new();
|
||||
if let Some((id, _)) = key.host() {
|
||||
dns.insert(MaybeWildcard::WithWildcard(format!("{id}.embassy")));
|
||||
dns.insert(MaybeWildcard::WithWildcard(key.local_address().to_string()));
|
||||
} else {
|
||||
dns.insert(MaybeWildcard::WithoutWildcard("embassy".to_owned()));
|
||||
dns.insert(MaybeWildcard::WithWildcard(hostname.local_domain_name()));
|
||||
dns.insert(MaybeWildcard::WithoutWildcard(hostname.no_dot_host_name()));
|
||||
dns.insert(MaybeWildcard::WithoutWildcard("localhost".to_owned()));
|
||||
let mut ips = BTreeSet::new();
|
||||
for hostname in hostnames {
|
||||
if let Ok(ip) = hostname.parse::<IpAddr>() {
|
||||
ips.insert(ip);
|
||||
} else {
|
||||
dns.insert(MaybeWildcard::WithoutWildcard(hostname.clone())); // TODO: wildcards?
|
||||
}
|
||||
}
|
||||
dns.insert(MaybeWildcard::WithWildcard(key.tor_address().to_string()));
|
||||
Self { dns, ips }
|
||||
}
|
||||
}
|
||||
@@ -443,14 +470,3 @@ pub fn make_leaf_cert(
|
||||
let cert = builder.build();
|
||||
Ok(cert)
|
||||
}
|
||||
|
||||
pub fn ssl() -> ParentHandler {
|
||||
ParentHandler::new().subcommand("size", from_fn_async(size).with_remote_cli::<CliContext>())
|
||||
}
|
||||
|
||||
pub async fn size(ctx: RpcContext) -> Result<String, Error> {
|
||||
Ok(format!(
|
||||
"Cert Catch size: {}",
|
||||
ctx.net_controller.ssl.cert_cache.read().await.len()
|
||||
))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use axum::routing::{any, get, post};
|
||||
use axum::Router;
|
||||
use digest::Digest;
|
||||
use futures::future::ready;
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use http::header::ACCEPT_ENCODING;
|
||||
use http::request::Parts as RequestParts;
|
||||
use http::{HeaderMap, Method, StatusCode};
|
||||
@@ -28,7 +27,6 @@ use crate::context::{DiagnosticContext, InstallContext, RpcContext, SetupContext
|
||||
use crate::core::rpc_continuations::RequestGuid;
|
||||
use crate::db::subscribe;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::install::PKG_PUBLIC_DIR;
|
||||
use crate::middleware::auth::{Auth, HasValidSession};
|
||||
use crate::middleware::cors::Cors;
|
||||
use crate::middleware::db::SyncDb;
|
||||
@@ -131,8 +129,7 @@ pub fn main_ui_server_router(ctx: RpcContext) -> Router {
|
||||
"/ws/rpc/*path",
|
||||
get({
|
||||
let ctx = ctx.clone();
|
||||
move |headers: HeaderMap,
|
||||
x::Path(path): x::Path<String>,
|
||||
move |x::Path(path): x::Path<String>,
|
||||
ws: axum::extract::ws::WebSocketUpgrade| async move {
|
||||
match RequestGuid::from(&path) {
|
||||
None => {
|
||||
@@ -155,7 +152,6 @@ pub fn main_ui_server_router(ctx: RpcContext) -> Router {
|
||||
let path = request
|
||||
.uri()
|
||||
.path()
|
||||
.clone()
|
||||
.strip_prefix("/rest/rpc/")
|
||||
.unwrap_or_default();
|
||||
match RequestGuid::from(&path) {
|
||||
|
||||
@@ -28,13 +28,44 @@ use crate::logs::{
|
||||
cli_logs_generic_follow, cli_logs_generic_nofollow, fetch_logs, follow_logs, journalctl,
|
||||
LogFollowResponse, LogResponse, LogSource,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind, ResultExt as _};
|
||||
|
||||
pub const SYSTEMD_UNIT: &str = "tor@default";
|
||||
const STARTING_HEALTH_TIMEOUT: u64 = 120; // 2min
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct OnionStore(BTreeMap<OnionAddressV3, TorSecretKeyV3>);
|
||||
impl Map for OnionStore {
|
||||
type Key = OnionAddressV3;
|
||||
type Value = TorSecretKeyV3;
|
||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||
Ok(key.get_address_without_dot_onion())
|
||||
}
|
||||
}
|
||||
impl OnionStore {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn insert(&mut self, key: TorSecretKeyV3) {
|
||||
self.0.insert(key.public().get_onion_address(), key);
|
||||
}
|
||||
}
|
||||
impl Model<OnionStore> {
|
||||
pub fn new_key(&mut self) -> Result<TorSecretKeyV3, Error> {
|
||||
let key = TorSecretKeyV3::generate();
|
||||
self.insert(&key.public().get_onion_address(), &key)?;
|
||||
Ok(key)
|
||||
}
|
||||
pub fn insert_key(&mut self, key: &TorSecretKeyV3) -> Result<(), Error> {
|
||||
self.insert(&key.public().get_onion_address(), &key)
|
||||
}
|
||||
pub fn get_key(&self, address: &OnionAddressV3) -> Result<TorSecretKeyV3, Error> {
|
||||
self.as_idx(address).or_not_found(address)?.de()
|
||||
}
|
||||
}
|
||||
|
||||
enum ErrorLogSeverity {
|
||||
Fatal { wipe_state: bool },
|
||||
Unknown { wipe_state: bool },
|
||||
@@ -208,33 +239,29 @@ impl TorController {
|
||||
pub async fn add(
|
||||
&self,
|
||||
key: TorSecretKeyV3,
|
||||
external: u16,
|
||||
target: SocketAddr,
|
||||
) -> Result<Arc<()>, Error> {
|
||||
bindings: Vec<(u16, SocketAddr)>,
|
||||
) -> Result<Vec<Arc<()>>, Error> {
|
||||
let (reply, res) = oneshot::channel();
|
||||
self.0
|
||||
.send
|
||||
.send(TorCommand::AddOnion {
|
||||
key,
|
||||
external,
|
||||
target,
|
||||
bindings,
|
||||
reply,
|
||||
})
|
||||
.ok()
|
||||
.ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))?;
|
||||
.map_err(|_| Error::new(eyre!("TorControl died"), ErrorKind::Tor))?;
|
||||
res.await
|
||||
.ok()
|
||||
.ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))
|
||||
.map_err(|_| Error::new(eyre!("TorControl died"), ErrorKind::Tor))
|
||||
}
|
||||
|
||||
pub async fn gc(
|
||||
&self,
|
||||
key: Option<TorSecretKeyV3>,
|
||||
addr: Option<OnionAddressV3>,
|
||||
external: Option<u16>,
|
||||
) -> Result<(), Error> {
|
||||
self.0
|
||||
.send
|
||||
.send(TorCommand::GC { key, external })
|
||||
.send(TorCommand::GC { addr, external })
|
||||
.ok()
|
||||
.ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))
|
||||
}
|
||||
@@ -279,12 +306,11 @@ type AuthenticatedConnection = AuthenticatedConn<
|
||||
enum TorCommand {
|
||||
AddOnion {
|
||||
key: TorSecretKeyV3,
|
||||
external: u16,
|
||||
target: SocketAddr,
|
||||
reply: oneshot::Sender<Arc<()>>,
|
||||
bindings: Vec<(u16, SocketAddr)>,
|
||||
reply: oneshot::Sender<Vec<Arc<()>>>,
|
||||
},
|
||||
GC {
|
||||
key: Option<TorSecretKeyV3>,
|
||||
addr: Option<OnionAddressV3>,
|
||||
external: Option<u16>,
|
||||
},
|
||||
GetInfo {
|
||||
@@ -302,7 +328,13 @@ async fn torctl(
|
||||
tor_control: SocketAddr,
|
||||
tor_socks: SocketAddr,
|
||||
recv: &mut mpsc::UnboundedReceiver<TorCommand>,
|
||||
services: &mut BTreeMap<[u8; 64], BTreeMap<u16, BTreeMap<SocketAddr, Weak<()>>>>,
|
||||
services: &mut BTreeMap<
|
||||
OnionAddressV3,
|
||||
(
|
||||
TorSecretKeyV3,
|
||||
BTreeMap<u16, BTreeMap<SocketAddr, Weak<()>>>,
|
||||
),
|
||||
>,
|
||||
wipe_state: &AtomicBool,
|
||||
health_timeout: &mut Duration,
|
||||
) -> Result<(), Error> {
|
||||
@@ -420,27 +452,32 @@ async fn torctl(
|
||||
match command {
|
||||
TorCommand::AddOnion {
|
||||
key,
|
||||
external,
|
||||
target,
|
||||
bindings,
|
||||
reply,
|
||||
} => {
|
||||
let mut service = if let Some(service) = services.remove(&key.as_bytes()) {
|
||||
let addr = key.public().get_onion_address();
|
||||
let mut service = if let Some((_key, service)) = services.remove(&addr) {
|
||||
debug_assert_eq!(key, _key);
|
||||
service
|
||||
} else {
|
||||
BTreeMap::new()
|
||||
};
|
||||
let mut binding = service.remove(&external).unwrap_or_default();
|
||||
let rc = if let Some(rc) =
|
||||
Weak::upgrade(&binding.remove(&target).unwrap_or_default())
|
||||
{
|
||||
rc
|
||||
} else {
|
||||
Arc::new(())
|
||||
};
|
||||
binding.insert(target, Arc::downgrade(&rc));
|
||||
service.insert(external, binding);
|
||||
services.insert(key.as_bytes(), service);
|
||||
reply.send(rc).unwrap_or_default();
|
||||
let mut rcs = Vec::with_capacity(bindings.len());
|
||||
for (external, target) in bindings {
|
||||
let mut binding = service.remove(&external).unwrap_or_default();
|
||||
let rc = if let Some(rc) =
|
||||
Weak::upgrade(&binding.remove(&target).unwrap_or_default())
|
||||
{
|
||||
rc
|
||||
} else {
|
||||
Arc::new(())
|
||||
};
|
||||
binding.insert(target, Arc::downgrade(&rc));
|
||||
service.insert(external, binding);
|
||||
rcs.push(rc);
|
||||
}
|
||||
services.insert(addr, (key, service));
|
||||
reply.send(rcs).unwrap_or_default();
|
||||
}
|
||||
TorCommand::GetInfo { reply, .. } => {
|
||||
reply
|
||||
@@ -480,8 +517,7 @@ async fn torctl(
|
||||
)
|
||||
.await?;
|
||||
|
||||
for (key, service) in std::mem::take(services) {
|
||||
let key = TorSecretKeyV3::from(key);
|
||||
for (addr, (key, service)) in std::mem::take(services) {
|
||||
let bindings = service
|
||||
.iter()
|
||||
.flat_map(|(ext, int)| {
|
||||
@@ -491,7 +527,7 @@ async fn torctl(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if !bindings.is_empty() {
|
||||
services.insert(key.as_bytes(), service);
|
||||
services.insert(addr, (key.clone(), service));
|
||||
connection
|
||||
.add_onion_v3(&key, false, false, false, None, &mut bindings.iter())
|
||||
.await?;
|
||||
@@ -503,31 +539,33 @@ async fn torctl(
|
||||
match command {
|
||||
TorCommand::AddOnion {
|
||||
key,
|
||||
external,
|
||||
target,
|
||||
bindings,
|
||||
reply,
|
||||
} => {
|
||||
let mut rm_res = Ok(());
|
||||
let onion_base = key
|
||||
.public()
|
||||
.get_onion_address()
|
||||
.get_address_without_dot_onion();
|
||||
let mut service = if let Some(service) = services.remove(&key.as_bytes()) {
|
||||
let addr = key.public().get_onion_address();
|
||||
let onion_base = addr.get_address_without_dot_onion();
|
||||
let mut service = if let Some((_key, service)) = services.remove(&addr) {
|
||||
debug_assert_eq!(_key, key);
|
||||
rm_res = connection.del_onion(&onion_base).await;
|
||||
service
|
||||
} else {
|
||||
BTreeMap::new()
|
||||
};
|
||||
let mut binding = service.remove(&external).unwrap_or_default();
|
||||
let rc = if let Some(rc) =
|
||||
Weak::upgrade(&binding.remove(&target).unwrap_or_default())
|
||||
{
|
||||
rc
|
||||
} else {
|
||||
Arc::new(())
|
||||
};
|
||||
binding.insert(target, Arc::downgrade(&rc));
|
||||
service.insert(external, binding);
|
||||
let mut rcs = Vec::with_capacity(bindings.len());
|
||||
for (external, target) in bindings {
|
||||
let mut binding = service.remove(&external).unwrap_or_default();
|
||||
let rc = if let Some(rc) =
|
||||
Weak::upgrade(&binding.remove(&target).unwrap_or_default())
|
||||
{
|
||||
rc
|
||||
} else {
|
||||
Arc::new(())
|
||||
};
|
||||
binding.insert(target, Arc::downgrade(&rc));
|
||||
service.insert(external, binding);
|
||||
rcs.push(rc);
|
||||
}
|
||||
let bindings = service
|
||||
.iter()
|
||||
.flat_map(|(ext, int)| {
|
||||
@@ -536,25 +574,21 @@ async fn torctl(
|
||||
.map(|(addr, _)| (*ext, SocketAddr::from(*addr)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
services.insert(key.as_bytes(), service);
|
||||
reply.send(rc).unwrap_or_default();
|
||||
services.insert(addr, (key.clone(), service));
|
||||
reply.send(rcs).unwrap_or_default();
|
||||
rm_res?;
|
||||
connection
|
||||
.add_onion_v3(&key, false, false, false, None, &mut bindings.iter())
|
||||
.await?;
|
||||
}
|
||||
TorCommand::GC { key, external } => {
|
||||
for key in if key.is_some() {
|
||||
itertools::Either::Left(key.into_iter().map(|k| k.as_bytes()))
|
||||
TorCommand::GC { addr, external } => {
|
||||
for addr in if addr.is_some() {
|
||||
itertools::Either::Left(addr.into_iter())
|
||||
} else {
|
||||
itertools::Either::Right(services.keys().cloned().collect_vec().into_iter())
|
||||
} {
|
||||
let key = TorSecretKeyV3::from(key);
|
||||
let onion_base = key
|
||||
.public()
|
||||
.get_onion_address()
|
||||
.get_address_without_dot_onion();
|
||||
if let Some(mut service) = services.remove(&key.as_bytes()) {
|
||||
if let Some((key, mut service)) = services.remove(&addr) {
|
||||
let onion_base: String = addr.get_address_without_dot_onion();
|
||||
for external in if external.is_some() {
|
||||
itertools::Either::Left(external.into_iter())
|
||||
} else {
|
||||
@@ -583,7 +617,7 @@ async fn torctl(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if !bindings.is_empty() {
|
||||
services.insert(key.as_bytes(), service);
|
||||
services.insert(addr, (key.clone(), service));
|
||||
}
|
||||
rm_res?;
|
||||
if !bindings.is_empty() {
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use imbl_value::InternedString;
|
||||
use models::ResultExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use tokio_rustls::rustls::pki_types::{
|
||||
@@ -16,38 +18,36 @@ use tokio_rustls::rustls::{RootCertStore, ServerConfig};
|
||||
use tokio_rustls::{LazyConfigAcceptor, TlsConnector};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::net::keys::Key;
|
||||
use crate::net::ssl::SslManager;
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::{BackTrackingReader, TimeoutStream};
|
||||
use crate::util::serde::MaybeUtf8String;
|
||||
|
||||
// not allowed: <=1024, >=32768, 5355, 5432, 9050, 6010, 9051, 5353
|
||||
|
||||
pub struct VHostController {
|
||||
ssl: Arc<SslManager>,
|
||||
db: PatchDb,
|
||||
servers: Mutex<BTreeMap<u16, VHostServer>>,
|
||||
}
|
||||
impl VHostController {
|
||||
pub fn new(ssl: Arc<SslManager>) -> Self {
|
||||
pub fn new(db: PatchDb) -> Self {
|
||||
Self {
|
||||
ssl,
|
||||
db,
|
||||
servers: Mutex::new(BTreeMap::new()),
|
||||
}
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn add(
|
||||
&self,
|
||||
key: Key,
|
||||
hostname: Option<String>,
|
||||
external: u16,
|
||||
target: SocketAddr,
|
||||
connect_ssl: Result<(), AlpnInfo>,
|
||||
connect_ssl: Result<(), AlpnInfo>, // Ok: yes, connect using ssl, pass through alpn; Err: connect tcp, use provided strategy for alpn
|
||||
) -> Result<Arc<()>, Error> {
|
||||
let mut writable = self.servers.lock().await;
|
||||
let server = if let Some(server) = writable.remove(&external) {
|
||||
server
|
||||
} else {
|
||||
VHostServer::new(external, self.ssl.clone()).await?
|
||||
VHostServer::new(external, self.db.clone()).await?
|
||||
};
|
||||
let rc = server
|
||||
.add(
|
||||
@@ -55,7 +55,6 @@ impl VHostController {
|
||||
TargetInfo {
|
||||
addr: target,
|
||||
connect_ssl,
|
||||
key,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -79,13 +78,18 @@ impl VHostController {
|
||||
struct TargetInfo {
|
||||
addr: SocketAddr,
|
||||
connect_ssl: Result<(), AlpnInfo>,
|
||||
key: Key,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum AlpnInfo {
|
||||
Reflect,
|
||||
Specified(Vec<Vec<u8>>),
|
||||
Specified(Vec<MaybeUtf8String>),
|
||||
}
|
||||
impl Default for AlpnInfo {
|
||||
fn default() -> Self {
|
||||
Self::Reflect
|
||||
}
|
||||
}
|
||||
|
||||
struct VHostServer {
|
||||
@@ -94,7 +98,7 @@ struct VHostServer {
|
||||
}
|
||||
impl VHostServer {
|
||||
#[instrument(skip_all)]
|
||||
async fn new(port: u16, ssl: Arc<SslManager>) -> Result<Self, Error> {
|
||||
async fn new(port: u16, db: PatchDb) -> Result<Self, Error> {
|
||||
// check if port allowed
|
||||
let listener = TcpListener::bind(SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port))
|
||||
.await
|
||||
@@ -105,13 +109,13 @@ impl VHostServer {
|
||||
_thread: tokio::spawn(async move {
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
Ok((stream, sock_addr)) => {
|
||||
let stream =
|
||||
Box::pin(TimeoutStream::new(stream, Duration::from_secs(300)));
|
||||
let mut stream = BackTrackingReader::new(stream);
|
||||
stream.start_buffering();
|
||||
let mapping = mapping.clone();
|
||||
let ssl = ssl.clone();
|
||||
let db = db.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = async {
|
||||
let mid = match LazyConfigAcceptor::new(
|
||||
@@ -167,6 +171,7 @@ impl VHostServer {
|
||||
.find(|(_, rc)| rc.strong_count() > 0)
|
||||
.or_else(|| {
|
||||
if target_name
|
||||
.as_ref()
|
||||
.map(|s| s.parse::<IpAddr>().is_ok())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
@@ -184,8 +189,22 @@ impl VHostServer {
|
||||
if let Some(target) = target {
|
||||
let mut tcp_stream =
|
||||
TcpStream::connect(target.addr).await?;
|
||||
let key =
|
||||
ssl.with_certs(target.key, target.addr.ip()).await?;
|
||||
let hostnames = target_name
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.map(InternedString::intern)
|
||||
.chain(std::iter::once(InternedString::from_display(
|
||||
&sock_addr.ip(),
|
||||
)))
|
||||
.collect();
|
||||
let key = db
|
||||
.mutate(|v| {
|
||||
v.as_private_mut()
|
||||
.as_key_store_mut()
|
||||
.as_local_certs_mut()
|
||||
.cert_for(&hostnames)
|
||||
})
|
||||
.await?;
|
||||
let cfg = ServerConfig::builder()
|
||||
.with_no_client_auth();
|
||||
let mut cfg =
|
||||
@@ -202,8 +221,9 @@ impl VHostServer {
|
||||
})
|
||||
.collect::<Result<_, Error>>()?,
|
||||
PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
|
||||
key.key()
|
||||
.openssl_key_ed25519()
|
||||
key.leaf
|
||||
.keys
|
||||
.ed25519
|
||||
.private_key_to_pkcs8()?,
|
||||
)),
|
||||
)
|
||||
@@ -218,8 +238,9 @@ impl VHostServer {
|
||||
})
|
||||
.collect::<Result<_, Error>>()?,
|
||||
PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
|
||||
key.key()
|
||||
.openssl_key_nistp256()
|
||||
key.leaf
|
||||
.keys
|
||||
.nistp256
|
||||
.private_key_to_pkcs8()?,
|
||||
)),
|
||||
)
|
||||
@@ -233,7 +254,7 @@ impl VHostServer {
|
||||
let mut store = RootCertStore::empty();
|
||||
store.add(
|
||||
CertificateDer::from(
|
||||
key.root_ca().to_der()?,
|
||||
key.root.to_der()?,
|
||||
),
|
||||
).with_kind(crate::ErrorKind::OpenSsl)?;
|
||||
store
|
||||
@@ -249,9 +270,9 @@ impl VHostServer {
|
||||
let mut target_stream =
|
||||
TlsConnector::from(Arc::new(client_cfg))
|
||||
.connect_with(
|
||||
ServerName::try_from(
|
||||
key.key().internal_address(),
|
||||
).with_kind(crate::ErrorKind::OpenSsl)?,
|
||||
ServerName::IpAddress(
|
||||
target.addr.ip().into(),
|
||||
),
|
||||
tcp_stream,
|
||||
|conn| {
|
||||
cfg.alpn_protocols.extend(
|
||||
@@ -302,7 +323,7 @@ impl VHostServer {
|
||||
.await
|
||||
}
|
||||
Err(AlpnInfo::Specified(alpn)) => {
|
||||
cfg.alpn_protocols = alpn;
|
||||
cfg.alpn_protocols = alpn.into_iter().map(|a| a.0).collect();
|
||||
let mut tls_stream =
|
||||
match mid.into_stream(Arc::new(cfg)).await {
|
||||
Ok(a) => a,
|
||||
|
||||
Reference in New Issue
Block a user