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)]
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}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    // ============================================================================
294    // Check 1: HTTPS Requirement Tests
295    // ============================================================================
296
297    #[test]
298    fn test_http_allowed_when_tls_not_required() {
299        let enforcer = TlsEnforcer::permissive();
300        let conn = TlsConnection::new_http();
301
302        enforcer
303            .validate_connection(&conn)
304            .unwrap_or_else(|e| panic!("expected HTTP allowed when TLS not required: {e}"));
305    }
306
307    #[test]
308    fn test_http_rejected_when_tls_required() {
309        let enforcer = TlsEnforcer::standard();
310        let conn = TlsConnection::new_http();
311
312        let result = enforcer.validate_connection(&conn);
313        assert!(matches!(result, Err(SecurityError::TlsRequired { .. })));
314    }
315
316    #[test]
317    fn test_https_allowed_when_tls_required() {
318        let enforcer = TlsEnforcer::standard();
319        let conn = TlsConnection::new_secure(TlsVersion::V1_3);
320
321        enforcer
322            .validate_connection(&conn)
323            .unwrap_or_else(|e| panic!("expected HTTPS allowed when TLS required: {e}"));
324    }
325
326    // ============================================================================
327    // Check 2: TLS Version Minimum Tests
328    // ============================================================================
329
330    #[test]
331    fn test_tls_1_0_rejected_when_min_1_3() {
332        let enforcer = TlsEnforcer::strict(); // min_version = TLS 1.3
333        let conn = TlsConnection::new_secure(TlsVersion::V1_0);
334
335        let result = enforcer.validate_connection(&conn);
336        assert!(matches!(result, Err(SecurityError::TlsVersionTooOld { .. })));
337    }
338
339    #[test]
340    fn test_tls_1_2_rejected_when_min_1_3() {
341        let enforcer = TlsEnforcer::strict(); // min_version = TLS 1.3
342        let conn = TlsConnection::new_secure(TlsVersion::V1_2);
343
344        let result = enforcer.validate_connection(&conn);
345        assert!(matches!(result, Err(SecurityError::TlsVersionTooOld { .. })));
346    }
347
348    #[test]
349    fn test_tls_1_3_allowed_when_min_1_2() {
350        let enforcer = TlsEnforcer::standard(); // min_version = TLS 1.2
351        let conn = TlsConnection::new_secure(TlsVersion::V1_3);
352
353        enforcer
354            .validate_connection(&conn)
355            .unwrap_or_else(|e| panic!("expected TLS 1.3 allowed when min 1.2: {e}"));
356    }
357
358    #[test]
359    fn test_tls_1_2_allowed_when_min_1_2() {
360        let enforcer = TlsEnforcer::standard(); // min_version = TLS 1.2
361        let conn = TlsConnection::new_secure(TlsVersion::V1_2);
362
363        enforcer
364            .validate_connection(&conn)
365            .unwrap_or_else(|e| panic!("expected TLS 1.2 allowed when min 1.2: {e}"));
366    }
367
368    #[test]
369    fn test_tls_version_check_skipped_for_http() {
370        // When connection is HTTP, version check is irrelevant
371        let enforcer = TlsEnforcer::permissive();
372        let conn = TlsConnection::new_http();
373
374        // Even though version is V1_2, this passes because is_secure=false
375        enforcer
376            .validate_connection(&conn)
377            .unwrap_or_else(|e| panic!("expected version check skipped for HTTP: {e}"));
378    }
379
380    // ============================================================================
381    // Check 3: mTLS Requirement Tests
382    // ============================================================================
383
384    #[test]
385    fn test_client_cert_optional_when_mtls_not_required() {
386        let enforcer = TlsEnforcer::standard(); // mtls_required = false
387        let conn = TlsConnection::new_secure(TlsVersion::V1_3);
388
389        enforcer.validate_connection(&conn).unwrap_or_else(|e| {
390            panic!("expected no client cert needed when mTLS not required: {e}")
391        });
392    }
393
394    #[test]
395    fn test_client_cert_required_when_mtls_required() {
396        let enforcer = TlsEnforcer::strict(); // mtls_required = true
397        let conn = TlsConnection::new_secure(TlsVersion::V1_3);
398
399        let result = enforcer.validate_connection(&conn);
400        assert!(matches!(result, Err(SecurityError::MtlsRequired { .. })));
401    }
402
403    #[test]
404    fn test_client_cert_allowed_when_mtls_required() {
405        let enforcer = TlsEnforcer::strict(); // mtls_required = true
406        let conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
407
408        enforcer.validate_connection(&conn).unwrap_or_else(|e| {
409            panic!("expected valid client cert accepted when mTLS required: {e}")
410        });
411    }
412
413    // ============================================================================
414    // Check 4: Client Certificate Validity Tests
415    // ============================================================================
416
417    #[test]
418    fn test_invalid_cert_rejected() {
419        let enforcer = TlsEnforcer::strict();
420        let conn = TlsConnection {
421            is_secure:         true,
422            version:           TlsVersion::V1_3,
423            has_client_cert:   true,
424            client_cert_valid: false, // Invalid!
425        };
426
427        let result = enforcer.validate_connection(&conn);
428        assert!(matches!(result, Err(SecurityError::InvalidClientCert { .. })));
429    }
430
431    #[test]
432    fn test_valid_cert_accepted() {
433        let enforcer = TlsEnforcer::strict();
434        let conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
435
436        enforcer
437            .validate_connection(&conn)
438            .unwrap_or_else(|e| panic!("expected valid cert accepted: {e}"));
439    }
440
441    // ============================================================================
442    // Combination Tests (Multiple Checks)
443    // ============================================================================
444
445    #[test]
446    fn test_all_3_tls_settings_enforced_together() {
447        let enforcer = TlsEnforcer::strict();
448        // strict: tls_required=true, mtls_required=true, min_version=V1_3
449
450        // This should pass all checks
451        let valid_conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
452        enforcer
453            .validate_connection(&valid_conn)
454            .unwrap_or_else(|e| panic!("expected all checks to pass: {e}"));
455
456        // Fails check 1: HTTP when TLS required
457        let http_conn = TlsConnection::new_http();
458        assert!(matches!(
459            enforcer.validate_connection(&http_conn),
460            Err(SecurityError::TlsRequired { .. })
461        ));
462
463        // Fails check 2: TLS 1.2 when min 1.3 required
464        let old_tls_conn = TlsConnection::new_secure(TlsVersion::V1_2);
465        assert!(matches!(
466            enforcer.validate_connection(&old_tls_conn),
467            Err(SecurityError::TlsVersionTooOld { .. })
468        ));
469
470        // Fails check 3: No client cert when mTLS required
471        let no_cert_conn = TlsConnection::new_secure(TlsVersion::V1_3);
472        assert!(matches!(
473            enforcer.validate_connection(&no_cert_conn),
474            Err(SecurityError::MtlsRequired { .. })
475        ));
476    }
477
478    // ============================================================================
479    // Error Message Tests
480    // ============================================================================
481
482    #[test]
483    fn test_error_messages_clear_and_loggable() {
484        let enforcer = TlsEnforcer::strict();
485
486        let tls_required_err = enforcer.validate_connection(&TlsConnection::new_http());
487        if let Err(SecurityError::TlsRequired { detail }) = tls_required_err {
488            assert!(!detail.is_empty());
489            assert!(detail.contains("HTTP") || detail.contains("HTTPS"));
490        } else {
491            panic!("Expected TlsRequired error");
492        }
493
494        let tls_version_err =
495            enforcer.validate_connection(&TlsConnection::new_secure(TlsVersion::V1_0));
496        if let Err(SecurityError::TlsVersionTooOld { current, required }) = tls_version_err {
497            assert_eq!(current, TlsVersion::V1_0);
498            assert_eq!(required, TlsVersion::V1_3);
499        } else {
500            panic!("Expected TlsVersionTooOld error");
501        }
502    }
503
504    // ============================================================================
505    // Configuration Tests
506    // ============================================================================
507
508    #[test]
509    fn test_permissive_config() {
510        let config = TlsConfig::permissive();
511        assert!(!config.tls_required);
512        assert!(!config.mtls_required);
513        assert_eq!(config.min_version, TlsVersion::V1_2);
514    }
515
516    #[test]
517    fn test_standard_config() {
518        let config = TlsConfig::standard();
519        assert!(config.tls_required);
520        assert!(!config.mtls_required);
521        assert_eq!(config.min_version, TlsVersion::V1_2);
522    }
523
524    #[test]
525    fn test_strict_config() {
526        let config = TlsConfig::strict();
527        assert!(config.tls_required);
528        assert!(config.mtls_required);
529        assert_eq!(config.min_version, TlsVersion::V1_3);
530    }
531
532    #[test]
533    fn test_enforcer_helpers() {
534        let permissive = TlsEnforcer::permissive();
535        assert!(!permissive.config().tls_required);
536
537        let standard = TlsEnforcer::standard();
538        assert!(standard.config().tls_required);
539
540        let strict = TlsEnforcer::strict();
541        assert!(strict.config().mtls_required);
542    }
543
544    // ============================================================================
545    // TlsVersion Tests
546    // ============================================================================
547
548    #[test]
549    fn test_tls_version_display() {
550        assert_eq!(TlsVersion::V1_0.to_string(), "TLS 1.0");
551        assert_eq!(TlsVersion::V1_1.to_string(), "TLS 1.1");
552        assert_eq!(TlsVersion::V1_2.to_string(), "TLS 1.2");
553        assert_eq!(TlsVersion::V1_3.to_string(), "TLS 1.3");
554    }
555
556    #[test]
557    fn test_tls_version_ordering() {
558        assert!(TlsVersion::V1_0 < TlsVersion::V1_1);
559        assert!(TlsVersion::V1_1 < TlsVersion::V1_2);
560        assert!(TlsVersion::V1_2 < TlsVersion::V1_3);
561        assert!(TlsVersion::V1_3 > TlsVersion::V1_2);
562    }
563
564    #[test]
565    fn test_tls_connection_helpers() {
566        let http_conn = TlsConnection::new_http();
567        assert!(!http_conn.is_secure);
568
569        let secure_conn = TlsConnection::new_secure(TlsVersion::V1_3);
570        assert!(secure_conn.is_secure);
571        assert!(!secure_conn.has_client_cert);
572
573        let mtls_conn = TlsConnection::new_secure_with_client_cert(TlsVersion::V1_3);
574        assert!(mtls_conn.is_secure);
575        assert!(mtls_conn.has_client_cert);
576        assert!(mtls_conn.client_cert_valid);
577    }
578
579    // ============================================================================
580    // Edge Case Tests
581    // ============================================================================
582
583    #[test]
584    fn test_custom_config_from_individual_settings() {
585        let config = TlsConfig {
586            tls_required:  true,
587            mtls_required: false,
588            min_version:   TlsVersion::V1_2,
589        };
590
591        let enforcer = TlsEnforcer::from_config(config);
592
593        // HTTP should fail (tls_required=true)
594        let http_conn = TlsConnection::new_http();
595        assert!(matches!(
596            enforcer.validate_connection(&http_conn),
597            Err(SecurityError::TlsRequired { .. })
598        ));
599
600        // HTTPS with TLS 1.2 should pass
601        let secure_conn = TlsConnection::new_secure(TlsVersion::V1_2);
602        enforcer
603            .validate_connection(&secure_conn)
604            .unwrap_or_else(|e| panic!("expected HTTPS with TLS 1.2 to pass: {e}"));
605
606        // HTTPS without client cert should pass (mtls_required=false)
607        let no_cert_conn = TlsConnection::new_secure(TlsVersion::V1_3);
608        enforcer
609            .validate_connection(&no_cert_conn)
610            .unwrap_or_else(|e| panic!("expected HTTPS without client cert to pass: {e}"));
611    }
612
613    #[test]
614    fn test_http_with_certificate_info_still_fails_when_tls_required() {
615        let enforcer = TlsEnforcer::standard(); // tls_required=true
616
617        // Even with client cert info, HTTP should fail
618        let http_with_cert_info = TlsConnection {
619            is_secure:         false, // Still HTTP
620            version:           TlsVersion::V1_2,
621            has_client_cert:   true,
622            client_cert_valid: true,
623        };
624
625        assert!(matches!(
626            enforcer.validate_connection(&http_with_cert_info),
627            Err(SecurityError::TlsRequired { .. })
628        ));
629    }
630}