allframe_core/grpc/
tls.rs

1//! TLS configuration for gRPC servers
2//!
3//! This module provides TLS configuration options for secure gRPC connections.
4
5use std::{env, path::PathBuf};
6
7/// TLS configuration for gRPC servers
8#[derive(Debug, Clone)]
9pub struct TlsConfig {
10    /// Path to the server certificate file (PEM format)
11    pub cert_path: PathBuf,
12    /// Path to the server private key file (PEM format)
13    pub key_path: PathBuf,
14    /// Optional path to client CA certificate for mTLS
15    pub client_ca_path: Option<PathBuf>,
16}
17
18impl TlsConfig {
19    /// Create a new TLS configuration
20    pub fn new(cert_path: impl Into<PathBuf>, key_path: impl Into<PathBuf>) -> Self {
21        Self {
22            cert_path: cert_path.into(),
23            key_path: key_path.into(),
24            client_ca_path: None,
25        }
26    }
27
28    /// Enable mutual TLS (mTLS) with a client CA certificate
29    pub fn with_client_ca(mut self, client_ca_path: impl Into<PathBuf>) -> Self {
30        self.client_ca_path = Some(client_ca_path.into());
31        self
32    }
33
34    /// Create TLS configuration from environment variables
35    ///
36    /// Reads from:
37    /// - `GRPC_TLS_CERT` or `TLS_CERT_PATH` - Certificate path
38    /// - `GRPC_TLS_KEY` or `TLS_KEY_PATH` - Key path
39    /// - `GRPC_TLS_CLIENT_CA` or `TLS_CLIENT_CA_PATH` - Optional client CA path
40    pub fn from_env() -> Option<Self> {
41        let cert_path = env::var("GRPC_TLS_CERT")
42            .or_else(|_| env::var("TLS_CERT_PATH"))
43            .ok()?;
44
45        let key_path = env::var("GRPC_TLS_KEY")
46            .or_else(|_| env::var("TLS_KEY_PATH"))
47            .ok()?;
48
49        let client_ca_path = env::var("GRPC_TLS_CLIENT_CA")
50            .or_else(|_| env::var("TLS_CLIENT_CA_PATH"))
51            .ok()
52            .map(PathBuf::from);
53
54        Some(Self {
55            cert_path: PathBuf::from(cert_path),
56            key_path: PathBuf::from(key_path),
57            client_ca_path,
58        })
59    }
60
61    /// Load the certificate and key files
62    #[cfg(feature = "grpc-tls")]
63    pub fn load(&self) -> Result<(Vec<u8>, Vec<u8>), TlsError> {
64        let cert = std::fs::read(&self.cert_path).map_err(|e| TlsError::CertificateLoad {
65            path: self.cert_path.clone(),
66            source: e.to_string(),
67        })?;
68
69        let key = std::fs::read(&self.key_path).map_err(|e| TlsError::KeyLoad {
70            path: self.key_path.clone(),
71            source: e.to_string(),
72        })?;
73
74        Ok((cert, key))
75    }
76
77    /// Load the client CA certificate if configured
78    #[cfg(feature = "grpc-tls")]
79    pub fn load_client_ca(&self) -> Result<Option<Vec<u8>>, TlsError> {
80        match &self.client_ca_path {
81            Some(path) => {
82                let ca = std::fs::read(path).map_err(|e| TlsError::ClientCaLoad {
83                    path: path.clone(),
84                    source: e.to_string(),
85                })?;
86                Ok(Some(ca))
87            }
88            None => Ok(None),
89        }
90    }
91}
92
93/// Errors that can occur during TLS configuration
94#[derive(Debug)]
95pub enum TlsError {
96    /// Failed to load certificate file
97    CertificateLoad {
98        /// Path that failed to load
99        path: PathBuf,
100        /// Error details
101        source: String,
102    },
103    /// Failed to load key file
104    KeyLoad {
105        /// Path that failed to load
106        path: PathBuf,
107        /// Error details
108        source: String,
109    },
110    /// Failed to load client CA file
111    ClientCaLoad {
112        /// Path that failed to load
113        path: PathBuf,
114        /// Error details
115        source: String,
116    },
117    /// Failed to configure TLS
118    Configuration(String),
119}
120
121impl std::fmt::Display for TlsError {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        match self {
124            TlsError::CertificateLoad { path, source } => {
125                write!(f, "Failed to load certificate from {:?}: {}", path, source)
126            }
127            TlsError::KeyLoad { path, source } => {
128                write!(f, "Failed to load key from {:?}: {}", path, source)
129            }
130            TlsError::ClientCaLoad { path, source } => {
131                write!(f, "Failed to load client CA from {:?}: {}", path, source)
132            }
133            TlsError::Configuration(msg) => write!(f, "TLS configuration error: {}", msg),
134        }
135    }
136}
137
138impl std::error::Error for TlsError {}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_tls_config_new() {
146        let config = TlsConfig::new("/path/to/cert.pem", "/path/to/key.pem");
147        assert_eq!(config.cert_path, PathBuf::from("/path/to/cert.pem"));
148        assert_eq!(config.key_path, PathBuf::from("/path/to/key.pem"));
149        assert!(config.client_ca_path.is_none());
150    }
151
152    #[test]
153    fn test_tls_config_with_client_ca() {
154        let config = TlsConfig::new("/path/to/cert.pem", "/path/to/key.pem")
155            .with_client_ca("/path/to/ca.pem");
156
157        assert_eq!(
158            config.client_ca_path,
159            Some(PathBuf::from("/path/to/ca.pem"))
160        );
161    }
162
163    #[test]
164    fn test_tls_error_display() {
165        let err = TlsError::CertificateLoad {
166            path: PathBuf::from("/path/to/cert.pem"),
167            source: "file not found".to_string(),
168        };
169        assert!(err.to_string().contains("cert.pem"));
170        assert!(err.to_string().contains("file not found"));
171    }
172}