Skip to content

Commit c522f5f

Browse files
committed
feat: implements ca-crl support in zTunnel
Signed-off-by: nilekh <[email protected]>
1 parent afa6722 commit c522f5f

File tree

17 files changed

+881
-18
lines changed

17 files changed

+881
-18
lines changed

Cargo.lock

Lines changed: 130 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ keyed_priority_queue = "0.4"
7272
libc = "0.2"
7373
log = "0.4"
7474
nix = { version = "0.30", features = ["socket", "sched", "uio", "fs", "ioctl", "user", "net", "mount", "resource" ] }
75+
notify = "6.1"
76+
notify-debouncer-full = "0.3"
7577
once_cell = "1.21"
7678
num_cpus = "1.16"
7779
ppp = "2.3"
@@ -156,6 +158,7 @@ oid-registry = "0.8"
156158
rcgen = { version = "0.14", features = ["pem", "x509-parser"] }
157159
x509-parser = { version = "0.17", default-features = false, features = ["verify"] }
158160
ctor = "0.5"
161+
tempfile = "3.21"
159162

160163
[lints.clippy]
161164
# This rule makes code more confusing

src/config.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ const HTTP2_FRAME_SIZE: &str = "HTTP2_FRAME_SIZE";
7878

7979
const UNSTABLE_ENABLE_SOCKS5: &str = "UNSTABLE_ENABLE_SOCKS5";
8080

81+
const ENABLE_CRL: &str = "ENABLE_CRL";
82+
const CRL_PATH: &str = "CRL_PATH";
83+
const ALLOW_EXPIRED_CRL: &str = "ALLOW_EXPIRED_CRL";
84+
8185
const DEFAULT_WORKER_THREADS: u16 = 2;
8286
const DEFAULT_ADMIN_PORT: u16 = 15000;
8387
const DEFAULT_READINESS_PORT: u16 = 15021;
@@ -108,6 +112,7 @@ const DEFAULT_ROOT_CERT_PROVIDER: &str = "./var/run/secrets/istio/root-cert.pem"
108112
const TOKEN_PROVIDER_ENV: &str = "AUTH_TOKEN";
109113
const DEFAULT_TOKEN_PROVIDER: &str = "./var/run/secrets/tokens/istio-token";
110114
const CERT_SYSTEM: &str = "SYSTEM";
115+
const DEFAULT_CRL_PATH: &str = "./var/run/secrets/istio/crl/ca-crl.pem";
111116

112117
const PROXY_MODE_DEDICATED: &str = "dedicated";
113118
const PROXY_MODE_SHARED: &str = "shared";
@@ -311,6 +316,15 @@ pub struct Config {
311316
pub ztunnel_workload: Option<state::WorkloadInfo>,
312317

313318
pub ipv6_enabled: bool,
319+
320+
/// Enable CRL (Certificate Revocation List) checking
321+
pub enable_crl: bool,
322+
323+
/// Path to CRL file
324+
pub crl_path: PathBuf,
325+
326+
/// Allow expired CRL (for testing/rollout scenarios)
327+
pub allow_expired_crl: bool,
314328
}
315329

316330
#[derive(serde::Serialize, Clone, Copy, Debug)]
@@ -865,6 +879,10 @@ pub fn construct_config(pc: ProxyConfig) -> Result<Config, Error> {
865879
ztunnel_identity,
866880
ztunnel_workload,
867881
ipv6_enabled,
882+
883+
enable_crl: parse_default(ENABLE_CRL, false)?,
884+
crl_path: parse_default(CRL_PATH, PathBuf::from(DEFAULT_CRL_PATH))?,
885+
allow_expired_crl: parse_default(ALLOW_EXPIRED_CRL, false)?,
868886
})
869887
}
870888

