nono_proxy/config.rs
1//! Proxy configuration types.
2//!
3//! Defines the configuration for the proxy server, including allowed hosts,
4//! credential routes, and external proxy settings.
5
6use ipnet::IpNet;
7use serde::{Deserialize, Serialize};
8use std::net::IpAddr;
9
10/// Credential injection mode determining how credentials are inserted into requests.
11#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum InjectMode {
14 /// Inject credential into an HTTP header (default)
15 #[default]
16 Header,
17 /// Replace a pattern in the URL path with the credential
18 UrlPath,
19 /// Add or replace a query parameter with the credential
20 QueryParam,
21 /// Use HTTP Basic Authentication (credential format: "username:password")
22 BasicAuth,
23}
24
25/// Configuration for the proxy server.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ProxyConfig {
28 /// Bind address (default: 127.0.0.1)
29 #[serde(default = "default_bind_addr")]
30 pub bind_addr: IpAddr,
31
32 /// Bind port (0 = OS-assigned ephemeral port)
33 #[serde(default)]
34 pub bind_port: u16,
35
36 /// Allowed hosts for CONNECT mode (exact match + wildcards).
37 /// Empty = allow all hosts (except deny list).
38 #[serde(default)]
39 pub allowed_hosts: Vec<String>,
40
41 /// Additional CIDR ranges to deny (on top of built-in defaults).
42 #[serde(default)]
43 pub deny_cidrs: Vec<IpNet>,
44
45 /// Reverse proxy credential routes.
46 #[serde(default)]
47 pub routes: Vec<RouteConfig>,
48
49 /// External (enterprise) proxy URL for passthrough mode.
50 /// When set, CONNECT requests are chained to this proxy.
51 #[serde(default)]
52 pub external_proxy: Option<ExternalProxyConfig>,
53
54 /// Maximum concurrent connections (0 = unlimited).
55 #[serde(default)]
56 pub max_connections: usize,
57}
58
59impl Default for ProxyConfig {
60 fn default() -> Self {
61 Self {
62 bind_addr: default_bind_addr(),
63 bind_port: 0,
64 allowed_hosts: Vec::new(),
65 deny_cidrs: Vec::new(),
66 routes: Vec::new(),
67 external_proxy: None,
68 max_connections: 256,
69 }
70 }
71}
72
73fn default_bind_addr() -> IpAddr {
74 IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)
75}
76
77/// Configuration for a reverse proxy credential route.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct RouteConfig {
80 /// Path prefix for routing (e.g., "/openai")
81 pub prefix: String,
82
83 /// Upstream URL to forward to (e.g., "https://api.openai.com")
84 pub upstream: String,
85
86 /// Keystore account name to load the credential from.
87 /// If `None`, no credential is injected.
88 pub credential_key: Option<String>,
89
90 /// Injection mode (default: "header")
91 #[serde(default)]
92 pub inject_mode: InjectMode,
93
94 // --- Header mode fields ---
95 /// HTTP header name for the credential (default: "Authorization")
96 /// Only used when inject_mode is "header".
97 #[serde(default = "default_inject_header")]
98 pub inject_header: String,
99
100 /// Format string for the credential value. `{}` is replaced with the secret.
101 /// Default: "Bearer {}"
102 /// Only used when inject_mode is "header".
103 #[serde(default = "default_credential_format")]
104 pub credential_format: String,
105
106 // --- URL path mode fields ---
107 /// Pattern to match in incoming URL path. Use {} as placeholder for phantom token.
108 /// Example: "/bot{}/" matches "/bot<token>/getMe"
109 /// Only used when inject_mode is "url_path".
110 #[serde(default)]
111 pub path_pattern: Option<String>,
112
113 /// Pattern for outgoing URL path. Use {} as placeholder for real credential.
114 /// Defaults to same as path_pattern if not specified.
115 /// Only used when inject_mode is "url_path".
116 #[serde(default)]
117 pub path_replacement: Option<String>,
118
119 // --- Query param mode fields ---
120 /// Name of the query parameter to add/replace with the credential.
121 /// Only used when inject_mode is "query_param".
122 #[serde(default)]
123 pub query_param_name: Option<String>,
124
125 /// Explicit environment variable name for the phantom token (e.g., "OPENAI_API_KEY").
126 ///
127 /// When set, this is used as the SDK API key env var name instead of deriving
128 /// it from `credential_key.to_uppercase()`. Required when `credential_key` is
129 /// an `op://` URI (which would produce a nonsensical env var name).
130 #[serde(default)]
131 pub env_var: Option<String>,
132}
133
134fn default_inject_header() -> String {
135 "Authorization".to_string()
136}
137
138fn default_credential_format() -> String {
139 "Bearer {}".to_string()
140}
141
142/// Configuration for an external (enterprise) proxy.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct ExternalProxyConfig {
145 /// Proxy address (e.g., "squid.corp.internal:3128")
146 pub address: String,
147
148 /// Optional authentication for the external proxy.
149 pub auth: Option<ExternalProxyAuth>,
150}
151
152/// Authentication for an external proxy.
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct ExternalProxyAuth {
155 /// Keystore account name for proxy credentials.
156 pub keyring_account: String,
157
158 /// Authentication scheme (only "basic" supported).
159 #[serde(default = "default_auth_scheme")]
160 pub scheme: String,
161}
162
163fn default_auth_scheme() -> String {
164 "basic".to_string()
165}
166
167#[cfg(test)]
168#[allow(clippy::unwrap_used)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_default_config() {
174 let config = ProxyConfig::default();
175 assert_eq!(config.bind_addr, IpAddr::V4(std::net::Ipv4Addr::LOCALHOST));
176 assert_eq!(config.bind_port, 0);
177 assert!(config.allowed_hosts.is_empty());
178 assert!(config.routes.is_empty());
179 assert!(config.external_proxy.is_none());
180 }
181
182 #[test]
183 fn test_config_serialization() {
184 let config = ProxyConfig {
185 allowed_hosts: vec!["api.openai.com".to_string()],
186 ..Default::default()
187 };
188 let json = serde_json::to_string(&config).unwrap();
189 let deserialized: ProxyConfig = serde_json::from_str(&json).unwrap();
190 assert_eq!(deserialized.allowed_hosts, vec!["api.openai.com"]);
191 }
192}