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