src/proxy.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ impl DefaultSocketFactory {
120120
socket2::SockRef::from(&s).set_tcp_keepalive(&ka)
121121
);
122122
}
123+
#[cfg(target_os = "linux")]
123124
if cfg.user_timeout_enabled {
124125
// https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/
125126
// TCP_USER_TIMEOUT = TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT.
@@ -262,6 +263,10 @@ pub(super) struct ProxyInputs {
262263
resolver: Option<Arc<dyn Resolver + Send + Sync>>,
263264
// If true, inbound connections created with these inputs will not attempt to preserve the original source IP.
264265
pub disable_inbound_freebind: bool,
266+
// CRL manager for certificate revocation checking
267+
pub(super) crl_manager: Option<Arc<tls::crl::CrlManager>>,
268+
// Pool registry for draining HTTP/2 connection pools on CRL reload
269+
pub(super) pool_registry: pool::PoolRegistry,
265270
}
266271

267272
#[allow(clippy::too_many_arguments)]
@@ -275,6 +280,8 @@ impl ProxyInputs {
275280
resolver: Option<Arc<dyn Resolver + Send + Sync>>,
276281
local_workload_information: Arc<LocalWorkloadInformation>,
277282
disable_inbound_freebind: bool,
283+
crl_manager: Option<Arc<tls::crl::CrlManager>>,
284+
pool_registry: pool::PoolRegistry,
278285
) -> Arc<Self> {
279286
Arc::new(Self {
280287
cfg,
@@ -285,6 +292,8 @@ impl ProxyInputs {
285292
local_workload_information,
286293
resolver,
287294
disable_inbound_freebind,
295+
crl_manager,
296+
pool_registry,
288297
})
289298
}
290299
}

src/proxy/connection_manager.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,39 @@ impl ConnectionManager {
260260
// potentially large copy under read lock, could require optimization
261261
self.drains.read().expect("mutex").keys().cloned().collect()
262262
}
263+
264+
/// Close all inbound and outbound connections
265+
/// This is called when certificate revocations are detected
266+
pub fn close_all_connections(&self) {
267+
// Close all inbound connections
268+
let inbound_conns = self.connections();
269+
let inbound_count = inbound_conns.len();
270+
271+
if inbound_count > 0 {
272+
tracing::info!("Closing {} inbound connection(s)", inbound_count);
273+
// Spawn async tasks to close connections without blocking
274+
for conn in inbound_conns {
275+
let cm = self.clone();
276+
tokio::spawn(async move {
277+
cm.close(&conn).await;
278+
});
279+
}
280+
}
281+
282+
// Clear outbound connections (they will be recreated on next use)
283+
let mut outbound = self.outbound_connections.write().expect("mutex");
284+
let outbound_count = outbound.len();
285+
if outbound_count > 0 {
286+
tracing::info!("Clearing {} outbound connection(s)", outbound_count);
287+
outbound.clear();
288+
}
289+
290+
tracing::info!(
291+
"Connection closure initiated: {} inbound, {} outbound",
292+
inbound_count,
293+
outbound_count
294+
);
295+
}
263296
}
264297

265298
#[derive(serde::Serialize)]

src/proxy/inbound.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ impl Inbound {
8383
let pi = self.pi.clone();
8484
let acceptor = InboundCertProvider {
8585
local_workload: self.pi.local_workload_information.clone(),
86+
crl_manager: self.pi.crl_manager.clone(),
8687
};
8788

8889
// Safety: we set nodelay directly in tls_server, so it is safe to convert to a normal listener.
@@ -683,6 +684,7 @@ impl InboundFlagError {
683684
#[derive(Clone)]
684685
struct InboundCertProvider {
685686
local_workload: Arc<LocalWorkloadInformation>,
687+
crl_manager: Option<Arc<tls::crl::CrlManager>>,
686688
}
687689

688690
#[async_trait::async_trait]
@@ -693,7 +695,7 @@ impl crate::tls::ServerCertProvider for InboundCertProvider {
693695
"fetching cert"
694696
);
695697
let cert = self.local_workload.fetch_certificate().await?;
696-
Ok(Arc::new(cert.server_config()?))
698+
Ok(Arc::new(cert.server_config(self.crl_manager.clone())?))
697699
}
698700
}
699701

@@ -914,6 +916,8 @@ mod tests {
914916
None,
915917
local_workload,
916918
false,
919+
None,
920+
crate::proxy::pool::PoolRegistry::new(),
917921
));
918922
let inbound_request = Inbound::build_inbound_request(&pi, conn, &request_parts).await;
919923
match want {

0 commit comments

Comments
 (0)