4949
5050use anyhow:: { Context , Result } ;
5151use async_trait:: async_trait;
52- use reqwest:: { Client , StatusCode } ;
52+ use reqwest:: { Client , Identity , StatusCode } ;
5353use serde:: { Deserialize , Serialize } ;
5454use std:: collections:: HashMap ;
5555use std:: fmt;
@@ -369,13 +369,23 @@ trait VaultTransport: Send + Sync {
369369#[ derive( Debug , Default ) ]
370370struct ReqwestVaultTransport ;
371371
372+ impl ReqwestVaultTransport {
373+ fn build_client ( ) -> Result < Client > {
374+ let mut builder = Client :: builder ( )
375+ . timeout ( std:: time:: Duration :: from_secs ( 10 ) ) ;
376+
377+ if let Some ( identity) = load_mtls_identity ( ) {
378+ builder = builder. identity ( identity) ;
379+ }
380+
381+ builder. build ( ) . context ( "creating HTTP client" )
382+ }
383+ }
384+
372385#[ async_trait]
373386impl VaultTransport for ReqwestVaultTransport {
374387 async fn get ( & self , url : & str , token : & str ) -> Result < VaultHttpResponse > {
375- let response = Client :: builder ( )
376- . timeout ( std:: time:: Duration :: from_secs ( 10 ) )
377- . build ( )
378- . context ( "creating HTTP client" ) ?
388+ let response = Self :: build_client ( ) ?
379389 . get ( url)
380390 . header ( "X-Vault-Token" , token)
381391 . send ( )
@@ -393,10 +403,7 @@ impl VaultTransport for ReqwestVaultTransport {
393403 token : & str ,
394404 payload : & serde_json:: Value ,
395405 ) -> Result < VaultHttpResponse > {
396- let response = Client :: builder ( )
397- . timeout ( std:: time:: Duration :: from_secs ( 10 ) )
398- . build ( )
399- . context ( "creating HTTP client" ) ?
406+ let response = Self :: build_client ( ) ?
400407 . post ( url)
401408 . header ( "X-Vault-Token" , token)
402409 . json ( payload)
@@ -410,6 +417,39 @@ impl VaultTransport for ReqwestVaultTransport {
410417 }
411418}
412419
420+ /// Load mTLS client certificate identity from environment variables.
421+ ///
422+ /// Supports two modes:
423+ /// 1. Inline PEM via `VAULT_CLIENT_CERT` and `VAULT_CLIENT_KEY` env vars
424+ /// 2. File paths via `VAULT_CLIENT_CERT_PATH` and `VAULT_CLIENT_KEY_PATH` env vars
425+ ///
426+ /// Returns `None` if neither set (mTLS is optional — degradation path).
427+ fn load_mtls_identity ( ) -> Option < Identity > {
428+ let cert_pem = std:: env:: var ( "VAULT_CLIENT_CERT" ) . ok ( )
429+ . or_else ( || {
430+ let path = std:: env:: var ( "VAULT_CLIENT_CERT_PATH" ) . ok ( ) ?;
431+ std:: fs:: read_to_string ( path) . ok ( )
432+ } ) ?;
433+
434+ let key_pem = std:: env:: var ( "VAULT_CLIENT_KEY" ) . ok ( )
435+ . or_else ( || {
436+ let path = std:: env:: var ( "VAULT_CLIENT_KEY_PATH" ) . ok ( ) ?;
437+ std:: fs:: read_to_string ( path) . ok ( )
438+ } ) ?;
439+
440+ let identity_pem = format ! ( "{}\n {}" , cert_pem, key_pem) ;
441+ match Identity :: from_pem ( identity_pem. as_bytes ( ) ) {
442+ Ok ( identity) => {
443+ debug ! ( "mTLS client identity loaded for Vault connections" ) ;
444+ Some ( identity)
445+ }
446+ Err ( e) => {
447+ warn ! ( "Failed to load mTLS client identity: {}" , e) ;
448+ None
449+ }
450+ }
451+ }
452+
413453// =============================================================================
414454// Vault Client Implementation
415455// =============================================================================
@@ -495,8 +535,15 @@ impl VaultClient {
495535 // Configure HTTP client with security-conscious defaults:
496536 // - 10 second timeout prevents resource exhaustion from hanging connections
497537 // - TLS certificate validation enabled by default (reqwest behavior)
498- let http_client = Client :: builder ( )
499- . timeout ( std:: time:: Duration :: from_secs ( 10 ) )
538+ // - mTLS client identity loaded from VAULT_CLIENT_CERT / VAULT_CLIENT_KEY
539+ let mut builder = Client :: builder ( )
540+ . timeout ( std:: time:: Duration :: from_secs ( 10 ) ) ;
541+
542+ if let Some ( identity) = load_mtls_identity ( ) {
543+ builder = builder. identity ( identity) ;
544+ }
545+
546+ let http_client = builder
500547 . build ( )
501548 . context ( "creating HTTP client" ) ?;
502549
0 commit comments