forked from Deuxfleurs/tricot
Better handle get_cert for https request (faster path, hostname verification)
This commit is contained in:
parent
e8b789f5e0
commit
11c6f0b1c2
6 changed files with 85 additions and 29 deletions
|
@ -19,20 +19,21 @@ use crate::proxy_config::ProxyConfig;
|
||||||
pub struct CertStore {
|
pub struct CertStore {
|
||||||
consul: Consul,
|
consul: Consul,
|
||||||
certs: RwLock<HashMap<String, Arc<Cert>>>,
|
certs: RwLock<HashMap<String, Arc<Cert>>>,
|
||||||
|
rx_proxy_config: watch::Receiver<Arc<ProxyConfig>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CertStore {
|
impl CertStore {
|
||||||
pub fn new(consul: Consul) -> Arc<Self> {
|
pub fn new(consul: Consul, rx_proxy_config: watch::Receiver<Arc<ProxyConfig>>) -> Arc<Self> {
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
consul,
|
consul,
|
||||||
certs: RwLock::new(HashMap::new()),
|
certs: RwLock::new(HashMap::new()),
|
||||||
|
rx_proxy_config,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn watch_proxy_config(
|
pub async fn watch_proxy_config(self: Arc<Self>) {
|
||||||
self: Arc<Self>,
|
let mut rx_proxy_config = self.rx_proxy_config.clone();
|
||||||
mut rx_proxy_config: watch::Receiver<Arc<ProxyConfig>>,
|
|
||||||
) {
|
|
||||||
while rx_proxy_config.changed().await.is_ok() {
|
while rx_proxy_config.changed().await.is_ok() {
|
||||||
let mut domains: HashSet<String> = HashSet::new();
|
let mut domains: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
|
@ -50,6 +51,35 @@ impl CertStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_cert_for_https(self: &Arc<Self>, domain: &str) -> Result<Arc<Cert>> {
|
||||||
|
// Check if domain is authorized
|
||||||
|
if !self
|
||||||
|
.rx_proxy_config
|
||||||
|
.borrow()
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.any(|ent| ent.host == domain)
|
||||||
|
{
|
||||||
|
bail!("Domain {} should not have a TLS certificate.", domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check in local memory if it exists
|
||||||
|
let certs = self.certs.read().unwrap();
|
||||||
|
if let Some(cert) = certs.get(domain) {
|
||||||
|
if !cert.is_old() {
|
||||||
|
return Ok(cert.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found in local memory
|
||||||
|
tokio::spawn(self.clone().get_cert_task(domain.to_string()));
|
||||||
|
bail!("Certificate not found (will try to get it in background)");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_cert_task(self: Arc<Self>, domain: String) -> Result<Arc<Cert>> {
|
||||||
|
self.get_cert(domain.as_str()).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_cert(self: &Arc<Self>, domain: &str) -> Result<Arc<Cert>> {
|
pub async fn get_cert(self: &Arc<Self>, domain: &str) -> Result<Arc<Cert>> {
|
||||||
// First, try locally.
|
// First, try locally.
|
||||||
{
|
{
|
||||||
|
@ -196,7 +226,7 @@ pub struct StoreResolver(pub Arc<CertStore>);
|
||||||
impl rustls::server::ResolvesServerCert for StoreResolver {
|
impl rustls::server::ResolvesServerCert for StoreResolver {
|
||||||
fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
|
fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
|
||||||
let domain = client_hello.server_name()?;
|
let domain = client_hello.server_name()?;
|
||||||
let cert = futures::executor::block_on(self.0.get_cert(domain)).ok()?;
|
let cert = self.0.get_cert_for_https(domain).ok()?;
|
||||||
Some(cert.certkey.clone())
|
Some(cert.certkey.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,11 @@ impl Consul {
|
||||||
Ok(resp.into_iter().map(|n| n.node).collect::<Vec<_>>())
|
Ok(resp.into_iter().map(|n| n.node).collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn watch_node(&self, host: &str, idx: Option<usize>) -> Result<(ConsulNodeCatalog, usize)> {
|
pub async fn watch_node(
|
||||||
|
&self,
|
||||||
|
host: &str,
|
||||||
|
idx: Option<usize>,
|
||||||
|
) -> Result<(ConsulNodeCatalog, usize)> {
|
||||||
debug!("watch_node {} {:?}", host, idx);
|
debug!("watch_node {} {:?}", host, idx);
|
||||||
|
|
||||||
let url = match idx {
|
let url = match idx {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::sync::Arc;
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
|
@ -5,10 +5,10 @@ use anyhow::Result;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
use http::header::{HeaderName, HeaderValue};
|
||||||
use hyper::server::conn::Http;
|
use hyper::server::conn::Http;
|
||||||
use hyper::service::service_fn;
|
use hyper::service::service_fn;
|
||||||
use hyper::{Body, Request, Response, StatusCode};
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
use http::header::{HeaderName, HeaderValue};
|
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tokio_rustls::TlsAcceptor;
|
use tokio_rustls::TlsAcceptor;
|
||||||
|
@ -121,7 +121,10 @@ async fn handle(
|
||||||
let mut response = reverse_proxy::call(remote_addr.ip(), &to_addr, req).await?;
|
let mut response = reverse_proxy::call(remote_addr.ip(), &to_addr, req).await?;
|
||||||
|
|
||||||
for (header, value) in proxy_to.add_headers.iter() {
|
for (header, value) in proxy_to.add_headers.iter() {
|
||||||
response.headers_mut().insert(HeaderName::from_bytes(header.as_bytes())?, HeaderValue::from_str(value)?);
|
response.headers_mut().insert(
|
||||||
|
HeaderName::from_bytes(header.as_bytes())?,
|
||||||
|
HeaderValue::from_str(value)?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
|
35
src/main.rs
35
src/main.rs
|
@ -18,11 +18,19 @@ use log::*;
|
||||||
#[structopt(name = "tricot")]
|
#[structopt(name = "tricot")]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
/// Address of consul server
|
/// Address of consul server
|
||||||
#[structopt(long = "consul-addr", env = "TRICOT_CONSUL_HOST", default_value = "http://127.0.0.1:8500/")]
|
#[structopt(
|
||||||
|
long = "consul-addr",
|
||||||
|
env = "TRICOT_CONSUL_HOST",
|
||||||
|
default_value = "http://127.0.0.1:8500/"
|
||||||
|
)]
|
||||||
pub consul_addr: String,
|
pub consul_addr: String,
|
||||||
|
|
||||||
/// Prefix of Tricot's entries in Consul KV space
|
/// Prefix of Tricot's entries in Consul KV space
|
||||||
#[structopt(long = "consul-kv-prefix", env = "TRICOT_CONSUL_KV_PREFIX", default_value = "tricot/")]
|
#[structopt(
|
||||||
|
long = "consul-kv-prefix",
|
||||||
|
env = "TRICOT_CONSUL_KV_PREFIX",
|
||||||
|
default_value = "tricot/"
|
||||||
|
)]
|
||||||
pub consul_kv_prefix: String,
|
pub consul_kv_prefix: String,
|
||||||
|
|
||||||
/// Node name
|
/// Node name
|
||||||
|
@ -30,15 +38,22 @@ struct Opt {
|
||||||
pub node_name: String,
|
pub node_name: String,
|
||||||
|
|
||||||
/// Bind address for HTTP server
|
/// Bind address for HTTP server
|
||||||
#[structopt(long = "http-bind-addr", env = "TRICOT_HTTP_BIND_ADDR", default_value = "0.0.0.0:80")]
|
#[structopt(
|
||||||
|
long = "http-bind-addr",
|
||||||
|
env = "TRICOT_HTTP_BIND_ADDR",
|
||||||
|
default_value = "0.0.0.0:80"
|
||||||
|
)]
|
||||||
pub http_bind_addr: SocketAddr,
|
pub http_bind_addr: SocketAddr,
|
||||||
|
|
||||||
/// Bind address for HTTPS server
|
/// Bind address for HTTPS server
|
||||||
#[structopt(long = "https-bind-addr", env = "TRICOT_HTTPS_BIND_ADDR", default_value = "0.0.0.0:443")]
|
#[structopt(
|
||||||
|
long = "https-bind-addr",
|
||||||
|
env = "TRICOT_HTTPS_BIND_ADDR",
|
||||||
|
default_value = "0.0.0.0:443"
|
||||||
|
)]
|
||||||
pub https_bind_addr: SocketAddr,
|
pub https_bind_addr: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
|
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
if std::env::var("RUST_LOG").is_err() {
|
if std::env::var("RUST_LOG").is_err() {
|
||||||
|
@ -53,16 +68,12 @@ async fn main() {
|
||||||
let consul = consul::Consul::new(&opt.consul_addr, &opt.consul_kv_prefix, &opt.node_name);
|
let consul = consul::Consul::new(&opt.consul_addr, &opt.consul_kv_prefix, &opt.node_name);
|
||||||
let mut rx_proxy_config = proxy_config::spawn_proxy_config_task(consul.clone());
|
let mut rx_proxy_config = proxy_config::spawn_proxy_config_task(consul.clone());
|
||||||
|
|
||||||
let cert_store = cert_store::CertStore::new(consul.clone());
|
let cert_store = cert_store::CertStore::new(consul.clone(), rx_proxy_config.clone());
|
||||||
tokio::spawn(
|
tokio::spawn(cert_store.clone().watch_proxy_config());
|
||||||
cert_store
|
|
||||||
.clone()
|
|
||||||
.watch_proxy_config(rx_proxy_config.clone()),
|
|
||||||
);
|
|
||||||
|
|
||||||
tokio::spawn(http::serve_http(opt.http_bind_addr, consul.clone()));
|
tokio::spawn(http::serve_http(opt.http_bind_addr, consul.clone()));
|
||||||
tokio::spawn(https::serve_https(
|
tokio::spawn(https::serve_https(
|
||||||
opt.https_bind_addr,
|
opt.https_bind_addr,
|
||||||
cert_store.clone(),
|
cert_store.clone(),
|
||||||
rx_proxy_config.clone(),
|
rx_proxy_config.clone(),
|
||||||
));
|
));
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::{atomic, Arc};
|
use std::sync::{atomic, Arc};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::{cmp, time::Duration};
|
use std::{cmp, time::Duration};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use futures::future::BoxFuture;
|
||||||
use futures::stream::{FuturesUnordered, StreamExt};
|
use futures::stream::{FuturesUnordered, StreamExt};
|
||||||
use futures::future::{BoxFuture};
|
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
use tokio::{sync::watch, time::sleep};
|
use tokio::{sync::watch, time::sleep};
|
||||||
|
@ -45,7 +45,11 @@ fn retry_to_time(retries: u32, max_time: Duration) -> Duration {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_tricot_tag(tag: &str, target_addr: SocketAddr, add_headers: &[(String, String)]) -> Option<ProxyEntry> {
|
fn parse_tricot_tag(
|
||||||
|
tag: &str,
|
||||||
|
target_addr: SocketAddr,
|
||||||
|
add_headers: &[(String, String)],
|
||||||
|
) -> Option<ProxyEntry> {
|
||||||
let splits = tag.split(' ').collect::<Vec<_>>();
|
let splits = tag.split(' ').collect::<Vec<_>>();
|
||||||
if (splits.len() != 2 && splits.len() != 3) || splits[0] != "tricot" {
|
if (splits.len() != 2 && splits.len() != 3) || splits[0] != "tricot" {
|
||||||
return None;
|
return None;
|
||||||
|
@ -89,10 +93,13 @@ fn parse_consul_catalog(catalog: &ConsulNodeCatalog) -> Vec<ProxyEntry> {
|
||||||
_ => match catalog.node.address.parse() {
|
_ => match catalog.node.address.parse() {
|
||||||
Ok(ip) => ip,
|
Ok(ip) => ip,
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Could not get address for service {} at node {}", svc.service, catalog.node.node);
|
warn!(
|
||||||
|
"Could not get address for service {} at node {}",
|
||||||
|
svc.service, catalog.node.node
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
let addr = SocketAddr::new(ip_addr, svc.port);
|
let addr = SocketAddr::new(ip_addr, svc.port);
|
||||||
|
|
||||||
|
@ -182,7 +189,8 @@ pub fn spawn_proxy_config_task(consul: Consul) -> watch::Receiver<Arc<ProxyConfi
|
||||||
watch_state.retries += 1;
|
watch_state.retries += 1;
|
||||||
watch_state.last_idx = None;
|
watch_state.last_idx = None;
|
||||||
|
|
||||||
let will_retry_in = retry_to_time(watch_state.retries, Duration::from_secs(600));
|
let will_retry_in =
|
||||||
|
retry_to_time(watch_state.retries, Duration::from_secs(600));
|
||||||
error!(
|
error!(
|
||||||
"Failed to query consul for node {}. Will retry in {}s. {}",
|
"Failed to query consul for node {}. Will retry in {}s. {}",
|
||||||
node,
|
node,
|
||||||
|
|
Loading…
Reference in a new issue