finish api

This commit is contained in:
Aiden McClelland
2025-10-29 13:52:57 -06:00
parent 5580ff6f01
commit 0bd79b28b4
9 changed files with 535 additions and 274 deletions

View File

@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use clap::Parser;
@@ -14,11 +14,12 @@ use visit_rs::Visit;
use crate::context::config::ClientConfig;
use crate::context::CliContext;
use crate::net::gateway::{Bind, BindTcp};
use crate::net::tls::{ChainedHandler, TlsListener};
use crate::net::tls::TlsListener;
use crate::net::web_server::{Accept, Acceptor, MetadataVisitor, WebServer};
use crate::prelude::*;
use crate::tunnel::context::{TunnelConfig, TunnelContext};
use crate::tunnel::tunnel_router;
use crate::tunnel::web::TunnelCertHandler;
use crate::util::logger::LOGGER;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -47,14 +48,19 @@ async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
let mut sub = https_db.subscribe("/webserver".parse().unwrap()).await;
while sub.recv().await.is_some() {
while let Err(e) = async {
if let Some(addr) = https_db.peek().await.as_webserver().de()? {
let webserver = https_db.peek().await.into_webserver();
if webserver.as_enabled().de()? {
let addr = webserver.as_listen().de()?.or_not_found("listen address")?;
acceptor_setter.send_if_modified(|a| {
let key = WebserverListener::Https(addr);
if !a.contains_key(&key) {
match (|| {
Ok::<_, Error>(TlsListener::new(
BindTcp.bind(addr)?,
BasicCertHandler(https_db.clone()),
TunnelCertHandler {
db: https_db.clone(),
crypto_provider: Arc::new(tokio_rustls::rustls::crypto::ring::default_provider()),
},
))
})() {
Ok(l) => {
@@ -130,8 +136,8 @@ async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
.await
.with_kind(crate::ErrorKind::Unknown)?;
sig_handler.wait_for_abort().await;
https_thread.wait_for_abort().await;
sig_handler.wait_for_abort().await.with_kind(ErrorKind::Unknown)?;
https_thread.wait_for_abort().await.with_kind(ErrorKind::Unknown)?;
Ok::<_, Error>(server)
}

View File

@@ -6,7 +6,7 @@ use std::sync::Arc;
use async_acme::acme::{Identifier, ACME_TLS_ALPN_NAME};
use clap::builder::ValueParserFactory;
use clap::Parser;
use futures::{FutureExt, StreamExt};
use futures::StreamExt;
use imbl_value::InternedString;
use itertools::Itertools;
use models::{ErrorData, FromStrParser};

View File

@@ -552,7 +552,6 @@ async fn watch_ip(
let managed = device_proxy.managed().await?;
if !managed {
dbg!("unmanaged", &iface);
return Ok(());
}
let dac = device_proxy.active_connection().await?;

View File

@@ -18,6 +18,7 @@ use crate::net::web_server::{Accept, AcceptStream, MetadataVisitor};
use crate::prelude::*;
use crate::util::io::{BackTrackingIO, ReadWriter};
use crate::util::serde::MaybeUtf8String;
use crate::util::sync::SyncMutex;
#[derive(Debug, Clone, VisitFields)]
pub struct TlsMetadata<M> {
@@ -115,13 +116,15 @@ impl ResolvesServerCert for SingleCertResolver {
pub struct TlsListener<A: Accept, H: for<'a> TlsHandler<'a, A>> {
pub accept: A,
pub tls_handler: H,
in_progress: Vec<
BoxFuture<
'static,
(
H,
Result<Option<(TlsMetadata<A::Metadata>, AcceptStream)>, Error>,
),
in_progress: SyncMutex<
Vec<
BoxFuture<
'static,
(
H,
Result<Option<(TlsMetadata<A::Metadata>, AcceptStream)>, Error>,
),
>,
>,
>,
}
@@ -130,7 +133,7 @@ impl<A: Accept, H: for<'a> TlsHandler<'a, A>> TlsListener<A, H> {
Self {
accept,
tls_handler: cert_handler,
in_progress: Vec::new(),
in_progress: SyncMutex::new(Vec::new()),
}
}
}
@@ -145,105 +148,103 @@ where
&mut self,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
loop {
if let Some((idx, (handler, res))) =
self.in_progress
.iter_mut()
.enumerate()
.find_map(|(idx, fut)| match fut.poll_unpin(cx) {
Poll::Ready(a) => Some((idx, a)),
Poll::Pending => None,
})
{
drop(self.in_progress.swap_remove(idx));
if let Some(res) = res.transpose() {
self.tls_handler = handler;
return Poll::Ready(res);
}
continue;
}
if let Poll::Ready((metadata, stream)) = self.accept.poll_accept(cx)? {
crate::dbg!("ACCEPTED");
let mut tls_handler = self.tls_handler.clone();
self.in_progress.push(
async move {
let res = async {
let mut acceptor = LazyConfigAcceptor::new(
Acceptor::default(),
BackTrackingIO::new(stream),
);
let mut mid: tokio_rustls::StartHandshake<
BackTrackingIO<AcceptStream>,
> = match (&mut acceptor).await {
Ok(a) => a,
Err(e) => {
let mut stream =
acceptor.take_io().or_not_found("acceptor io")?;
let (_, buf) = stream.rewind();
if std::str::from_utf8(buf)
.ok()
.and_then(|buf| {
buf.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.next()
})
.map_or(false, |buf| {
regex::Regex::new("[A-Z]+ (.+) HTTP/1")
.unwrap()
.is_match(buf)
})
{
handle_http_on_https(stream).await.log_err();
return Ok(None);
} else {
return Err(e).with_kind(ErrorKind::Network);
}
}
};
let hello = mid.client_hello();
crate::dbg!("getting config");
if let Some(cfg) = tls_handler.get_config(&hello, &metadata).await {
crate::dbg!("config gotten");
let metadata = TlsMetadata {
inner: metadata,
tls_info: TlsHandshakeInfo {
sni: hello.server_name().map(InternedString::intern),
alpn: hello
.alpn()
.into_iter()
.flatten()
.map(|a| MaybeUtf8String(a.to_vec()))
.collect(),
},
};
let buffered = mid.io.stop_buffering();
mid.io
.write_all(&buffered)
.await
.with_kind(ErrorKind::Network)?;
return Ok(Some((
metadata,
Box::pin(mid.into_stream(Arc::new(cfg)).await?) as AcceptStream,
)));
}
crate::dbg!("no config");
Ok(None)
self.in_progress.mutate(|in_progress| {
loop {
if let Some((idx, (handler, res))) =
in_progress.iter_mut().enumerate().find_map(|(idx, fut)| {
match fut.poll_unpin(cx) {
Poll::Ready(a) => Some((idx, a)),
Poll::Pending => None,
}
.await;
(tls_handler, res)
})
{
drop(in_progress.swap_remove(idx));
if let Some(res) = res.transpose() {
self.tls_handler = handler;
return Poll::Ready(res);
}
.boxed(),
);
continue;
}
break;
}
continue;
}
Poll::Pending
if let Poll::Ready((metadata, stream)) = self.accept.poll_accept(cx)? {
let mut tls_handler = self.tls_handler.clone();
in_progress.push(
async move {
let res = async {
let mut acceptor = LazyConfigAcceptor::new(
Acceptor::default(),
BackTrackingIO::new(stream),
);
let mut mid: tokio_rustls::StartHandshake<
BackTrackingIO<AcceptStream>,
> = match (&mut acceptor).await {
Ok(a) => a,
Err(e) => {
let mut stream =
acceptor.take_io().or_not_found("acceptor io")?;
let (_, buf) = stream.rewind();
if std::str::from_utf8(buf)
.ok()
.and_then(|buf| {
buf.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.next()
})
.map_or(false, |buf| {
regex::Regex::new("[A-Z]+ (.+) HTTP/1")
.unwrap()
.is_match(buf)
})
{
handle_http_on_https(stream).await.log_err();
return Ok(None);
} else {
return Err(e).with_kind(ErrorKind::Network);
}
}
};
let hello = mid.client_hello();
if let Some(cfg) = tls_handler.get_config(&hello, &metadata).await {
let metadata = TlsMetadata {
inner: metadata,
tls_info: TlsHandshakeInfo {
sni: hello.server_name().map(InternedString::intern),
alpn: hello
.alpn()
.into_iter()
.flatten()
.map(|a| MaybeUtf8String(a.to_vec()))
.collect(),
},
};
let buffered = mid.io.stop_buffering();
mid.io
.write_all(&buffered)
.await
.with_kind(ErrorKind::Network)?;
return Ok(Some((
metadata,
Box::pin(mid.into_stream(Arc::new(cfg)).await?)
as AcceptStream,
)));
}
Ok(None)
}
.await;
(tls_handler, res)
}
.boxed(),
);
continue;
}
break;
}
Poll::Pending
})
}
}

View File

@@ -331,7 +331,7 @@ pub async fn show_config(
}
}) {
wan_addr
} else if let Some(webserver) = peek.as_webserver().de()? {
} else if let Some(webserver) = peek.as_webserver().as_listen().de()? {
webserver.ip()
} else {
ctx.net_iface

View File

@@ -31,15 +31,42 @@ impl SignatureAuthContext for TunnelContext {
) -> impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send {
let peek = self.db().peek().await;
peek.as_webserver()
.as_listen()
.de()
.map(|a| a.as_ref().map(InternedString::from_display))
.transpose()
.into_iter()
.chain(
std::iter::from_fn(move || Some(peek.as_certificates().keys()))
.flatten_ok()
.map_ok(|h| h.0)
.flatten_ok(),
std::iter::once_with(move || {
peek.as_webserver()
.as_certificate()
.de()
.ok()
.flatten()
.and_then(|cert_data| cert_data.cert.0.first().cloned())
.and_then(|cert| cert.subject_alt_names())
.into_iter()
.flatten()
.filter_map(|san| {
san.dnsname().map(InternedString::from).or_else(|| {
san.ipaddress().and_then(|ip_bytes| {
let ip: std::net::IpAddr = match ip_bytes.len() {
4 => std::net::IpAddr::V4(std::net::Ipv4Addr::from(
<[u8; 4]>::try_from(ip_bytes).ok()?,
)),
16 => std::net::IpAddr::V6(std::net::Ipv6Addr::from(
<[u8; 16]>::try_from(ip_bytes).ok()?,
)),
_ => return None,
};
Some(InternedString::from_display(&ip))
})
})
})
.map(Ok)
.collect::<Vec<_>>()
})
.flatten(),
)
}
fn check_pubkey(

View File

@@ -1,5 +1,5 @@
use std::collections::BTreeMap;
use std::net::{SocketAddr, SocketAddrV4};
use std::net::SocketAddrV4;
use std::path::PathBuf;
use clap::builder::ValueParserFactory;
@@ -23,7 +23,7 @@ use crate::prelude::*;
use crate::sign::AnyVerifyingKey;
use crate::tunnel::auth::SignerInfo;
use crate::tunnel::context::TunnelContext;
use crate::tunnel::web::TunnelCertData;
use crate::tunnel::web::WebserverInfo;
use crate::tunnel::wg::WgServer;
use crate::util::serde::{apply_expr, deserialize_from_str, serialize_display, HandlerExtSerde};
@@ -76,11 +76,10 @@ impl ValueParserFactory for GatewayPort {
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct TunnelDatabase {
pub webserver: Option<SocketAddr>,
pub webserver: WebserverInfo,
pub sessions: Sessions,
pub password: Option<String>,
pub auth_pubkeys: HashMap<AnyVerifyingKey, SignerInfo>,
pub certificates: Option<TunnelCertData>,
pub gateways: OrdMap<GatewayId, NetworkInterfaceInfo>,
pub wg: WgServer,
pub port_forwards: PortForwards,

View File

@@ -7,7 +7,6 @@ use rpc_toolkit::Server;
use crate::middleware::auth::Auth;
use crate::middleware::cors::Cors;
use crate::net::static_server::{bad_request, not_found, server_error};
use crate::net::web_server::{Accept, WebServer};
use crate::rpc_continuations::Guid;
use crate::tunnel::context::TunnelContext;

View File

@@ -1,6 +1,7 @@
use std::collections::{BTreeSet, VecDeque};
use std::collections::VecDeque;
use std::io::Write;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::sync::Arc;
use clap::Parser;
use imbl_value::{json, InternedString};
@@ -10,6 +11,8 @@ use openssl::x509::X509;
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio_rustls::rustls::crypto::CryptoProvider;
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use tokio_rustls::rustls::server::ClientHello;
use tokio_rustls::rustls::ServerConfig;
@@ -20,124 +23,281 @@ use crate::net::web_server::Accept;
use crate::prelude::*;
use crate::tunnel::context::TunnelContext;
use crate::tunnel::db::TunnelDatabase;
use crate::util::serde::Pem;
use crate::util::serde::{HandlerExtSerde, Pem};
#[derive(Debug, Deserialize, Serialize, Parser)]
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct WebserverInfo {
pub enabled: bool,
pub listen: Option<SocketAddr>,
pub certificate: Option<TunnelCertData>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TunnelCertData {
#[arg(long)]
pub key: Pem<PKey<Private>>,
#[arg(long)]
pub cert: Pem<Vec<X509>>,
}
pub struct TunnelCertHandler(TypedPatchDb<TunnelDatabase>);
#[derive(Clone)]
pub struct TunnelCertHandler {
pub db: TypedPatchDb<TunnelDatabase>,
pub crypto_provider: Arc<CryptoProvider>,
}
impl<'a, A> TlsHandler<'a, A> for TunnelCertHandler
where
A: Accept,
A: Accept + 'a,
<A as Accept>::Metadata: Send + Sync,
{
async fn get_config(
&'a mut self,
hello: &'a ClientHello<'a>,
metadata: &'a <A as Accept>::Metadata,
_: &'a ClientHello<'a>,
_: &'a <A as Accept>::Metadata,
) -> Option<ServerConfig> {
let cert_info = self
.db
.peek()
.await
.as_webserver()
.as_certificate()
.de()
.log_err()??;
let cert_chain: Vec<_> = cert_info
.cert
.0
.iter()
.map(|c| Ok::<_, Error>(CertificateDer::from(c.to_der()?)))
.collect::<Result<_, _>>()
.log_err()?;
let cert_key = cert_info.key.0.private_key_to_pkcs8().log_err()?;
Some(
ServerConfig::builder_with_provider(self.crypto_provider.clone())
.with_safe_default_protocol_versions()
.log_err()?
.with_no_client_auth()
.with_single_cert(
cert_chain,
PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(cert_key)),
)
.log_err()?,
)
}
}
pub fn web_api<C: Context>() -> ParentHandler<C> {
ParentHandler::new()
.subcommand("init", from_fn_async(init_web_rpc).no_cli())
.subcommand(
"init",
from_fn_async(init_web_cli)
.with_about("Initialize the webserver")
.no_display(),
from_fn_async(init_web)
.no_display()
.with_about("Initialize the webserver"),
)
.subcommand(
"set-listen",
from_fn_async(set_listen)
.no_display()
.with_about("Set the listen address for the webserver")
.with_call_remote::<CliContext>(),
)
.subcommand(
"get-listen",
from_fn_async(get_listen)
.with_display_serializable()
.with_about("Get the listen address for the webserver")
.with_call_remote::<CliContext>(),
)
.subcommand(
"get-available-ips",
from_fn_async(get_available_ips)
.with_display_serializable()
.with_about("Get available IP addresses to bind to")
.with_call_remote::<CliContext>(),
)
.subcommand(
"import-certificate",
from_fn_async(import_certificate)
.with_about("Import a certificate to use for the webserver")
from_fn_async(import_certificate_rpc).no_cli(),
)
.subcommand(
"import-certificate",
from_fn_async(import_certificate_cli)
.no_display()
.with_about("Import a certificate to use for the webserver"),
)
.subcommand(
"generate-certificate",
from_fn_async(generate_certificate)
.with_about("Generate a self signed certificaet to use for the webserver")
.with_call_remote::<CliContext>(),
)
.subcommand(
"enable",
from_fn_async(enable_web)
.with_about("Enable the webserver")
.no_display()
.with_call_remote::<CliContext>(),
)
// .subcommand(
// "forget-certificate",
// from_fn_async(forget_certificate)
// .with_about("Forget a certificate that was imported into the webserver")
// .no_display()
// .with_call_remote::<CliContext>(),
// )
.subcommand(
"uninit",
from_fn_async(uninit_web)
.with_about("Disable the webserver")
"disable",
from_fn_async(disable_web)
.no_display()
.with_about("Disable the webserver")
.with_call_remote::<CliContext>(),
)
.subcommand(
"reset",
from_fn_async(reset_web)
.no_display()
.with_about("Reset the webserver")
.with_call_remote::<CliContext>(),
)
}
pub async fn import_certificate(
pub async fn import_certificate_rpc(
ctx: TunnelContext,
cert_data: TunnelCertData,
) -> Result<(), Error> {
let mut saninfo = BTreeSet::new();
let leaf = cert_data.cert.get(0).ok_or_else(|| {
Error::new(
eyre!("certificate chain is empty"),
ErrorKind::InvalidRequest,
)
})?;
for san in leaf.subject_alt_names().into_iter().flatten() {
if let Some(dns) = san.dnsname() {
saninfo.insert(dns.into());
}
if let Some(ip) = san.ipaddress() {
if let Ok::<[u8; 4], _>(ip) = ip.try_into() {
saninfo.insert(InternedString::from_display(&Ipv4Addr::from_bits(
u32::from_be_bytes(ip),
)));
} else if let Ok::<[u8; 16], _>(ip) = ip.try_into() {
saninfo.insert(InternedString::from_display(&Ipv6Addr::from_bits(
u128::from_be_bytes(ip),
)));
}
}
}
ctx.db
.mutate(|db| {
db.as_certificates_mut()
.insert(&JsonKey(saninfo), &cert_data)
db.as_webserver_mut()
.as_certificate_mut()
.ser(&Some(cert_data))
})
.await
.result?;
Ok(())
}
#[derive(Debug, Deserialize, Serialize, Parser)]
pub struct InitWebParams {
listen: SocketAddr,
pub async fn import_certificate_cli(
HandlerArgs {
context,
parent_method,
method,
..
}: HandlerArgs<CliContext>,
) -> Result<(), Error> {
println!("Please paste in your PEM encoded private key: ");
let mut stdin_lines = BufReader::new(tokio::io::stdin()).lines();
let mut key_string = String::new();
while let Some(line) = stdin_lines.next_line().await? {
key_string.push_str(&line);
key_string.push_str("\n");
if line.trim().starts_with("-----END") {
break;
}
}
let key: Pem<PKey<Private>> = key_string.parse()?;
println!("Please paste in your PEM encoded certificate (or certificate chain): ");
let mut chain = Vec::<X509>::new();
loop {
let mut cert_string = String::new();
while let Some(line) = stdin_lines.next_line().await? {
cert_string.push_str(&line);
cert_string.push_str("\n");
if line.trim().starts_with("-----END") {
break;
}
}
let cert: Pem<X509> = cert_string.parse()?;
let key = cert.0.public_key()?;
if let Some(prev) = chain.last() {
if !prev.verify(&key)? {
return Err(Error::new(
eyre!(
"Invalid Fullchain: Previous cert was not signed by this certificate's key"
),
ErrorKind::InvalidSignature,
));
}
}
let is_root = cert.0.verify(&key)?;
chain.push(cert.0);
if is_root {
break;
}
}
context
.call_remote::<TunnelContext>(
&parent_method.iter().chain(method.iter()).join("."),
to_value(&TunnelCertData {
key,
cert: Pem(chain),
})?,
)
.await?;
Ok(())
}
pub async fn init_web_rpc(
#[derive(Debug, Deserialize, Serialize, Parser)]
pub struct GenerateCertParams {
#[arg(help = "Subject Alternative Name(s)")]
pub subject: Vec<InternedString>,
}
pub async fn generate_certificate(
ctx: TunnelContext,
InitWebParams { listen }: InitWebParams,
) -> Result<(), Error> {
GenerateCertParams { subject }: GenerateCertParams,
) -> Result<Pem<X509>, Error> {
let saninfo = SANInfo::new(&subject.into_iter().collect());
let key = crate::net::ssl::generate_key()?;
let cert = crate::net::ssl::make_self_signed((&key, &saninfo))?;
ctx.db
.mutate(|db| {
if db.as_certificates().de()?.is_empty() {
return Err(Error::new(
eyre!("No certificate available"),
ErrorKind::OpenSsl,
));
}
if db.as_password().transpose_ref().is_none() {
return Err(Error::new(
eyre!("Password not set"),
ErrorKind::Authorization,
));
}
db.as_webserver_mut()
.as_certificate_mut()
.ser(&Some(TunnelCertData {
key: Pem(key),
cert: Pem(vec![cert.clone()]),
}))
})
.await
.result?;
db.as_webserver_mut().ser(&Some(listen))?;
Ok(Pem(cert))
}
#[derive(Debug, Deserialize, Serialize, Parser)]
#[serde(rename_all = "camelCase")]
pub struct SetListenParams {
pub listen: SocketAddr,
}
pub async fn set_listen(
ctx: TunnelContext,
SetListenParams { listen }: SetListenParams,
) -> Result<(), Error> {
// Validate that the address is available to bind
tokio::net::TcpListener::bind(listen)
.await
.with_kind(ErrorKind::Network)
.with_ctx(|_| {
(
ErrorKind::Network,
format!("{} is not available to bind to", listen),
)
})?;
ctx.db
.mutate(|db| {
db.as_webserver_mut().as_listen_mut().ser(&Some(listen))?;
Ok(())
})
@@ -145,31 +305,130 @@ pub async fn init_web_rpc(
.result
}
pub async fn uninit_web(ctx: TunnelContext) -> Result<(), Error> {
pub async fn get_listen(ctx: TunnelContext) -> Result<Option<SocketAddr>, Error> {
ctx.db.peek().await.as_webserver().as_listen().de()
}
pub async fn get_available_ips(ctx: TunnelContext) -> Result<Vec<IpAddr>, Error> {
let ips = ctx.net_iface.peek(|interfaces| {
interfaces
.values()
.filter_map(|info| {
info.ip_info
.as_ref()
.and_then(|ip_info| ip_info.subnets.iter().next().map(|subnet| subnet.addr()))
})
.collect::<Vec<IpAddr>>()
});
Ok(ips)
}
pub async fn enable_web(ctx: TunnelContext) -> Result<(), Error> {
ctx.db
.mutate(|db| db.as_webserver_mut().ser(&None))
.mutate(|db| {
if db.as_webserver().as_listen().transpose_ref().is_none() {
return Err(Error::new(
eyre!("Listen is not set"),
ErrorKind::ParseNetAddress,
));
}
if db.as_webserver().as_certificate().transpose_ref().is_none() {
return Err(Error::new(
eyre!("Certificate is not set"),
ErrorKind::OpenSsl,
));
}
if db.as_password().transpose_ref().is_none() {
return Err(Error::new(
eyre!("Password is not set"),
ErrorKind::Authorization,
));
};
db.as_webserver_mut().as_enabled_mut().ser(&true)
})
.await
.result
}
pub async fn init_web_cli(
HandlerArgs {
context,
parent_method,
method,
params: InitWebParams { listen },
..
}: HandlerArgs<CliContext, InitWebParams>,
) -> Result<(), Error> {
pub async fn disable_web(ctx: TunnelContext) -> Result<(), Error> {
ctx.db
.mutate(|db| db.as_webserver_mut().as_enabled_mut().ser(&false))
.await
.result
}
pub async fn reset_web(ctx: TunnelContext) -> Result<(), Error> {
ctx.db
.mutate(|db| {
db.as_webserver_mut().as_enabled_mut().ser(&false)?;
db.as_webserver_mut().as_listen_mut().ser(&None)?;
db.as_webserver_mut().as_certificate_mut().ser(&None)?;
db.as_password_mut().ser(&None)?;
Ok(())
})
.await
.result
}
pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
loop {
match context
.call_remote::<TunnelContext>(
&parent_method.iter().chain(method.iter()).join("."),
to_value(&InitWebParams { listen })?,
)
match ctx
.call_remote::<TunnelContext>("web.enable", json!({}))
.await
{
Ok(_) => println!("Webserver Initialized"),
Err(e) if e.code == ErrorKind::ParseNetAddress as i32 => {
println!("A listen address has not been set yet. Setting one up now...");
let available_ips = from_value::<Vec<IpAddr>>(
ctx.call_remote::<TunnelContext>("web.get-available-ips", json!({}))
.await?,
)?;
let suggested_addr = available_ips
.into_iter()
.find(|ip| match ip {
IpAddr::V4(ipv4) => !ipv4.is_private() && !ipv4.is_loopback(),
IpAddr::V6(ipv6) => {
!ipv6.is_loopback()
&& !ipv6.is_unique_local()
&& !ipv6.is_unicast_link_local()
}
})
.map(|ip| SocketAddr::new(ip, 8443))
.unwrap_or_else(|| SocketAddr::from((Ipv6Addr::UNSPECIFIED, 8443)));
let (mut readline, _writer) = rustyline_async::Readline::new(format!(
"Listen Address [{}]: ",
suggested_addr
))
.with_kind(ErrorKind::Filesystem)?;
let listen: SocketAddr = loop {
match readline.readline().await.with_kind(ErrorKind::Filesystem)? {
rustyline_async::ReadlineEvent::Line(l) if !l.trim().is_empty() => {
match l.trim().parse() {
Ok(addr) => break addr,
Err(_) => {
println!("Invalid socket address. Please enter in format IP:PORT (e.g., 0.0.0.0:8443)");
readline.clear_history();
}
}
}
rustyline_async::ReadlineEvent::Line(_) => {
break suggested_addr;
}
_ => return Err(Error::new(eyre!("Aborted"), ErrorKind::Unknown)),
}
};
ctx.call_remote::<TunnelContext>(
"web.set-listen",
to_value(&SetListenParams { listen })?,
)
.await?;
}
Err(e) if e.code == ErrorKind::OpenSsl as i32 => {
println!(
"StartTunnel has not been set up with an SSL Certificate yet. Setting one up now..."
@@ -206,15 +465,25 @@ pub async fn init_web_cli(
}
}
if self_signed {
let listen = from_value::<Option<SocketAddr>>(
ctx.call_remote::<TunnelContext>("web.get-listen", json!({}))
.await?,
)?
.filter(|a| !a.ip().is_unspecified());
writeln!(
writer,
"Enter the name(s) to sign the certificate for, separated by commas."
)?;
readline.clear_history();
let default_prompt = if let Some(listen) = listen {
format!("Subject Alternative Name(s) [{}]: ", listen.ip())
} else {
"Subject Alternative Name(s): ".to_string()
};
readline
.update_prompt(&format!("Subject Alternative Name(s) [{}]: ", listen.ip()))
.update_prompt(&default_prompt)
.with_kind(ErrorKind::Filesystem)?;
let mut saninfo = BTreeSet::new();
let mut saninfo = Vec::new();
loop {
match readline.readline().await.with_kind(ErrorKind::Filesystem)? {
rustyline_async::ReadlineEvent::Line(l) if !l.trim().is_empty() => {
@@ -222,83 +491,44 @@ pub async fn init_web_cli(
break;
}
rustyline_async::ReadlineEvent::Line(_) => {
saninfo.extend(
listen
.map(|l| l.ip())
.as_ref()
.map(InternedString::from_display),
);
readline.clear_history();
if !saninfo.is_empty() {
break;
}
}
_ => return Err(Error::new(eyre!("Aborted"), ErrorKind::Unknown)),
}
}
let key = crate::net::ssl::gen_nistp256()?;
let cert = crate::net::ssl::make_self_signed((&key, &SANInfo::new(&saninfo)))?;
context
.call_remote::<TunnelContext>(
"web.import-certificate",
to_value(&TunnelCertData {
key: Pem(key),
cert: Pem(vec![cert]),
})?,
)
.await?;
ctx.call_remote::<TunnelContext>(
"web.generate-certificate",
to_value(&GenerateCertParams { subject: saninfo })?,
)
.await?;
} else {
drop((readline, writer));
println!("Please paste in your PEM encoded private key: ");
let mut stdin_lines = BufReader::new(tokio::io::stdin()).lines();
let mut key_string = String::new();
while let Some(line) = stdin_lines.next_line().await? {
key_string.push_str(&line);
key_string.push_str("\n");
if line.trim().starts_with("-----END") {
break;
}
}
let key: Pem<PKey<Private>> = key_string.parse()?;
println!(
"Please paste in your PEM encoded certificate (or certificate chain): "
);
let mut chain = Vec::new();
loop {
let mut cert_string = String::new();
while let Some(line) = stdin_lines.next_line().await? {
cert_string.push_str(&line);
cert_string.push_str("\n");
if line.trim().starts_with("-----END") {
break;
}
}
let cert: Pem<X509> = cert_string.parse()?;
let is_root = cert.0.authority_key_id().is_none();
chain.push(cert.0);
if is_root {
break;
}
}
context
.call_remote::<TunnelContext>(
"web.import-certificate",
to_value(&TunnelCertData {
key,
cert: Pem(chain),
})?,
)
.await?;
import_certificate_cli(HandlerArgs {
context: ctx.clone(),
parent_method: vec!["web", "import-certificate"].into(),
method: VecDeque::new(),
params: Empty {},
inherited_params: Empty {},
raw_params: json!({}),
})
.await?;
}
}
Err(e) if e.code == ErrorKind::Authorization as i32 => {
println!("A password has not been setup yet. Setting one up now...");
super::auth::set_password_cli(HandlerArgs {
context: context.clone(),
context: ctx.clone(),
parent_method: vec!["auth", "set-password"].into(),
method: VecDeque::new(),
params: Empty {},