Compare commits

...

4 commits

Author SHA1 Message Date
asonix 4a9e1b2bc4 Enable serving over TLS, communicating to pict-rs over TLS
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-01 18:14:29 -06:00
asonix d0df658201 Add rustls-channel-resolver, enable rustls 0.21 2024-02-01 17:28:29 -06:00
asonix 04f59df6ba Update dependencies (minor & point) 2024-02-01 17:25:04 -06:00
asonix 8834bb7f0b Update minify-html 2024-02-01 17:24:44 -06:00
4 changed files with 1064 additions and 289 deletions

1128
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -16,21 +16,25 @@ default = []
[dependencies]
actix-rt = "2.7.0"
actix-web = { version = "4.0.0", default-features = false }
awc = { version = "3.0.0", default-features = false }
actix-web = { version = "4.0.0", default-features = false, features = ["rustls-0_21"] }
awc = { version = "3.0.0", default-features = false, features = ["rustls-0_21"] }
bcrypt = "0.15"
clap = { version = "4.0.2", features = ["derive", "env"] }
color-eyre = "0.6.2"
console-subscriber = "0.2"
mime = "0.3"
minify-html = "0.11.1"
minify-html = "0.15.0"
opentelemetry = "0.21"
opentelemetry_sdk = { version = "0.21", features = ["rt-tokio"] }
opentelemetry-otlp = "0.14"
qrcodegen = "1.7"
rustls = "0.21"
rustls-channel-resolver = "0.1.0"
rustls-pemfile = "2.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sled = { version = "0.34.7", features = ["zstd"] }
tokio = { version = "1", default-features = false, features = ["sync"] }
tokio = { version = "1", default-features = false, features = ["fs", "sync"] }
thiserror = "1.0"
tracing = "0.1"
tracing-error = "0.2"
@ -44,16 +48,16 @@ tracing-subscriber = { version = "0.3", features = [
] }
url = { version = "2.2", features = ["serde"] }
uuid = { version = "1", features = ["serde", "v4"] }
color-eyre = "0.6.2"
webpki-roots = "0.26.0"
[dependencies.tracing-actix-web]
version = "0.7.8"
version = "0.7.9"
default-features = false
features = ["emit_event_on_error", "opentelemetry_0_21"]
[dependencies.tracing-awc]
version = "0.1.8"
version = "0.1.9"
default-features = false
features = ["opentelemetry_0_21"]

View file

