2021-12-06 22:08:22 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate anyhow;
|
|
|
|
|
2022-01-24 18:28:18 +00:00
|
|
|
use log::*;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use futures::{FutureExt, TryFutureExt};
|
2021-12-07 17:19:51 +00:00
|
|
|
use std::net::SocketAddr;
|
2021-12-07 16:56:15 +00:00
|
|
|
use structopt::StructOpt;
|
2022-01-24 18:28:18 +00:00
|
|
|
use tokio::select;
|
|
|
|
use tokio::sync::watch;
|
2021-12-07 16:56:15 +00:00
|
|
|
|
2021-12-07 12:50:44 +00:00
|
|
|
mod cert;
|
|
|
|
mod cert_store;
|
2021-12-06 22:08:22 +00:00
|
|
|
mod consul;
|
2021-12-07 12:50:44 +00:00
|
|
|
mod http;
|
2021-12-07 14:20:45 +00:00
|
|
|
mod https;
|
2022-12-05 17:43:48 +00:00
|
|
|
mod metrics;
|
2021-12-06 22:08:22 +00:00
|
|
|
mod proxy_config;
|
2021-12-07 14:20:45 +00:00
|
|
|
mod reverse_proxy;
|
2021-12-09 11:18:23 +00:00
|
|
|
mod tls_util;
|
2021-12-06 22:08:22 +00:00
|
|
|
|
2022-01-24 18:28:18 +00:00
|
|
|
use proxy_config::ProxyConfig;
|
|
|
|
|
|
|
|
#[cfg(feature = "dhat-heap")]
|
|
|
|
#[global_allocator]
|
|
|
|
static ALLOC: dhat::Alloc = dhat::Alloc;
|
2021-12-06 22:08:22 +00:00
|
|
|
|
2021-12-07 16:56:15 +00:00
|
|
|
#[derive(StructOpt, Debug)]
|
|
|
|
#[structopt(name = "tricot")]
|
|
|
|
struct Opt {
|
|
|
|
/// Address of consul server
|
2021-12-07 17:50:58 +00:00
|
|
|
#[structopt(
|
|
|
|
long = "consul-addr",
|
|
|
|
env = "TRICOT_CONSUL_HOST",
|
2021-12-08 11:10:58 +00:00
|
|
|
default_value = "http://127.0.0.1:8500"
|
2021-12-07 17:50:58 +00:00
|
|
|
)]
|
2021-12-07 16:56:15 +00:00
|
|
|
pub consul_addr: String,
|
|
|
|
|
2021-12-30 19:08:10 +00:00
|
|
|
/// CA certificate for Consul server with TLS
|
|
|
|
#[structopt(long = "consul-ca-cert", env = "TRICOT_CONSUL_CA_CERT")]
|
|
|
|
pub consul_ca_cert: Option<String>,
|
|
|
|
|
2022-08-24 15:53:33 +00:00
|
|
|
/// Skip TLS verification for Consul
|
|
|
|
#[structopt(long = "consul-tls-skip-verify", env = "TRICOT_CONSUL_TLS_SKIP_VERIFY")]
|
|
|
|
pub consul_tls_skip_verify: bool,
|
|
|
|
|
2021-12-30 19:08:10 +00:00
|
|
|
/// Client certificate for Consul server with TLS
|
|
|
|
#[structopt(long = "consul-client-cert", env = "TRICOT_CONSUL_CLIENT_CERT")]
|
|
|
|
pub consul_client_cert: Option<String>,
|
|
|
|
|
|
|
|
/// Client key for Consul server with TLS
|
|
|
|
#[structopt(long = "consul-client-key", env = "TRICOT_CONSUL_CLIENT_KEY")]
|
|
|
|
pub consul_client_key: Option<String>,
|
|
|
|
|
2021-12-07 16:56:15 +00:00
|
|
|
/// Prefix of Tricot's entries in Consul KV space
|
2021-12-07 17:50:58 +00:00
|
|
|
#[structopt(
|
|
|
|
long = "consul-kv-prefix",
|
|
|
|
env = "TRICOT_CONSUL_KV_PREFIX",
|
|
|
|
default_value = "tricot/"
|
|
|
|
)]
|
2021-12-07 16:56:15 +00:00
|
|
|
pub consul_kv_prefix: String,
|
|
|
|
|
|
|
|
/// Node name
|
|
|
|
#[structopt(long = "node-name", env = "TRICOT_NODE_NAME", default_value = "<none>")]
|
|
|
|
pub node_name: String,
|
2021-12-07 17:19:51 +00:00
|
|
|
|
|
|
|
/// Bind address for HTTP server
|
2021-12-07 17:50:58 +00:00
|
|
|
#[structopt(
|
|
|
|
long = "http-bind-addr",
|
|
|
|
env = "TRICOT_HTTP_BIND_ADDR",
|
|
|
|
default_value = "0.0.0.0:80"
|
|
|
|
)]
|
2021-12-07 17:19:51 +00:00
|
|
|
pub http_bind_addr: SocketAddr,
|
|
|
|
|
|
|
|
/// Bind address for HTTPS server
|
2021-12-07 17:50:58 +00:00
|
|
|
#[structopt(
|
|
|
|
long = "https-bind-addr",
|
|
|
|
env = "TRICOT_HTTPS_BIND_ADDR",
|
|
|
|
default_value = "0.0.0.0:443"
|
|
|
|
)]
|
2021-12-07 17:19:51 +00:00
|
|
|
pub https_bind_addr: SocketAddr,
|
2021-12-08 11:16:28 +00:00
|
|
|
|
2022-12-05 17:43:48 +00:00
|
|
|
/// Bind address for metrics server (Prometheus format over HTTP)
|
|
|
|
#[structopt(long = "metrics-bind-addr", env = "TRICOT_METRICS_BIND_ADDR")]
|
|
|
|
pub metrics_bind_addr: Option<SocketAddr>,
|
|
|
|
|
2021-12-08 11:16:28 +00:00
|
|
|
/// E-mail address for Let's Encrypt certificate requests
|
2021-12-08 12:28:07 +00:00
|
|
|
#[structopt(long = "letsencrypt-email", env = "TRICOT_LETSENCRYPT_EMAIL")]
|
2021-12-08 11:16:28 +00:00
|
|
|
pub letsencrypt_email: String,
|
2021-12-09 14:43:19 +00:00
|
|
|
|
|
|
|
/// Enable compression of responses
|
|
|
|
#[structopt(long = "enable-compression", env = "TRICOT_ENABLE_COMPRESSION")]
|
|
|
|
pub enable_compression: bool,
|
|
|
|
|
|
|
|
/// Mime types for which to enable compression (comma-separated list)
|
|
|
|
#[structopt(
|
|
|
|
long = "compress-mime-types",
|
|
|
|
env = "TRICOT_COMPRESS_MIME_TYPES",
|
2021-12-09 15:09:41 +00:00
|
|
|
default_value = "text/html,text/plain,text/css,text/javascript,text/xml,application/javascript,application/json,application/xml,image/svg+xml,font/ttf"
|
2021-12-09 14:43:19 +00:00
|
|
|
)]
|
|
|
|
pub compress_mime_types: String,
|
2021-12-07 16:56:15 +00:00
|
|
|
}
|
|
|
|
|
2021-12-07 14:20:45 +00:00
|
|
|
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
|
2021-12-06 22:08:22 +00:00
|
|
|
async fn main() {
|
2022-01-24 18:28:18 +00:00
|
|
|
#[cfg(feature = "dhat-heap")]
|
|
|
|
let _profiler = dhat::Profiler::new_heap();
|
|
|
|
|
2021-12-07 12:50:44 +00:00
|
|
|
if std::env::var("RUST_LOG").is_err() {
|
2021-12-08 10:24:25 +00:00
|
|
|
std::env::set_var("RUST_LOG", "tricot=info")
|
2021-12-07 12:50:44 +00:00
|
|
|
}
|
2021-12-06 22:08:22 +00:00
|
|
|
pretty_env_logger::init();
|
2021-12-07 16:56:15 +00:00
|
|
|
|
2021-12-08 16:50:40 +00:00
|
|
|
// Abort on panic (same behavior as in Go)
|
|
|
|
std::panic::set_hook(Box::new(|panic_info| {
|
|
|
|
error!("{}", panic_info.to_string());
|
|
|
|
std::process::abort();
|
|
|
|
}));
|
|
|
|
|
2021-12-07 16:56:15 +00:00
|
|
|
let opt = Opt::from_args();
|
|
|
|
|
2021-12-06 22:08:22 +00:00
|
|
|
info!("Starting Tricot");
|
|
|
|
|
2022-01-24 18:28:18 +00:00
|
|
|
let (exit_signal, provoke_exit) = watch_ctrl_c();
|
|
|
|
let exit_on_err = move |err: anyhow::Error| {
|
|
|
|
error!("Error: {}", err);
|
|
|
|
let _ = provoke_exit.send(true);
|
|
|
|
};
|
|
|
|
|
2022-12-05 17:43:48 +00:00
|
|
|
let metrics_server = metrics::MetricsServer::init(opt.metrics_bind_addr);
|
|
|
|
|
2021-12-30 19:45:28 +00:00
|
|
|
let consul_config = consul::ConsulConfig {
|
2021-12-30 19:08:10 +00:00
|
|
|
addr: opt.consul_addr.clone(),
|
|
|
|
ca_cert: opt.consul_ca_cert.clone(),
|
2022-08-24 15:53:33 +00:00
|
|
|
tls_skip_verify: opt.consul_tls_skip_verify,
|
2021-12-30 19:08:10 +00:00
|
|
|
client_cert: opt.consul_client_cert.clone(),
|
|
|
|
client_key: opt.consul_client_key.clone(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let consul = consul::Consul::new(consul_config, &opt.consul_kv_prefix, &opt.node_name)
|
|
|
|
.expect("Error creating Consul client");
|
2022-01-24 18:28:18 +00:00
|
|
|
let rx_proxy_config =
|
|
|
|
proxy_config::spawn_proxy_config_task(consul.clone(), exit_signal.clone());
|
2021-12-06 22:08:22 +00:00
|
|
|
|
2021-12-08 12:28:07 +00:00
|
|
|
let cert_store = cert_store::CertStore::new(
|
|
|
|
consul.clone(),
|
|
|
|
rx_proxy_config.clone(),
|
|
|
|
opt.letsencrypt_email.clone(),
|
2022-01-24 18:28:18 +00:00
|
|
|
exit_on_err.clone(),
|
2021-12-08 12:28:07 +00:00
|
|
|
);
|
2021-12-08 16:50:40 +00:00
|
|
|
|
2022-12-05 17:43:48 +00:00
|
|
|
let metrics_task = tokio::spawn(
|
|
|
|
metrics_server
|
|
|
|
.run(wait_from(exit_signal.clone()))
|
|
|
|
.map_err(exit_on_err.clone())
|
|
|
|
.then(|_| async { info!("Metrics server exited") }),
|
|
|
|
);
|
|
|
|
|
2022-01-24 18:28:18 +00:00
|
|
|
let http_task = tokio::spawn(
|
|
|
|
http::serve_http(
|
|
|
|
opt.http_bind_addr,
|
|
|
|
consul.clone(),
|
|
|
|
wait_from(exit_signal.clone()),
|
|
|
|
)
|
|
|
|
.map_err(exit_on_err.clone())
|
|
|
|
.then(|_| async { info!("HTTP server exited") }),
|
|
|
|
);
|
2021-12-09 14:43:19 +00:00
|
|
|
|
|
|
|
let https_config = https::HttpsConfig {
|
|
|
|
bind_addr: opt.https_bind_addr,
|
|
|
|
enable_compression: opt.enable_compression,
|
|
|
|
compress_mime_types: opt
|
|
|
|
.compress_mime_types
|
2021-12-09 22:38:56 +00:00
|
|
|
.split(',')
|
2021-12-09 14:43:19 +00:00
|
|
|
.map(|x| x.to_string())
|
|
|
|
.collect(),
|
|
|
|
};
|
2022-01-24 18:28:18 +00:00
|
|
|
|
|
|
|
let https_task = tokio::spawn(
|
|
|
|
https::serve_https(
|
|
|
|
https_config,
|
|
|
|
cert_store.clone(),
|
|
|
|
rx_proxy_config.clone(),
|
|
|
|
exit_signal.clone(),
|
|
|
|
)
|
|
|
|
.map_err(exit_on_err.clone())
|
|
|
|
.then(|_| async { info!("HTTPS server exited") }),
|
2021-12-08 16:50:40 +00:00
|
|
|
);
|
2021-12-06 22:40:41 +00:00
|
|
|
|
2022-01-24 18:28:18 +00:00
|
|
|
let dump_task = tokio::spawn(dump_config_on_change(rx_proxy_config, exit_signal.clone()));
|
|
|
|
|
2022-12-05 17:43:48 +00:00
|
|
|
let _ = metrics_task.await.expect("Tokio task await failure");
|
2022-01-24 18:28:18 +00:00
|
|
|
let _ = http_task.await.expect("Tokio task await failure");
|
|
|
|
let _ = https_task.await.expect("Tokio task await failure");
|
|
|
|
let _ = dump_task.await.expect("Tokio task await failure");
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn dump_config_on_change(
|
|
|
|
mut rx_proxy_config: watch::Receiver<Arc<ProxyConfig>>,
|
|
|
|
mut must_exit: watch::Receiver<bool>,
|
|
|
|
) {
|
|
|
|
while !*must_exit.borrow() {
|
|
|
|
select!(
|
|
|
|
c = rx_proxy_config.changed() => {
|
|
|
|
if !c.is_ok() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ = must_exit.changed() => continue,
|
|
|
|
);
|
2022-01-19 13:53:19 +00:00
|
|
|
println!("---- PROXY CONFIGURATION ----");
|
2021-12-07 17:19:51 +00:00
|
|
|
for ent in rx_proxy_config.borrow().entries.iter() {
|
2022-01-19 13:53:19 +00:00
|
|
|
println!(" {}", ent);
|
2021-12-07 17:19:51 +00:00
|
|
|
}
|
2022-01-19 13:53:19 +00:00
|
|
|
println!();
|
2021-12-06 22:08:22 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-08 16:50:40 +00:00
|
|
|
|
2022-01-24 18:28:18 +00:00
|
|
|
/// Creates a watch that contains `false`, and that changes
|
|
|
|
/// to `true` when a Ctrl+C signal is received.
|
|
|
|
pub fn watch_ctrl_c() -> (watch::Receiver<bool>, Arc<watch::Sender<bool>>) {
|
|
|
|
let (send_cancel, watch_cancel) = watch::channel(false);
|
|
|
|
let send_cancel = Arc::new(send_cancel);
|
|
|
|
let send_cancel_2 = send_cancel.clone();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
tokio::signal::ctrl_c()
|
|
|
|
.await
|
|
|
|
.expect("failed to install CTRL+C signal handler");
|
|
|
|
info!("Received CTRL+C, shutting down.");
|
|
|
|
send_cancel.send(true).unwrap();
|
|
|
|
});
|
|
|
|
(watch_cancel, send_cancel_2)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn wait_from(mut chan: watch::Receiver<bool>) {
|
|
|
|
while !*chan.borrow() {
|
|
|
|
if chan.changed().await.is_err() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2021-12-08 16:50:40 +00:00
|
|
|
}
|