Skip to main content

sentinel_config/
upstreams.rs

1//! Upstream configuration types
2//!
3//! This module contains configuration types for upstream backends
4//! including load balancing, health checks, and connection pooling.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9use validator::Validate;
10
11use sentinel_common::types::{HealthCheckType, LoadBalancingAlgorithm};
12
13// ============================================================================
14// Sticky Session Configuration
15// ============================================================================
16
17/// Cookie SameSite policy for sticky session cookies
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum SameSitePolicy {
21    /// Lax - Cookies sent with top-level navigations and GET from third-party sites
22    #[default]
23    Lax,
24    /// Strict - Cookies only sent in first-party context
25    Strict,
26    /// None - Cookies sent in all contexts (requires Secure)
27    None,
28}
29
30impl std::fmt::Display for SameSitePolicy {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            SameSitePolicy::Lax => write!(f, "Lax"),
34            SameSitePolicy::Strict => write!(f, "Strict"),
35            SameSitePolicy::None => write!(f, "None"),
36        }
37    }
38}
39
40/// Configuration for cookie-based sticky sessions
41///
42/// When enabled, the load balancer will set an affinity cookie on responses
43/// and use it to route subsequent requests to the same backend.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct StickySessionConfig {
46    /// Cookie name for session affinity (e.g., "SERVERID")
47    pub cookie_name: String,
48
49    /// Cookie TTL in seconds (e.g., 3600 for 1 hour)
50    pub cookie_ttl_secs: u64,
51
52    /// Cookie path (e.g., "/")
53    #[serde(default = "default_cookie_path")]
54    pub cookie_path: String,
55
56    /// Whether to set Secure and HttpOnly flags on the cookie
57    #[serde(default = "default_cookie_secure")]
58    pub cookie_secure: bool,
59
60    /// SameSite policy for the cookie
61    #[serde(default)]
62    pub cookie_same_site: SameSitePolicy,
63
64    /// Fallback load balancing algorithm when no cookie or target unavailable
65    #[serde(default = "default_sticky_fallback")]
66    pub fallback: LoadBalancingAlgorithm,
67}
68
69fn default_cookie_path() -> String {
70    "/".to_string()
71}
72
73fn default_cookie_secure() -> bool {
74    true
75}
76
77fn default_sticky_fallback() -> LoadBalancingAlgorithm {
78    LoadBalancingAlgorithm::RoundRobin
79}
80
81// ============================================================================
82// Upstream Configuration
83// ============================================================================
84
85/// Upstream configuration
86#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
87pub struct UpstreamConfig {
88    /// Unique upstream identifier
89    pub id: String,
90
91    /// Upstream targets
92    #[validate(length(min = 1, message = "At least one target is required"))]
93    pub targets: Vec<UpstreamTarget>,
94
95    /// Load balancing algorithm
96    #[serde(default = "default_lb_algorithm")]
97    pub load_balancing: LoadBalancingAlgorithm,
98
99    /// Sticky session configuration (for cookie-based session affinity)
100    pub sticky_session: Option<StickySessionConfig>,
101
102    /// Health check configuration
103    pub health_check: Option<HealthCheck>,
104
105    /// Connection pool settings
106    #[serde(default)]
107    pub connection_pool: ConnectionPoolConfig,
108
109    /// Timeouts
110    #[serde(default)]
111    pub timeouts: UpstreamTimeouts,
112
113    /// TLS configuration for upstream connections
114    pub tls: Option<UpstreamTlsConfig>,
115
116    /// HTTP version configuration
117    #[serde(default)]
118    pub http_version: HttpVersionConfig,
119}
120
121/// HTTP version configuration for upstream connections
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct HttpVersionConfig {
124    /// Minimum HTTP version (1 or 2)
125    #[serde(default = "default_min_http_version")]
126    pub min_version: u8,
127
128    /// Maximum HTTP version (1 or 2)
129    #[serde(default = "default_max_http_version")]
130    pub max_version: u8,
131
132    /// H2 ping interval in seconds (0 to disable)
133    #[serde(default)]
134    pub h2_ping_interval_secs: u64,
135
136    /// Maximum concurrent H2 streams per connection
137    #[serde(default = "default_max_h2_streams")]
138    pub max_h2_streams: usize,
139}
140
141impl Default for HttpVersionConfig {
142    fn default() -> Self {
143        Self {
144            min_version: default_min_http_version(),
145            max_version: default_max_http_version(),
146            h2_ping_interval_secs: 0,
147            max_h2_streams: default_max_h2_streams(),
148        }
149    }
150}
151
152fn default_min_http_version() -> u8 {
153    1
154}
155
156fn default_max_http_version() -> u8 {
157    2 // Enable HTTP/2 by default when TLS is used
158}
159
160fn default_max_h2_streams() -> usize {
161    100
162}
163
164/// Individual upstream target
165#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
166pub struct UpstreamTarget {
167    /// Target address (host:port)
168    pub address: String,
169
170    /// Weight for weighted load balancing
171    #[serde(default = "default_weight")]
172    pub weight: u32,
173
174    /// Maximum concurrent requests
175    pub max_requests: Option<u32>,
176
177    /// Target metadata/tags
178    #[serde(default)]
179    pub metadata: HashMap<String, String>,
180}
181
182// ============================================================================
183// Health Check Configuration
184// ============================================================================
185
186/// Health check configuration
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct HealthCheck {
189    /// Health check type
190    #[serde(rename = "type")]
191    pub check_type: HealthCheckType,
192
193    /// Interval between checks
194    #[serde(default = "default_health_check_interval")]
195    pub interval_secs: u64,
196
197    /// Timeout for health check
198    #[serde(default = "default_health_check_timeout")]
199    pub timeout_secs: u64,
200
201    /// Number of successes to mark healthy
202    #[serde(default = "default_healthy_threshold")]
203    pub healthy_threshold: u32,
204
205    /// Number of failures to mark unhealthy
206    #[serde(default = "default_unhealthy_threshold")]
207    pub unhealthy_threshold: u32,
208}
209
210// ============================================================================
211// Connection Pool Configuration
212// ============================================================================
213
214/// Connection pool configuration
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct ConnectionPoolConfig {
217    /// Maximum connections per target
218    #[serde(default = "default_max_connections_per_target")]
219    pub max_connections: usize,
220
221    /// Maximum idle connections
222    #[serde(default = "default_max_idle_connections")]
223    pub max_idle: usize,
224
225    /// Idle timeout
226    #[serde(default = "default_idle_timeout")]
227    pub idle_timeout_secs: u64,
228
229    /// Connection lifetime
230    pub max_lifetime_secs: Option<u64>,
231}
232
233impl Default for ConnectionPoolConfig {
234    fn default() -> Self {
235        Self {
236            max_connections: default_max_connections_per_target(),
237            max_idle: default_max_idle_connections(),
238            idle_timeout_secs: default_idle_timeout(),
239            max_lifetime_secs: None,
240        }
241    }
242}
243
244// ============================================================================
245// Upstream Timeouts
246// ============================================================================
247
248/// Upstream timeouts
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct UpstreamTimeouts {
251    /// Connection timeout
252    #[serde(default = "default_connect_timeout")]
253    pub connect_secs: u64,
254
255    /// Request timeout
256    #[serde(default = "default_upstream_request_timeout")]
257    pub request_secs: u64,
258
259    /// Read timeout
260    #[serde(default = "default_read_timeout")]
261    pub read_secs: u64,
262
263    /// Write timeout
264    #[serde(default = "default_write_timeout")]
265    pub write_secs: u64,
266}
267
268impl Default for UpstreamTimeouts {
269    fn default() -> Self {
270        Self {
271            connect_secs: default_connect_timeout(),
272            request_secs: default_upstream_request_timeout(),
273            read_secs: default_read_timeout(),
274            write_secs: default_write_timeout(),
275        }
276    }
277}
278
279// ============================================================================
280// Upstream TLS Configuration
281// ============================================================================
282
283/// Upstream TLS configuration
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct UpstreamTlsConfig {
286    /// SNI hostname
287    pub sni: Option<String>,
288
289    /// Skip certificate verification (DANGEROUS - testing only)
290    #[serde(default)]
291    pub insecure_skip_verify: bool,
292
293    /// Client certificate for mTLS
294    pub client_cert: Option<PathBuf>,
295
296    /// Client key for mTLS
297    pub client_key: Option<PathBuf>,
298
299    /// CA certificates
300    pub ca_cert: Option<PathBuf>,
301}
302
303// ============================================================================
304// Upstream Peer (for Phase 0 testing)
305// ============================================================================
306
307/// Simple upstream peer for Phase 0 testing
308#[derive(Debug, Clone)]
309pub struct UpstreamPeer {
310    pub address: String,
311    pub tls: bool,
312    pub host: String,
313    pub connect_timeout_secs: u64,
314    pub read_timeout_secs: u64,
315    pub write_timeout_secs: u64,
316}
317
318// ============================================================================
319// Default Value Functions
320// ============================================================================
321
322fn default_lb_algorithm() -> LoadBalancingAlgorithm {
323    LoadBalancingAlgorithm::RoundRobin
324}
325
326fn default_weight() -> u32 {
327    1
328}
329
330fn default_health_check_interval() -> u64 {
331    10
332}
333
334fn default_health_check_timeout() -> u64 {
335    5
336}
337
338fn default_healthy_threshold() -> u32 {
339    2
340}
341
342fn default_unhealthy_threshold() -> u32 {
343    3
344}
345
346fn default_max_connections_per_target() -> usize {
347    100
348}
349
350fn default_max_idle_connections() -> usize {
351    20
352}
353
354fn default_idle_timeout() -> u64 {
355    60
356}
357
358pub(crate) fn default_connect_timeout() -> u64 {
359    10
360}
361
362fn default_upstream_request_timeout() -> u64 {
363    60
364}
365
366pub(crate) fn default_read_timeout() -> u64 {
367    30
368}
369
370pub(crate) fn default_write_timeout() -> u64 {
371    30
372}