mirror of
https://github.com/actix/examples
synced 2024-12-03 18:22:14 +01:00
feat: add cert-watch example
This commit is contained in:
parent
741815074a
commit
183c924220
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"] }
|
||||
|
18
https-tls/cert-watch/Cargo.toml
Normal file
18
https-tls/cert-watch/Cargo.toml
Normal 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"
|
38
https-tls/cert-watch/README.md
Normal file
38
https-tls/cert-watch/README.md
Normal 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
|
25
https-tls/cert-watch/cert.pem
Normal file
25
https-tls/cert-watch/cert.pem
Normal 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-----
|
28
https-tls/cert-watch/key.pem
Normal file
28
https-tls/cert-watch/key.pem
Normal 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-----
|
129
https-tls/cert-watch/src/main.rs
Normal file
129
https-tls/cert-watch/src/main.rs
Normal 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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user