mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
finish api
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
@@ -14,11 +14,12 @@ use visit_rs::Visit;
|
|||||||
use crate::context::config::ClientConfig;
|
use crate::context::config::ClientConfig;
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::net::gateway::{Bind, BindTcp};
|
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::net::web_server::{Accept, Acceptor, MetadataVisitor, WebServer};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::tunnel::context::{TunnelConfig, TunnelContext};
|
use crate::tunnel::context::{TunnelConfig, TunnelContext};
|
||||||
use crate::tunnel::tunnel_router;
|
use crate::tunnel::tunnel_router;
|
||||||
|
use crate::tunnel::web::TunnelCertHandler;
|
||||||
use crate::util::logger::LOGGER;
|
use crate::util::logger::LOGGER;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[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;
|
let mut sub = https_db.subscribe("/webserver".parse().unwrap()).await;
|
||||||
while sub.recv().await.is_some() {
|
while sub.recv().await.is_some() {
|
||||||
while let Err(e) = async {
|
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| {
|
acceptor_setter.send_if_modified(|a| {
|
||||||
let key = WebserverListener::Https(addr);
|
let key = WebserverListener::Https(addr);
|
||||||
if !a.contains_key(&key) {
|
if !a.contains_key(&key) {
|
||||||
match (|| {
|
match (|| {
|
||||||
Ok::<_, Error>(TlsListener::new(
|
Ok::<_, Error>(TlsListener::new(
|
||||||
BindTcp.bind(addr)?,
|
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) => {
|
Ok(l) => {
|
||||||
@@ -130,8 +136,8 @@ async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
|||||||
.await
|
.await
|
||||||
.with_kind(crate::ErrorKind::Unknown)?;
|
.with_kind(crate::ErrorKind::Unknown)?;
|
||||||
|
|
||||||
sig_handler.wait_for_abort().await;
|
sig_handler.wait_for_abort().await.with_kind(ErrorKind::Unknown)?;
|
||||||
https_thread.wait_for_abort().await;
|
https_thread.wait_for_abort().await.with_kind(ErrorKind::Unknown)?;
|
||||||
|
|
||||||
Ok::<_, Error>(server)
|
Ok::<_, Error>(server)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||||||
use async_acme::acme::{Identifier, ACME_TLS_ALPN_NAME};
|
use async_acme::acme::{Identifier, ACME_TLS_ALPN_NAME};
|
||||||
use clap::builder::ValueParserFactory;
|
use clap::builder::ValueParserFactory;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::StreamExt;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{ErrorData, FromStrParser};
|
use models::{ErrorData, FromStrParser};
|
||||||
|
|||||||
@@ -552,7 +552,6 @@ async fn watch_ip(
|
|||||||
|
|
||||||
let managed = device_proxy.managed().await?;
|
let managed = device_proxy.managed().await?;
|
||||||
if !managed {
|
if !managed {
|
||||||
dbg!("unmanaged", &iface);
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let dac = device_proxy.active_connection().await?;
|
let dac = device_proxy.active_connection().await?;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use crate::net::web_server::{Accept, AcceptStream, MetadataVisitor};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::io::{BackTrackingIO, ReadWriter};
|
use crate::util::io::{BackTrackingIO, ReadWriter};
|
||||||
use crate::util::serde::MaybeUtf8String;
|
use crate::util::serde::MaybeUtf8String;
|
||||||
|
use crate::util::sync::SyncMutex;
|
||||||
|
|
||||||
#[derive(Debug, Clone, VisitFields)]
|
#[derive(Debug, Clone, VisitFields)]
|
||||||
pub struct TlsMetadata<M> {
|
pub struct TlsMetadata<M> {
|
||||||
@@ -115,13 +116,15 @@ impl ResolvesServerCert for SingleCertResolver {
|
|||||||
pub struct TlsListener<A: Accept, H: for<'a> TlsHandler<'a, A>> {
|
pub struct TlsListener<A: Accept, H: for<'a> TlsHandler<'a, A>> {
|
||||||
pub accept: A,
|
pub accept: A,
|
||||||
pub tls_handler: H,
|
pub tls_handler: H,
|
||||||
in_progress: Vec<
|
in_progress: SyncMutex<
|
||||||
BoxFuture<
|
Vec<
|
||||||
'static,
|
BoxFuture<
|
||||||
(
|
'static,
|
||||||
H,
|
(
|
||||||
Result<Option<(TlsMetadata<A::Metadata>, AcceptStream)>, Error>,
|
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 {
|
Self {
|
||||||
accept,
|
accept,
|
||||||
tls_handler: cert_handler,
|
tls_handler: cert_handler,
|
||||||
in_progress: Vec::new(),
|
in_progress: SyncMutex::new(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,105 +148,103 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
loop {
|
self.in_progress.mutate(|in_progress| {
|
||||||
if let Some((idx, (handler, res))) =
|
loop {
|
||||||
self.in_progress
|
if let Some((idx, (handler, res))) =
|
||||||
.iter_mut()
|
in_progress.iter_mut().enumerate().find_map(|(idx, fut)| {
|
||||||
.enumerate()
|
match fut.poll_unpin(cx) {
|
||||||
.find_map(|(idx, fut)| match fut.poll_unpin(cx) {
|
Poll::Ready(a) => Some((idx, a)),
|
||||||
Poll::Ready(a) => Some((idx, a)),
|
Poll::Pending => None,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
.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;
|
||||||
);
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ pub async fn show_config(
|
|||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
wan_addr
|
wan_addr
|
||||||
} else if let Some(webserver) = peek.as_webserver().de()? {
|
} else if let Some(webserver) = peek.as_webserver().as_listen().de()? {
|
||||||
webserver.ip()
|
webserver.ip()
|
||||||
} else {
|
} else {
|
||||||
ctx.net_iface
|
ctx.net_iface
|
||||||
|
|||||||
@@ -31,15 +31,42 @@ impl SignatureAuthContext for TunnelContext {
|
|||||||
) -> impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send {
|
) -> impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send {
|
||||||
let peek = self.db().peek().await;
|
let peek = self.db().peek().await;
|
||||||
peek.as_webserver()
|
peek.as_webserver()
|
||||||
|
.as_listen()
|
||||||
.de()
|
.de()
|
||||||
.map(|a| a.as_ref().map(InternedString::from_display))
|
.map(|a| a.as_ref().map(InternedString::from_display))
|
||||||
.transpose()
|
.transpose()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(
|
.chain(
|
||||||
std::iter::from_fn(move || Some(peek.as_certificates().keys()))
|
std::iter::once_with(move || {
|
||||||
.flatten_ok()
|
peek.as_webserver()
|
||||||
.map_ok(|h| h.0)
|
.as_certificate()
|
||||||
.flatten_ok(),
|
.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(
|
fn check_pubkey(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::net::{SocketAddr, SocketAddrV4};
|
use std::net::SocketAddrV4;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::builder::ValueParserFactory;
|
use clap::builder::ValueParserFactory;
|
||||||
@@ -23,7 +23,7 @@ use crate::prelude::*;
|
|||||||
use crate::sign::AnyVerifyingKey;
|
use crate::sign::AnyVerifyingKey;
|
||||||
use crate::tunnel::auth::SignerInfo;
|
use crate::tunnel::auth::SignerInfo;
|
||||||
use crate::tunnel::context::TunnelContext;
|
use crate::tunnel::context::TunnelContext;
|
||||||
use crate::tunnel::web::TunnelCertData;
|
use crate::tunnel::web::WebserverInfo;
|
||||||
use crate::tunnel::wg::WgServer;
|
use crate::tunnel::wg::WgServer;
|
||||||
use crate::util::serde::{apply_expr, deserialize_from_str, serialize_display, HandlerExtSerde};
|
use crate::util::serde::{apply_expr, deserialize_from_str, serialize_display, HandlerExtSerde};
|
||||||
|
|
||||||
@@ -76,11 +76,10 @@ impl ValueParserFactory for GatewayPort {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
pub struct TunnelDatabase {
|
pub struct TunnelDatabase {
|
||||||
pub webserver: Option<SocketAddr>,
|
pub webserver: WebserverInfo,
|
||||||
pub sessions: Sessions,
|
pub sessions: Sessions,
|
||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
pub auth_pubkeys: HashMap<AnyVerifyingKey, SignerInfo>,
|
pub auth_pubkeys: HashMap<AnyVerifyingKey, SignerInfo>,
|
||||||
pub certificates: Option<TunnelCertData>,
|
|
||||||
pub gateways: OrdMap<GatewayId, NetworkInterfaceInfo>,
|
pub gateways: OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
pub wg: WgServer,
|
pub wg: WgServer,
|
||||||
pub port_forwards: PortForwards,
|
pub port_forwards: PortForwards,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use rpc_toolkit::Server;
|
|||||||
use crate::middleware::auth::Auth;
|
use crate::middleware::auth::Auth;
|
||||||
use crate::middleware::cors::Cors;
|
use crate::middleware::cors::Cors;
|
||||||
use crate::net::static_server::{bad_request, not_found, server_error};
|
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::rpc_continuations::Guid;
|
||||||
use crate::tunnel::context::TunnelContext;
|
use crate::tunnel::context::TunnelContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::{BTreeSet, VecDeque};
|
use std::collections::VecDeque;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use imbl_value::{json, InternedString};
|
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 rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
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::server::ClientHello;
|
||||||
use tokio_rustls::rustls::ServerConfig;
|
use tokio_rustls::rustls::ServerConfig;
|
||||||
|
|
||||||
@@ -20,124 +23,281 @@ use crate::net::web_server::Accept;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::tunnel::context::TunnelContext;
|
use crate::tunnel::context::TunnelContext;
|
||||||
use crate::tunnel::db::TunnelDatabase;
|
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")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TunnelCertData {
|
pub struct TunnelCertData {
|
||||||
#[arg(long)]
|
|
||||||
pub key: Pem<PKey<Private>>,
|
pub key: Pem<PKey<Private>>,
|
||||||
#[arg(long)]
|
|
||||||
pub cert: Pem<Vec<X509>>,
|
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
|
impl<'a, A> TlsHandler<'a, A> for TunnelCertHandler
|
||||||
where
|
where
|
||||||
A: Accept,
|
A: Accept + 'a,
|
||||||
|
<A as Accept>::Metadata: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn get_config(
|
async fn get_config(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
hello: &'a ClientHello<'a>,
|
_: &'a ClientHello<'a>,
|
||||||
metadata: &'a <A as Accept>::Metadata,
|
_: &'a <A as Accept>::Metadata,
|
||||||
) -> Option<ServerConfig> {
|
) -> 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> {
|
pub fn web_api<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand("init", from_fn_async(init_web_rpc).no_cli())
|
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"init",
|
"init",
|
||||||
from_fn_async(init_web_cli)
|
from_fn_async(init_web)
|
||||||
.with_about("Initialize the webserver")
|
.no_display()
|
||||||
.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(
|
.subcommand(
|
||||||
"import-certificate",
|
"import-certificate",
|
||||||
from_fn_async(import_certificate)
|
from_fn_async(import_certificate_rpc).no_cli(),
|
||||||
.with_about("Import a certificate to use for the webserver")
|
)
|
||||||
|
.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()
|
.no_display()
|
||||||
.with_call_remote::<CliContext>(),
|
.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(
|
.subcommand(
|
||||||
"uninit",
|
"disable",
|
||||||
from_fn_async(uninit_web)
|
from_fn_async(disable_web)
|
||||||
.with_about("Disable the webserver")
|
|
||||||
.no_display()
|
.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>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn import_certificate(
|
pub async fn import_certificate_rpc(
|
||||||
ctx: TunnelContext,
|
ctx: TunnelContext,
|
||||||
cert_data: TunnelCertData,
|
cert_data: TunnelCertData,
|
||||||
) -> Result<(), Error> {
|
) -> 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
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_certificates_mut()
|
db.as_webserver_mut()
|
||||||
.insert(&JsonKey(saninfo), &cert_data)
|
.as_certificate_mut()
|
||||||
|
.ser(&Some(cert_data))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Parser)]
|
pub async fn import_certificate_cli(
|
||||||
pub struct InitWebParams {
|
HandlerArgs {
|
||||||
listen: SocketAddr,
|
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,
|
ctx: TunnelContext,
|
||||||
InitWebParams { listen }: InitWebParams,
|
GenerateCertParams { subject }: GenerateCertParams,
|
||||||
) -> Result<(), Error> {
|
) -> 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
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
if db.as_certificates().de()?.is_empty() {
|
db.as_webserver_mut()
|
||||||
return Err(Error::new(
|
.as_certificate_mut()
|
||||||
eyre!("No certificate available"),
|
.ser(&Some(TunnelCertData {
|
||||||
ErrorKind::OpenSsl,
|
key: Pem(key),
|
||||||
));
|
cert: Pem(vec![cert.clone()]),
|
||||||
}
|
}))
|
||||||
if db.as_password().transpose_ref().is_none() {
|
})
|
||||||
return Err(Error::new(
|
.await
|
||||||
eyre!("Password not set"),
|
.result?;
|
||||||
ErrorKind::Authorization,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
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(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -145,31 +305,130 @@ pub async fn init_web_rpc(
|
|||||||
.result
|
.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
|
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
|
.await
|
||||||
.result
|
.result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init_web_cli(
|
pub async fn disable_web(ctx: TunnelContext) -> Result<(), Error> {
|
||||||
HandlerArgs {
|
ctx.db
|
||||||
context,
|
.mutate(|db| db.as_webserver_mut().as_enabled_mut().ser(&false))
|
||||||
parent_method,
|
.await
|
||||||
method,
|
.result
|
||||||
params: InitWebParams { listen },
|
}
|
||||||
..
|
|
||||||
}: HandlerArgs<CliContext, InitWebParams>,
|
pub async fn reset_web(ctx: TunnelContext) -> Result<(), Error> {
|
||||||
) -> 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 {
|
loop {
|
||||||
match context
|
match ctx
|
||||||
.call_remote::<TunnelContext>(
|
.call_remote::<TunnelContext>("web.enable", json!({}))
|
||||||
&parent_method.iter().chain(method.iter()).join("."),
|
|
||||||
to_value(&InitWebParams { listen })?,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => println!("Webserver Initialized"),
|
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 => {
|
Err(e) if e.code == ErrorKind::OpenSsl as i32 => {
|
||||||
println!(
|
println!(
|
||||||
"StartTunnel has not been set up with an SSL Certificate yet. Setting one up now..."
|
"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 {
|
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!(
|
writeln!(
|
||||||
writer,
|
writer,
|
||||||
"Enter the name(s) to sign the certificate for, separated by commas."
|
"Enter the name(s) to sign the certificate for, separated by commas."
|
||||||
)?;
|
)?;
|
||||||
readline.clear_history();
|
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
|
readline
|
||||||
.update_prompt(&format!("Subject Alternative Name(s) [{}]: ", listen.ip()))
|
.update_prompt(&default_prompt)
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
let mut saninfo = BTreeSet::new();
|
let mut saninfo = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
match readline.readline().await.with_kind(ErrorKind::Filesystem)? {
|
match readline.readline().await.with_kind(ErrorKind::Filesystem)? {
|
||||||
rustyline_async::ReadlineEvent::Line(l) if !l.trim().is_empty() => {
|
rustyline_async::ReadlineEvent::Line(l) if !l.trim().is_empty() => {
|
||||||
@@ -222,83 +491,44 @@ pub async fn init_web_cli(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
rustyline_async::ReadlineEvent::Line(_) => {
|
rustyline_async::ReadlineEvent::Line(_) => {
|
||||||
|
saninfo.extend(
|
||||||
|
listen
|
||||||
|
.map(|l| l.ip())
|
||||||
|
.as_ref()
|
||||||
|
.map(InternedString::from_display),
|
||||||
|
);
|
||||||
readline.clear_history();
|
readline.clear_history();
|
||||||
|
if !saninfo.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(Error::new(eyre!("Aborted"), ErrorKind::Unknown)),
|
_ => 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
|
ctx.call_remote::<TunnelContext>(
|
||||||
.call_remote::<TunnelContext>(
|
"web.generate-certificate",
|
||||||
"web.import-certificate",
|
to_value(&GenerateCertParams { subject: saninfo })?,
|
||||||
to_value(&TunnelCertData {
|
)
|
||||||
key: Pem(key),
|
.await?;
|
||||||
cert: Pem(vec![cert]),
|
|
||||||
})?,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
} else {
|
} else {
|
||||||
drop((readline, writer));
|
drop((readline, writer));
|
||||||
println!("Please paste in your PEM encoded private key: ");
|
import_certificate_cli(HandlerArgs {
|
||||||
let mut stdin_lines = BufReader::new(tokio::io::stdin()).lines();
|
context: ctx.clone(),
|
||||||
let mut key_string = String::new();
|
parent_method: vec!["web", "import-certificate"].into(),
|
||||||
|
method: VecDeque::new(),
|
||||||
while let Some(line) = stdin_lines.next_line().await? {
|
params: Empty {},
|
||||||
key_string.push_str(&line);
|
inherited_params: Empty {},
|
||||||
key_string.push_str("\n");
|
raw_params: json!({}),
|
||||||
if line.trim().starts_with("-----END") {
|
})
|
||||||
break;
|
.await?;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) if e.code == ErrorKind::Authorization as i32 => {
|
Err(e) if e.code == ErrorKind::Authorization as i32 => {
|
||||||
println!("A password has not been setup yet. Setting one up now...");
|
println!("A password has not been setup yet. Setting one up now...");
|
||||||
|
|
||||||
super::auth::set_password_cli(HandlerArgs {
|
super::auth::set_password_cli(HandlerArgs {
|
||||||
context: context.clone(),
|
context: ctx.clone(),
|
||||||
parent_method: vec!["auth", "set-password"].into(),
|
parent_method: vec!["auth", "set-password"].into(),
|
||||||
method: VecDeque::new(),
|
method: VecDeque::new(),
|
||||||
params: Empty {},
|
params: Empty {},
|
||||||
|
|||||||
Reference in New Issue
Block a user