refuse tls hellos that carry encrypted client hello before squid sees them
This commit is contained in:
parent
de43dac33c
commit
dff662c6b3
4 changed files with 1537 additions and 43 deletions
|
|
@ -38,7 +38,9 @@ not the whole daemon-shared crate.
|
|||
`storage` (btrfs subvolumes, quotas, snapshots, disk measurement), `files`
|
||||
(pause marker, socket dir, legacy tmpfiles) and `infra` (gateway nginx, forge
|
||||
admin, infra containers). `egress` holds four sub-commands that never
|
||||
touch the socket, `egress-load`, `egress-pin`, `egress-lease` and `egress-whois`; the
|
||||
touch the socket, `egress-load`, `egress-pin`, `egress-lease` and `egress-whois`, and
|
||||
`egress_ech` a fifth, `egress-ech-relay`, the long-running ClientHello check in front of
|
||||
squid's HTTPS intercept port; the
|
||||
`hive-egress` unit, udev, a timer and dnsmasq run the first three to fill the sets of
|
||||
the anti-spoofing table and the interception exemptions, and squid runs the last to
|
||||
name the agent of an intercepted flow (see
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
|||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
|
||||
use crate::egress_ech::same_host;
|
||||
|
||||
/// The sub-command squid runs to learn which agent holds a source address.
|
||||
pub(crate) const WHOIS: &str = "egress-whois";
|
||||
|
||||
|
|
@ -344,7 +346,9 @@ fn unrestricted_names() -> Vec<String> {
|
|||
|
||||
/// The agent whose container holds the IPv4 address `ip`, as dnsmasq's lease
|
||||
/// file and the bindings of the ports say: the lease names a MAC, the MAC is
|
||||
/// bound to one port, and that port's machine is `h-<agent>`. `None` when any
|
||||
/// bound to one port, and that port's machine is `h-<agent>`. An address in the
|
||||
/// pool of the ECH relay (`egress_ech`) stands for the leased address it was
|
||||
/// derived from. `None` when any
|
||||
/// step is missing or ambiguous (two leases for the address under different
|
||||
/// MACs, a MAC bound twice, a machine that is no agent, a name that is no
|
||||
/// identifier): a flow whose agent cannot be told is refused, never guessed.
|
||||
|
|
@ -357,7 +361,7 @@ pub(crate) fn agent_of(ip: &str, leases: &str, pins: &[Pin], now: u64) -> Option
|
|||
0 => true,
|
||||
t => t > now,
|
||||
};
|
||||
(live && addr.parse::<std::net::Ipv4Addr>().ok()? == ip).then(|| Mac::parse(mac))
|
||||
(live && same_host(ip, addr.parse::<std::net::Ipv4Addr>().ok()?)).then(|| Mac::parse(mac))
|
||||
});
|
||||
let mac = macs.next()??;
|
||||
if macs.any(|other| other != Some(mac)) {
|
||||
|
|
@ -373,20 +377,25 @@ pub(crate) fn agent_of(ip: &str, leases: &str, pins: &[Pin], now: u64) -> Option
|
|||
Some(agent.to_owned())
|
||||
}
|
||||
|
||||
/// [`agent_of`] on the files as they are now, which are read for every call.
|
||||
pub(crate) fn agent_of_now(ip: &str) -> Option<String> {
|
||||
let leasefile = std::env::var("HIVE_EGRESS_LEASEFILE").unwrap_or_else(|_| LEASEFILE.into());
|
||||
let pins = parse_state(&std::fs::read_to_string(state_path()).unwrap_or_default());
|
||||
let leases = std::fs::read_to_string(leasefile).unwrap_or_default();
|
||||
agent_of(ip, &leases, &pins, now_secs())
|
||||
}
|
||||
|
||||
/// `egress-whois`: squid's external ACL helper. Each line on stdin starts with
|
||||
/// a source address; the answer is `OK user=<agent>` or `ERR`. The two files
|
||||
/// are read for every line, since squid caches the answers itself.
|
||||
fn whois() -> Result<()> {
|
||||
use std::io::{BufRead as _, Write as _};
|
||||
let leasefile = std::env::var("HIVE_EGRESS_LEASEFILE").unwrap_or_else(|_| LEASEFILE.into());
|
||||
let stdin = std::io::stdin();
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
for line in stdin.lock().lines() {
|
||||
let line = line.context("read stdin")?;
|
||||
let ip = line.split_whitespace().next().unwrap_or_default();
|
||||
let pins = parse_state(&std::fs::read_to_string(state_path()).unwrap_or_default());
|
||||
let leases = std::fs::read_to_string(&leasefile).unwrap_or_default();
|
||||
match agent_of(ip, &leases, &pins, now_secs()) {
|
||||
match agent_of_now(ip) {
|
||||
Some(agent) => writeln!(stdout, "OK user={agent}"),
|
||||
None => writeln!(stdout, "ERR"),
|
||||
}
|
||||
|
|
@ -1117,6 +1126,31 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_relay_pool_address_names_the_agent_it_was_derived_from() {
|
||||
let pins = [
|
||||
pin("vb-h-atlas", 10, 1, "h-atlas"),
|
||||
pin("vb-h-uma", 11, 2, "h-uma"),
|
||||
];
|
||||
let leases = lease_line(NOW + 60, 1, "10.42.0.5") + &lease_line(NOW + 60, 2, "10.42.0.6");
|
||||
assert_eq!(
|
||||
agent_of("198.18.0.5", &leases, &pins, NOW).as_deref(),
|
||||
Some("atlas")
|
||||
);
|
||||
assert_eq!(
|
||||
agent_of("198.18.0.6", &leases, &pins, NOW).as_deref(),
|
||||
Some("uma")
|
||||
);
|
||||
// No lease behind the pool address, and an address of the pool that
|
||||
// is itself leased: neither names anyone.
|
||||
assert_eq!(agent_of("198.18.0.7", &leases, &pins, NOW), None);
|
||||
let leased_pool = lease_line(NOW + 60, 1, "198.18.0.9");
|
||||
assert_eq!(agent_of("198.18.0.9", &leased_pool, &pins, NOW), None);
|
||||
// Two leases that share the pool address are ambiguous.
|
||||
let twins = lease_line(NOW + 60, 1, "10.42.0.5") + &lease_line(NOW + 60, 2, "10.44.0.5");
|
||||
assert_eq!(agent_of("198.18.0.5", &twins, &pins, NOW), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_address_nobody_can_be_told_to_hold_has_no_agent() {
|
||||
let pins = [
|
||||
|
|
|
|||
1116
hive-priv/src/egress_ech.rs
Normal file
1116
hive-priv/src/egress_ech.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -23,7 +23,8 @@
|
|||
//! turns ephemeral, with [`salvage_io`] for its space, reflink and lock calls),
|
||||
//! [`prestart`] (the check of a container's root that runs before each start,
|
||||
//! as a sub-command), [`egress`] (the bridge anti-spoofing bindings, also
|
||||
//! sub-commands), [`storage`] (btrfs subvolumes, quotas, snapshots, disk
|
||||
//! sub-commands), [`egress_ech`] (the `ClientHello` check in front of squid's
|
||||
//! HTTPS intercept port), [`storage`] (btrfs subvolumes, quotas, snapshots, disk
|
||||
//! measurement), [`files`] (pause marker, socket dir, legacy tmpfiles),
|
||||
//! [`infra`] (gateway nginx, infra containers) and [`closure`] (the swarm-signature
|
||||
//! check before an apply, and the cache-facts refresh). Helpers more than one family uses are in [`shared`].
|
||||
|
|
@ -32,6 +33,7 @@ mod closure;
|
|||
mod container;
|
||||
mod container_config;
|
||||
mod egress;
|
||||
mod egress_ech;
|
||||
mod files;
|
||||
mod infra;
|
||||
mod prestart;
|
||||
|
|
@ -60,6 +62,10 @@ async fn main() -> Result<()> {
|
|||
if let Some(code) = egress::run(&args) {
|
||||
std::process::exit(code);
|
||||
}
|
||||
// `egress-ech-relay` is a long-running unit of its own, not a request either.
|
||||
if let Some(code) = egress_ech::run(&args).await {
|
||||
std::process::exit(code);
|
||||
}
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
|
|
|
|||
Loading…
Reference in a new issue