Skip to content

Commit

Permalink
more tls metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
blind-oracle committed Jun 2, 2024
1 parent 775ef49 commit 496fabd
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ url = "2.5"
uuid = { version = "1.8", features = ["v7"] }
webpki-roots = "0.26"
x509-parser = "0.16"
zeroize = { version = "1.8", features = ["derive"] }

[dev-dependencies]
hex-literal = "0.4"
Expand Down
20 changes: 13 additions & 7 deletions src/tls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,24 @@ pub fn prepare_server_config(
session_storage: Arc<dyn StoresServerSessions + Send + Sync>,
additional_alpn: Vec<Vec<u8>>,
ticket_lifetime: Duration,
registry: &Registry,
) -> ServerConfig {
let mut cfg = ServerConfig::builder_with_protocol_versions(&[&TLS13, &TLS12])
.with_no_client_auth()
.with_cert_resolver(resolver);

// Set custom session storage with to allow effective TLS session resumption
cfg.session_storage = session_storage;

// Enable ticketer
let ticketer = TicketSwitcher::new(ticket_lifetime.as_secs() as u32, || {
Ok(Box::new(tickets::Ticketer::new()))
})
.unwrap();
let session_storage = sessions::WithMetrics(session_storage, sessions::Metrics::new(registry));
cfg.session_storage = Arc::new(session_storage);

// Enable ticketer to encrypt/decrypt TLS tickets
let ticketer = tickets::WithMetrics(
TicketSwitcher::new(ticket_lifetime.as_secs() as u32, move || {
Ok(Box::new(tickets::Ticketer::new()))
})
.unwrap(),
tickets::Metrics::new(registry),
);
cfg.ticketer = Arc::new(ticketer);

// Enable tickets
Expand Down Expand Up @@ -215,6 +220,7 @@ pub async fn setup(
vec![]
},
cli.http_server.tls_ticket_lifetime,
registry,
);

Ok(config)
Expand Down
74 changes: 68 additions & 6 deletions src/tls/sessions.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
use std::time::Duration;
use std::{sync::Arc, time::Duration};

use ahash::RandomState;
use moka::sync::Cache;
use prometheus::{register_int_counter_vec_with_registry, IntCounterVec, Registry};
use rustls::server::StoresServerSessions;
use zeroize::ZeroizeOnDrop;

type Key = Vec<u8>;
type Val = Vec<u8>;

/// Sessions are considered highly sensitive data, so wipe the memory when
/// they're removed from storage. We can't do anything with the returned Vec<u8>,
/// but it's better than nothing.
#[derive(Debug, PartialEq, Eq, Hash, Clone, ZeroizeOnDrop)]
struct Val(Vec<u8>);

fn weigher(k: &Key, v: &Val) -> u32 {
(k.len() + v.len()) as u32
(k.len() + v.0.len()) as u32
}

