Skip to content

Commit 6cfcad3

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

File tree

14 files changed

+864
-15
lines changed

14 files changed

+864
-15
lines changed

Cargo.lock

Lines changed: 129 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: 2 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"

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ pub(super) struct ProxyInputs {
262262
resolver: Option<Arc<dyn Resolver + Send + Sync>>,
263263
// If true, inbound connections created with these inputs will not attempt to preserve the original source IP.
264264
pub disable_inbound_freebind: bool,
265+
// CRL manager for certificate revocation checking
266+
pub(super) crl_manager: Option<Arc<tls::crl::CrlManager>>,
267+
// Pool registry for draining HTTP/2 connection pools on CRL reload
268+
pub(super) pool_registry: pool::PoolRegistry,
265269
}
266270

267271
#[allow(clippy::too_many_arguments)]
@@ -275,6 +279,8 @@ impl ProxyInputs {
275279
resolver: Option<Arc<dyn Resolver + Send + Sync>>,
276280
local_workload_information: Arc<LocalWorkloadInformation>,
277281
disable_inbound_freebind: bool,
282+
crl_manager: Option<Arc<tls::crl::CrlManager>>,
283+
pool_registry: pool::PoolRegistry,
278284
) -> Arc<Self> {
279285
Arc::new(Self {
280286
cfg,
@@ -285,6 +291,8 @@ impl ProxyInputs {
285291
local_workload_information,
286292
resolver,
287293
disable_inbound_freebind,
294+
crl_manager,
295+
pool_registry,
288296
})
289297
}
290298
}

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 {

src/proxy/outbound.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ impl Outbound {
7979
self.pi.cfg.clone(),
8080
self.pi.socket_factory.clone(),
8181
self.pi.local_workload_information.clone(),
82+
self.pi.crl_manager.clone(),
8283
);
84+
85+
// Register pool with registry for CRL-triggered draining
86+
self.pi.pool_registry.register(pool.clone());
87+
8388
let pi = self.pi.clone();
8489
let accept = async move |drain: DrainWatcher, force_shutdown: watch::Receiver<()>| {
8590
loop {
@@ -247,7 +252,8 @@ impl OutboundConnection {
247252
.local_workload_information
248253
.fetch_certificate()
249254
.await?;
250-
let connector = cert.outbound_connector(wl_key.dst_id.clone())?;
255+
let connector =
256+
cert.outbound_connector(wl_key.dst_id.clone(), self.pi.crl_manager.clone())?;
251257
let tls_stream = connector.connect(upgraded).await?;
252258

253259
// Spawn inner CONNECT tunnel
@@ -809,6 +815,7 @@ mod tests {
809815
cfg.clone(),
810816
sock_fact,
811817
local_workload_information.clone(),
818+
None,
812819
),
813820
hbone_port: cfg.inbound_addr.port(),
814821
};

0 commit comments

Comments
 (0)