forked from Deuxfleurs/tricot
Use self-signed certificates if necessary
This commit is contained in:
parent
d7f3c13fa4
commit
7599dfc0ef
6 changed files with 106 additions and 8 deletions
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
target
|
||||||
|
|
33
Cargo.lock
generated
33
Cargo.lock
generated
|
@ -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",
|
||||||
|
]
|
||||||
|
|
|
@ -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
23
Dockerfile
Normal 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"]
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Reference in a new issue