@@ -11,24 +11,26 @@ use std::{
1111} ;
1212
1313use axum_extra:: extract:: cookie:: Key ;
14+ use defguard_certs:: { CertificateError , CertificateInfo } ;
15+ use defguard_grpc_tls:: { certs:: server_tls_config, server:: certificate_serial_interceptor} ;
1416use defguard_version:: {
1517 ComponentInfo , DefguardComponent , Version , get_tracing_variables,
1618 server:: { DefguardVersionLayer , grpc:: DefguardVersionInterceptor } ,
1719} ;
18- use tokio:: sync:: { broadcast, mpsc, oneshot} ;
19- use tokio_stream:: wrappers:: UnboundedReceiverStream ;
20- use tonic:: {
21- Request , Response , Status , Streaming ,
22- transport:: { Identity , Server , ServerTlsConfig } ,
20+ use tokio:: {
21+ fs:: remove_file,
22+ sync:: { broadcast, mpsc, oneshot} ,
2323} ;
24+ use tokio_stream:: wrappers:: UnboundedReceiverStream ;
25+ use tonic:: { Request , Response , Status , Streaming , service:: InterceptorLayer , transport:: Server } ;
2426use tower:: ServiceBuilder ;
2527use tracing:: Instrument ;
2628
2729use crate :: {
2830 LogsReceiver , MIN_CORE_VERSION , VERSION , acme,
2931 acme:: Port80Permit ,
3032 error:: ApiError ,
31- http:: { GRPC_CERT_NAME , GRPC_KEY_NAME } ,
33+ http:: { CORE_CLIENT_CERT_NAME , GRPC_CA_CERT_NAME , GRPC_CERT_NAME , GRPC_KEY_NAME } ,
3234 proto:: {
3335 AcmeCertificate , AcmeChallenge , AcmeIssueEvent , AcmeLogs , AcmeProgress , AcmeStep ,
3436 CoreRequest , CoreResponse , DeviceInfo , acme_issue_event, core_request, core_response,
@@ -40,9 +42,13 @@ use crate::{
4042type ClientMap = HashMap < SocketAddr , mpsc:: UnboundedSender < Result < CoreRequest , Status > > > ;
4143
4244#[ derive( Debug , Clone , Default ) ]
43- pub struct Configuration {
45+ pub struct TlsConfig {
4446 pub grpc_key_pem : String ,
4547 pub grpc_cert_pem : String ,
48+ /// PEM-encoded CA certificate used to verify Core's mTLS client certificate chain.
49+ pub grpc_ca_cert_pem : String ,
50+ /// DER-encoded Core client certificate; used to extract and pin the expected serial.
51+ pub core_client_cert_der : Vec < u8 > ,
4652}
4753
4854pub ( crate ) struct ProxyServer {
@@ -51,7 +57,7 @@ pub(crate) struct ProxyServer {
5157 results : Arc < RwLock < HashMap < u64 , oneshot:: Sender < core_response:: Payload > > > > ,
5258 pub ( crate ) connected : Arc < AtomicBool > ,
5359 pub ( crate ) core_version : Arc < Mutex < Option < Version > > > ,
54- config : Arc < Mutex < Option < Configuration > > > ,
60+ tls_config : Arc < Mutex < Option < TlsConfig > > > ,
5561 cookie_key : Arc < RwLock < Option < Key > > > ,
5662 cert_dir : PathBuf ,
5763 reset_tx : broadcast:: Sender < ( ) > ,
@@ -87,7 +93,7 @@ impl ProxyServer {
8793 results : Arc :: new ( RwLock :: new ( HashMap :: new ( ) ) ) ,
8894 connected : Arc :: new ( AtomicBool :: new ( false ) ) ,
8995 core_version : Arc :: new ( Mutex :: new ( None ) ) ,
90- config : Arc :: new ( Mutex :: new ( None ) ) ,
96+ tls_config : Arc :: new ( Mutex :: new ( None ) ) ,
9197 cert_dir,
9298 reset_tx,
9399 https_cert_tx,
@@ -98,17 +104,17 @@ impl ProxyServer {
98104 }
99105 }
100106
101- pub ( crate ) fn configure ( & self , config : Configuration ) {
107+ pub ( crate ) fn configure ( & self , config : TlsConfig ) {
102108 let mut lock = self
103- . config
109+ . tls_config
104110 . lock ( )
105111 . expect ( "Failed to acquire lock on config mutex when applying proxy configuration" ) ;
106112 * lock = Some ( config) ;
107113 }
108114
109- pub ( crate ) fn get_configuration ( & self ) -> Option < Configuration > {
115+ pub ( crate ) fn get_tls_config ( & self ) -> Option < TlsConfig > {
110116 let lock = self
111- . config
117+ . tls_config
112118 . lock ( )
113119 . expect ( "Failed to acquire lock on config mutex when retrieving proxy configuration" ) ;
114120 lock. clone ( )
@@ -119,19 +125,27 @@ impl ProxyServer {
119125 F : Future < Output = ( ) > + Send + ' static ,
120126 {
121127 info ! ( "Starting gRPC server on {addr}" ) ;
122- let config = self . get_configuration ( ) ;
123- let ( grpc_cert, grpc_key) = if let Some ( cfg) = config {
124- ( cfg. grpc_cert_pem , cfg. grpc_key_pem )
125- } else {
126- return Err ( anyhow:: anyhow!( "gRPC server configuration is missing" ) ) ;
127- } ;
128-
129- let identity = Identity :: from_pem ( grpc_cert, grpc_key) ;
130- let mut builder =
131- Server :: builder ( ) . tls_config ( ServerTlsConfig :: new ( ) . identity ( identity) ) ?;
128+ let tls_config = self
129+ . get_tls_config ( )
130+ . ok_or_else ( || anyhow:: anyhow!( "gRPC server TLS configuration is missing" ) ) ?;
131+
132+ // Extract Core client cert serial for pinning (None in no-TLS mode).
133+ let expected_serial = CertificateInfo :: from_der ( & tls_config. core_client_cert_der )
134+ . map_err ( |e : CertificateError | anyhow:: anyhow!( "invalid core client cert DER: {e}" ) ) ?
135+ . serial ;
136+
137+ let tls_config = server_tls_config (
138+ & tls_config. grpc_cert_pem ,
139+ & tls_config. grpc_key_pem ,
140+ & tls_config. grpc_ca_cert_pem ,
141+ ) ?;
142+ let mut builder = Server :: builder ( ) . tls_config ( tls_config) ?;
132143
133144 let own_version = Version :: parse ( VERSION ) ?;
134145 let versioned_service = ServiceBuilder :: new ( )
146+ . layer ( InterceptorLayer :: new ( certificate_serial_interceptor (
147+ expected_serial,
148+ ) ) )
135149 . layer ( tonic:: service:: InterceptorLayer :: new (
136150 DefguardVersionInterceptor :: new (
137151 own_version. clone ( ) ,
@@ -197,7 +211,7 @@ impl ProxyServer {
197211
198212 pub ( crate ) fn setup_completed ( & self ) -> bool {
199213 let lock = self
200- . config
214+ . tls_config
201215 . lock ( )
202216 . expect ( "Failed to acquire lock on config mutex when checking setup status" ) ;
203217 lock. is_some ( )
@@ -213,7 +227,7 @@ impl Clone for ProxyServer {
213227 connected : Arc :: clone ( & self . connected ) ,
214228 core_version : Arc :: clone ( & self . core_version ) ,
215229 cookie_key : Arc :: clone ( & self . cookie_key ) ,
216- config : Arc :: clone ( & self . config ) ,
230+ tls_config : Arc :: clone ( & self . tls_config ) ,
217231 cert_dir : self . cert_dir . clone ( ) ,
218232 reset_tx : self . reset_tx . clone ( ) ,
219233 https_cert_tx : self . https_cert_tx . clone ( ) ,
@@ -343,26 +357,33 @@ impl proxy_server::Proxy for ProxyServer {
343357 debug ! ( "Received purge request, removing gRPC certificate files" ) ;
344358 let cert_path = self . cert_dir . join ( GRPC_CERT_NAME ) ;
345359 let key_path = self . cert_dir . join ( GRPC_KEY_NAME ) ;
360+ let ca_cert_path = self . cert_dir . join ( GRPC_CA_CERT_NAME ) ;
361+ let core_client_cert_path = self . cert_dir . join ( CORE_CLIENT_CERT_NAME ) ;
362+
363+ let remove_cert_file = async |path : & std:: path:: Path , label : & str | -> Result < ( ) , Status > {
364+ match remove_file ( path) . await {
365+ Ok ( ( ) ) => {
366+ info ! ( "Removed {label} at {}" , path. display( ) ) ;
367+ Ok ( ( ) )
368+ }
369+ Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: NotFound => {
370+ debug ! ( "{label} not found at {}, skipping removal" , path. display( ) ) ;
371+ Ok ( ( ) )
372+ }
373+ Err ( err) => {
374+ error ! ( "Failed to remove {label} at {}: {err}" , path. display( ) ) ;
375+ Err ( Status :: internal ( format ! ( "Failed to remove {label}" ) ) )
376+ }
377+ }
378+ } ;
346379
347- if let Err ( err) = tokio:: fs:: remove_file ( & cert_path) . await
348- && err. kind ( ) != std:: io:: ErrorKind :: NotFound
349- {
350- error ! (
351- "Failed to remove gRPC certificate at {:?}: {err}" ,
352- cert_path
353- ) ;
354- return Err ( Status :: internal ( "Failed to remove gRPC certificate" ) ) ;
355- }
356-
357- if let Err ( err) = tokio:: fs:: remove_file ( & key_path) . await
358- && err. kind ( ) != std:: io:: ErrorKind :: NotFound
359- {
360- error ! ( "Failed to remove gRPC key at {:?}: {err}" , key_path) ;
361- return Err ( Status :: internal ( "Failed to remove gRPC key" ) ) ;
362- }
380+ remove_cert_file ( & cert_path, "gRPC certificate" ) . await ?;
381+ remove_cert_file ( & key_path, "gRPC key" ) . await ?;
382+ remove_cert_file ( & ca_cert_path, "CA certificate" ) . await ?;
383+ remove_cert_file ( & core_client_cert_path, "Core client certificate" ) . await ?;
363384
364385 * self
365- . config
386+ . tls_config
366387 . lock ( )
367388 . expect ( "Failed to lock config mutex during purge" ) = None ;
368389 * self
0 commit comments