1use crate::env_vars;
7use std::env;
8use tracing::{debug, info, warn};
9use wisegate_core::ConfigProvider;
10
11#[derive(Clone, Debug)]
16pub struct StartupConfig {
17 pub listen_port: u16,
19 pub forward_port: u16,
21 pub bind_address: String,
23 pub verbose: bool,
25 pub quiet: bool,
27}
28
29pub fn print_startup_info(startup_config: &StartupConfig, config: &impl ConfigProvider) {
61 if startup_config.quiet {
62 return;
63 }
64
65 let rate_config = config.rate_limit_config();
66 let proxy_config = config.proxy_config();
67 let allowed_proxy_ips = config.allowed_proxy_ips();
68
69 info!(
70 version = env!("CARGO_PKG_VERSION"),
71 listen_port = startup_config.listen_port,
72 forward_port = startup_config.forward_port,
73 bind_address = %startup_config.bind_address,
74 "WiseGate starting"
75 );
76
77 info!(
78 max_requests = rate_config.max_requests,
79 window_secs = rate_config.window_duration.as_secs(),
80 "Rate limiting configured"
81 );
82
83 info!(
84 timeout_secs = proxy_config.timeout.as_secs(),
85 max_body_mb = proxy_config.max_body_size_mb(),
86 "Proxy configured"
87 );
88
89 let mode = if allowed_proxy_ips.is_some() {
90 "strict"
91 } else {
92 "permissive"
93 };
94 let trusted_proxies = allowed_proxy_ips.map(|ips| ips.len()).unwrap_or(0);
95
96 info!(
97 mode = mode,
98 trusted_proxies = trusted_proxies,
99 blocked_ips = config.blocked_ips().len(),
100 blocked_methods = config.blocked_methods().len(),
101 blocked_patterns = config.blocked_patterns().len(),
102 "Security configured"
103 );
104
105 let basic_auth_enabled = config.is_basic_auth_enabled();
107 let bearer_auth_enabled = config.is_bearer_auth_enabled();
108
109 if basic_auth_enabled || bearer_auth_enabled {
110 info!(
111 basic_auth = basic_auth_enabled,
112 basic_auth_users = config.auth_credentials().len(),
113 bearer_token = bearer_auth_enabled,
114 realm = config.auth_realm(),
115 "Authentication configured"
116 );
117 } else {
118 debug!("Authentication disabled (no credentials or bearer token configured)");
119 }
120
121 warn_on_suspicious_config(startup_config, config);
122
123 if startup_config.verbose {
125 print_env_config();
126 }
127}
128
129fn warn_on_suspicious_config(startup_config: &StartupConfig, config: &impl ConfigProvider) {
135 if let Some(proxy_ips) = config.allowed_proxy_ips() {
136 if proxy_ips.iter().any(|ip| ip == "0.0.0.0") {
138 warn!(
139 "CC_REVERSE_PROXY_IPS contains 0.0.0.0 — this is the bind sentinel, \
140 not a real proxy IP. Strict mode will reject every request."
141 );
142 }
143 } else if startup_config.bind_address == "0.0.0.0"
147 && !config.is_auth_enabled()
148 && config.blocked_ips().is_empty()
149 {
150 warn!(
151 "Listening on 0.0.0.0 in permissive mode with no authentication and no IP \
152 blocklist — this proxy is openly reachable. Set CC_REVERSE_PROXY_IPS, \
153 CC_HTTP_BASIC_AUTH, or BLOCKED_IPS, or bind to 127.0.0.1 for local testing."
154 );
155 }
156}
157
158fn print_env_config() {
160 for &var_name in env_vars::all_env_vars() {
161 match env::var(var_name) {
162 Ok(value) => {
163 let display_value = mask_sensitive_value(var_name, &value);
164 debug!(name = var_name, value = %display_value, "Environment variable");
165 }
166 Err(_) => {
167 debug!(name = var_name, value = "[NOT SET]", "Environment variable");
168 }
169 }
170 }
171}
172
173fn mask_sensitive_value(var_name: &str, value: &str) -> String {
178 let upper = var_name.to_uppercase();
179 if upper.contains("IP")
180 || upper.contains("PROXY")
181 || upper.contains("AUTH")
182 || upper.contains("TOKEN")
183 || upper.contains("BEARER")
184 || upper.contains("PASSWORD")
185 || upper.contains("SECRET")
186 {
187 "[CONFIGURED]".to_string()
188 } else {
189 value.to_string()
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
202 fn test_mask_sensitive_value_with_ip() {
203 assert_eq!(
204 mask_sensitive_value("BLOCKED_IPS", "192.168.1.1"),
205 "[CONFIGURED]"
206 );
207 assert_eq!(
208 mask_sensitive_value("TRUSTED_PROXY_IPS", "10.0.0.1"),
209 "[CONFIGURED]"
210 );
211 assert_eq!(
212 mask_sensitive_value("CC_REVERSE_PROXY_IPS", "172.16.0.1"),
213 "[CONFIGURED]"
214 );
215 }
216
217 #[test]
218 fn test_mask_sensitive_value_with_proxy() {
219 assert_eq!(
220 mask_sensitive_value("PROXY_ALLOWLIST", "10.0.0.1"),
221 "[CONFIGURED]"
222 );
223 assert_eq!(
224 mask_sensitive_value("TRUSTED_PROXY_IPS_VAR", "CUSTOM_VAR"),
225 "[CONFIGURED]"
226 );
227 }
228
229 #[test]
230 fn test_mask_sensitive_value_with_auth() {
231 assert_eq!(
232 mask_sensitive_value("CC_HTTP_BASIC_AUTH", "admin:secret"),
233 "[CONFIGURED]"
234 );
235 assert_eq!(
236 mask_sensitive_value("CC_HTTP_BASIC_AUTH_REALM", "MyRealm"),
237 "[CONFIGURED]"
238 );
239 }
240
241 #[test]
242 fn test_mask_sensitive_value_with_token() {
243 assert_eq!(
244 mask_sensitive_value("CC_BEARER_TOKEN", "my-secret-token"),
245 "[CONFIGURED]"
246 );
247 }
248
249 #[test]
250 fn test_mask_sensitive_value_non_sensitive() {
251 assert_eq!(mask_sensitive_value("RATE_LIMIT_REQUESTS", "100"), "100");
252 assert_eq!(mask_sensitive_value("MAX_BODY_SIZE_MB", "50"), "50");
253 assert_eq!(
254 mask_sensitive_value("BLOCKED_METHODS", "TRACE,CONNECT"),
255 "TRACE,CONNECT"
256 );
257 assert_eq!(
258 mask_sensitive_value("BLOCKED_PATTERNS", ".php,.env"),
259 ".php,.env"
260 );
261 }
262}