Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Close TLS and QUIC links on certificate expiration #1564

Merged
merged 23 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
188c460
Add boilerplate for spawning expiration task
oteffahi Oct 22, 2024
032a54b
Add initial implementation of expiration_task
oteffahi Oct 23, 2024
c6b0735
Rework logic to avoid unsoundness of sleep_until with long durations
oteffahi Oct 24, 2024
b4ba3f5
Fix instant resolve of mpsc channel recv after closing it
oteffahi Oct 24, 2024
c1042e7
Integrate expiration task in listen/connect logic
oteffahi Oct 24, 2024
8e35114
Clarify error message
oteffahi Oct 24, 2024
59a3944
Move cert expiration logic to zenoh-link-commons crate, replace mpsc …
oteffahi Oct 25, 2024
fd20c5e
Add cert expiration to quic link
oteffahi Oct 25, 2024
65d3b41
Fix formatting
oteffahi Oct 25, 2024
efb6cb9
Switch design to one expiration task per link instance
oteffahi Nov 8, 2024
05f0bc2
Fix constant name in comment
oteffahi Nov 8, 2024
06d43a3
Add trace log for expiration task
oteffahi Nov 8, 2024
ab0cb1a
Use tokio::sync::OnceCell instead of std::sync::OnceLock
oteffahi Nov 8, 2024
2f4613c
Make close link on certificate expiration configurable from json file
oteffahi Nov 12, 2024
e583532
Fix computation of sleep duration from expiration time
oteffahi Nov 15, 2024
d2e873c
Use Arc::new_cyclic instead of OnceCell, Remove LinkCertExpirationInf…
oteffahi Nov 15, 2024
171ec01
Refactor sleep logic out of expiration task
oteffahi Nov 15, 2024
5c98c32
Move start log from links to expiration task
oteffahi Nov 15, 2024
2f25983
chore: Rename variable
oteffahi Nov 15, 2024
6e2a6e8
Replace String with &'static str
oteffahi Nov 15, 2024
bde2aad
Avoid calling get_cert_chain_expiration_time when expiration is not m…
oteffahi Nov 15, 2024
cb0d6c4
Rework to grant exclusive access to close operation and properly hand…
oteffahi Nov 19, 2024
bdba659
Optimize concurrency of closing link and cancelation of expiration_task
oteffahi Nov 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ socket2 = { version = "0.5.7", features = ["all"] }
stop-token = "0.7.0"
syn = "2.0"
tide = "0.16.0"
time = "0.3.36"
token-cell = { version = "1.5.0", default-features = false }
tokio = { version = "1.40.0", default-features = false } # Default features are disabled due to some crates' requirements
tokio-util = "0.7.12"
Expand Down
4 changes: 4 additions & 0 deletions DEFAULT_CONFIG.json5
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,10 @@
// This could be dangerous because your CA can have signed a server cert for foo.com, that's later being used to host a server at baz.com. If you wan't your
// ca to verify that the server at baz.com is actually baz.com, let this be true (default).
verify_name_on_connect: true,
// Whether or not to close links when remote certificates expires.
// If set to true, links that require certificates (tls/quic) will automatically disconnect when the time of expiration of the remote certificate chain is reached
// note that mTLS (client authentication) is required for a listener to disconnect a client on expiration
close_link_on_expiration: false,
},
},
/// Shared memory configuration.
Expand Down
1 change: 1 addition & 0 deletions commons/zenoh-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ validated_struct::validator! {
connect_private_key: Option<String>,
connect_certificate: Option<String>,
verify_name_on_connect: Option<bool>,
close_link_on_expiration: Option<bool>,
// Skip serializing field because they contain secrets
#[serde(skip_serializing)]
root_ca_certificate_base64: Option<SecretValue>,
Expand Down
1 change: 1 addition & 0 deletions io/zenoh-link-commons/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ futures = { workspace = true }
rustls = { workspace = true, optional = true }
rustls-webpki = { workspace = true, optional = true }
serde = { workspace = true, features = ["default"] }
time = { workspace = true }
tokio = { workspace = true, features = [
"fs",
"io-util",
Expand Down
101 changes: 101 additions & 0 deletions io/zenoh-link-commons/src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,104 @@ impl WebPkiVerifierAnyServerName {
Self { roots }
}
}

pub mod expiration {
use std::{net::SocketAddr, sync::Weak};

use time::OffsetDateTime;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;

use crate::LinkUnicastTrait;

#[derive(Debug)]
pub struct LinkCertExpirationManager {
token: CancellationToken,
handle: Option<JoinHandle<()>>,
}

impl LinkCertExpirationManager {
pub fn new(
link: Weak<dyn LinkUnicastTrait>,
src_addr: SocketAddr,
dst_addr: SocketAddr,
link_type: String,
expiration_time: OffsetDateTime,
) -> Self {
let token = CancellationToken::new();
let handle = zenoh_runtime::ZRuntime::Acceptor.spawn(expiration_task(
link,
src_addr,
dst_addr,
link_type,
expiration_time,
token.clone(),
));
Self {
token,
handle: Some(handle),
}
}
}

// Cleanup expiration task when manager is dropped
impl Drop for LinkCertExpirationManager {
fn drop(&mut self) {
if let Some(handle) = self.handle.take() {
self.token.cancel();
zenoh_runtime::ZRuntime::Acceptor.block_in_place(async {
let _ = handle.await;
})
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

async fn expiration_task(
link: Weak<dyn LinkUnicastTrait>,
src_addr: SocketAddr,
dst_addr: SocketAddr,
link_type: String,
oteffahi marked this conversation as resolved.
Show resolved Hide resolved
expiration_time: OffsetDateTime,
token: CancellationToken,
) {
// NOTE: should expose or tune sleep duration
const MAX_EXPIRATION_SLEEP_DURATION: tokio::time::Duration =
tokio::time::Duration::from_secs(600);

oteffahi marked this conversation as resolved.
Show resolved Hide resolved
loop {
let now = OffsetDateTime::now_utc();
if expiration_time <= now {
// close link
if let Some(link) = link.upgrade() {
tracing::warn!(
"Closing {} link {:?} => {:?} : remote certificate chain expired",
link_type.to_uppercase(),
src_addr,
dst_addr,
);
if let Err(e) = link.close().await {
tracing::error!(
"Error closing {} link {:?} => {:?} : {}",
link_type.to_uppercase(),
src_addr,
dst_addr,
e
)
}
}
break;
}
// next sleep duration is the minimum between MAX_EXPIRATION_SLEEP_DURATION and the duration till next expiration
// this mitigates the unsoundness of using `tokio::time::sleep` with long durations
let next_expiration_duration = std::time::Duration::try_from(expiration_time - now)
.expect("expiration_time should be greater than now");
let sleep_duration =
tokio::time::Duration::min(MAX_EXPIRATION_SLEEP_DURATION, next_expiration_duration);

tokio::select! {
_ = token.cancelled() => break,
_ = tokio::time::sleep(sleep_duration) => {},
}
}
}
}
1 change: 1 addition & 0 deletions io/zenoh-links/zenoh-link-quic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ rustls-pemfile = { workspace = true }
rustls-pki-types = { workspace = true }
rustls-webpki = { workspace = true }
secrecy = { workspace = true }
time = { workspace = true }
tokio = { workspace = true, features = [
"fs",
"io-util",
Expand Down
3 changes: 3 additions & 0 deletions io/zenoh-links/zenoh-link-quic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,7 @@ pub mod config {

pub const TLS_VERIFY_NAME_ON_CONNECT: &str = "verify_name_on_connect";
pub const TLS_VERIFY_NAME_ON_CONNECT_DEFAULT: bool = true;

pub const TLS_CLOSE_LINK_ON_EXPIRATION: &str = "close_link_on_expiration";
pub const TLS_CLOSE_LINK_ON_EXPIRATION_DEFAULT: bool = false;
}
Loading