1mod capacity;
15mod limits;
16mod recheck;
17mod terminate;
18mod wire;
19
20use std::io::{self, ErrorKind, Write as _};
21use std::net::{SocketAddr, TcpListener, TcpStream};
22use std::sync::atomic::{AtomicUsize, Ordering};
23use std::thread;
24use std::time::Instant;
25
26use openmoat_core::{CompiledPolicy, Decision, Verdict};
27use zeroize::Zeroizing;
28
29pub use self::capacity::RULE_CAPACITY;
30use self::capacity::{Full, Refusals};
31pub use self::limits::Limits;
32pub(crate) use self::wire::response;
33use self::wire::{read_head, read_hello, refuse, respond, summary};
34use crate::audit::{Connection, Recorder};
35use crate::body::{self, BodyEnd, Framing};
36use crate::broker::{Broker, Channel, Watch};
37use crate::decide::{self, RULE_REQUEST, RULE_SNI};
38use crate::local::{self, LocalService};
39use crate::request::{Request, Target};
40use crate::session::{AuthError, RULE_SESSION, Sessions};
41use crate::slots::{PENDING, Share, Shares, WITHOUT_CREDENTIAL};
42use crate::tls::Tls;
43use crate::tunnel::{self, Forward};
44use crate::upstream::{self, Refusal, Resolve};
45
46const MALFORMED_CHUNKS: &str = "malformed chunked request body";
48
49#[derive(Clone, Copy)]
51pub struct Proxy<'a> {
52 pub policy: &'a CompiledPolicy<'a>,
54 pub resolver: &'a dyn Resolve,
56 pub recorder: &'a dyn Recorder,
58 pub limits: &'a Limits,
60 pub broker: &'a Broker,
62 pub loopback_ok: &'a [SocketAddr],
65 pub allow_ech: bool,
68 pub sessions: Option<&'a Sessions<'a>>,
73 pub local: Option<&'a dyn LocalService>,
76 pub tls: Option<&'a Tls>,
81}
82
83impl std::fmt::Debug for Proxy<'_> {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.debug_struct("Proxy")
86 .field("limits", &self.limits)
87 .field("broker", &self.broker)
88 .field("loopback_ok", &self.loopback_ok)
89 .field("allow_ech", &self.allow_ech)
90 .field("sessions", &self.sessions)
91 .field("local", &self.local.is_some())
92 .field("tls", &self.tls.is_some())
93 .finish_non_exhaustive()
94 }
95}
96
97struct Served<'s> {
99 addresses: &'s CompiledPolicy<'s>,
100 ports: &'s CompiledPolicy<'s>,
102 shares: Shares,
103}
104
105#[derive(Clone, Copy)]
108struct Turn<'s> {
109 session: Option<&'s str>,
110 started: Instant,
111}
112
113impl Proxy<'_> {
114 pub fn serve(&self, listener: &TcpListener) -> io::Result<()> {
117 self.limits.check()?;
118 let address_policy = decide::address_policy(self.policy.policy());
119 let addresses = CompiledPolicy::compile(&address_policy, self.policy.context())
120 .map_err(io::Error::other)?;
121 let port_policy = decide::port_policy(self.policy.policy());
122 let ports = CompiledPolicy::compile(&port_policy, self.policy.context())
123 .map_err(io::Error::other)?;
124 let served = Served {
125 addresses: &addresses,
126 ports: &ports,
127 shares: Shares::new(self.limits.max_connections),
128 };
129 let served = &served;
130 let active = AtomicUsize::new(0);
131 let refusals = Refusals::default();
132 thread::scope(|scope| {
133 loop {
134 let client = match listener.accept() {
135 Ok((client, _)) => client,
136 Err(e) if is_transient(&e) => continue,
137 Err(e) => return Err(e),
138 };
139 refusals.flush(self.recorder, Instant::now());
140 let _ = client.set_write_timeout(Some(self.limits.handshake_timeout));
141 let full = |kind: Full| {
142 respond(&client, 503, kind.reply());
143 refusals.refused(kind, self.recorder, Instant::now());
144 };
145 let slot = Slot::take(&active, self.limits.max_connections);
146 let Some(slot) = slot else {
147 full(Full::Slots);
148 continue;
149 };
150 let pending = if self.sessions.is_some() {
153 let Some(share) = served.shares.take(PENDING) else {
154 full(Full::Pending);
155 continue;
156 };
157 Some(share)
158 } else {
159 None
160 };
161 let _ = thread::Builder::new().spawn_scoped(scope, move || {
164 let _slot = slot;
165 self.handle(&client, served, pending);
166 });
167 }
168 })
169 }
170
171 fn handle(&self, client: &TcpStream, served: &Served<'_>, pending: Option<Share<'_>>) {
172 let started = Instant::now();
173 let timeout = Some(self.limits.handshake_timeout);
174 if client.set_read_timeout(timeout).is_err() || client.set_write_timeout(timeout).is_err() {
175 return;
176 }
177 let mut buf = Zeroizing::new(Vec::new());
179 let deadline = started + self.limits.handshake_timeout;
180 if let Some(local) = self.local
182 && !local.prefix().is_empty()
183 && local::starts_with(client, local.prefix(), deadline)
184 {
185 local.serve(client);
186 return;
187 }
188 let request = match read_head(client, &mut buf, self.limits.max_head_bytes, deadline) {
189 Ok(request) => request,
190 Err(why) => {
191 let decision = Decision::single(Verdict::Deny, RULE_REQUEST, why);
192 let turn = Turn {
193 session: None,
194 started,
195 };
196 self.record(turn, None, &decision);
197 refuse(client, 400, &decision.reasons[0]);
198 return;
199 }
200 };
201 let Some((session, _share)) = self.admit(client, &request, served, started) else {
202 return;
203 };
204 drop(pending);
205 let turn = Turn {
206 session: session.as_deref(),
207 started,
208 };
209 let mut watch = Watch::new(self.broker, request.host());
213 if let Some(leak) = watch.next(&buf) {
214 let decision = leak.decision(request.host());
215 self.record(turn, Some(&request), &decision);
216 refuse(client, 403, &summary(&decision));
217 return;
218 }
219 let rest = Zeroizing::new(buf.split_off(request.head_len));
220 let decision = decide::host_port(self.policy, served.ports, request.host(), request.port());
221 if decision.verdict != Verdict::Allow {
222 self.record(turn, Some(&request), &decision);
223 refuse(client, 403, &summary(&decision));
224 return;
225 }
226 if let Some(refusal) = self.tainted(turn, &request, None) {
227 self.record(turn, Some(&request), &refusal);
228 refuse(client, 403, &summary(&refusal));
229 return;
230 }
231 let routed = match &request.target {
233 Target::Http { head, host, port } => {
234 match self.broker.admit(host, Channel::Plain(*port), head) {
235 Ok(reasons) => reasons,
236 Err(refusal) => {
237 self.record(turn, Some(&request), &refusal);
238 refuse(client, 403, &summary(&refusal));
239 return;
240 }
241 }
242 }
243 Target::Connect { .. } => Vec::new(),
244 };
245 if let Target::Connect { host, .. } = &request.target
246 && let Some(tls) = self.terminating(host, turn)
247 {
248 self.terminate(
249 client,
250 &request,
251 turn,
252 &decision,
253 rest.to_vec(),
254 served,
255 tls,
256 );
257 return;
258 }
259 let mut body = BodyEnd::new(match &request.target {
263 Target::Http { head, .. } => body::framing(head),
264 Target::Connect { .. } => Framing::None,
265 });
266 let Ok(body_in_rest) = body.feed(&rest) else {
267 let decision = Decision::single(Verdict::Deny, RULE_REQUEST, MALFORMED_CHUNKS);
268 self.record(turn, Some(&request), &decision);
269 refuse(client, 400, MALFORMED_CHUNKS);
270 return;
271 };
272 let upstream = match upstream::connect(
273 self.resolver,
274 request.host(),
275 request.port(),
276 served.addresses,
277 self.loopback_ok,
278 self.limits.connect_timeout,
279 ) {
280 Ok(upstream) => upstream,
281 Err(refusal) => {
282 let decision = refusal.decision(request.host());
283 self.record(turn, Some(&request), &decision);
284 let status = if matches!(refusal, Refusal::Address(..) | Refusal::Denied(..)) {
285 403
286 } else {
287 502
288 };
289 refuse(client, status, &summary(&decision));
290 return;
291 }
292 };
293 let mut inspect = |chunk: &[u8]| {
295 let Ok(n) = body.feed(chunk) else {
296 let decision = Decision::single(Verdict::Deny, RULE_REQUEST, MALFORMED_CHUNKS);
298 self.record(turn, Some(&request), &decision);
299 return Forward::Stop;
300 };
301 if let Some(leak) = watch.next(&chunk[..n]) {
302 self.record(turn, Some(&request), &leak.decision(request.host()));
303 return Forward::Stop;
304 }
305 if n < chunk.len() || body.done() {
306 Forward::Part(n)
307 } else {
308 Forward::All
309 }
310 };
311 let (first_bytes, inspect) = match &request.target {
312 Target::Connect { host, .. } => {
313 match self.check_tunnel(client, host, rest.to_vec(), &decision, &request, turn) {
314 Some(hello) => (Zeroizing::new(hello), None),
315 None => return,
316 }
317 }
318 Target::Http { head, host, port } => {
319 let mut decision = decision.clone();
320 decision.reasons.extend(self.broker.withheld(host, *port));
321 decision.reasons.extend(routed);
322 if !self.record(turn, Some(&request), &decision) {
323 refuse(client, 403, "audit log unavailable");
324 return;
325 }
326 let inspect: tunnel::Inspect<'_> = &mut inspect;
327 let injected = self.broker.inject(host, Channel::Plain(*port), head);
330 let head = injected.as_deref().map_or(head.as_slice(), Vec::as_slice);
331 let body_bytes = &rest[..body_in_rest];
332 (Zeroizing::new([head, body_bytes].concat()), Some(inspect))
333 }
334 };
335 if (&upstream).write_all(&first_bytes).is_ok() {
336 let still_allowed = || self.still_untainted(turn, &request);
337 let recheck = turn.session.map(|_| tunnel::Recheck {
338 every: self.limits.taint_recheck,
339 still_allowed: &still_allowed,
340 });
341 tunnel::relay(
342 client,
343 &upstream,
344 self.limits.idle_timeout,
345 inspect,
346 recheck,
347 );
348 }
349 }
350
351 fn admit<'s>(
358 &'s self,
359 client: &TcpStream,
360 request: &Request,
361 served: &'s Served<'_>,
362 started: Instant,
363 ) -> Option<(Option<String>, Option<Share<'s>>)> {
364 let unnamed = Turn {
365 session: None,
366 started,
367 };
368 let Some(sessions) = self.sessions else {
369 return Some((None, None));
370 };
371 let (session, holder) = match sessions.verify(&request.credentials) {
372 Ok(session) => {
373 let holder = session.clone();
374 (Some(session), holder)
375 }
376 Err(AuthError::Missing) if sessions.serves_without_credential() => {
377 (None, WITHOUT_CREDENTIAL.to_owned())
378 }
379 Err(why) => {
380 let decision = Decision::single(Verdict::Deny, RULE_SESSION, why.to_string());
381 self.record(unnamed, Some(request), &decision);
382 refuse(client, 407, &summary(&decision));
383 return None;
384 }
385 };
386 if let Some(share) = served.shares.take(&holder) {
387 return Some((session, Some(share)));
388 }
389 let turn = Turn {
390 session: session.as_deref(),
391 started,
392 };
393 let holder = if session.is_some() {
394 "the session already holds"
395 } else {
396 "requests without a credential already hold"
397 };
398 let decision = Decision::single(
399 Verdict::Deny,
400 RULE_SESSION,
401 format!(
402 "{holder} {} connections, half of the proxy's",
403 served.shares.cap()
404 ),
405 );
406 self.record(turn, Some(request), &decision);
407 refuse(client, 503, &summary(&decision));
408 None
409 }
410
411 fn check_tunnel(
416 &self,
417 client: &TcpStream,
418 host: &str,
419 mut hello: Vec<u8>,
420 allowed: &Decision,
421 request: &Request,
422 turn: Turn<'_>,
423 ) -> Option<Vec<u8>> {
424 let answered = (&*client).write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n");
425 let deadline = Instant::now() + self.limits.handshake_timeout;
426 let read = answered
427 .map_err(|e| format!("answering the CONNECT: {e}"))
428 .and_then(|()| read_hello(client, &mut hello, deadline));
429 let refusal = match read {
430 Ok((_, true)) if !self.allow_ech => Some(format!(
433 "TLS ClientHello for CONNECT {host} carries an encrypted server name (ECH), \
434 so the server may serve another site; `sandbox.proxy_allow_ech: true` \
435 in the policy allows it"
436 )),
437 Ok((Some(name), _)) if name == host => None,
438 Ok((Some(name), _)) => Some(format!(
439 "TLS server name {name} does not match CONNECT {host}"
440 )),
441 Ok((None, _)) if host.parse::<std::net::IpAddr>().is_ok() => None,
444 Ok((None, _)) => Some(format!(
445 "TLS ClientHello for CONNECT {host} has no server name"
446 )),
447 Err(why) => Some(why),
448 };
449 if let Some(why) = refusal {
450 let decision = Decision::single(Verdict::Deny, RULE_SNI, why);
451 self.record(turn, Some(request), &decision);
452 return None;
453 }
454 self.record(turn, Some(request), allowed).then_some(hello)
455 }
456
457 fn record(&self, turn: Turn<'_>, request: Option<&Request>, decision: &Decision) -> bool {
460 let connection = Connection {
461 session: turn.session,
462 method: request.map(|r| r.method.as_str()),
463 destination: request.map(|r| (r.host(), r.port())),
464 terminated: false,
465 decision,
466 latency_us: u64::try_from(turn.started.elapsed().as_micros()).unwrap_or(u64::MAX),
467 };
468 self.write_row(&connection)
469 }
470
471 fn write_row(&self, connection: &Connection<'_>) -> bool {
473 match self.recorder.record(connection) {
474 Ok(()) => true,
475 Err(error) => {
476 self.recorder.unrecorded(&error);
479 false
480 }
481 }
482 }
483}
484
485struct Slot<'a>(&'a AtomicUsize);
488
489impl<'a> Slot<'a> {
490 fn take(active: &'a AtomicUsize, max: usize) -> Option<Self> {
491 let slot = Self(active);
494 (active.fetch_add(1, Ordering::AcqRel) < max).then_some(slot)
495 }
496}
497
498impl Drop for Slot<'_> {
499 fn drop(&mut self) {
500 self.0.fetch_sub(1, Ordering::AcqRel);
501 }
502}
503
504fn is_transient(e: &io::Error) -> bool {
505 matches!(
506 e.kind(),
507 ErrorKind::Interrupted | ErrorKind::ConnectionAborted | ErrorKind::ConnectionReset
508 )
509}
510
511#[cfg(test)]
512mod tests;