1use std::io::{self, ErrorKind, Read as _, Write as _};
11use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
12use std::sync::atomic::{AtomicUsize, Ordering};
13use std::thread;
14use std::time::{Duration, Instant};
15
16use openmoat_core::{CompiledPolicy, Decision, Verdict};
17use zeroize::Zeroizing;
18
19use crate::audit::{Connection, Recorder};
20use crate::broker::{Broker, Watch};
21use crate::decide::{self, RULE_REQUEST, RULE_SNI};
22use crate::request::{self, Request, Target};
23use crate::sni::{self, Hello};
24use crate::tunnel;
25use crate::upstream::{self, Refusal, Resolve};
26
27const LINGER: Duration = Duration::from_secs(1);
29const LINGER_BYTES: usize = 1 << 20;
30
31#[derive(Debug, Clone)]
33pub struct Limits {
34 pub max_head_bytes: usize,
36 pub handshake_timeout: Duration,
38 pub idle_timeout: Duration,
40 pub connect_timeout: Duration,
42 pub max_connections: usize,
44}
45
46impl Limits {
47 fn check(&self) -> io::Result<()> {
50 let now = Instant::now();
51 let sizes = [
52 ("max_head_bytes", self.max_head_bytes),
53 ("max_connections", self.max_connections),
54 ];
55 let timeouts = [
56 ("handshake_timeout", self.handshake_timeout),
57 ("idle_timeout", self.idle_timeout),
58 ("connect_timeout", self.connect_timeout),
59 ];
60 let bad = sizes
61 .iter()
62 .find(|(_, n)| *n == 0)
63 .map(|(name, _)| name)
64 .or_else(|| {
65 timeouts
66 .iter()
67 .find(|(_, d)| d.is_zero() || now.checked_add(*d).is_none())
68 .map(|(name, _)| name)
69 });
70 match bad {
71 Some(name) => Err(io::Error::new(
72 ErrorKind::InvalidInput,
73 format!("proxy limit `{name}` is zero or too large"),
74 )),
75 None => Ok(()),
76 }
77 }
78}
79
80impl Default for Limits {
81 fn default() -> Self {
82 Self {
83 max_head_bytes: 16 * 1024,
84 handshake_timeout: Duration::from_secs(10),
85 idle_timeout: Duration::from_mins(2),
86 connect_timeout: Duration::from_secs(10),
87 max_connections: 256,
88 }
89 }
90}
91
92#[derive(Clone, Copy)]
94pub struct Proxy<'a> {
95 pub policy: &'a CompiledPolicy<'a>,
97 pub resolver: &'a dyn Resolve,
99 pub recorder: &'a dyn Recorder,
101 pub limits: &'a Limits,
103 pub broker: &'a Broker,
105 pub loopback_ok: &'a [SocketAddr],
108}
109
110impl std::fmt::Debug for Proxy<'_> {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.debug_struct("Proxy")
113 .field("limits", &self.limits)
114 .field("broker", &self.broker)
115 .field("loopback_ok", &self.loopback_ok)
116 .finish_non_exhaustive()
117 }
118}
119
120impl Proxy<'_> {
121 pub fn serve(&self, listener: &TcpListener) -> io::Result<()> {
124 self.limits.check()?;
125 let address_policy = decide::address_policy(self.policy.policy());
126 let addresses = CompiledPolicy::compile(&address_policy, self.policy.context())
127 .map_err(io::Error::other)?;
128 let addresses = &addresses;
129 let active = AtomicUsize::new(0);
130 thread::scope(|scope| {
131 loop {
132 let client = match listener.accept() {
133 Ok((client, _)) => client,
134 Err(e) if is_transient(&e) => continue,
135 Err(e) => return Err(e),
136 };
137 let _ = client.set_write_timeout(Some(self.limits.handshake_timeout));
138 let slot = Slot::take(&active, self.limits.max_connections);
139 let Some(slot) = slot else {
140 respond(&client, 503, "too many connections");
141 continue;
142 };
143 let _ = thread::Builder::new().spawn_scoped(scope, move || {
146 let _slot = slot;
147 self.handle(&client, addresses);
148 });
149 }
150 })
151 }
152
153 fn handle(&self, client: &TcpStream, addresses: &CompiledPolicy<'_>) {
154 let started = Instant::now();
155 let timeout = Some(self.limits.handshake_timeout);
156 if client.set_read_timeout(timeout).is_err() || client.set_write_timeout(timeout).is_err() {
157 return;
158 }
159 let mut buf = Vec::new();
160 let deadline = started + self.limits.handshake_timeout;
161 let request = match read_head(client, &mut buf, self.limits.max_head_bytes, deadline) {
162 Ok(request) => request,
163 Err(why) => {
164 let decision = Decision::single(Verdict::Deny, RULE_REQUEST, why);
165 self.record(None, &decision, started);
166 refuse(client, 400, &decision.reasons[0]);
167 return;
168 }
169 };
170 let mut watch = Watch::new(self.broker, request.host());
174 if let Some(leak) = watch.next(&buf) {
175 let decision = leak.decision(request.host());
176 self.record(Some(&request), &decision, started);
177 refuse(client, 403, &summary(&decision));
178 return;
179 }
180 let rest = buf.split_off(request.head_len);
181 let decision = decide::host(self.policy, request.host());
182 if decision.verdict != Verdict::Allow {
183 self.record(Some(&request), &decision, started);
184 refuse(client, 403, &summary(&decision));
185 return;
186 }
187 let upstream = match upstream::connect(
188 self.resolver,
189 request.host(),
190 request.port(),
191 addresses,
192 self.loopback_ok,
193 self.limits.connect_timeout,
194 ) {
195 Ok(upstream) => upstream,
196 Err(refusal) => {
197 let decision = refusal.decision(request.host());
198 self.record(Some(&request), &decision, started);
199 let status = if matches!(refusal, Refusal::Address(..)) {
200 403
201 } else {
202 502
203 };
204 refuse(client, status, &summary(&decision));
205 return;
206 }
207 };
208 let mut inspect = |chunk: &[u8]| match watch.next(chunk) {
211 None => true,
212 Some(leak) => {
213 self.record(Some(&request), &leak.decision(request.host()), started);
214 false
215 }
216 };
217 let (first_bytes, inspect) = match &request.target {
218 Target::Connect { host, .. } => {
219 match self.check_tunnel(client, host, rest, &decision, &request, started) {
220 Some(hello) => (Zeroizing::new(hello), None),
221 None => return,
222 }
223 }
224 Target::Http { head, host, .. } => {
225 let mut decision = decision.clone();
226 decision
227 .reasons
228 .extend(self.broker.withheld(host).iter().map(|id| {
229 format!("secret `{id}` not injected: plain HTTP needs `plain_http: true`")
230 }));
231 if !self.record(Some(&request), &decision, started) {
232 refuse(client, 403, "audit log unavailable");
233 return;
234 }
235 let inspect: tunnel::Inspect<'_> = &mut inspect;
236 let injected = self.broker.inject(host, head);
239 let head = injected.as_deref().map_or(head.as_slice(), Vec::as_slice);
240 (Zeroizing::new([head, &rest].concat()), Some(inspect))
241 }
242 };
243 if (&upstream).write_all(&first_bytes).is_ok() {
244 tunnel::relay(client, &upstream, self.limits.idle_timeout, inspect);
245 }
246 }
247
248 fn check_tunnel(
253 &self,
254 client: &TcpStream,
255 host: &str,
256 mut hello: Vec<u8>,
257 allowed: &Decision,
258 request: &Request,
259 started: Instant,
260 ) -> Option<Vec<u8>> {
261 let answered = (&*client).write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n");
262 let deadline = Instant::now() + self.limits.handshake_timeout;
263 let read = answered
264 .map_err(|e| format!("answering the CONNECT: {e}"))
265 .and_then(|()| read_hello(client, &mut hello, deadline));
266 let refusal = match read {
267 Ok(Some(name)) if name == host => None,
268 Ok(Some(name)) => Some(format!(
269 "TLS server name {name} does not match CONNECT {host}"
270 )),
271 Ok(None) if host.parse::<std::net::IpAddr>().is_ok() => None,
274 Ok(None) => Some(format!(
275 "TLS ClientHello for CONNECT {host} has no server name"
276 )),
277 Err(why) => Some(why),
278 };
279 if let Some(why) = refusal {
280 let decision = Decision::single(Verdict::Deny, RULE_SNI, why);
281 self.record(Some(request), &decision, started);
282 return None;
283 }
284 self.record(Some(request), allowed, started)
285 .then_some(hello)
286 }
287
288 fn record(&self, request: Option<&Request>, decision: &Decision, started: Instant) -> bool {
291 let connection = Connection {
292 method: request.map(|r| r.method.as_str()),
293 destination: request.map(|r| (r.host(), r.port())),
294 decision,
295 latency_us: u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX),
296 };
297 match self.recorder.record(&connection) {
298 Ok(()) => true,
299 Err(error) => {
300 self.recorder.unrecorded(&error);
303 false
304 }
305 }
306 }
307}
308
309struct Slot<'a>(&'a AtomicUsize);
312
313impl<'a> Slot<'a> {
314 fn take(active: &'a AtomicUsize, max: usize) -> Option<Self> {
315 let slot = Self(active);
318 (active.fetch_add(1, Ordering::AcqRel) < max).then_some(slot)
319 }
320}
321
322impl Drop for Slot<'_> {
323 fn drop(&mut self) {
324 self.0.fetch_sub(1, Ordering::AcqRel);
325 }
326}
327
328fn read_head(
330 client: &TcpStream,
331 buf: &mut Vec<u8>,
332 max: usize,
333 deadline: Instant,
334) -> Result<Request, String> {
335 loop {
336 match request::parse(buf) {
337 Ok(Some(request)) => return Ok(request),
338 Ok(None) if buf.len() >= max => {
339 return Err(format!("request head larger than {max} bytes"));
340 }
341 Ok(None) => read_more(client, buf, max, deadline)?,
342 Err(e) => return Err(e.to_string()),
343 }
344 }
345}
346
347fn read_hello(
349 client: &TcpStream,
350 buf: &mut Vec<u8>,
351 deadline: Instant,
352) -> Result<Option<String>, String> {
353 let max = sni::MAX_HELLO + 1024;
355 loop {
356 match sni::server_name(buf) {
357 Ok(Hello::ServerName(name)) => return Ok(name),
358 Ok(Hello::Incomplete) if buf.len() >= max => {
359 return Err(sni::HelloError::TooLarge.to_string());
360 }
361 Ok(Hello::Incomplete) => read_more(client, buf, max, deadline)?,
362 Err(e) => return Err(e.to_string()),
363 }
364 }
365}
366
367fn read_more(
370 client: &TcpStream,
371 buf: &mut Vec<u8>,
372 max: usize,
373 deadline: Instant,
374) -> Result<(), String> {
375 let left = deadline.saturating_duration_since(Instant::now());
376 if left.is_zero() || client.set_read_timeout(Some(left)).is_err() {
377 return Err("the client did not finish its handshake in time".to_owned());
378 }
379 let mut chunk = [0u8; 4096];
380 let want = chunk.len().min(max.saturating_sub(buf.len())).max(1);
381 match (&*client).read(&mut chunk[..want]) {
382 Ok(0) => Err("client closed the connection early".to_owned()),
383 Ok(n) => {
384 buf.extend_from_slice(&chunk[..n]);
385 Ok(())
386 }
387 Err(e) if e.kind() == ErrorKind::Interrupted => Ok(()),
388 Err(e) => Err(format!("reading from the client: {e}")),
389 }
390}
391
392fn summary(decision: &Decision) -> String {
393 format!(
394 "{} ({})",
395 decision.reasons.join("; "),
396 decision.rules.join(", ")
397 )
398}
399
400fn refuse(client: &TcpStream, status: u16, message: &str) {
408 respond(client, status, message);
409 let _ = client.shutdown(Shutdown::Write);
410 let until = Instant::now() + LINGER;
411 let mut left = LINGER_BYTES;
412 let mut sink = [0; 8192];
413 while left > 0 {
414 let rest = until.saturating_duration_since(Instant::now());
415 if rest.is_zero() || client.set_read_timeout(Some(rest)).is_err() {
416 return;
417 }
418 match (&*client).read(&mut sink) {
419 Ok(0) | Err(_) => return,
420 Ok(n) => left = left.saturating_sub(n),
421 }
422 }
423}
424
425fn respond(client: &TcpStream, status: u16, message: &str) {
426 let reason = match status {
427 400 => "Bad Request",
428 403 => "Forbidden",
429 502 => "Bad Gateway",
430 _ => "Service Unavailable",
431 };
432 let message: String = message
434 .chars()
435 .map(|c| if c.is_control() { '?' } else { c })
436 .collect();
437 let body = format!("moat proxy: {message}\n");
438 let response = format!(
439 "HTTP/1.1 {status} {reason}\r\nContent-Type: text/plain; charset=utf-8\r\n\
440 Content-Length: {}\r\nConnection: close\r\n\r\n{body}",
441 body.len()
442 );
443 let _ = (&*client).write_all(response.as_bytes());
444}
445
446fn is_transient(e: &io::Error) -> bool {
447 matches!(
448 e.kind(),
449 ErrorKind::Interrupted | ErrorKind::ConnectionAborted | ErrorKind::ConnectionReset
450 )
451}
452
453#[cfg(test)]
454mod tests;