Skip to main content

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)]
62pub enum TlsVersion {
63    /// TLS 1.0 (deprecated, insecure)
64    V1_0,
65    /// TLS 1.1 (deprecated, insecure)
66    V1_1,
67    /// TLS 1.2 (modern baseline)
68    V1_2,
69    /// TLS 1.3 (current standard)
70    V1_3,
71}
72
73impl fmt::Display for TlsVersion {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            Self::V1_0 => write!(f, "TLS 1.0"),
77            Self::V1_1 => write!(f, "TLS 1.1"),
78            Self::V1_2 => write!(f, "TLS 1.2"),
79            Self::V1_3 => write!(f, "TLS 1.3"),
80        }
81    }
82}
83
84/// TLS connection information extracted from HTTP request.
85///
86/// This struct captures the essential TLS/security information from an
87/// incoming connection. It's created by the HTTP adapter layer and passed
88/// to the enforcer for validation.
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct TlsConnection {
91    /// Whether the connection is over HTTPS/TLS (true) or HTTP (false)
92    pub is_secure: bool,
93
94    /// The TLS protocol version used (only valid if is_secure=true)
95    pub version: TlsVersion,
96
97    /// Whether a client certificate was presented
98    pub has_client_cert: bool,
99
100    /// Whether the client certificate has been validated by the server
101    /// (Note: The server does this validation; this flag indicates the result)
102    pub client_cert_valid: bool,
103}
104
105impl TlsConnection {
106    /// Create a new TLS connection info (typically HTTP, not secure)
107    #[must_use]
108    pub fn new_http() -> Self {
109        Self {
110            is_secure:         false,
111            version:           TlsVersion::V1_2, // Irrelevant for HTTP
112            has_client_cert:   false,
113            client_cert_valid: false,
114        }
115    }
116
117    /// Create a new secure TLS connection
118    #[must_use]
119    pub fn new_secure(version: TlsVersion) -> Self {
120        Self {
121            is_secure: true,
122            version,
123            has_client_cert: false,
124            client_cert_valid: false,
125        }
126    }
127
128    /// Create a new secure TLS connection with a valid client certificate
129    #[must_use]
130    pub fn new_secure_with_client_cert(version: TlsVersion) -> Self {
131        Self {
132            is_secure: true,
133            version,
134            has_client_cert: true,
135            client_cert_valid: true,
136        }
137    }
138}
139
140/// TLS Security Configuration
141///
142/// Defines what TLS/SSL requirements must be met for a connection.
143/// This is typically derived from a SecurityProfile.
144#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
145pub struct TlsConfig {
146    /// If true, all connections must be HTTPS (TLS required)
147    pub tls_required: bool,
148
149    /// If true, all connections must include a valid client certificate
150    pub mtls_required: bool,
151
152    /// Minimum allowed TLS version (enforce via check 2)
153    pub min_version: TlsVersion,
154}
155
156impl TlsConfig {
157    /// Create a permissive TLS configuration (for development/staging)
158    ///
159    /// - HTTPS optional
160    /// - Client certs optional
161    /// - TLS 1.2 minimum (if TLS is used)
162    #[must_use]
163    pub fn permissive() -> Self {
164        Self {
165            tls_required:  false,
166            mtls_required: false,
167            min_version:   TlsVersion::V1_2,
168        }
169    }
170
171    /// Create a standard TLS configuration (for production)
172    ///
173    /// - HTTPS required
174    /// - Client certs optional
175    /// - TLS 1.2 minimum
176    #[must_use]
177    pub fn standard() -> Self {
178        Self {
179            tls_required:  true,
180            mtls_required: false,
181            min_version:   TlsVersion::V1_2,
182        }
183    }
184
185    /// Create a strict TLS configuration (for regulated environments)
186    ///
187    /// - HTTPS required
188    /// - Client certs required (mTLS)
189    /// - TLS 1.3 minimum
190    #[must_use]
191    pub fn strict() -> Self {
192        Self {
193            tls_required:  true,
194            mtls_required: true,
195            min_version:   TlsVersion::V1_3,
196        }
197    }
198}
199
200/// TLS Security Enforcer
201///
202/// Validates incoming connections against TLS security requirements.
203/// Used as the first layer in the security middleware pipeline.
204#[derive(Debug, Clone)]
205pub struct TlsEnforcer {
206    config: TlsConfig,
207}
208
209impl TlsEnforcer {
210    /// Create a new TLS enforcer from configuration
211    #[must_use]
212    pub fn from_config(config: TlsConfig) -> Self {
213        Self { config }
214    }
215
216    /// Create enforcer with permissive settings (development)
217    #[must_use]
218    pub fn permissive() -> Self {
219        Self::from_config(TlsConfig::permissive())
220    }
221
222    /// Create enforcer with standard settings (production)
223    #[must_use]
224    pub fn standard() -> Self {
225        Self::from_config(TlsConfig::standard())
226    }
227
228    /// Create enforcer with strict settings (regulated)
229    #[must_use]
230    pub fn strict() -> Self {
231        Self::from_config(TlsConfig::strict())
232    }
233
234    /// Validate a TLS connection against the enforcer's configuration
235    ///
236    /// Performs 4 validation checks in order:
237    /// 1. HTTPS requirement (if tls_required=true, reject HTTP)
238    /// 2. Minimum TLS version (if secure, check version >= min_version)
239    /// 3. mTLS requirement (if mtls_required=true, require client cert)
240    /// 4. Client cert validity (if client cert present, it must be valid)
241    ///
242    /// Returns Ok(()) if all checks pass, Err(TlsError) if any fail.
243    pub fn validate_connection(&self, conn: &TlsConnection) -> Result<()> {
244        // Check 1: HTTPS requirement
245        if self.config.tls_required && !conn.is_secure {
246            return Err(SecurityError::TlsRequired {
247                detail: "HTTPS required, but connection is HTTP".to_string(),
248            });
249        }
250
251        // Check 2: TLS version minimum (only check if connection is secure)
252        if conn.is_secure && conn.version < self.config.min_version {
253            return Err(SecurityError::TlsVersionTooOld {
254                current:  conn.version,
255                required: self.config.min_version,
256            });
257        }
258
259        // Check 3: mTLS requirement
260        if self.config.mtls_required && !conn.has_client_cert {
261            return Err(SecurityError::MtlsRequired {
262                detail: "Client certificate required, but none provided".to_string(),
263            });
264        }
265
266        // Check 4: Client certificate validity
267        if conn.has_client_cert && !conn.client_cert_valid {
268            return Err(SecurityError::InvalidClientCert {
269                detail: "Client certificate provided but validation failed".to_string(),
270            });
271        }
272
273        Ok(())
274    }
275
276    /// Get the underlying configuration
277    #[must_use]
278    pub const fn config(&self) -> &TlsConfig {
279        &self.config
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    // ============================================================================
288    // Check 1: HTTPS Requirement Tests
289    // ============================================================================
290
291    #[test]
292    fn test_http_allowed_when_tls_not_required() {
293        let enforcer = TlsEnforcer::permissive();
294        let conn = TlsConnection::new_http();
295
296        assert!(enforcer.validate_connection(&conn).is_ok());
297    }
298
299    #[test]
300    fn test_http_rejected_when_tls_required() {
301        let enforcer = TlsEnforcer::standard();
302        let conn = TlsConnection::new_http();
303
304        let result = enforcer.validate_connection(&conn);
305        assert!(matches!(result, Err(SecurityError::TlsRequired { .. })));
306    }
307
308    #[test]
309    fn test_https_allowed_when_tls_required() {
310        let enforcer = TlsEnforcer::standard();
311        let conn = TlsConnection::new_secure(TlsVersion::V1_3);
312
313        assert!(enforcer.validate_connection(&conn).is_ok());
314    }
315
316    // ============================================================================
317    // Check 2: TLS Version Minimum Tests
318    // ============================================================================
319
320    #[test]
321    fn test_tls_1_0_rejected_when_min_1_3() {
322        let enforcer = TlsEnforcer::strict(); // min_version = TLS 1.3
323        let conn = TlsConnection::new_secure(TlsVersion::V1_0);
324
325        let result = enforcer.validate_connection(&conn);
326        assert!(matches!(result, Err(SecurityError::TlsVersionTooOld { .. })));
327    }
328
329    #[test]
330    fn test_tls_1_2_rejected_when_min_1_3() {
331        let enforcer = TlsEnforcer::strict(); // min_version = TLS 1.3
332        let conn = TlsConnection::new_secure(TlsVersion::V1_2);
333
334        let result = enforcer.validate_connection(&conn);
335        assert!(matches!(result, Err(SecurityError::TlsVersionTooOld { .. })));
336    }
337
338    #[test]
339    fn test_tls_1_3_allowed_when_min_1_2() {
340        let enforcer = TlsEnforcer::standard(); // min_version = TLS 1.2
341        let conn = TlsConnection::new_secure(TlsVersion::V1_3);
342
343        assert!(enforcer.validate_connection(&conn).is_ok());
344    }
345
346    #[test]
347    fn test_tls_1_2_allowed_when_min_1_2() {
348        let enforcer = TlsEnforcer::standard(); // min_version = TLS 1.2
349        let conn = TlsConnection::new_secure(TlsVersion::V1_2);
350
351        assert!(enforcer.validate_connection(&conn).is_ok());
352    }
353
354    #[test]
355    fn test_tls_version_check_skipped_for_http() {
356        // When connection is HTTP, version check is irrelevant
357        let enforcer = TlsEnforcer::permissive();
358        let conn = TlsConnection::new_http();
359
360        // Even though version is V1_2, this passes because is_secure=false
361        assert!(enforcer.validate_connection(&conn).is_ok());
362    }
363
364    // ============================================================================
365    // Check 3: mTLS Requirement Tests
366    // ============================================================================
367
368    #[test]
369    fn test_client_cert_optional_when_mtls_not_required() {
370        let enforcer = TlsEnforcer::standard(); // mtls_required = false
371        let conn = TlsConnection::new_secure(TlsVersion::V1_3);
372
373        assert!(enforcer.validate_connection(&conn).is_ok());
374    }
375
376    #[test]
377    fn test_client_cert_required_when_mtls_required() {
378        let enforcer = TlsEnforcer::strict(); // mtls_required = true
379        let conn = TlsConnection::new_secure(TlsVersion::V1_3);
380
381        let result = enforcer.validate_connection(&conn);
382        assert!(matches!(result, Err(SecurityError::MtlsRequired { .. })));
383    }
384
385    #[test]
386    fn test_client_cert_allowed_when_mtls_required() {
387        let enforcer = TlsEnforcer::strict(); // mtls_required = true
388        let conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
389
390        assert!(enforcer.validate_connection(&conn).is_ok());
391    }
392
393    // ============================================================================
394    // Check 4: Client Certificate Validity Tests
395    // ============================================================================
396
397    #[test]
398    fn test_invalid_cert_rejected() {
399        let enforcer = TlsEnforcer::strict();
400        let conn = TlsConnection {
401            is_secure:         true,
402            version:           TlsVersion::V1_3,
403            has_client_cert:   true,
404            client_cert_valid: false, // Invalid!
405        };
406
407        let result = enforcer.validate_connection(&conn);
408        assert!(matches!(result, Err(SecurityError::InvalidClientCert { .. })));
409    }
410
411    #[test]
412    fn test_valid_cert_accepted() {
413        let enforcer = TlsEnforcer::strict();
414        let conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
415
416        assert!(enforcer.validate_connection(&conn).is_ok());
417    }
418
419    // ============================================================================
420    // Combination Tests (Multiple Checks)
421    // ============================================================================
422
423    #[test]
424    fn test_all_3_tls_settings_enforced_together() {
425        let enforcer = TlsEnforcer::strict();
426        // strict: tls_required=true, mtls_required=true, min_version=V1_3
427
428        // This should pass all checks
429        let valid_conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
430        assert!(enforcer.validate_connection(&valid_conn).is_ok());
431
432        // Fails check 1: HTTP when TLS required
433        let http_conn = TlsConnection::new_http();
434        assert!(matches!(
435            enforcer.validate_connection(&http_conn),
436            Err(SecurityError::TlsRequired { .. })
437        ));
438
439        // Fails check 2: TLS 1.2 when min 1.3 required
440        let old_tls_conn = TlsConnection::new_secure(TlsVersion::V1_2);
441        assert!(matches!(
442            enforcer.validate_connection(&old_tls_conn),
443            Err(SecurityError::TlsVersionTooOld { .. })
444        ));
445
446        // Fails check 3: No client cert when mTLS required
447        let no_cert_conn = TlsConnection::new_secure(TlsVersion::V1_3);
448        assert!(matches!(
449            enforcer.validate_connection(&no_cert_conn),
450            Err(SecurityError::MtlsRequired { .. })
451        ));
452    }
453
454    // ============================================================================
455    // Error Message Tests
456    // ============================================================================
457
458    #[test]
459    fn test_error_messages_clear_and_loggable() {
460        let enforcer = TlsEnforcer::strict();
461
462        let tls_required_err = enforcer.validate_connection(&TlsConnection::new_http());
463        if let Err(SecurityError::TlsRequired { detail }) = tls_required_err {
464            assert!(!detail.is_empty());
465            assert!(detail.contains("HTTP") || detail.contains("HTTPS"));
466        } else {
467            panic!("Expected TlsRequired error");
468        }
469
470        let tls_version_err =
471            enforcer.validate_connection(&TlsConnection::new_secure(TlsVersion::V1_0));
472        if let Err(SecurityError::TlsVersionTooOld { current, required }) = tls_version_err {
473            assert_eq!(current, TlsVersion::V1_0);
474            assert_eq!(required, TlsVersion::V1_3);
475        } else {
476            panic!("Expected TlsVersionTooOld error");
477        }
478    }
479
480    // ============================================================================
481    // Configuration Tests
482    // ============================================================================
483
484    #[test]
485    fn test_permissive_config() {
486        let config = TlsConfig::permissive();
487        assert!(!config.tls_required);
488        assert!(!config.mtls_required);
489        assert_eq!(config.min_version, TlsVersion::V1_2);
490    }
491
492    #[test]
493    fn test_standard_config() {
494        let config = TlsConfig::standard();
495        assert!(config.tls_required);
496        assert!(!config.mtls_required);
497        assert_eq!(config.min_version, TlsVersion::V1_2);
498    }
499
500    #[test]
501    fn test_strict_config() {
502        let config = TlsConfig::strict();
503        assert!(config.tls_required);
504        assert!(config.mtls_required);
505        assert_eq!(config.min_version, TlsVersion::V1_3);
506    }
507
508    #[test]
509    fn test_enforcer_helpers() {
510        let permissive = TlsEnforcer::permissive();
511        assert!(!permissive.config().tls_required);
512
513        let standard = TlsEnforcer::standard();
514        assert!(standard.config().tls_required);
515
516        let strict = TlsEnforcer::strict();
517        assert!(strict.config().mtls_required);
518    }
519
520    // ============================================================================
521    // TlsVersion Tests
522    // ============================================================================
523
524    #[test]
525    fn test_tls_version_display() {
526        assert_eq!(TlsVersion::V1_0.to_string(), "TLS 1.0");
527        assert_eq!(TlsVersion::V1_1.to_string(), "TLS 1.1");
528        assert_eq!(TlsVersion::V1_2.to_string(), "TLS 1.2");
529        assert_eq!(TlsVersion::V1_3.to_string(), "TLS 1.3");
530    }
531
532    #[test]
533    fn test_tls_version_ordering() {
534        assert!(TlsVersion::V1_0 < TlsVersion::V1_1);
535        assert!(TlsVersion::V1_1 < TlsVersion::V1_2);
536        assert!(TlsVersion::V1_2 < TlsVersion::V1_3);
537        assert!(TlsVersion::V1_3 > TlsVersion::V1_2);
538    }
539
540    #[test]
541    fn test_tls_connection_helpers() {
542        let http_conn = TlsConnection::new_http();
543        assert!(!http_conn.is_secure);
544
545        let secure_conn = TlsConnection::new_secure(TlsVersion::V1_3);
546        assert!(secure_conn.is_secure);
547        assert!(!secure_conn.has_client_cert);
548
549        let mtls_conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
550        assert!(mtls_conn.is_secure);
551        assert!(mtls_conn.has_client_cert);
552        assert!(mtls_conn.client_cert_valid);
553    }
554
555    // ============================================================================
556    // Edge Case Tests
557    // ============================================================================
558
559    #[test]
560    fn test_custom_config_from_individual_settings() {
561        let config = TlsConfig {
562            tls_required:  true,
563            mtls_required: false,
564            min_version:   TlsVersion::V1_2,
565        };
566
567        let enforcer = TlsEnforcer::from_config(config);
568
569        // HTTP should fail (tls_required=true)
570        let http_conn = TlsConnection::new_http();
571        assert!(matches!(
572            enforcer.validate_connection(&http_conn),
573            Err(SecurityError::TlsRequired { .. })
574        ));
575
576        // HTTPS with TLS 1.2 should pass
577        let secure_conn = TlsConnection::new_secure(TlsVersion::V1_2);
578        assert!(enforcer.validate_connection(&secure_conn).is_ok());
579
580        // HTTPS without client cert should pass (mtls_required=false)
581        let no_cert_conn = TlsConnection::new_secure(TlsVersion::V1_3);
582        assert!(enforcer.validate_connection(&no_cert_conn).is_ok());
583    }
584
585    #[test]
586    fn test_http_with_certificate_info_still_fails_when_tls_required() {
587        let enforcer = TlsEnforcer::standard(); // tls_required=true
588
589        // Even with client cert info, HTTP should fail
590        let http_with_cert_info = TlsConnection {
591            is_secure:         false, // Still HTTP
592            version:           TlsVersion::V1_2,
593            has_client_cert:   true,
594            client_cert_valid: true,
595        };
596
597        assert!(matches!(
598            enforcer.validate_connection(&http_with_cert_info),
599            Err(SecurityError::TlsRequired { .. })
600        ));
601    }
602}