mirror of
https://github.com/actix/examples
synced 2025-05-14 00:43:53 +02:00
feat: add tls-hot-reload example
This commit is contained in:
parent
dfce64475b
commit
0293f48530
24
.taplo.toml
24
.taplo.toml
@ -7,23 +7,23 @@ column_width = 100
|
||||
[[rule]]
|
||||
include = ["**/Cargo.toml"]
|
||||
keys = [
|
||||
"dependencies",
|
||||
"*-dependencies",
|
||||
"workspace.dependencies",
|
||||
"workspace.*-dependencies",
|
||||
"target.*.dependencies",
|
||||
"target.*.*-dependencies",
|
||||
"dependencies",
|
||||
"*-dependencies",
|
||||
"workspace.dependencies",
|
||||
"workspace.*-dependencies",
|
||||
"target.*.dependencies",
|
||||
"target.*.*-dependencies",
|
||||
]
|
||||
formatting.reorder_keys = true
|
||||
|
||||
[[rule]]
|
||||
include = ["**/Cargo.toml"]
|
||||
keys = [
|
||||
"dependencies.*",
|
||||
"*-dependencies.*",
|
||||
"workspace.dependencies.*",
|
||||
"workspace.*-dependencies.*",
|
||||
"target.*.dependencies",
|
||||
"target.*.*-dependencies",
|
||||
"dependencies.*",
|
||||
"*-dependencies.*",
|
||||
"workspace.dependencies.*",
|
||||
"workspace.*-dependencies.*",
|
||||
"target.*.dependencies",
|
||||
"target.*.*-dependencies",
|
||||
]
|
||||
formatting.reorder_keys = false
|
||||
|
35
Cargo.lock
generated
35
Cargo.lock
generated
@ -81,7 +81,6 @@ dependencies = [
|
||||
"actix-web",
|
||||
"casbin",
|
||||
"env_logger",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3157,6 +3156,30 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-tls-hot-reload"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"color-eyre",
|
||||
"examples-common",
|
||||
"eyre",
|
||||
"notify",
|
||||
"rustls 0.23.27",
|
||||
"rustls-channel-resolver",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "examples-common"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"rustls 0.23.27",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.12"
|
||||
@ -6967,6 +6990,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-channel-resolver"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "173e7e58f0cbb61d2ea2b21af1b5ed6e35fa6c17818fc2cc5d903b44b4715d01"
|
||||
dependencies = [
|
||||
"rand 0.9.1",
|
||||
"rustls 0.23.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-client-cert"
|
||||
version = "0.0.0"
|
||||
|
@ -23,6 +23,7 @@ members = [
|
||||
"databases/postgres",
|
||||
"databases/redis",
|
||||
"docker",
|
||||
"examples-common",
|
||||
"forms/form",
|
||||
"forms/multipart-s3",
|
||||
"forms/multipart",
|
||||
@ -34,6 +35,7 @@ members = [
|
||||
"https-tls/acme-letsencrypt",
|
||||
"https-tls/awc-https",
|
||||
"https-tls/cert-watch",
|
||||
"https-tls/hot-reload",
|
||||
"https-tls/openssl",
|
||||
"https-tls/rustls-client-cert",
|
||||
"https-tls/rustls",
|
||||
@ -101,6 +103,7 @@ color-eyre = "0.6"
|
||||
derive_more = "2"
|
||||
dotenvy = "0.15"
|
||||
env_logger = "0.11"
|
||||
examples-common = { path = "./examples-common" }
|
||||
eyre = { version = "0.6", default-features = false, features = ["auto-install", "track-caller"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
log = "0.4"
|
||||
|
@ -4,8 +4,6 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-web.workspace = true
|
||||
|
||||
actix-web = { workspace = true }
|
||||
casbin = "2"
|
||||
env_logger.workspace = true
|
||||
tokio.workspace = true
|
||||
env_logger = { workspace = true }
|
||||
|
9
examples-common/Cargo.toml
Normal file
9
examples-common/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "examples-common"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
rustls = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
21
examples-common/src/lib.rs
Normal file
21
examples-common/src/lib.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::{EnvFilter, prelude::*};
|
||||
|
||||
/// Initializes standard tracing subscriber.
|
||||
pub fn init_standard_logger() {
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
EnvFilter::builder()
|
||||
.with_default_directive(LevelFilter::INFO.into())
|
||||
.from_env_lossy(),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
}
|
||||
|
||||
/// Load default rustls provider, to be done once for the process.
|
||||
pub fn init_rustls_provider() {
|
||||
rustls::crypto::aws_lc_rs::default_provider()
|
||||
.install_default()
|
||||
.unwrap();
|
||||
}
|
15
https-tls/hot-reload/Cargo.toml
Normal file
15
https-tls/hot-reload/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "example-tls-hot-reload"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-web = { workspace = true, features = ["rustls-0_23"] }
|
||||
color-eyre = { workspace = true }
|
||||
examples-common = { workspace = true }
|
||||
eyre = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
rustls = { workspace = true }
|
||||
rustls-channel-resolver = "0.3"
|
||||
tokio = { workspace = true, features = ["time", "rt", "macros"] }
|
||||
tracing = { workspace = true }
|
63
https-tls/hot-reload/README.md
Normal file
63
https-tls/hot-reload/README.md
Normal file
@ -0,0 +1,63 @@
|
||||
# HTTPS Server With TLS Cert/Key Hot Reload
|
||||
|
||||
## Usage
|
||||
|
||||
All documentation assumes your terminal is in this directly (`cd https-tls/hot-reload`).
|
||||
|
||||
### 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:
|
||||
|
||||
```shell
|
||||
$ mkcert -install
|
||||
```
|
||||
|
||||
If you want to generate your own cert/private key file, then run:
|
||||
|
||||
```shell
|
||||
$ mkcert -key-file key.pem -cert-file cert.pem 127.0.0.1 localhost
|
||||
```
|
||||
|
||||
### Running The Example Server
|
||||
|
||||
```shell
|
||||
$ RUST_LOG=info,example=debug cargo run
|
||||
Starting HTTPS server at https://localhost:8443
|
||||
```
|
||||
|
||||
Reload the server by modifying the certificate metadata:
|
||||
|
||||
```shell
|
||||
$ touch cert.pem
|
||||
```
|
||||
|
||||
For a deeper inspection, use a tool like [`inspect-cert-chain`] between refreshes of the cert/key files using [`mkcert`] as shown above:
|
||||
|
||||
```shell
|
||||
$ inspect-cert-chain --host=localhost --port=8443
|
||||
...
|
||||
Serial Number:
|
||||
06:81:db:16:ff:c4:73:69:73:69:ae:d1:0e:3d:d1:5e
|
||||
...
|
||||
|
||||
$ mkcert -key-file key.pem -cert-file cert.pem 127.0.0.1 localhost
|
||||
...
|
||||
|
||||
$ inspect-cert-chain --host=localhost --port=8443
|
||||
...
|
||||
Serial Number:
|
||||
00:a8:39:e7:aa:2e:73:18:f6:4e:d5:71:1e:c7:21:51:58
|
||||
...
|
||||
```
|
||||
|
||||
Observing a change in the serial number without restarting the server demonstrates that the setup works.
|
||||
|
||||
### Client
|
||||
|
||||
- [HTTPie]: `http --verify=no :8443`
|
||||
- cURL: `curl -v --insecure https://127.0.0.1:8443`
|
||||
- Browser: navigate to <https://127.0.0.1:8443>
|
||||
|
||||
[`mkcert`]: https://github.com/FiloSottile/mkcert
|
||||
[httpie]: https://httpie.io/cli
|
||||
[`inspect-cert-chain`]: https://github.com/robjtede/inspect-cert-chain
|
25
https-tls/hot-reload/cert.pem
Normal file
25
https-tls/hot-reload/cert.pem
Normal file
@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIELDCCApSgAwIBAgIRAJV3HOzuRhQcriZIKUYROVkwDQYJKoZIhvcNAQELBQAw
|
||||
aTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMR8wHQYDVQQLDBZyb2JA
|
||||
c29tYnJhLmxvY2FsIChSb2IpMSYwJAYDVQQDDB1ta2NlcnQgcm9iQHNvbWJyYS5s
|
||||
b2NhbCAoUm9iKTAeFw0yNTA1MTIwMzIwNTdaFw0yNzA4MTIwMzIwNTdaMFQxJzAl
|
||||
BgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEpMCcGA1UECwwg
|
||||
cm9iQE1hY0Jvb2tQcm8ubG9jYWxkb21haW4gKFJvYikwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQDsKN1MBUrrXkhDDXIfhvciA03pVrhXLrla9slY2p/t
|
||||
VPgayFASUfRs1KcyD8+8tpn3z6BWsUup67vfJFM7u1HTHbkEAXWwnEBXOc+503le
|
||||
JAip33dXSXZQcLErcS0Ad1P26t/lVKctZgNuPiOCHG9OaU3BacGCFCECg0bc4lez
|
||||
c8B+jhGgPjKholBMA6lLJrBZPS/R0aPRTdUbiaG7pa7U3Au4/wnQmF62zGkpN7ZY
|
||||
nwExeLDixw/YZLO5Mc6sFzYTFFTfAVwzzYYWoej+tKoMyJFnA8vx+qY6HMVseNOM
|
||||
xXY+x8pCEmeEs7DUYbCIMHwihkg4T+XFVpvc9OvMUFsrAgMBAAGjZDBiMA4GA1Ud
|
||||
DwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQX/S2W
|
||||
axc/eafsJz5icz2r7oqvUDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJ
|
||||
KoZIhvcNAQELBQADggGBAEnR2sgqDpNYDX4CW+cbkpdKW5dwAO/fATM24vv+EcL2
|
||||
hI3nAaUAYKRixtGgbvBEVUmdlQmQHR0hj9L8MZ73/c/G0dakJFf3V5feVCDKXLGo
|
||||
pL2lP7m6O6H4Q+BErHHO2FTMILZNmxG6+EmCZCVz4FpVvXfMFGih4cMk7Wedb/0U
|
||||
yj0vcg2BqHFr8HaXbqu0AwkMkKTW0N4QkW2+6V+Uki8cm1m9a/KRtINGxd24ljwf
|
||||
xM/KOp1ifWvoVupiAVHeCgJNKs06frI4AMf1HbFmyAftnOGwp+fIrfUMZbnp+Coj
|
||||
NBXPoM5Zq1eheXtTEbny22EOy7IrtjDNSXgJNUUsbEL4w3GH8W2OdbXQMb0zH9Bj
|
||||
lBRSMNkKPXoQgTHum9UATqQPBhgcP2JD4g89BYqv0JHgPYWuA+LUvSfpObxXebS/
|
||||
oLRHhQpDU3zXKem6Lr5GSMZGDz2gAECVVpGalIdvmyL1LHiHFmb7oTvswxaSPcfj
|
||||
OKnFl5b+4ezaAW4D6dLxhg==
|
||||
-----END CERTIFICATE-----
|
28
https-tls/hot-reload/key.pem
Normal file
28
https-tls/hot-reload/key.pem
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDsKN1MBUrrXkhD
|
||||
DXIfhvciA03pVrhXLrla9slY2p/tVPgayFASUfRs1KcyD8+8tpn3z6BWsUup67vf
|
||||
JFM7u1HTHbkEAXWwnEBXOc+503leJAip33dXSXZQcLErcS0Ad1P26t/lVKctZgNu
|
||||
PiOCHG9OaU3BacGCFCECg0bc4lezc8B+jhGgPjKholBMA6lLJrBZPS/R0aPRTdUb
|
||||
iaG7pa7U3Au4/wnQmF62zGkpN7ZYnwExeLDixw/YZLO5Mc6sFzYTFFTfAVwzzYYW
|
||||
oej+tKoMyJFnA8vx+qY6HMVseNOMxXY+x8pCEmeEs7DUYbCIMHwihkg4T+XFVpvc
|
||||
9OvMUFsrAgMBAAECggEBAIfu4apvZXdjVp7Z73W8PyYh1sfX9dWg/GoioTT26pU2
|
||||
knUAFi7lY5b9NJv5Q+7xAGEG7tjXxqCxIvvHMe2w3eFyO1vV50NYPSS4Dxx8YGDS
|
||||
xvXYvh3NGEAnDaPeyjN5fCgle+jKOExGavUa6V9sNJlivbH1yL+yDGog3DoqQqb0
|
||||
f6jo4hmwlRlsd7o+YsrW9v8vCWWDYKoYh13e77iQBGVw1b32qILeMEuQhhuDsh2T
|
||||
Qd+nyUIBSuXaPtrwSQgWrkNn1fBSHwvMsfXijwTK06LBq23h0g5rG7w7JtG4JEum
|
||||
XNq+7DaZJDYa0fq5gIDhl5GV2MQNEfbjuYJ/fN09SukCgYEA9HPPasVy7Pdv8YAk
|
||||
ylsV9vEz/qMt+MIlg5Z2Y6HjWI7WwDOD4pR7gDdaJq5ymlR7I8EokppA6wOMM3Te
|
||||
eog55DzWiOQxZTyPTXzpEKdxi9BZDI6F+tJO3Zdj5ppWVD4NN48NatGsun/kEIPL
|
||||
IqY+AAKRxf+els74BTRuKQ3R5i8CgYEA91DE3HZu9ZmSv5rknKCO/c+3iQi2UNa6
|
||||
XsqH75tU+dPI/1U2z9gLOvu9JF5StVSmfdxk4NIzcJRJW//l7vzTKH58p/M9CeE5
|
||||
JYUtJkd96WvJ1jB+obTFnkC4yCYN9jm2K+9kVFcDkwFUVrhomkIby/+dktr7R3T9
|
||||
lsyfcKikF8UCgYEA8HzGf4oESDAdRv8EMrdtYmVk+4vZfDKz6UKq8dWf7c2IY8nK
|
||||
Y6wj272YyRkx0bZu9nveyGtMlmgFE9JT1UQTgACCJmYoWio76MWMHEA+qoesM3g7
|
||||
QsiHoeR/+au4ZmQtaI0pa/8e6NNMsRqXS100/ZmJg7q4cDDpO2WbQnRAHS0CgYEA
|
||||
9GzEE2uNoHgGXA32sYHRsLGRIAMXRO/jw/mAveOT6VFRzmBmyqYn+0R/m6kJLyOZ
|
||||
ZLzkinnU0wgLNLzFgBwpiVTxWIACrHgGpblodPOlUoPwOBs3nBPwV8Z5mX5awCYr
|
||||
kGKJkv1oj+p5czfQUdzSYhygnFqGjAno8xgK4Cob+00CgYEApLj4DB4lkwWGOfQE
|
||||
RkTHFz1mWhBTtQ5Kc0+Jnw/jAcQYUnlOpMK0+72LzWRjYweYuUdIvWOGaajFuVhw
|
||||
H86HoarFU2sO8GnTJR1P35wBgheXG1Lumo7AVRorrq694totQfzqXrJxVnKG6jvW
|
||||
bFLe+y8oZBvIa0OntEPWsLegasc=
|
||||
-----END PRIVATE KEY-----
|
96
https-tls/hot-reload/src/main.rs
Normal file
96
https-tls/hot-reload/src/main.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use std::path::Path;
|
||||
|
||||
use actix_web::{
|
||||
App, HttpRequest, HttpResponse, HttpServer, http::header::ContentType, middleware, web,
|
||||
};
|
||||
use eyre::WrapErr as _;
|
||||
use notify::{Event, RecursiveMode, Watcher as _};
|
||||
use rustls::{
|
||||
ServerConfig,
|
||||
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
|
||||
sign::CertifiedKey,
|
||||
};
|
||||
|
||||
async fn index(req: HttpRequest) -> HttpResponse {
|
||||
tracing::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(flavor = "current_thread")]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
color_eyre::install()?;
|
||||
examples_common::init_standard_logger();
|
||||
examples_common::init_rustls_provider();
|
||||
|
||||
// initial load of certificate and key files
|
||||
let cert_key = load_certified_key()?;
|
||||
|
||||
// signal channel used to notify rustls of cert/key file changes
|
||||
let (reload_tx, cert_resolver) = rustls_channel_resolver::channel::<8>(cert_key);
|
||||
|
||||
let rustls_config = ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_cert_resolver(cert_resolver);
|
||||
|
||||
// unsupervised watcher thread which will just shutdown when the server stops
|
||||
tracing::debug!("Setting up cert watcher");
|
||||
|
||||
let mut file_watcher =
|
||||
notify::recommended_watcher(move |res: notify::Result<Event>| match res {
|
||||
Ok(ev) => {
|
||||
tracing::info!("files changed: {:?}", ev.paths);
|
||||
|
||||
let cert_key = load_certified_key().unwrap();
|
||||
reload_tx.update(cert_key);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("file watch error: {err}");
|
||||
}
|
||||
})
|
||||
.wrap_err("Failed to set up file watcher")?;
|
||||
|
||||
file_watcher
|
||||
.watch(Path::new("cert.pem"), RecursiveMode::NonRecursive)
|
||||
.wrap_err("Failed to watch cert file")?;
|
||||
file_watcher
|
||||
.watch(Path::new("key.pem"), RecursiveMode::NonRecursive)
|
||||
.wrap_err("Failed to watch key file")?;
|
||||
|
||||
tracing::info!("Starting HTTPS server at https://localhost:8443");
|
||||
|
||||
// start running server as normal (as opposed to in a loop like the cert-watch example)
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.service(web::resource("/").to(index))
|
||||
.wrap(middleware::Logger::default().log_target("@"))
|
||||
})
|
||||
.workers(2)
|
||||
.bind_rustls_0_23(("127.0.0.1", 8443), rustls_config)?
|
||||
.run()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_certified_key() -> eyre::Result<rustls::sign::CertifiedKey> {
|
||||
// load TLS key/cert files
|
||||
let cert_chain = CertificateDer::pem_file_iter("cert.pem")
|
||||
.wrap_err("Could not locate certificate chain file")?
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
// load TLS private key file
|
||||
let key =
|
||||
PrivateKeyDer::from_pem_file("key.pem").wrap_err("Could not locate PKCS 8 private keys")?;
|
||||
|
||||
// parse private key by crypto provider
|
||||
let key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key)
|
||||
.wrap_err("Private key type is unsupported")?;
|
||||
|
||||
Ok(CertifiedKey::new(cert_chain, key))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user