Appmgr/feature/lan (#209)

* WIP: Lan with blocking

* stuff

* appmgr: non-ui lan services

* dbus linker errors

* appmgr: allocate on stack

* dns resolves finally

* cleanup

* appmgr: generate ssl if missing

* appmgr: remove -p for purge

Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Keagan McClelland
2021-02-24 12:59:05 -07:00
committed by Aiden McClelland
parent 882cfde5f3
commit c83baec363
14 changed files with 728 additions and 652 deletions

View File

@@ -8,13 +8,47 @@ use failure::ResultExt as _;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use crate::util::{PersistencePath, YamlUpdateHandle};
use crate::util::{Invoke, PersistencePath, YamlUpdateHandle};
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 },
}
#[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)]
pub lan: Option<Option<LanOptions>>,
}
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
},
})
}
}
pub const ETC_TOR_RC: &'static str = "/etc/tor/torrc";
@@ -184,28 +218,118 @@ pub async fn write_services(hidden_services: &ServicesMap) -> Result<(), Error>
}
pub async fn write_lan_services(hidden_services: &ServicesMap) -> Result<(), Error> {
let hostname = tokio::fs::read_to_string(ETC_HOSTNAME).await?;
let mut f = tokio::fs::File::create(ETC_NGINX_SERVICES_CONF).await?;
for (name, service) in &hidden_services.map {
if service
.ports
.iter()
.filter(|p| p.internal == 80)
.next()
.is_none()
{
continue;
}
f.write_all(
format!(
include_str!("nginx.conf.template"),
hostname = hostname.trim(),
app_id = name,
app_ip = service.ip,
)
.as_bytes(),
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?;
let hostname_str = hostname
.trim()
.strip_suffix(".onion")
.ok_or_else(|| failure::format_err!("invalid tor hostname"))
.no_code()?;
for mapping in &service.ports {
match &mapping.lan {
Some(LanOptions::Standard) => {
let base_path = PersistencePath::from_ref("apps").join(&app_id);
let key_path = base_path.join("cert-local.key.pem").path();
if tokio::fs::metadata(&key_path).await.is_err() {
tokio::process::Command::new("openssl")
.arg("ecparam")
.arg("-genkey")
.arg("-name")
.arg("prime256v1")
.arg("-noout")
.arg("-out")
.arg(&key_path)
.invoke("OpenSSL GenKey")
.await?;
}
let conf_path = base_path.join("cert-local.csr.conf").path();
if tokio::fs::metadata(&conf_path).await.is_err() {
tokio::fs::write(
&conf_path,
format!(
include_str!("cert-local.csr.conf.template"),
hostname = hostname_str
),
)
.await?;
}
let req_path = base_path.join("cert-local.csr").path();
if tokio::fs::metadata(&req_path).await.is_err() {
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("OpenSSL Req")
.await?;
}
let cert_path = base_path.join("cert-local.crt.pem").path();
if tokio::fs::metadata(&cert_path).await.is_err() {
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("OpenSSL GenKey")
.await?;
}
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?
}
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(),
)
.await?
}
None => (),
}
}
}
Ok(())