appmgr: basic tor sync

This commit is contained in:
Aiden McClelland
2021-06-23 17:59:22 -06:00
parent 8871c6eec4
commit 08bb7caa81
10 changed files with 610 additions and 753 deletions

View File

@@ -28,7 +28,6 @@ async fn inner_main(cfg_path: Option<&str>) -> Result<(), Error> {
command: embassy::main_api,
context: ctx,
status: status_fn,
});
let status_ctx = rpc_ctx.clone();
let status_daemon = daemon(

View File

@@ -11,7 +11,8 @@ use serde::Deserialize;
use sqlx::SqlitePool;
use tokio::fs::File;
use crate::net::mdns::LanHandle;
use crate::net::mdns::{enable_lan, LanHandle};
use crate::net::tor::TorController;
use crate::util::{from_toml_async_reader, AsyncFileExt, Container};
use crate::{Error, ResultExt};
@@ -30,8 +31,8 @@ pub struct RpcContextSeed {
pub db: PatchDb,
pub secret_store: SqlitePool,
pub docker: Docker,
// pub lan_handle: Container<LanHandle>,
// pub
pub lan_handle: Container<LanHandle>,
pub tor_controller: TorController,
}
#[derive(Clone)]
@@ -50,14 +51,19 @@ impl RpcContext {
} else {
RpcContextConfig::default()
};
let db = PatchDb::open(
base.db
.unwrap_or_else(|| Path::new("/mnt/embassy-os/embassy.db").to_owned()),
)
.await?;
let mut db_handle = db.handle();
let lan_handle = Container::new();
lan_handle.set(enable_lan(&mut db_handle).await?).await;
let tor_controller = TorController::init(&mut db_handle).await?;
let seed = Arc::new(RpcContextSeed {
bind_rpc: base.bind_rpc.unwrap_or(([127, 0, 0, 1], 5959).into()),
bind_ws: base.bind_ws.unwrap_or(([127, 0, 0, 1], 5960).into()),
db: PatchDb::open(
base.db
.unwrap_or_else(|| Path::new("/mnt/embassy-os/embassy.db").to_owned()),
)
.await?,
db,
secret_store: SqlitePool::connect(&format!(
"sqlite://{}",
base.secret_store
@@ -66,6 +72,8 @@ impl RpcContext {
))
.await?,
docker: Docker::connect_with_unix_defaults()?,
lan_handle,
tor_controller,
});
Ok(Self(seed))
}

View File

@@ -173,6 +173,11 @@ impl From<bollard::errors::Error> for Error {
Error::new(e, ErrorKind::Docker)
}
}
impl From<torut::control::ConnError> for Error {
fn from(e: torut::control::ConnError) -> Self {
Error::new(anyhow!("{:?}", e), ErrorKind::Tor)
}
}
impl From<Error> for RpcError {
fn from(e: Error) -> Self {
let mut data_object = serde_json::Map::with_capacity(2);

View File

@@ -90,6 +90,12 @@ impl<'a> Id<&'a str> {
Id(self.0.to_owned())
}
}
impl<S: AsRef<str>> std::ops::Deref for Id<S> {
type Target = S;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<S: AsRef<str>> std::fmt::Display for Id<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_ref())
@@ -168,6 +174,12 @@ impl<S: AsRef<str>> std::fmt::Display for InterfaceId<S> {
write!(f, "{}", &self.0)
}
}
impl<S: AsRef<str>> std::ops::Deref for InterfaceId<S> {
type Target = S;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl<S: AsRef<str>> AsRef<str> for InterfaceId<S> {
fn as_ref(&self) -> &str {
self.0.as_ref()

View File

@@ -107,3 +107,5 @@ impl Drop for LanHandle {
}
}
}
unsafe impl Send for LanHandle {}
unsafe impl Sync for LanHandle {}

View File

@@ -1,664 +1,263 @@
use std::collections::{BTreeSet, HashMap};
use std::net::Ipv4Addr;
use std::os::unix::process::ExitStatusExt;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::Arc;
use anyhow::anyhow;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use futures::future::BoxFuture;
use futures::FutureExt;
use indexmap::IndexMap;
use patch_db::DbHandle;
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use tokio::net::TcpStream;
use tokio::sync::RwLock;
use torut::control::{AsyncEvent, AuthenticatedConn, ConnError};
use torut::onion::TorSecretKeyV3;
use crate::util::Invoke;
use crate::s9pk::manifest::TorConfig;
use crate::{Error, ResultExt as _};
#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum LanOptions {
Standard,
Custom { port: u16 },
fn event_handler(event: AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>> {
async move { Ok(()) }.boxed()
}
#[derive(Debug, Clone, Copy, serde::Serialize)]
pub struct PortMapping {
pub internal: u16,
pub tor: u16,
pub lan: Option<LanOptions>, // only for http interfaces
}
impl<'de> serde::de::Deserialize<'de> for PortMapping {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
pub struct PortMappingIF {
pub internal: u16,
pub tor: u16,
#[serde(default, deserialize_with = "deserialize_some")]
pub lan: Option<Option<LanOptions>>,
#[derive(Clone)]
pub struct TorController(Arc<RwLock<TorControllerInner>>);
impl TorController {
pub async fn init<Db: DbHandle>(
tor_cp: SocketAddr,
db: &mut Db,
secrets: &mut PoolConnection<Sqlite>,
) -> Result<Self, Error> {
Ok(TorController(Arc::new(RwLock::new(
TorControllerInner::init(tor_cp, db, secrets).await?,
))))
}
pub async fn sync<Db: DbHandle>(
&self,
db: &mut Db,
secrets: &mut PoolConnection<Sqlite>,
) -> Result<(), Error> {
let new = TorControllerInner::get_services(db, secrets).await?;
if &new != &self.0.read().await.services {
self.0.write().await.sync(new).await?;
}
fn deserialize_some<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: serde::de::Deserialize<'de>,
D: serde::de::Deserializer<'de>,
{
serde::de::Deserialize::deserialize(deserializer).map(Some)
}
let input_format: PortMappingIF = serde::de::Deserialize::deserialize(deserializer)?;
Ok(PortMapping {
internal: input_format.internal,
tor: input_format.tor,
lan: if let Some(lan) = input_format.lan {
lan
} else if input_format.tor == 80 {
Some(LanOptions::Standard)
} else {
None
},
})
Ok(())
}
}
pub const ETC_TOR_RC: &'static str = "/etc/tor/torrc";
pub const HIDDEN_SERVICE_DIR_ROOT: &'static str = "/var/lib/tor";
pub const ETC_HOSTNAME: &'static str = "/etc/hostname";
pub const ETC_NGINX_SERVICES_CONF: &'static str = "/etc/nginx/sites-available/start9-services.conf";
type AuthenticatedConnection = AuthenticatedConn<
TcpStream,
fn(AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>>,
>;
#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "lowercase")]
pub enum HiddenServiceVersion {
V1,
V2,
V3,
}
impl From<HiddenServiceVersion> for usize {
fn from(v: HiddenServiceVersion) -> Self {
match v {
HiddenServiceVersion::V1 => 1,
HiddenServiceVersion::V2 => 2,
HiddenServiceVersion::V3 => 3,
}
}
}
// impl std::convert::TryFrom<usize> for HiddenServiceVersion {
// type Error = anyhow::Error;
// fn try_from(v: usize) -> Result<Self, Self::Error> {
// Ok(match v {
// 1 => HiddenServiceVersion::V1,
// 2 => HiddenServiceVersion::V2,
// 3 => HiddenServiceVersion::V3,
// n => bail!("Invalid HiddenServiceVersion {}", n),
// })
// }
// }
impl Default for HiddenServiceVersion {
fn default() -> Self {
HiddenServiceVersion::V3
}
}
impl std::fmt::Display for HiddenServiceVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "HiddenServiceVersion {}", usize::from(*self))
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct HiddenServiceConfig {
ip: Ipv4Addr,
cfg: TorConfig,
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct Service {
pub ip: Ipv4Addr,
pub ports: Vec<PortMapping>,
#[serde(default)]
pub hidden_service_version: HiddenServiceVersion,
pub struct TorControllerInner {
connection: AuthenticatedConnection,
services: IndexMap<[u8; 64], HiddenServiceConfig>,
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct NewService {
pub ports: Vec<PortMapping>,
#[serde(default)]
pub hidden_service_version: HiddenServiceVersion,
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct ServicesMap {
pub map: HashMap<String, Service>,
pub ips: BTreeSet<Ipv4Addr>,
}
impl Default for ServicesMap {
fn default() -> Self {
ServicesMap {
map: Default::default(),
ips: Default::default(),
}
}
}
impl ServicesMap {
pub fn add(&mut self, name: String, service: NewService) -> Ipv4Addr {
let ip = self
.map
.get(&name)
.map(|a| a.ip.clone())
.unwrap_or_else(|| {
Ipv4Addr::from(
u32::from(
self.ips
.range(..)
.next_back()
.cloned()
.unwrap_or_else(|| crate::HOST_IP.into()),
) + 1,
)
});
self.ips.insert(ip);
self.map.insert(
name,
Service {
ip,
ports: service.ports,
hidden_service_version: service.hidden_service_version,
},
);
ip
}
pub fn remove(&mut self, name: &str) {
let s = self.map.remove(name);
if let Some(s) = s {
self.ips.remove(&s.ip);
}
}
}
pub async fn write_services(hidden_services: &ServicesMap) -> Result<(), Error> {
tokio::fs::copy(crate::TOR_RC, ETC_TOR_RC)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("{} -> {}", crate::TOR_RC, ETC_TOR_RC),
)
})?;
let mut f = tokio::fs::OpenOptions::new()
.append(true)
.open(ETC_TOR_RC)
.await?;
f.write_all(b"\n").await?;
for (name, service) in &hidden_services.map {
if service.ports.is_empty() {
continue;
}
f.write_all(b"\n").await?;
f.write_all(format!("# HIDDEN SERVICE FOR {}\n", name).as_bytes())
impl TorControllerInner {
async fn get_services<Db: DbHandle>(
db: &mut Db,
secrets: &mut PoolConnection<Sqlite>,
) -> Result<IndexMap<[u8; 64], HiddenServiceConfig>, Error> {
let pkg_ids = crate::db::DatabaseModel::new()
.package_data()
.keys(db)
.await?;
f.write_all(
format!(
"HiddenServiceDir {}/app-{}/\n",
HIDDEN_SERVICE_DIR_ROOT, name
)
.as_bytes(),
)
.await?;
f.write_all(format!("{}\n", service.hidden_service_version).as_bytes())
.await?;
for port in &service.ports {
f.write_all(
format!(
"HiddenServicePort {} {}:{}\n",
port.tor, service.ip, port.internal
)
.as_bytes(),
)
.await?;
}
f.write_all(b"\n").await?;
}
Ok(())
}
pub async fn write_lan_services(hidden_services: &ServicesMap) -> Result<(), Error> {
let mut f = tokio::fs::File::create(ETC_NGINX_SERVICES_CONF).await?;
for (app_id, service) in &hidden_services.map {
let hostname = tokio::fs::read_to_string(
Path::new(HIDDEN_SERVICE_DIR_ROOT)
.join(format!("app-{}", app_id))
.join("hostname"),
)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("{}/app-{}/hostname", HIDDEN_SERVICE_DIR_ROOT, app_id),
)
})?;
let hostname_str = hostname
.trim()
.strip_suffix(".onion")
.ok_or_else(|| anyhow!("{}", hostname))
.with_kind(crate::ErrorKind::InvalidOnionAddress)?;
for mapping in &service.ports {
match &mapping.lan {
Some(LanOptions::Standard) => {
log::info!("Writing LAN certificates for {}", app_id);
let base_path: PathBuf = todo!(); //PersistencePath::from_ref("apps").join(&app_id);
let key_path = base_path.join("cert-local.key.pem");
let conf_path = base_path.join("cert-local.csr.conf");
let req_path = base_path.join("cert-local.csr");
let cert_path = base_path.join("cert-local.crt.pem");
let fullchain_path = base_path.join("cert-local.fullchain.crt.pem");
if !fullchain_path.exists() || tokio::fs::metadata(&key_path).await.is_err() {
let mut fullchain_file = tokio::fs::File::create(&fullchain_path).await?;
tokio::process::Command::new("openssl")
.arg("ecparam")
.arg("-genkey")
.arg("-name")
.arg("prime256v1")
.arg("-noout")
.arg("-out")
.arg(&key_path)
.invoke(crate::ErrorKind::OpenSSL)
.await
.map_err(|e| {
let ctx = format!("GenKey: {}", e);
crate::Error::new(e.source.context(ctx), e.kind)
})?;
tokio::fs::write(
&conf_path,
format!(
include_str!("cert-local.csr.conf.template"),
hostname = hostname_str
),
)
.await?;
tokio::process::Command::new("openssl")
.arg("req")
.arg("-config")
.arg(&conf_path)
.arg("-key")
.arg(&key_path)
.arg("-new")
.arg("-addext")
.arg(format!(
"subjectAltName=DNS:{hostname}.local",
hostname = hostname_str
))
.arg("-out")
.arg(&req_path)
.invoke(crate::ErrorKind::OpenSSL)
.await
.map_err(|e| {
let ctx = format!("Req: {}", e);
crate::Error::new(e.source.context(ctx), e.kind)
})?;
tokio::process::Command::new("openssl")
.arg("ca")
.arg("-batch")
.arg("-config")
.arg("/root/agent/ca/intermediate/openssl.conf")
.arg("-rand_serial")
.arg("-keyfile")
.arg("/root/agent/ca/intermediate/private/embassy-int-ca.key.pem")
.arg("-cert")
.arg("/root/agent/ca/intermediate/certs/embassy-int-ca.crt.pem")
.arg("-extensions")
.arg("server_cert")
.arg("-days")
.arg("365")
.arg("-notext")
.arg("-in")
.arg(&req_path)
.arg("-out")
.arg(&cert_path)
.invoke(crate::ErrorKind::OpenSSL)
.await
.map_err(|e| {
let ctx = format!("CA: {}", e);
crate::Error::new(e.source.context(ctx), e.kind)
})?;
log::info!("Writing fullchain to: {}", fullchain_path.display());
tokio::io::copy(
&mut tokio::fs::File::open(&cert_path).await?,
&mut fullchain_file,
)
.await?;
tokio::io::copy(
&mut tokio::fs::File::open(
"/root/agent/ca/intermediate/certs/embassy-int-ca.crt.pem",
)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
"/root/agent/ca/intermediate/certs/embassy-int-ca.crt.pem",
)
})?,
&mut fullchain_file,
)
.await?;
tokio::io::copy(
&mut tokio::fs::File::open(
"/root/agent/ca/certs/embassy-root-ca.cert.pem",
)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
"/root/agent/ca/certs/embassy-root-ca.cert.pem",
)
})?,
&mut fullchain_file,
)
.await?;
log::info!("{} written successfully", fullchain_path.display());
}
f.write_all(
format!(
include_str!("nginx-standard.conf.template"),
hostname = hostname_str,
app_ip = service.ip,
internal_port = mapping.internal,
app_id = app_id,
)
.as_bytes(),
)
.await?;
f.sync_all().await?;
}
Some(LanOptions::Custom { port }) => {
f.write_all(
format!(
include_str!("nginx.conf.template"),
hostname = hostname_str,
app_ip = service.ip,
port = port,
internal_port = mapping.internal,
)
.as_bytes(),
)
let mut services = IndexMap::new();
for pkg_id in pkg_ids {
if let Some(installed) = crate::db::DatabaseModel::new()
.package_data()
.idx_model(&pkg_id)
.expect(db)
.await?
.installed()
.check(db)
.await?
{
let ifaces = installed
.clone()
.manifest()
.interfaces()
.get(db)
.await?
}
None => (),
}
}
}
Ok(())
}
pub async fn read_tor_address(name: &str, timeout: Option<Duration>) -> Result<String, Error> {
log::info!("Retrieving Tor hidden service address for {}.", name);
let addr_path = Path::new(HIDDEN_SERVICE_DIR_ROOT)
.join(format!("app-{}", name))
.join("hostname");
if let Some(timeout) = timeout {
let start = Instant::now();
while {
if addr_path.exists() {
false
} else {
if start.elapsed() >= timeout {
log::warn!("Timed out waiting for tor to start.");
false
} else {
true
.to_owned();
for (iface_id, cfgs) in ifaces.0 {
if let Some(tor_cfg) = cfgs.tor_config {
if let Some(key) = sqlx::query!(
"SELECT key FROM tor WHERE package = ? AND interface = ?",
*pkg_id,
*iface_id,
)
.fetch_optional(&mut *secrets)
.await?
{
if key.key.len() != 64 {
return Err(Error::new(
anyhow!("Invalid key length"),
crate::ErrorKind::Database,
));
}
let mut buf = [0; 64];
buf.clone_from_slice(&key.key);
services.insert(
buf,
HiddenServiceConfig {
ip: installed
.clone()
.interface_info()
.ip()
.get(db)
.await?
.to_owned(),
cfg: tor_cfg,
},
);
}
}
}
}
} {
tokio::time::sleep(Duration::from_millis(100)).await;
}
Ok(services)
}
let tor_addr = match tokio::fs::read_to_string(&addr_path).await {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
Err(e).with_ctx(|_| (crate::ErrorKind::NotFound, addr_path.display().to_string()))
}
a => a.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
addr_path.display().to_string(),
async fn add_svc(
&mut self,
key: &TorSecretKeyV3,
config: &HiddenServiceConfig,
) -> Result<(), Error> {
self.connection
.add_onion_v3(
key,
false,
false,
false,
None,
&mut config
.cfg
.port_mapping
.iter()
.map(|(external, internal)| {
(*external, SocketAddr::from((config.ip, *internal)))
})
.collect::<Vec<_>>()
.iter(),
)
}),
}?;
Ok(tor_addr.trim().to_owned())
}
.await?;
Ok(())
}
pub async fn read_tor_key(
name: &str,
version: HiddenServiceVersion,
timeout: Option<Duration>,
) -> Result<String, Error> {
log::info!("Retrieving Tor hidden service key for {}.", name);
let addr_path = Path::new(HIDDEN_SERVICE_DIR_ROOT)
.join(format!("app-{}", name))
.join(match version {
HiddenServiceVersion::V3 => "hs_ed25519_secret_key",
_ => "private_key",
});
if let Some(timeout) = timeout {
let start = Instant::now();
while {
if addr_path.exists() {
false
} else {
if start.elapsed() >= timeout {
log::warn!("Timed out waiting for tor to start.");
false
} else {
true
async fn sync(
&mut self,
services: IndexMap<[u8; 64], HiddenServiceConfig>,
) -> Result<(), Error> {
for (key, new) in &services {
let tor_key = TorSecretKeyV3::from(key.clone());
if let Some(old) = self.services.remove(&key[..]) {
if new != &old {
self.connection
.del_onion(
&tor_key
.public()
.get_onion_address()
.get_address_without_dot_onion(),
)
.await?;
self.add_svc(&tor_key, new).await?;
}
} else {
self.add_svc(&tor_key, new).await?;
}
} {
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
let tor_key = match version {
HiddenServiceVersion::V3 => {
let mut f = tokio::fs::File::open(&addr_path).await.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
addr_path.display().to_string(),
for (key, _) in self.services.drain(..) {
self.connection
.del_onion(
&TorSecretKeyV3::from(key)
.public()
.get_onion_address()
.get_address_without_dot_onion(),
)
})?;
let mut buf = [0; 96];
f.read_exact(&mut buf).await?;
base32::encode(base32::Alphabet::RFC4648 { padding: false }, &buf[32..]).to_lowercase()
.await?;
}
_ => tokio::fs::read_to_string(&addr_path)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
addr_path.display().to_string(),
)
})?
.trim_end_matches("\u{0}")
.to_string(),
};
Ok(tor_key.trim().to_owned())
}
pub async fn set_svc(
name: &str,
service: NewService,
) -> Result<(Ipv4Addr, Option<String>, Option<String>), Error> {
log::info!(
"Adding Tor hidden service {} to {}.",
name,
crate::SERVICES_YAML
);
let is_listening = !service.ports.is_empty();
// let path = PersistencePath::from_ref(crate::SERVICES_YAML);
let mut hidden_services: ServicesMap = todo!(); //services_map_mut(path).await?;
let ver = service.hidden_service_version;
let ip = hidden_services.add(name.to_owned(), service);
log::info!("Adding Tor hidden service {} to {}.", name, ETC_TOR_RC);
write_services(&hidden_services).await?;
let addr_path = Path::new(HIDDEN_SERVICE_DIR_ROOT)
.join(format!("app-{}", name))
.join("hostname");
tokio::fs::remove_file(addr_path).await.or_else(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
Ok(())
} else {
Err(e)
}
})?;
#[cfg(target_os = "linux")]
nix::unistd::sync();
log::info!("Reloading Tor.");
let svc_exit = std::process::Command::new("service")
.args(&["tor", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::ErrorKind::Tor,
"Failed to Reload Tor: {}",
svc_exit
.code()
.or_else(|| { svc_exit.signal().map(|a| 128 + a) })
.unwrap_or(0)
);
let addr = if is_listening {
Some(read_tor_address(name, Some(Duration::from_secs(30))).await?)
} else {
None
};
let key = if is_listening {
Some(read_tor_key(name, ver, Some(Duration::from_secs(30))).await?)
} else {
None
};
write_lan_services(&hidden_services).await?;
log::info!("Reloading Nginx.");
let svc_exit = std::process::Command::new("service")
.args(&["nginx", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::ErrorKind::Nginx,
"Failed to Reload Nginx: {}",
svc_exit
.code()
.or_else(|| { svc_exit.signal().map(|a| 128 + a) })
.unwrap_or(0)
);
Ok((ip, addr, key))
}
pub async fn rm_svc(name: &str) -> Result<(), Error> {
log::info!(
"Removing Tor hidden service {} from {}.",
name,
crate::SERVICES_YAML
);
// let path = PersistencePath::from_ref(crate::SERVICES_YAML);
let mut hidden_services: ServicesMap = todo!(); //services_map_mut(path).await?;
hidden_services.remove(name);
let hidden_service_path = Path::new(HIDDEN_SERVICE_DIR_ROOT).join(format!("app-{}", name));
log::info!("Removing {}", hidden_service_path.display());
if hidden_service_path.exists() {
tokio::fs::remove_dir_all(hidden_service_path).await?;
self.services = services;
Ok(())
}
log::info!("Removing Tor hidden service {} from {}.", name, ETC_TOR_RC);
write_services(&hidden_services).await?;
log::info!("Reloading Tor.");
let svc_exit = std::process::Command::new("service")
.args(&["tor", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::ErrorKind::Tor,
"Failed to Reload Tor: {}",
svc_exit.code().unwrap_or(0)
);
write_lan_services(&hidden_services).await?;
log::info!("Reloading Nginx.");
let svc_exit = std::process::Command::new("service")
.args(&["nginx", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::ErrorKind::Nginx,
"Failed to Reload Nginx: {}",
svc_exit
.code()
.or_else(|| { svc_exit.signal().map(|a| 128 + a) })
.unwrap_or(0)
);
Ok(())
}
pub async fn change_key(
name: &str,
key: Option<&ed25519_dalek::ExpandedSecretKey>,
) -> Result<(), Error> {
let hidden_service_path = Path::new(HIDDEN_SERVICE_DIR_ROOT).join(format!("app-{}", name));
log::info!("Removing {}", hidden_service_path.display());
if hidden_service_path.exists() {
tokio::fs::remove_dir_all(&hidden_service_path)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
hidden_service_path.display().to_string(),
)
})?;
async fn init<Db: DbHandle>(
tor_cp: SocketAddr,
db: &mut Db,
secrets: &mut PoolConnection<Sqlite>,
) -> Result<Self, Error> {
let mut conn = torut::control::UnauthenticatedConn::new(
TcpStream::connect(tor_cp).await?, // TODO
);
let auth = conn
.load_protocol_info()
.await?
.make_auth_data()?
.ok_or_else(|| anyhow!("Cookie Auth Not Available"))
.with_kind(crate::ErrorKind::Tor)?;
conn.authenticate(&auth).await?;
let mut connection: AuthenticatedConnection = conn.into_authenticated().await;
connection.set_async_event_handler(Some(event_handler));
let mut res = TorControllerInner {
connection,
services: IndexMap::new(),
};
res.sync(Self::get_services(db, secrets).await?).await?;
Ok(res)
}
if let Some(key) = key {
tokio::fs::create_dir_all(&hidden_service_path).await?;
let key_path = hidden_service_path.join("hs_ed25519_secret_key");
let mut key_data = b"== ed25519v1-secret: type0 ==".to_vec();
key_data.extend_from_slice(&key.to_bytes());
tokio::fs::write(&key_path, key_data)
}
#[tokio::test]
async fn test() {
let mut conn = torut::control::UnauthenticatedConn::new(
TcpStream::connect(SocketAddr::from(([127, 0, 0, 1], 9051)))
.await
.with_ctx(|_| (crate::ErrorKind::Filesystem, key_path.display().to_string()))?;
}
log::info!("Reloading Tor.");
let svc_exit = std::process::Command::new("service")
.args(&["tor", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::ErrorKind::Tor,
"Failed to Reload Tor: {}",
svc_exit.code().unwrap_or(0)
.unwrap(), // TODO
);
// let mut info = crate::apps::list_info_mut().await?;
// if let Some(mut i) = info.get_mut(name) {
// if i.tor_address.is_some() {
// i.tor_address = Some(read_tor_address(name, Some(Duration::from_secs(30))).await?);
// }
// }
Ok(())
}
pub async fn reload() -> Result<(), Error> {
// let path = PersistencePath::from_ref(crate::SERVICES_YAML);
let hidden_services = todo!(); //services_map(&path).await?;
log::info!("Syncing Tor hidden services to {}.", ETC_TOR_RC);
write_services(&hidden_services).await?;
log::info!("Reloading Tor.");
let svc_exit = std::process::Command::new("service")
.args(&["tor", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::ErrorKind::Tor,
"Failed to Reload Tor: {}",
svc_exit.code().unwrap_or(0)
);
Ok(())
}
pub async fn restart() -> Result<(), Error> {
// let path = PersistencePath::from_ref(crate::SERVICES_YAML);
let hidden_services = todo!(); //services_map(&path).await?;
log::info!("Syncing Tor hidden services to {}.", ETC_TOR_RC);
write_services(&hidden_services).await?;
log::info!("Restarting Tor.");
let svc_exit = std::process::Command::new("service")
.args(&["tor", "restart"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::ErrorKind::Tor,
"Failed to Restart Tor: {}",
svc_exit.code().unwrap_or(0)
);
Ok(())
let auth = conn
.load_protocol_info()
.await
.unwrap()
.make_auth_data()
.unwrap()
.ok_or_else(|| anyhow!("Cookie Auth Not Available"))
.with_kind(crate::ErrorKind::Tor)
.unwrap();
conn.authenticate(&auth).await.unwrap();
let mut connection: AuthenticatedConn<
TcpStream,
fn(AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>>,
> = conn.into_authenticated().await;
let tor_key = torut::onion::TorSecretKeyV3::generate();
dbg!(connection.get_conf("SocksPort").await.unwrap());
connection
.add_onion_v3(
&tor_key,
false,
false,
false,
None,
&mut [(443_u16, SocketAddr::from(([127, 0, 0, 1], 8443)))].iter(),
)
.await
.unwrap();
connection
.add_onion_v3(
&tor_key,
false,
false,
false,
None,
&mut [(8443_u16, SocketAddr::from(([127, 0, 0, 1], 8443)))].iter(),
)
.await
.unwrap();
}

View File

@@ -17,7 +17,6 @@ use crate::dependencies::Dependencies;
use crate::id::{Id, InterfaceId, InvalidId, SYSTEM_ID};
use crate::migration::Migrations;
use crate::net::host::Hosts;
use crate::net::tor::HiddenServiceVersion;
use crate::status::health_check::{HealthCheckResult, HealthChecks};
use crate::util::Version;
use crate::volume::Volumes;
@@ -33,6 +32,12 @@ impl FromStr for PackageId {
Ok(PackageId(Id::try_from(s.to_owned())?))
}
}
impl<S: AsRef<str>> std::ops::Deref for PackageId<S> {
type Target = S;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl<S: AsRef<str>> AsRef<PackageId<S>> for PackageId<S> {
fn as_ref(&self) -> &PackageId<S> {
self
@@ -129,7 +134,7 @@ pub struct Manifest {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Interfaces(IndexMap<InterfaceId, Interface>); // TODO
pub struct Interfaces(pub IndexMap<InterfaceId, Interface>); // TODO
impl Interfaces {
pub async fn install(&self, ip: Ipv4Addr) -> Result<InterfaceInfo, Error> {
todo!()
@@ -139,37 +144,35 @@ impl Interfaces {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Interface {
tor_config: Option<TorConfig>,
lan_config: Option<IndexMap<u16, LanPortConfig>>,
ui: bool,
protocols: Vec<String>,
pub tor_config: Option<TorConfig>,
pub lan_config: Option<IndexMap<u16, LanPortConfig>>,
pub ui: bool,
pub protocols: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct TorConfig {
#[serde(default)]
hidden_service_version: HiddenServiceVersion,
port_mapping: IndexMap<u16, u16>,
pub port_mapping: IndexMap<u16, u16>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct LanPortConfig {
ssl: bool,
mapping: u16,
pub ssl: bool,
pub mapping: u16,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Assets {
#[serde(default)]
license: Option<PathBuf>,
pub license: Option<PathBuf>,
#[serde(default)]
icon: Option<PathBuf>,
pub icon: Option<PathBuf>,
#[serde(default)]
docker_images: Option<PathBuf>,
pub docker_images: Option<PathBuf>,
#[serde(default)]
instructions: Option<PathBuf>,
pub instructions: Option<PathBuf>,
}
impl Assets {
pub fn license_path(&self) -> &Path {

View File

@@ -798,6 +798,9 @@ pub fn parse_duration(arg: &str, matches: &ArgMatches<'_>) -> Result<Duration, E
pub struct Container<T>(RwLock<Option<T>>);
impl<T> Container<T> {
pub fn new() -> Self {
Container(RwLock::new(None))
}
pub async fn set(&self, value: T) {
*self.0.write().await = Some(value);
}