Use self-signed certificates if necessary

This commit is contained in:
Alex 2021-12-08 12:02:39 +01:00
parent d7f3c13fa4
commit 7599dfc0ef
No known key found for this signature in database
GPG key ID: EDABF9711E244EB1
6 changed files with 106 additions and 8 deletions

2
.dockerignore Normal file
View file

@ -0,0 +1,2 @@
target

33
Cargo.lock generated
View file

@ -993,6 +993,17 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "pem"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06673860db84d02a63942fa69cd9543f2624a5df3aea7f33173048fa7ad5cf1a"
dependencies = [
"base64",
"once_cell",
"regex",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -1146,6 +1157,18 @@ dependencies = [
"rand_core", "rand_core",
] ]
[[package]]
name = "rcgen"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5911d1403f4143c9d56a702069d593e8d0f3fab880a85e103604d0893ea31ba7"
dependencies = [
"chrono",
"pem",
"ring",
"yasna",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.1.57" version = "0.1.57"
@ -1893,6 +1916,7 @@ dependencies = [
"lazy_static", "lazy_static",
"log", "log",
"pretty_env_logger", "pretty_env_logger",
"rcgen",
"regex", "regex",
"reqwest", "reqwest",
"rustls 0.20.2", "rustls 0.20.2",
@ -2201,3 +2225,12 @@ dependencies = [
"winapi 0.2.8", "winapi 0.2.8",
"winapi-build", "winapi-build",
] ]
[[package]]
name = "yasna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75"
dependencies = [
"chrono",
]

View file

@ -32,3 +32,4 @@ unicase = "2"
lazy_static = "1.4" lazy_static = "1.4"
structopt = "0.3" structopt = "0.3"
glob = "0.3" glob = "0.3"
rcgen = "0.8"

23
Dockerfile Normal file
View file

@ -0,0 +1,23 @@
FROM rust:1.57-bullseye as builder
RUN apt-get update && \
apt-get install -y libssl-dev pkg-config
WORKDIR /srv
# Build dependencies and cache them
COPY Cargo.* ./
RUN mkdir -p src && \
echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \
cargo build --release && \
rm -r src && \
rm target/release/deps/tricot*
# Build final app
COPY ./src ./src
RUN cargo build --release
FROM debian:bullseye-slim
RUN apt-get update && apt-get install -y libssl1.1 iptables
COPY --from=builder /srv/target/release/tricot /usr/local/sbin/tricot
CMD ["/usr/local/sbin/tricot"]

View file

@ -19,6 +19,7 @@ use crate::proxy_config::*;
pub struct CertStore { pub struct CertStore {
consul: Consul, consul: Consul,
certs: RwLock<HashMap<String, Arc<Cert>>>, certs: RwLock<HashMap<String, Arc<Cert>>>,
self_signed_certs: RwLock<HashMap<String, Arc<Cert>>>,
rx_proxy_config: watch::Receiver<Arc<ProxyConfig>>, rx_proxy_config: watch::Receiver<Arc<ProxyConfig>>,
} }
@ -27,6 +28,7 @@ impl CertStore {
Arc::new(Self { Arc::new(Self {
consul, consul,
certs: RwLock::new(HashMap::new()), certs: RwLock::new(HashMap::new()),
self_signed_certs: RwLock::new(HashMap::new()),
rx_proxy_config, rx_proxy_config,
}) })
} }
@ -66,16 +68,23 @@ impl CertStore {
} }
// Check in local memory if it exists // Check in local memory if it exists
let certs = self.certs.read().unwrap(); if let Some(cert) = self.certs.read().unwrap().get(domain) {
if let Some(cert) = certs.get(domain) {
if !cert.is_old() { if !cert.is_old() {
return Ok(cert.clone()); return Ok(cert.clone());
} }
} }
// Not found in local memory // Not found in local memory, try to get it in background
tokio::spawn(self.clone().get_cert_task(domain.to_string())); tokio::spawn(self.clone().get_cert_task(domain.to_string()));
bail!("Certificate not found (will try to get it in background)");
// In the meantime, use a self-signed certificate
if let Some(cert) = self.self_signed_certs.read().unwrap().get(domain) {
if !cert.is_old() {
return Ok(cert.clone());
}
}
self.gen_self_signed_certificate(domain)
} }
pub async fn get_cert_task(self: Arc<Self>, domain: String) -> Result<Arc<Cert>> { pub async fn get_cert_task(self: Arc<Self>, domain: String) -> Result<Arc<Cert>> {
@ -221,6 +230,27 @@ impl CertStore {
info!("Cert successfully renewed: {}", domain); info!("Cert successfully renewed: {}", domain);
Ok(cert) Ok(cert)
} }
fn gen_self_signed_certificate(&self, domain: &str) -> Result<Arc<Cert>> {
let subject_alt_names = vec![domain.to_string(), "localhost".to_string()];
let cert = rcgen::generate_simple_self_signed(subject_alt_names)?;
let certser = CertSer {
hostname: domain.to_string(),
date: Utc::today().naive_utc(),
valid_days: 1024,
key_pem: cert.serialize_private_key_pem(),
cert_pem: cert.serialize_pem()?,
};
let cert = Arc::new(Cert::new(certser)?);
self.self_signed_certs
.write()
.unwrap()
.insert(domain.to_string(), cert.clone());
info!("Added self-signed certificate for {}", domain);
Ok(cert)
}
} }
pub struct StoreResolver(pub Arc<CertStore>); pub struct StoreResolver(pub Arc<CertStore>);
@ -228,7 +258,12 @@ 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 = self.0.get_cert_for_https(domain).ok()?; match self.0.get_cert_for_https(domain) {
Some(cert.certkey.clone()) Ok(cert) => Some(cert.certkey.clone()),
Err(e) => {
warn!("Could not get certificate for {}: {}", domain, e);
None
}
}
} }
} }

View file

@ -60,13 +60,17 @@ impl std::fmt::Display for ProxyEntry {
HostDescription::Hostname(h) => write!(f, "{}", h)?, HostDescription::Hostname(h) => write!(f, "{}", h)?,
HostDescription::Pattern(p) => write!(f, "Pattern('{}')", p.as_str())?, HostDescription::Pattern(p) => write!(f, "Pattern('{}')", p.as_str())?,
} }
write!(f, "{} {}", self.path_prefix.as_ref().unwrap_or(&String::new()), self.priority)?; write!(
f,
"{} {}",
self.path_prefix.as_ref().unwrap_or(&String::new()),
self.priority
)?;
if !self.add_headers.is_empty() { if !self.add_headers.is_empty() {
write!(f, "+Headers: {:?}", self.add_headers)?; write!(f, "+Headers: {:?}", self.add_headers)?;
} }
Ok(()) Ok(())
} }
} }
#[derive(Debug)] #[derive(Debug)]