1
0
mirror of https://github.com/actix/examples synced 2024-11-23 14:31:07 +01:00

feat: add cert-watch example

This commit is contained in:
Rob Ede 2024-02-06 02:50:23 +00:00
parent 741815074a
commit 183c924220
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
7 changed files with 279 additions and 1 deletions

39
Cargo.lock generated
View File

@ -1975,6 +1975,24 @@ dependencies = [
"libc",
]
[[package]]
name = "cert-watch"
version = "1.0.0"
dependencies = [
"actix-files",
"actix-web",
"color-eyre",
"env_logger",
"eyre",
"futures-util",
"log",
"notify 6.1.1",
"parking_lot 0.12.1",
"rustls 0.21.10",
"rustls-pemfile",
"tokio 1.35.1",
]
[[package]]
name = "cexpr"
version = "0.6.0"
@ -4765,7 +4783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d073fe2671399bed11572e89b1349d2ea54205a65282e7dc0b1e954c82ad597"
dependencies = [
"minijinja",
"notify",
"notify 5.2.0",
]
[[package]]
@ -5120,6 +5138,25 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.1",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio 0.8.9",
"walkdir",
"windows-sys 0.48.0",
]
[[package]]
name = "num-bigint"
version = "0.4.4"

View File

@ -33,6 +33,7 @@ members = [
"http-proxy",
"https-tls/acme-letsencrypt",
"https-tls/awc-https",
"https-tls/cert-watch",
"https-tls/openssl",
"https-tls/rustls-client-cert",
"https-tls/rustls",
@ -98,6 +99,8 @@ chrono = { version = "0.4.20", default-features = false, features = ["clock", "s
derive_more = "0.99.7"
dotenvy = "0.15"
env_logger = "0.11"
eyre = { version = "0.6", default-features = false, features = ["auto-install", "track-caller"] }
color-eyre = "0.6"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
log = "0.4"
openssl = { version = "0.10.60", features = ["v110"] }

View File

@ -0,0 +1,18 @@
[package]
name = "cert-watch"
version = "1.0.0"
edition = "2021"
[dependencies]
actix-web = { workspace = true, features = ["rustls-0_21"] }
actix-files.workspace = true
color-eyre.workspace = true
env_logger.workspace = true
eyre.workspace = true
futures-util.workspace = true
log.workspace = true
notify = "6"
rustls.workspace = true
rustls-pemfile = "1"
tokio = { workspace = true, features = ["time", "rt-multi-thread", "macros"] }
parking_lot = "0.12"

View File

@ -0,0 +1,38 @@
# HTTPS Server With TLS Cert/Key File Watcher
## Usage
### Certificate
We put the self-signed certificate in this directory as an example but your browser would complain that it isn't secure. So we recommend to use [`mkcert`] to trust it. To use local CA, you should run:
```sh
mkcert -install
```
If you want to generate your own cert/private key file, then run:
```sh
mkcert -key-file key.pem -cert-file cert.pem 127.0.0.1 localhost
```
### Running The Example Server
```console
$ cd https-tls/cert-watch
$ cargo run
starting HTTPS server at https://localhost:8443
```
Reload the server by modifying the certificate metadata:
```console
$ touch cert.pem
```
### Client
- cURL: `curl -v --insecure https://127.0.0.1:8443`
- Browser: go to <https://127.0.0.1:8443>
[`mkcert`]: https://github.com/FiloSottile/mkcert

View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEIjCCAoqgAwIBAgIRANtMOCE9la5aBBJkaPCckAUwDQYJKoZIhvcNAQELBQAw
aTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMR8wHQYDVQQLDBZyb2JA
c29tYnJhLmxvY2FsIChSb2IpMSYwJAYDVQQDDB1ta2NlcnQgcm9iQHNvbWJyYS5s
b2NhbCAoUm9iKTAeFw0yNDAyMDYwMTUzNTBaFw0yNjA1MDYwMDUzNTBaMEoxJzAl
BgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEfMB0GA1UECwwW
cm9iQHNvbWJyYS5sb2NhbCAoUm9iKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAJ+H+iCey/ET+cq3DxCMjkNaUS8M+LqWaEqGBGGGly+mRGfJ30BvaULf
JIR1V0NkT38t9X/Y/7Qnv8ONa2WTnGNGfgrp+DQGGQBkfi2ggaARd8XoY8WlZBVg
UzfZ/9tNqJKx41M2tYunL59ucmB7osmmU8m74oKqylA1TkYEKzyqvkg0AJ3RVslH
/vmq2LKidbofXQnZWvs2PaePR2q7XXv7+bBaTvLeUIU4Vjww6o2kJTN4//kp2dtH
iwfcUxTT6B22X512AXxojp6+X6B1Y1XhHqQcPuZUZ6ITOcS3zCXje/ijDXoiPn3o
mQTM9UeD3zTlq2vZbSeRT1TQzZzRVPUCAwEAAaNkMGIwDgYDVR0PAQH/BAQDAgWg
MBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFBf9LZZrFz95p+wnPmJz
Pavuiq9QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF
AAOCAYEAf3tXixdMuwjPVzrR8TImg7Oe9UcYIHxUN27HospX0gpvKRLuFmxNNhN+
aK0l765FaLH7acOY129BxAGzuG54+XQdeRcJH/SrNCeHfe5VCHc2+f/Fj6yjUrug
ZRlsbS57LFPgOaPFyej2bR4EDbpwGczc/ghCN4PBRGXC4onFGT7TsaAw3GaXnlMx
ToLuSnnZID9gcl6Wp0qR4baXJh5HrcxNOPIiO3k4U+70jcEty5mDSac0+mNsxgOp
O0w1S/YsQ/W+tG2lsb0huPw2XaAPu+GkkX1KmAV5PSVssIqmRcy6eqv4CkqKq1ur
WeKexHVO8VRQqwx8H/DZDnU1IxNx+khxxNSy16wo4LWU6BHfnEyOSW/S1DlsRUub
JtFg5dIkFBUmIBnqAUVvzIcwNvBZ5KvQ//mwVrsxWCdfre0o8lSaesc7GLMeaeht
jgP8h1ThjvilGfVHK0Skvnck46Ll2fPO5ZgNXUXlR8+1J3jYXPOYLWPgVLbMaLkQ
fQB2xLiv
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCfh/ognsvxE/nK
tw8QjI5DWlEvDPi6lmhKhgRhhpcvpkRnyd9Ab2lC3ySEdVdDZE9/LfV/2P+0J7/D
jWtlk5xjRn4K6fg0BhkAZH4toIGgEXfF6GPFpWQVYFM32f/bTaiSseNTNrWLpy+f
bnJge6LJplPJu+KCqspQNU5GBCs8qr5INACd0VbJR/75qtiyonW6H10J2Vr7Nj2n
j0dqu117+/mwWk7y3lCFOFY8MOqNpCUzeP/5KdnbR4sH3FMU0+gdtl+ddgF8aI6e
vl+gdWNV4R6kHD7mVGeiEznEt8wl43v4ow16Ij596JkEzPVHg9805atr2W0nkU9U
0M2c0VT1AgMBAAECggEAHBTDYpqRK45omdY/QJp9MD3lrHKMFcwD75pHiyM12Z1a
zSorshvqW2sL8oT1J1ew5qIgZLC90ehtSO7LyMWC8bam2ST2G7I3FGqcC0wFhTeN
7bhKV7AVPe4Gt/4Xm3LACZJmgW9P5ZU4PMgkOfeJYBV3CjuYU4fctOGtNYXmVncw
bVaNSbiekAPmChjDikFBbtp7tB+Qx7+kbpgSFrFraf1MRaHTJqE5z557Ttp7ttQ2
D9XciwYzSh1LsBqS6aYTyEMX/ttGENW6Y1evRRUI1biOS50E7bnTp/vuIUAs75PR
w0Vdcr9s1rcJs8Ht2aiteGyWv9ErGLSiTyJxX3aaAQKBgQDT+gg8Gr6D+e3NAxt6
2B85dXDA8Ziy3U5Tj46nTmsyK6C3i7eK8S3RG8dBWv76Z8jRmgWfvEsBVMwOCOTA
/Jnr7cJGRKV0oU5LVt/XdY3Yb9Ja0PrjHXP+DCLVz9gHzUkQ/RMyxhoT9UP5k0e4
vvYhq/cMKtSpmt0NESx0ovqevQKBgQDAqZ9IO+4RduMdaftbp/zDMIi9JX6vDkYX
Oc+clmB0jwJak9AEy59x5phAQaS+aPcXJiVXslXUQfE9f1xXambHrMhrrZodzsTM
fRUnTquVT9Xdptn6nTtHGr3V/n+V1qIzFJz5xMoKFjoYR7hUa6YU2xVXnHLyMuEb
epI6ZfSumQKBgHK+4j5G6+JdJFDZ4cI5w41C+Wo4XcRU79Vj3IDMflKGM1WoGA7q
RzbuponGTEgYbiioC2tQbfmmgV8HiWy+UEPaTFPlTPs5Zjx6JmlnhQUoYuIuReEz
TFq1DxZWkEaI5YiAtifB/NPY7JbpFuX22R2ZDP5VIRE+d3JfXYU1xByxAoGAZWym
ert5/92rgH+boMvVx9fUFGcZwwRrp6x6fD+59YKXxaFNAElF3gt8GU+1b7wIYDpn
rDwo7P3uBub2cNqF0xZFyFHy8UA54ED1EuVadNc7il1dIY8Gds9AItjAx9vfNa7j
WKXdiuPan4+aHW8yVoZjPOUSqihY00N6mZ206vkCgYBz4yTW/UqyVf90l7c9Pdf0
h7zmBIMTs0xiFc4jYtLNaueY6XJ2kCIAEC/m2qO7P/dz4nbTI1P0KYwrFrRZU1pw
NgsnWucXbZHfAyAKoOZnPy+V63MSxO9aYInYIpytIqZheRPv19B8c0g77jQi3isK
8gqCLEZjtxty24ihkq/WKg==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,129 @@
use std::{fs::File, io::BufReader, path::Path};
use actix_web::{
http::header::ContentType, middleware, web, App, HttpRequest, HttpResponse, HttpServer,
};
use log::debug;
use notify::{Event, RecursiveMode, Watcher as _};
use rustls::{Certificate, PrivateKey, ServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use tokio::sync::mpsc;
#[derive(Debug)]
struct TlsUpdated;
async fn index(req: HttpRequest) -> HttpResponse {
debug!("{req:?}");
HttpResponse::Ok().content_type(ContentType::html()).body(
"<!DOCTYPE html><html><body>\
<p>Welcome to your TLS-secured homepage!</p>\
</body></html>",
)
}
#[tokio::main(worker_threads = 2)]
async fn main() -> eyre::Result<()> {
color_eyre::install()?;
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
// signal channel used to notify main event loop of cert/key file changes
let (reload_tx, mut reload_rx) = mpsc::channel(1);
let mut file_watcher =
notify::recommended_watcher(move |res: notify::Result<Event>| match res {
Ok(ev) => {
log::info!("files changed: {:?}", ev.paths);
reload_tx.blocking_send(TlsUpdated).unwrap();
}
Err(err) => {
log::error!("file watch error: {err}");
}
})
.unwrap();
file_watcher
.watch(Path::new("cert.pem"), RecursiveMode::NonRecursive)
.unwrap();
file_watcher
.watch(Path::new("key.pem"), RecursiveMode::NonRecursive)
.unwrap();
// start HTTP server reload loop
//
// loop reloads on TLS changes and exits on normal ctrl-c (etc.) signals
loop {
// load TLS cert/key files and
let config = load_rustls_config();
log::info!("starting HTTPS server at https://localhost:8443");
let mut server = HttpServer::new(|| {
App::new()
.service(web::resource("/").to(index))
.wrap(middleware::Logger::default())
})
.workers(2)
.bind_rustls_021("127.0.0.1:8443", config)?
.run();
let server_hnd = server.handle();
tokio::select! {
// poll server continuously
res = &mut server => {
log::info!("server shut down via signal or manual command");
res?;
break;
},
// receiving a message to reload the server
Some(_) = reload_rx.recv() => {
log::info!("TLS cert or key updated");
// send stop signal; no need to wait for completion signal here
// since we're about to await the server itself
drop(server_hnd.stop(true));
// poll and await server shutdown before
server.await?;
// restart loop to reload cert/key files
continue;
}
}
}
Ok(())
}
fn load_rustls_config() -> rustls::ServerConfig {
// init server config builder with safe defaults
let config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth();
// load TLS key/cert files
let cert_file = &mut BufReader::new(File::open("cert.pem").unwrap());
let key_file = &mut BufReader::new(File::open("key.pem").unwrap());
// convert files to key/cert objects
let cert_chain = certs(cert_file)
.unwrap()
.into_iter()
.map(Certificate)
.collect();
let mut keys: Vec<PrivateKey> = pkcs8_private_keys(key_file)
.unwrap()
.into_iter()
.map(PrivateKey)
.collect();
// exit if no keys could be parsed
if keys.is_empty() {
eprintln!("Could not locate PKCS 8 private keys.");
std::process::exit(1);
}
config.with_single_cert(cert_chain, keys.remove(0)).unwrap()
}