auth dns over atproto
at main 304 lines 9.0 kB view raw
1use std::net::IpAddr; 2use std::path::Path; 3 4use serde::Deserialize; 5use thiserror::Error; 6 7#[derive(Debug, Error)] 8pub enum ConfigError { 9 #[error("io error: {0}")] 10 Io(#[from] std::io::Error), 11 #[error("toml parse error: {0}")] 12 Toml(#[from] toml::de::Error), 13} 14 15/// Top-level config covering all onis services. 16/// 17/// Load from a TOML file with [`OnisConfig::load`]. Every field has a default, 18/// so an empty (or missing) file produces a usable config. 19#[derive(Debug, Deserialize)] 20#[serde(default)] 21pub struct OnisConfig { 22 /// Configuration for the appview service. 23 pub appview: AppviewConfig, 24 /// Configuration for the DNS server. 25 pub dns: DnsConfig, 26 /// Configuration for the verification service. 27 pub verify: VerifyConfig, 28} 29 30impl OnisConfig { 31 /// Load config from `ONIS_CONFIG` env var path, or `onis.toml` in the 32 /// current directory. Returns defaults if the file does not exist. 33 pub fn load() -> Result<Self, ConfigError> { 34 let path = std::env::var("ONIS_CONFIG").unwrap_or_else(|_| "onis.toml".to_string()); 35 let path = Path::new(&path); 36 37 if path.exists() { 38 let content = std::fs::read_to_string(path)?; 39 Ok(toml::from_str(&content)?) 40 } else { 41 Ok(Self::default()) 42 } 43 } 44} 45 46impl Default for OnisConfig { 47 fn default() -> Self { 48 Self { 49 appview: AppviewConfig::default(), 50 dns: DnsConfig::default(), 51 verify: VerifyConfig::default(), 52 } 53 } 54} 55 56#[derive(Debug, Deserialize)] 57#[serde(default)] 58pub struct AppviewConfig { 59 /// Address and port for the appview HTTP server. 60 pub bind: String, 61 /// WebSocket URL for the TAP firehose. 62 pub tap_url: String, 63 /// Whether to acknowledge TAP messages. 64 pub tap_acks: bool, 65 /// Seconds to wait before reconnecting after a TAP connection error. 66 pub tap_reconnect_delay: u64, 67 /// Path to the shared zone index SQLite database. 68 pub index_path: String, 69 /// Directory for per-DID SQLite databases. 70 pub db_dir: String, 71 /// Database pool configuration. 72 pub database: DatabaseConfig, 73} 74 75impl Default for AppviewConfig { 76 fn default() -> Self { 77 Self { 78 bind: "0.0.0.0:3000".to_string(), 79 tap_url: "ws://localhost:2480/channel".to_string(), 80 tap_acks: true, 81 tap_reconnect_delay: 5, 82 index_path: "./data/index.db".to_string(), 83 db_dir: "./data/dbs".to_string(), 84 database: DatabaseConfig::default(), 85 } 86 } 87} 88 89#[derive(Debug, Clone, Deserialize)] 90#[serde(default)] 91pub struct DatabaseConfig { 92 /// Seconds to wait when the database is locked. 93 pub busy_timeout: u64, 94 /// Max connections for per-user database pools. 95 pub user_max_connections: u32, 96 /// Max connections for the shared index database pool. 97 pub index_max_connections: u32, 98} 99 100impl Default for DatabaseConfig { 101 fn default() -> Self { 102 Self { 103 busy_timeout: 5, 104 user_max_connections: 5, 105 index_max_connections: 10, 106 } 107 } 108} 109 110#[derive(Debug, Deserialize)] 111#[serde(default)] 112pub struct DnsConfig { 113 /// URL of the appview API. 114 pub appview_url: String, 115 /// Address for the DNS server to listen on. 116 pub bind: String, 117 /// Port for the DNS server. 118 pub port: u16, 119 /// Seconds before a TCP connection times out. 120 pub tcp_timeout: u64, 121 /// Minimum TTL enforced on all DNS responses. 122 pub ttl_floor: u32, 123 /// Log a warning for queries slower than this (milliseconds). 124 pub slow_query_threshold_ms: u64, 125 /// SOA record defaults for zones without a user-published SOA. 126 pub soa: SoaConfig, 127 /// NS records to serve for all zones (fully qualified, trailing dot). 128 pub ns: Vec<String>, 129 /// Bind address for the metrics HTTP server (e.g. "0.0.0.0:9100"). 130 pub metrics_bind: String, 131} 132 133impl Default for DnsConfig { 134 fn default() -> Self { 135 Self { 136 appview_url: "http://localhost:3000".to_string(), 137 bind: "0.0.0.0".to_string(), 138 port: 5353, 139 tcp_timeout: 30, 140 ttl_floor: 60, 141 slow_query_threshold_ms: 50, 142 soa: SoaConfig::default(), 143 ns: vec![ 144 "ns1.example.com.".to_string(), 145 "ns2.example.com.".to_string(), 146 ], 147 metrics_bind: "0.0.0.0:9100".to_string(), 148 } 149 } 150} 151 152#[derive(Debug, Deserialize)] 153#[serde(default)] 154pub struct SoaConfig { 155 /// SOA record TTL in seconds. 156 pub ttl: u32, 157 /// SOA refresh interval in seconds. 158 pub refresh: i32, 159 /// SOA retry interval in seconds. 160 pub retry: i32, 161 /// SOA expire interval in seconds. 162 pub expire: i32, 163 /// SOA minimum (negative cache) TTL in seconds. 164 pub minimum: u32, 165 /// SOA MNAME (primary nameserver, fully qualified). 166 pub mname: String, 167 /// SOA RNAME (admin email in DNS format, fully qualified). 168 pub rname: String, 169} 170 171impl Default for SoaConfig { 172 fn default() -> Self { 173 Self { 174 ttl: 3600, 175 refresh: 3600, 176 retry: 900, 177 expire: 604800, 178 minimum: 300, 179 mname: "ns1.example.com.".to_string(), 180 rname: "admin.example.com.".to_string(), 181 } 182 } 183} 184 185#[derive(Debug, Deserialize)] 186#[serde(default)] 187pub struct VerifyConfig { 188 /// Onis appview to call. 189 pub appview_url: String, 190 /// Address to start listening on for api. 191 pub bind: String, 192 /// Port used for api. 193 pub port: u16, 194 /// Seconds between scheduled verification runs. 195 pub check_interval: u64, 196 /// Seconds a zone must be stale before reverification. 197 pub recheck_interval: i64, 198 /// Expected NS records that indicate correct delegation. 199 pub expected_ns: Vec<String>, 200 /// Optional custom resolver IP addresses. 201 pub nameservers: Vec<String>, 202 /// Port used when resolving against custom nameservers. 203 pub dns_port: u16, 204} 205 206impl Default for VerifyConfig { 207 fn default() -> Self { 208 Self { 209 appview_url: "http://localhost:3000".to_string(), 210 bind: "0.0.0.0".to_string(), 211 port: 3001, 212 check_interval: 60, 213 recheck_interval: 3600, 214 expected_ns: vec![ 215 "ns1.example.com".to_string(), 216 "ns2.example.com".to_string(), 217 ], 218 nameservers: vec![], 219 dns_port: 53, 220 } 221 } 222} 223 224impl VerifyConfig { 225 /// Parse the nameservers list into IP addresses. 226 /// Returns None if the list is empty. 227 pub fn parse_nameservers(&self) -> Result<Option<Vec<IpAddr>>, std::net::AddrParseError> { 228 if self.nameservers.is_empty() { 229 return Ok(None); 230 } 231 let addrs: Result<Vec<IpAddr>, _> = self 232 .nameservers 233 .iter() 234 .map(|s| s.parse()) 235 .collect(); 236 addrs.map(Some) 237 } 238} 239 240#[cfg(test)] 241#[allow(clippy::unwrap_used)] 242mod tests { 243 use super::*; 244 245 #[test] 246 fn parse_nameservers_empty_returns_none() { 247 let config = VerifyConfig::default(); 248 let result = config.parse_nameservers().unwrap(); 249 assert_eq!(result, None); 250 } 251 252 #[test] 253 fn parse_nameservers_valid_ipv4() { 254 let config = VerifyConfig { 255 nameservers: vec!["1.1.1.1".to_string(), "8.8.8.8".to_string()], 256 ..Default::default() 257 }; 258 let result = config.parse_nameservers().unwrap().unwrap(); 259 assert_eq!(result.len(), 2); 260 assert_eq!(result, vec![ 261 "1.1.1.1".parse::<IpAddr>().unwrap(), 262 "8.8.8.8".parse::<IpAddr>().unwrap(), 263 ]); 264 } 265 266 #[test] 267 fn parse_nameservers_valid_ipv6() { 268 let config = VerifyConfig { 269 nameservers: vec!["2001:4860:4860::8888".to_string()], 270 ..Default::default() 271 }; 272 let result = config.parse_nameservers().unwrap().unwrap(); 273 assert_eq!(result.len(), 1); 274 assert_eq!(result, vec!["2001:4860:4860::8888".parse::<IpAddr>().unwrap()]); 275 } 276 277 #[test] 278 fn parse_nameservers_mixed_v4_v6() { 279 let config = VerifyConfig { 280 nameservers: vec!["1.1.1.1".to_string(), "::1".to_string()], 281 ..Default::default() 282 }; 283 let result = config.parse_nameservers().unwrap().unwrap(); 284 assert_eq!(result.len(), 2); 285 } 286 287 #[test] 288 fn parse_nameservers_invalid_returns_err() { 289 let config = VerifyConfig { 290 nameservers: vec!["not-an-ip".to_string()], 291 ..Default::default() 292 }; 293 assert!(config.parse_nameservers().is_err()); 294 } 295 296 #[test] 297 fn parse_nameservers_one_invalid_fails_all() { 298 let config = VerifyConfig { 299 nameservers: vec!["1.1.1.1".to_string(), "bad".to_string()], 300 ..Default::default() 301 }; 302 assert!(config.parse_nameservers().is_err()); 303 } 304}