pub struct Stats {
Expand Down Expand Up @@ -46,23 +53,78 @@ impl Storage {

impl StoresServerSessions for Storage {
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
self.cache.get(key)
self.cache.get(key).map(|x| x.0.clone())
}

fn put(&self, key: Vec<u8>, value: Vec<u8>) -> bool {
self.cache.insert(key, value);
self.cache.insert(key, Val(value));
true
}

fn take(&self, key: &[u8]) -> Option<Vec<u8>> {
self.cache.remove(key)
self.cache.remove(key).map(|x| x.0.clone())
}

fn can_cache(&self) -> bool {
true
}
}

#[derive(Debug)]
pub struct Metrics {
processed: IntCounterVec,
}

impl Metrics {
pub fn new(registry: &Registry) -> Self {
Self {
processed: register_int_counter_vec_with_registry!(
format!("tls_sessions"),
format!("Number of TLS sessions that were processed"),
&["action", "found"],
registry
)
.unwrap(),
}
}
}

#[derive(Debug)]
pub struct WithMetrics(pub Arc<dyn StoresServerSessions + Send + Sync>, pub Metrics);

impl WithMetrics {
fn record(&self, action: &str, ok: bool) {
self.1
.processed
.with_label_values(&[action, if ok { "yes" } else { "no" }])
.inc();
}
}

impl StoresServerSessions for WithMetrics {
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
let v = self.0.get(key);
self.record("get", v.is_some());
v
}

fn put(&self, key: Vec<u8>, value: Vec<u8>) -> bool {
let v = self.0.put(key, value);
self.record("put", v);
v
}

fn take(&self, key: &[u8]) -> Option<Vec<u8>> {
let v = self.0.take(key);
self.record("take", v.is_some());
v
}

fn can_cache(&self) -> bool {
self.0.can_cache()
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
76 changes: 69 additions & 7 deletions src/tls/tickets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,41 @@ use chacha20poly1305::{
aead::{Aead, AeadCore, KeyInit, OsRng},
XChaCha20Poly1305, XNonce,
};
use prometheus::{register_int_counter_vec_with_registry, IntCounterVec, Registry};
use rustls::server::ProducesTickets;
use zeroize::ZeroizeOnDrop;

// We're using 192-bit nonce
const NONCE_LEN: usize = 192 / 8;

#[derive(Debug)]
pub struct Metrics {
processed: IntCounterVec,
}

impl Metrics {
pub fn new(registry: &Registry) -> Self {
Self {
processed: register_int_counter_vec_with_registry!(
format!("tls_tickets"),
format!("Number of TLS tickets that were processed"),
&["action", "result"],
registry
)
.unwrap(),
}
}
}

/// Encrypts & decrypts tickets for TLS 1.3 session resumption.
/// Must be used with rustls::ticketer::TicketSwitcher to facilitate key rotation.
/// We're using XChaCha20Poly1305 authenicated encryption (AEAD)
/// Must be used with `rustls::ticketer::TicketSwitcher` to facilitate key rotation.
/// We're using XChaCha20Poly1305 authenicated encryption (AEAD).
/// ZeroizeOnDrop is derived below to make sure the encryption keys are wiped from
/// memory when the Ticketer is dropped.
/// See https://docs.rs/zeroize/latest/zeroize/#what-guarantees-does-this-crate-provide
#[derive(ZeroizeOnDrop)]
pub struct Ticketer {
#[zeroize(skip)]
counter: AtomicU32,
cipher: XChaCha20Poly1305,
}
Expand All @@ -28,11 +54,11 @@ impl fmt::Debug for Ticketer {

impl Ticketer {
pub fn new() -> Self {
// Generate random key that is valid for the lifetime of this ticketer
// Generate a random key that is valid for the lifetime of this ticketer
let key = XChaCha20Poly1305::generate_key(&mut OsRng);
let cipher = XChaCha20Poly1305::new(&key);

Self {
cipher,
cipher: XChaCha20Poly1305::new(&key),
counter: AtomicU32::new(0),
}
}
Expand Down Expand Up @@ -63,8 +89,7 @@ impl ProducesTickets for Ticketer {
let nonce = XNonce::from_slice(&cipher[0..NONCE_LEN]);

// Try to decrypt
let plaintext = self.cipher.decrypt(nonce, &cipher[NONCE_LEN..]).ok()?;
Some(plaintext)
self.cipher.decrypt(nonce, &cipher[NONCE_LEN..]).ok()
}

fn encrypt(&self, plain: &[u8]) -> Option<Vec<u8>> {
Expand All @@ -87,6 +112,40 @@ impl ProducesTickets for Ticketer {
}
}

#[derive(Debug)]
pub struct WithMetrics<T: ProducesTickets>(pub T, pub Metrics);

impl<T: ProducesTickets> WithMetrics<T> {
fn record(&self, action: &str, res: &Option<Vec<u8>>) {
self.1
.processed
.with_label_values(&[action, if res.is_some() { "ok" } else { "fail" }])
.inc();
}
}

impl<T: ProducesTickets> ProducesTickets for WithMetrics<T> {
fn enabled(&self) -> bool {
self.0.enabled()
}

fn lifetime(&self) -> u32 {
self.0.lifetime()
}

fn encrypt(&self, plain: &[u8]) -> Option<Vec<u8>> {
let res = self.0.encrypt(plain);
self.record("encrypt", &res);
res
}

fn decrypt(&self, cipher: &[u8]) -> Option<Vec<u8>> {
let res = self.0.decrypt(cipher);
self.record("decrypt", &res);
res
}
}

#[cfg(test)]
mod test {
use super::*;
Expand All @@ -106,5 +165,8 @@ mod test {
let ciphertext = t.encrypt(msg).unwrap();
let plaintext = t.decrypt(&ciphertext).unwrap();
assert_eq!(&msg[..], plaintext);

// Check that bad ciphertext fails to decrypt
assert!(t.decrypt(msg).is_none());
}
}

0 comments on commit 496fabd

Please sign in to comment.