fraiseql_core/security/tls_enforcer.rs
1//! TLS Security Enforcement
2//!
3//! This module provides TLS/SSL security enforcement for GraphQL connections.
4//! It validates:
5//! - HTTPS requirement (TLS mandatory)
6//! - Minimum TLS version
7//! - Mutual TLS (mTLS) requirement for client certificates
8//! - Client certificate validity
9//!
10//! # Architecture
11//!
12//! The TLS enforcer acts as a gatekeeper in the security middleware:
13//! ```text
14//! HTTP Request with TLS info
15//! ↓
16//! TlsEnforcer::validate_connection()
17//! ├─ Check 1: Is HTTPS required? (tls_required)
18//! ├─ Check 2: Is minimum TLS version met? (min_version)
19//! ├─ Check 3: Is mTLS required? (mtls_required)
20//! └─ Check 4: Is client cert valid? (client_cert_valid)
21//! ↓
22//! Result<()> (OK or TlsError)
23//! ```
24//!
25//! # Examples
26//!
27//! ```no_run
28//! use fraiseql_core::security::{TlsEnforcer, TlsConfig, TlsConnection, TlsVersion};
29//!
30//! // Create enforcer with strict configuration
31//! let config = TlsConfig {
32//! tls_required: true,
33//! mtls_required: true,
34//! min_version: TlsVersion::V1_3,
35//! };
36//! let enforcer = TlsEnforcer::from_config(config);
37//!
38//! // Validate a connection
39//! let conn = TlsConnection {
40//! is_secure: true,
41//! version: TlsVersion::V1_3,
42//! has_client_cert: true,
43//! client_cert_valid: true,
44//! };
45//!
46//! match enforcer.validate_connection(&conn) {
47//! Ok(()) => println!("Connection is secure"),
48//! Err(e) => eprintln!("TLS validation failed: {}", e),
49//! }
50//! ```
51
52use std::fmt;
53
54use serde::{Deserialize, Serialize};
55
56use crate::security::errors::{Result, SecurityError};
57
58/// TLS/SSL protocol version.
59///
60/// Represents the version of TLS/SSL used for the connection.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
62#[non_exhaustive]
63pub enum TlsVersion {
64 /// TLS 1.0 (deprecated, insecure)
65 V1_0,
66 /// TLS 1.1 (deprecated, insecure)
67 V1_1,
68 /// TLS 1.2 (modern baseline)
69 V1_2,
70 /// TLS 1.3 (current standard)
71 V1_3,
72}
73
74impl fmt::Display for TlsVersion {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::V1_0 => write!(f, "TLS 1.0"),
78 Self::V1_1 => write!(f, "TLS 1.1"),
79 Self::V1_2 => write!(f, "TLS 1.2"),
80 Self::V1_3 => write!(f, "TLS 1.3"),
81 }
82 }
83}
84
85/// TLS connection information extracted from HTTP request.
86///
87/// This struct captures the essential TLS/security information from an
88/// incoming connection. It's created by the HTTP adapter layer and passed
89/// to the enforcer for validation.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct TlsConnection {
92 /// Whether the connection is over HTTPS/TLS (true) or HTTP (false)
93 pub is_secure: bool,
94
95 /// The TLS protocol version used (only valid if `is_secure=true`)
96 pub version: TlsVersion,
97
98 /// Whether a client certificate was presented
99 pub has_client_cert: bool,
100
101 /// Whether the client certificate has been validated by the server
102 /// (Note: The server does this validation; this flag indicates the result)
103 pub client_cert_valid: bool,
104}
105
106impl TlsConnection {
107 /// Create a new TLS connection info (typically HTTP, not secure)
108 #[must_use]
109 pub const fn new_http() -> Self {
110 Self {
111 is_secure: false,
112 version: TlsVersion::V1_2, // Irrelevant for HTTP
113 has_client_cert: false,
114 client_cert_valid: false,
115 }
116 }
117
118 /// Create a new secure TLS connection
119 #[must_use]
120 pub const fn new_secure(version: TlsVersion) -> Self {
121 Self {
122 is_secure: true,
123 version,
124 has_client_cert: false,
125 client_cert_valid: false,
126 }
127 }
128
129 /// Create a new secure TLS connection with a valid client certificate
130 #[must_use]
131 pub const fn new_secure_with_client_cert(version: TlsVersion) -> Self {
132 Self {
133 is_secure: true,
134 version,
135 has_client_cert: true,
136 client_cert_valid: true,
137 }
138 }
139}
140
141/// TLS Security Configuration
142///
143/// Defines what TLS/SSL requirements must be met for a connection.
144/// This is typically derived from a `SecurityProfile`.
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
146pub struct TlsConfig {
147 /// If true, all connections must be HTTPS (TLS required)
148 pub tls_required: bool,
149
150 /// If true, all connections must include a valid client certificate
151 pub mtls_required: bool,
152
153 /// Minimum allowed TLS version (enforce via check 2)
154 pub min_version: TlsVersion,
155}
156
157impl TlsConfig {
158 /// Create a permissive TLS configuration (for development/staging)
159 ///
160 /// - HTTPS optional
161 /// - Client certs optional
162 /// - TLS 1.2 minimum (if TLS is used)
163 #[must_use]
164 pub const fn permissive() -> Self {
165 Self {
166 tls_required: false,
167 mtls_required: false,
168 min_version: TlsVersion::V1_2,
169 }
170 }
171
172 /// Create a standard TLS configuration (for production)
173 ///
174 /// - HTTPS required
175 /// - Client certs optional
176 /// - TLS 1.2 minimum
177 #[must_use]
178 pub const fn standard() -> Self {
179 Self {
180 tls_required: true,
181 mtls_required: false,
182 min_version: TlsVersion::V1_2,
183 }
184 }
185
186 /// Create a strict TLS configuration (for regulated environments)
187 ///
188 /// - HTTPS required
189 /// - Client certs required (mTLS)
190 /// - TLS 1.3 minimum
191 #[must_use]
192 pub const fn strict() -> Self {
193 Self {
194 tls_required: true,
195 mtls_required: true,
196 min_version: TlsVersion::V1_3,
197 }
198 }
199}
200
201/// TLS Security Enforcer
202///
203/// Validates incoming connections against TLS security requirements.
204/// Used as the first layer in the security middleware pipeline.
205#[derive(Debug, Clone)]
206pub struct TlsEnforcer {
207 config: TlsConfig,
208}
209
210impl TlsEnforcer {
211 /// Create a new TLS enforcer from configuration
212 #[must_use]
213 pub const fn from_config(config: TlsConfig) -> Self {
214 Self { config }
215 }
216
217 /// Create enforcer with permissive settings (development)
218 #[must_use]
219 pub const fn permissive() -> Self {
220 Self::from_config(TlsConfig::permissive())
221 }
222
223 /// Create enforcer with standard settings (production)
224 #[must_use]
225 pub const fn standard() -> Self {
226 Self::from_config(TlsConfig::standard())
227 }
228
229 /// Create enforcer with strict settings (regulated)
230 #[must_use]
231 pub const fn strict() -> Self {
232 Self::from_config(TlsConfig::strict())
233 }
234
235 /// Validate a TLS connection against the enforcer's configuration.
236 ///
237 /// Performs 4 validation checks in order:
238 /// 1. HTTPS requirement (if `tls_required=true`, reject HTTP)
239 /// 2. Minimum TLS version (if secure, check version >= `min_version`)
240 /// 3. mTLS requirement (if `mtls_required=true`, require client cert)
241 /// 4. Client cert validity (if client cert present, it must be valid)
242 ///
243 /// # Errors
244 ///
245 /// Returns [`SecurityError::TlsRequired`] if the connection is HTTP but TLS is required.
246 /// Returns [`SecurityError::TlsVersionTooOld`] if the TLS version is below `min_version`.
247 /// Returns [`SecurityError::MtlsRequired`] if mTLS is required but no client cert is present.
248 /// Returns [`SecurityError::InvalidClientCert`] if a client cert is present but invalid.
249 pub fn validate_connection(&self, conn: &TlsConnection) -> Result<()> {
250 // Check 1: HTTPS requirement
251 if self.config.tls_required && !conn.is_secure {
252 return Err(SecurityError::TlsRequired {
253 detail: "HTTPS required, but connection is HTTP".to_string(),
254 });
255 }
256
257 // Check 2: TLS version minimum (only check if connection is secure)
258 if conn.is_secure && conn.version < self.config.min_version {
259 return Err(SecurityError::TlsVersionTooOld {
260 current: conn.version,
261 required: self.config.min_version,
262 });
263 }
264
265 // Check 3: mTLS requirement
266 if self.config.mtls_required && !conn.has_client_cert {
267 return Err(SecurityError::MtlsRequired {
268 detail: "Client certificate required, but none provided".to_string(),
269 });
270 }
271
272 // Check 4: Client certificate validity
273 if conn.has_client_cert && !conn.client_cert_valid {
274 return Err(SecurityError::InvalidClientCert {
275 detail: "Client certificate provided but validation failed".to_string(),
276 });
277 }
278
279 Ok(())
280 }
281
282 /// Get the underlying configuration
283 #[must_use]
284 pub const fn config(&self) -> &TlsConfig {
285 &self.config
286 }
287}