Skip to main content

openmoat_proxy/
server.rs

1//! Accepting connections and deciding each one.
2//!
3//! Order per connection: read the head → (session mode) verify its credential
4//! and take the session's share of the slots → check it for brokered secrets
5//! bound elsewhere → decide the host → (session mode) apply the session's
6//! taint → resolve and check the addresses → connect → (CONNECT) answer 200,
7//! read the `ClientHello` and check its SNI → record → relay, watching a
8//! plain-HTTP body for brokered secrets too, and (session mode) checking the
9//! session's taint again every so often (`recheck.rs`). Nothing reaches the
10//! destination before the record is written; a failed record closes the
11//! connection. Connections refused at accept for capacity are recorded in
12//! coalesced rows (`capacity.rs`).
13
14mod 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
46/// Why a request whose chunked body cannot be followed is refused.
47const MALFORMED_CHUNKS: &str = "malformed chunked request body";
48
49/// The proxy: a policy, a resolver, an audit sink and limits.
50#[derive(Clone, Copy)]
51pub struct Proxy<'a> {
52    /// The policy hosts are decided by.
53    pub policy: &'a CompiledPolicy<'a>,
54    /// Name resolution.
55    pub resolver: &'a dyn Resolve,
56    /// Where every decision is recorded.
57    pub recorder: &'a dyn Recorder,
58    /// Resource limits.
59    pub limits: &'a Limits,
60    /// Brokered secrets; [`Broker::default`] for none.
61    pub broker: &'a Broker,
62    /// Loopback addresses the proxy may connect to despite the loopback ban.
63    /// Empty in `moat proxy`; integration tests list their local server.
64    pub loopback_ok: &'a [SocketAddr],
65    /// Forward a `ClientHello` that carries an encrypted server name (ECH,
66    /// ESNI); refused when false (`sandbox.proxy_allow_ech`).
67    pub allow_ech: bool,
68    /// Session mode (ADR-027, ADR-044): every request must carry one of these
69    /// credentials, and is decided with that session's taint and connection
70    /// share. `None` serves any local client with no session; `moat run` and
71    /// `moat proxy` always serve sessions.
72    pub sessions: Option<&'a Sessions<'a>>,
73    /// A service of this process that shares the port (ADR-028): `moat run`
74    /// answers its sandboxed hooks here. `None` in `moat proxy`.
75    pub local: Option<&'a dyn LocalService>,
76    /// TLS termination (ADR-043): a CONNECT to the host of a secret that says
77    /// `tls: terminate` is decrypted, and each request in it checked, given
78    /// the value and recorded. `None`: every tunnel is relayed as it is, as
79    /// in `moat proxy`, whose clients cannot be made to trust the CA.
80    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
97/// What every connection of one [`Proxy::serve`] shares.
98struct Served<'s> {
99    addresses: &'s CompiledPolicy<'s>,
100    /// [`decide::port_policy`] of the policy, compiled.
101    ports: &'s CompiledPolicy<'s>,
102    shares: Shares,
103}
104
105/// One connection's session (`None` without session mode) and start time,
106/// which each of its audit rows carries.
107#[derive(Clone, Copy)]
108struct Turn<'s> {
109    session: Option<&'s str>,
110    started: Instant,
111}
112
113impl Proxy<'_> {
114    /// Serve connections from `listener` until accepting fails.
115    /// Fails at once when the [`Limits`] are zero or too large.
116    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                // Until it authenticates, a connection counts against the
151                // share of all such connections (#555).
152                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                // A thread that cannot start drops (closes) this connection;
162                // `scope.spawn` would panic and take the proxy down instead.
163                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        // The head may hold the session's credential: zeroed once dropped.
178        let mut buf = Zeroizing::new(Vec::new());
179        let deadline = started + self.limits.handshake_timeout;
180        // Still counted as waiting to authenticate: the service checks its own token.
181        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        // `buf` holds the head and any body bytes read with it. Watching all of
210        // it before `rest` is split off leaves its end in the watch's tail, so a
211        // secret split between `rest` and the next body chunk is still found.
212        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        // A secret's `allow` rules, before anything goes upstream (ADR-043).
232        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        // A plain-HTTP request is one request: its body is forwarded, and
260        // nothing the client sends after it (body.rs). A tunnel carries TLS,
261        // where the proxy cannot see a secret or a second request.
262        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        // The body keeps being watched for brokered secrets as it goes by.
294        let mut inspect = |chunk: &[u8]| {
295            let Ok(n) = body.feed(chunk) else {
296                // The allow row is already written; this one says it was cut.
297                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                // Only the owner host's head carries a value; it is zeroed
328                // with `first_bytes` once written.
329                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    /// The connection's session and its share of the slots, or `None` once
352    /// the request has been refused and recorded: a missing or invalid
353    /// credential gets `407`, a session holding its share `503`. Without
354    /// session mode every request is admitted, with no session; where the
355    /// sessions serve requests without a credential, one that carries none
356    /// is admitted with no session, in the share of all such requests.
357    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    /// Answer the CONNECT, then hold the tunnel's first bytes until its SNI
412    /// is known to name the CONNECT host. Returns the bytes to forward. Every
413    /// outcome is recorded, a client that cannot be answered too: the
414    /// upstream connection is already open.
415    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            // With ECH the server acts on the encrypted inner name, which may
431            // be another site behind the same provider as the allowed host.
432            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            // An IP literal is never sent as SNI (RFC 6066 §3). The bytes are
442            // still a complete `ClientHello`: anything else is an `Err`.
443            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    /// Record a decision; `false` when it could not be recorded, which the
458    /// caller must treat as a deny.
459    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    /// Record `connection`; `false` when it could not be recorded.
472    fn write_row(&self, connection: &Connection<'_>) -> bool {
473        match self.recorder.record(connection) {
474            Ok(()) => true,
475            Err(error) => {
476                // The caller refuses the connection; the recorder reports the
477                // failure elsewhere, since the audit log is what failed.
478                self.recorder.unrecorded(&error);
479                false
480            }
481        }
482    }
483}
484
485/// One of the `max_connections` slots, released when dropped, even if the
486/// connection's thread panics.
487struct Slot<'a>(&'a AtomicUsize);
488
489impl<'a> Slot<'a> {
490    fn take(active: &'a AtomicUsize, max: usize) -> Option<Self> {
491        // A slot taken over the cap is given back at once; the count never
492        // stays above `max`.
493        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;