squid intercepts restricted and log agents' web traffic, named from the bound source address
This commit is contained in:
parent
03b9c39498
commit
712dcf2b25
2 changed files with 1005 additions and 154 deletions
|
|
@ -8,9 +8,9 @@
|
|||
//! - [`ENV_ALLOWED_FILE`]: the swarm-wide names, one per line, which
|
||||
//! `nix/host-modules/hive-squid.nix` reads as part of `allowed_dst`.
|
||||
//! - [`ENV_AGENT_ALLOW_FILE`]: squid configuration `include`d where the base
|
||||
//! list is allowed: for each of this hive's agents with entries, an `acl`
|
||||
//! for its squid user, an `acl` for its names and the `http_access allow`
|
||||
//! joining them, then the rules of the agents in `log` mode ([`log_rules`]).
|
||||
//! list is allowed: per agent with entries, `acl`s for its users and names
|
||||
//! and an `http_access allow` for the proxy port and one for the intercept
|
||||
//! ports, then the rules of the agents in `log` mode ([`log_rules`]).
|
||||
//!
|
||||
//! Squid reads both when it parses its configuration, so the path unit in
|
||||
//! `nix/host-modules/hive-squid.nix` reloads it when either changes.
|
||||
|
|
@ -328,7 +328,8 @@ fn log_agents(agents: &[String], perms: &permissions::Stored) -> Vec<String> {
|
|||
/// the agent is allowed never carries the mark. The mark is the last ACL of its
|
||||
/// rule: squid applies `annotate_transaction` when it is reached, whatever the
|
||||
/// rule decides after it. Everything in squid's configuration before the
|
||||
/// include is a deny that applies to these agents as to any other.
|
||||
/// include is a deny that applies to these agents as to any other. Each agent
|
||||
/// has a rule for the proxy port and one for the intercept ports (`icpt`).
|
||||
fn log_rules(logging: &[String]) -> String {
|
||||
if logging.is_empty() {
|
||||
return String::new();
|
||||
|
|
@ -338,7 +339,9 @@ fn log_rules(logging: &[String]) -> String {
|
|||
let _ = write!(
|
||||
rules,
|
||||
"acl agent_log_{agent} proxy_auth {agent}\n\
|
||||
http_access allow agent_log_{agent} !allowed_dst {MARK_ACL}\n"
|
||||
acl agent_log_{agent}_ix ext_user {agent}\n\
|
||||
http_access allow !icpt agent_log_{agent} !allowed_dst {MARK_ACL}\n\
|
||||
http_access allow icpt agent_log_{agent}_ix !allowed_dst {MARK_ACL}\n"
|
||||
);
|
||||
}
|
||||
rules
|
||||
|
|
@ -393,8 +396,10 @@ fn render(
|
|||
agent_count += names.len();
|
||||
agent_rules.push(format!(
|
||||
"acl agent_allow_{agent} proxy_auth {agent}\n\
|
||||
acl agent_allow_{agent}_ix ext_user {agent}\n\
|
||||
acl agent_allow_{agent}_dst dstdomain {}\n\
|
||||
http_access allow agent_allow_{agent} agent_allow_{agent}_dst\n",
|
||||
http_access allow !icpt agent_allow_{agent} agent_allow_{agent}_dst\n\
|
||||
http_access allow icpt agent_allow_{agent}_ix agent_allow_{agent}_dst\n",
|
||||
names.join(" ")
|
||||
));
|
||||
}
|
||||
|
|
@ -482,8 +487,10 @@ mod tests {
|
|||
r.agent_allow,
|
||||
"# source: swarm\n\
|
||||
acl agent_allow_atlas proxy_auth atlas\n\
|
||||
acl agent_allow_atlas_ix ext_user atlas\n\
|
||||
acl agent_allow_atlas_dst dstdomain crates.io pypi.org\n\
|
||||
http_access allow agent_allow_atlas agent_allow_atlas_dst\n"
|
||||
http_access allow !icpt agent_allow_atlas agent_allow_atlas_dst\n\
|
||||
http_access allow icpt agent_allow_atlas_ix agent_allow_atlas_dst\n"
|
||||
);
|
||||
let counts = r.counts.expect("a swarm list");
|
||||
assert_eq!((counts.swarm, counts.agent), (0, 2));
|
||||
|
|
@ -530,8 +537,10 @@ mod tests {
|
|||
r.agent_allow,
|
||||
"# source: swarm\n\
|
||||
acl agent_allow_atlas proxy_auth atlas\n\
|
||||
acl agent_allow_atlas_ix ext_user atlas\n\
|
||||
acl agent_allow_atlas_dst dstdomain crates.io\n\
|
||||
http_access allow agent_allow_atlas agent_allow_atlas_dst\n"
|
||||
http_access allow !icpt agent_allow_atlas agent_allow_atlas_dst\n\
|
||||
http_access allow icpt agent_allow_atlas_ix agent_allow_atlas_dst\n"
|
||||
);
|
||||
// Control: the same entries survive when nothing is always allowed.
|
||||
let bare = render(
|
||||
|
|
@ -586,19 +595,23 @@ mod tests {
|
|||
text,
|
||||
"# source: swarm\n\
|
||||
acl agent_allow_atlas proxy_auth atlas\n\
|
||||
acl agent_allow_atlas_ix ext_user atlas\n\
|
||||
acl agent_allow_atlas_dst dstdomain pypi.org\n\
|
||||
http_access allow agent_allow_atlas agent_allow_atlas_dst\n\
|
||||
http_access allow !icpt agent_allow_atlas agent_allow_atlas_dst\n\
|
||||
http_access allow icpt agent_allow_atlas_ix agent_allow_atlas_dst\n\
|
||||
acl log_mark annotate_transaction would_deny=1\n\
|
||||
acl agent_log_uma proxy_auth uma\n\
|
||||
http_access allow agent_log_uma !allowed_dst log_mark\n"
|
||||
acl agent_log_uma_ix ext_user uma\n\
|
||||
http_access allow !icpt agent_log_uma !allowed_dst log_mark\n\
|
||||
http_access allow icpt agent_log_uma_ix !allowed_dst log_mark\n"
|
||||
);
|
||||
// The rule of an agent with its own names also follows them.
|
||||
let both = log_list(&["atlas"]);
|
||||
let own = both
|
||||
.find("http_access allow agent_allow_atlas ")
|
||||
.find("http_access allow !icpt agent_allow_atlas ")
|
||||
.expect("own rule");
|
||||
let log = both
|
||||
.find("http_access allow agent_log_atlas ")
|
||||
.find("http_access allow !icpt agent_log_atlas ")
|
||||
.expect("log rule");
|
||||
assert!(own < log, "{both}");
|
||||
// Control: with no log agent there is no rule and no mark.
|
||||
|
|
@ -616,9 +629,10 @@ mod tests {
|
|||
let text = log_list(&["atlas", "iris", "uma"]);
|
||||
let rules: Vec<&str> = text
|
||||
.lines()
|
||||
.filter(|l| l.starts_with("http_access allow agent_log_"))
|
||||
.filter(|l| l.starts_with("http_access allow ") && l.contains(" agent_log_"))
|
||||
.collect();
|
||||
assert_eq!(rules.len(), 3, "{text}");
|
||||
// One rule for the proxy port and one for the intercept ports per agent.
|
||||
assert_eq!(rules.len(), 6, "{text}");
|
||||
for rule in rules {
|
||||
assert!(rule.ends_with(" !allowed_dst log_mark"), "{rule}");
|
||||
}
|
||||
|
|
@ -698,7 +712,8 @@ mod tests {
|
|||
.expect("renders");
|
||||
let text = text(&d.agent_allow());
|
||||
assert!(text.starts_with(SEED_MARKER), "{text}");
|
||||
assert!(text.contains("http_access allow agent_log_uma !allowed_dst log_mark"));
|
||||
assert!(text.contains("http_access allow !icpt agent_log_uma !allowed_dst log_mark"));
|
||||
assert!(text.contains("http_access allow icpt agent_log_uma_ix !allowed_dst log_mark"));
|
||||
}
|
||||
|
||||
struct Dir(tempfile::TempDir);
|
||||
|
|
@ -786,8 +801,10 @@ mod tests {
|
|||
text(&d.agent_allow()),
|
||||
"# source: swarm\n\
|
||||
acl agent_allow_atlas proxy_auth atlas\n\
|
||||
acl agent_allow_atlas_ix ext_user atlas\n\
|
||||
acl agent_allow_atlas_dst dstdomain pypi.org\n\
|
||||
http_access allow agent_allow_atlas agent_allow_atlas_dst\n"
|
||||
http_access allow !icpt agent_allow_atlas agent_allow_atlas_dst\n\
|
||||
http_access allow icpt agent_allow_atlas_ix agent_allow_atlas_dst\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -169,6 +169,12 @@ struct Rendered {
|
|||
/// The destination ACL comes first in each rule: squid checks ACLs left to
|
||||
/// right, so a swarm-wide rule never reaches a `proxy_auth` ACL, which would
|
||||
/// answer an anonymous client 407 instead of the 403.
|
||||
///
|
||||
/// A per-agent name gets two rules. For a request through the proxy port the
|
||||
/// agent is the one whose credential came with it; `!icpt` comes first, since a
|
||||
/// `proxy_auth` ACL evaluated on an intercepted request only logs a warning.
|
||||
/// For an intercepted request the agent is the user the `hive_agent` helper
|
||||
/// named (`ext_user`).
|
||||
fn render(list: &DenyList, agents: &[String]) -> Rendered {
|
||||
let mut skipped = Vec::new();
|
||||
let mut swarm_wide = Vec::new();
|
||||
|
|
@ -217,7 +223,9 @@ fn render(list: &DenyList, agents: &[String]) -> Rendered {
|
|||
rules,
|
||||
"acl deny_agent_{agent}_dst dstdomain {}\n\
|
||||
acl deny_agent_{agent} proxy_auth {agent}\n\
|
||||
http_access deny deny_agent_{agent}_dst deny_agent_{agent} {MARK_ACL}\n",
|
||||
acl deny_agent_{agent}_ix ext_user {agent}\n\
|
||||
http_access deny deny_agent_{agent}_dst !icpt deny_agent_{agent} {MARK_ACL}\n\
|
||||
http_access deny deny_agent_{agent}_dst icpt deny_agent_{agent}_ix {MARK_ACL}\n",
|
||||
names.join(" ")
|
||||
);
|
||||
}
|
||||
|
|
@ -292,7 +300,9 @@ mod tests {
|
|||
format!(
|
||||
"{MARK_LINE}acl deny_agent_atlas_dst dstdomain crates.io pypi.org\n\
|
||||
acl deny_agent_atlas proxy_auth atlas\n\
|
||||
http_access deny deny_agent_atlas_dst deny_agent_atlas deny_hit_mark\n"
|
||||
acl deny_agent_atlas_ix ext_user atlas\n\
|
||||
http_access deny deny_agent_atlas_dst !icpt deny_agent_atlas deny_hit_mark\n\
|
||||
http_access deny deny_agent_atlas_dst icpt deny_agent_atlas_ix deny_hit_mark\n"
|
||||
)
|
||||
);
|
||||
assert_eq!((r.swarm, r.agent), (0, 2));
|
||||
|
|
|
|||
Loading…
Reference in a new issue