@ -10,6 +10,9 @@ use actix_web::{
};
use awc::Client;
use clap::Parser;
use rustls::{
sign::CertifiedKey, Certificate, ClientConfig, OwnedTrustAnchor, PrivateKey, RootCertStore,
};
use sled::Db;
use std::{
io::Cursor,
@ -43,7 +46,7 @@ pub struct Config {
short,
long,
env = "PICTRS_AGGREGATOR_ADDR",
default_value = "0.0.0.0:8082",
default_value = "[::]:8082",
help = "The address and port the server binds to"
)]
addr: SocketAddr,
@ -57,6 +60,15 @@ pub struct Config {
)]
upstream: Url,
#[arg(
short = 'H',
long,
env = "PICTRS_AGGREGATOR_HOST",
default_value = "localhost:8082",
help = "The host at which pict-rs-aggregator is accessible"
)]
host: String,
#[arg(
short,
long,
@ -77,9 +89,16 @@ pub struct Config {
#[arg(
short,
long,
env = "PICTRS_AGGREGATOR_CONSOLE_ADDRESS",
help = "The address at which to bind the tokio-console exporter"
)]
console_address: Option<SocketAddr>,
#[arg(
long,
env = "PICTRS_AGGREGATOR_CONSOLE_EVENT_BUFFER_SIZE",
help = "The number of events to buffer in console. When unset, console is disabled"
help = "The number of events to buffer in console"
)]
console_event_buffer_size: Option<usize>,
@ -90,6 +109,62 @@ pub struct Config {
help = "URL for the OpenTelemetry Colletor"
)]
opentelemetry_url: Option<Url>,
#[arg(
long,
env = "PICTRS_AGGREGATOR_CERTIFICATE",
help = "The CA Certificate used to verify pict-rs' TLS certificate"
)]
certificate: Option<PathBuf>,
#[arg(
long,
env = "PICTRS_AGGREGATOR_SERVER_CERTIFICATE",
help = "The Certificate used to serve pict-rs-aggregator over TLS"
)]
server_certificate: Option<PathBuf>,
#[arg(
long,
env = "PICTRS_AGGREGATOR_SERVER_PRIVATE_KEY",
help = "The Private Key used to serve pict-rs-aggregator over TLS"
)]
server_private_key: Option<PathBuf>,
}
pub struct Tls {
certificate: PathBuf,
private_key: PathBuf,
}
impl Tls {
pub fn from_config(config: &Config) -> Option<Self> {
config
.server_certificate
.as_ref()
.zip(config.server_private_key.as_ref())
.map(|(cert, key)| Tls {
certificate: cert.clone(),
private_key: key.clone(),
})
}
pub async fn open_keys(&self) -> color_eyre::Result<CertifiedKey> {
let cert_bytes = tokio::fs::read(&self.certificate).await?;
let key_bytes = tokio::fs::read(&self.private_key).await?;
let certs = rustls_pemfile::certs(&mut cert_bytes.as_slice())
.map(|res| res.map(|c| Certificate(c.to_vec())))
.collect::<Result<Vec<_>, _>>()?;
let key = rustls_pemfile::private_key(&mut key_bytes.as_slice())?
.ok_or_else(|| color_eyre::eyre::eyre!("No key in keyfile"))?;
let signing_key =
rustls::sign::any_supported_type(&PrivateKey(Vec::from(key.secret_der())))?;
Ok(CertifiedKey::new(certs, signing_key))
}
}
pub fn accept() -> &'static str {
@ -109,6 +184,10 @@ impl Config {
self.console_event_buffer_size
}
pub fn console_address(&self) -> Option<SocketAddr> {
self.console_address
}
pub fn bind_address(&self) -> SocketAddr {
self.addr
}
@ -116,6 +195,34 @@ impl Config {
pub fn opentelemetry_url(&self) -> Option<&Url> {
self.opentelemetry_url.as_ref()
}
pub async fn build_rustls_client_config(&self) -> color_eyre::Result<ClientConfig> {
let mut root_store = RootCertStore {
roots: webpki_roots::TLS_SERVER_ROOTS
.iter()
.map(|root| {
OwnedTrustAnchor::from_subject_spki_name_constraints(
root.subject.to_vec(),
root.subject_public_key_info.to_vec(),
root.name_constraints.as_ref().map(|n| n.to_vec()),
)
})
.collect(),
};
if let Some(certificate) = &self.certificate {
let bytes = tokio::fs::read(certificate).await?;
for res in rustls_pemfile::certs(&mut bytes.as_slice()) {
root_store.add(&Certificate(res?.to_vec()))?;
}
}
Ok(ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
@ -139,6 +246,7 @@ impl std::fmt::Display for Direction {
#[derive(Clone)]
pub struct State {
upstream: Url,
host: String,
scope: String,
store: Store,
startup: SystemTime,
@ -274,6 +382,7 @@ impl State {
pub fn state(config: Config, scope: &str, db: Db) -> Result<State, sled::Error> {
Ok(State {
upstream: config.upstream,
host: config.host,
scope: scope.to_string(),
store: Store::new(&db)?,
startup: SystemTime::now(),
@ -1129,11 +1238,15 @@ async fn delete_entry(
}
fn qr(req: &HttpRequest, path: &web::Path<CollectionPath>, state: &web::Data<State>) -> String {
let host = req.head().headers().get("host").unwrap();
let host = req
.head()
.headers()
.get("host")
.and_then(|h| h.to_str().ok())
.unwrap_or(&state.host);
let url = format!(
"https://{}{}",
host.to_str().unwrap(),
"https://{host}{}",
state.public_collection_path(path.collection)
);

View file

@ -1,11 +1,13 @@
use actix_web::{App, HttpServer};
use awc::Client;
use awc::{Client, Connector};
use clap::Parser;
use console_subscriber::ConsoleLayer;
use opentelemetry::KeyValue;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{propagation::TraceContextPropagator, Resource};
use std::time::Duration;
use pict_rs_aggregator::Tls;
use rustls::ServerConfig;
use std::{net::SocketAddr, sync::Arc, time::Duration};
use tracing::subscriber::set_global_default;
use tracing_actix_web::TracingLogger;
use tracing_awc::Tracing;
@ -22,6 +24,7 @@ async fn main() -> color_eyre::Result<()> {
init_logger(
config.opentelemetry_url(),
config.console_address(),
config.console_event_buffer_size(),
)?;
@ -32,32 +35,74 @@ async fn main() -> color_eyre::Result<()> {
.path(db_path)
.cache_capacity(config.sled_cache_capacity())
.open()?;
let bind_address = config.bind_address();
let rustls_client_config = config.build_rustls_client_config().await?;
let tls = Tls::from_config(&config);
let state = pict_rs_aggregator::state(config, "", db)?;
tracing::info!("Launching on {bind_address}");
let server = HttpServer::new(move || {
let connector = Connector::new().rustls_021(Arc::new(rustls_client_config.clone()));
HttpServer::new(move || {
let client = Client::builder()
.wrap(Tracing)
.timeout(Duration::from_secs(30))
.add_default_header(("User-Agent", "pict_rs_aggregator-v0.5.0-beta.3"))
.add_default_header(("User-Agent", "pict_rs_aggregator-v0.5.0"))
.disable_redirects()
.connector(connector)
.finish();
App::new()
.wrap(TracingLogger::default())
.configure(|cfg| pict_rs_aggregator::configure(cfg, state.clone(), client))
})
.bind(bind_address)?
.run()
.await?;
});
if let Some(tls) = tls {
let key = tls.open_keys().await?;
let (tx, rx) = rustls_channel_resolver::channel::<32>(key);
let handle = actix_rt::spawn(async move {
let mut interval = actix_rt::time::interval(Duration::from_secs(30));
interval.tick().await;
loop {
interval.tick().await;
match tls.open_keys().await {
Ok(key) => tx.update(key),
Err(e) => tracing::error!("Failed to open keys for TLS {e}"),
}
}
});
let server_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_cert_resolver(rx);
tracing::info!("Serving pict-rs-aggregator over TLS on {bind_address}");
server
.bind_rustls_021(bind_address, server_config)?
.run()
.await?;
handle.abort();
let _ = handle.await;
} else {
tracing::info!("Serving pict-rs-aggregator on {bind_address}");
server.bind(bind_address)?.run().await?;
}
Ok(())
}
fn init_logger(
opentelemetry_url: Option<&Url>,
console_addr: Option<SocketAddr>,
console_event_buffer_size: Option<usize>,
) -> color_eyre::Result<()> {
color_eyre::install()?;
@ -76,19 +121,24 @@ fn init_logger(
.with(format_layer)
.with(ErrorLayer::default());
if let Some(buffer_size) = console_event_buffer_size {
let console_layer = ConsoleLayer::builder()
.with_default_env()
.server_addr(([0, 0, 0, 0], 6669))
.event_buffer_capacity(buffer_size)
.spawn();
if let Some(addr) = console_addr {
let builder = ConsoleLayer::builder().with_default_env().server_addr(addr);
let console_layer = if let Some(buffer_size) = console_event_buffer_size {
builder.event_buffer_capacity(buffer_size).spawn()
} else {
builder.spawn()
};
let subscriber = subscriber.with(console_layer);
init_subscriber(subscriber, targets, opentelemetry_url)
init_subscriber(subscriber, targets, opentelemetry_url)?;
tracing::info!("Serving tokio-console endpoint on {addr}");
} else {
init_subscriber(subscriber, targets, opentelemetry_url)
init_subscriber(subscriber, targets, opentelemetry_url)?;
}
Ok(())
}
fn init_subscriber<S>(