mirror of
https://github.com/actix/examples
synced 2025-02-17 07:23:29 +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",
|
"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]]
|
[[package]]
|
||||||
name = "cexpr"
|
name = "cexpr"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@ -4765,7 +4783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0d073fe2671399bed11572e89b1349d2ea54205a65282e7dc0b1e954c82ad597"
|
checksum = "0d073fe2671399bed11572e89b1349d2ea54205a65282e7dc0b1e954c82ad597"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"minijinja",
|
"minijinja",
|
||||||
"notify",
|
"notify 5.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5120,6 +5138,25 @@ dependencies = [
|
|||||||
"windows-sys 0.45.0",
|
"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]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
@ -33,6 +33,7 @@ members = [
|
|||||||
"http-proxy",
|
"http-proxy",
|
||||||
"https-tls/acme-letsencrypt",
|
"https-tls/acme-letsencrypt",
|
||||||
"https-tls/awc-https",
|
"https-tls/awc-https",
|
||||||
|
"https-tls/cert-watch",
|
||||||
"https-tls/openssl",
|
"https-tls/openssl",
|
||||||
"https-tls/rustls-client-cert",
|
"https-tls/rustls-client-cert",
|
||||||
"https-tls/rustls",
|
"https-tls/rustls",
|
||||||
@ -98,6 +99,8 @@ chrono = { version = "0.4.20", default-features = false, features = ["clock", "s
|
|||||||
derive_more = "0.99.7"
|
derive_more = "0.99.7"
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
env_logger = "0.11"
|
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"] }
|
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
openssl = { version = "0.10.60", features = ["v110"] }
|
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…
x
Reference in New Issue
Block a user