always include a hive's own allowedDomains in the rendered swarm-wide list
This commit is contained in:
parent
c5a876b845
commit
7e2f4ce7c8
1 changed files with 141 additions and 75 deletions
|
|
@ -15,19 +15,19 @@
|
|||
//! Squid reads both when it parses its configuration, so the path unit in
|
||||
//! `nix/host-modules/hive-squid.nix` reloads it when either changes.
|
||||
//!
|
||||
//! The swarm's list replaces the names `services.hyperhive.squid.allowedDomains`
|
||||
//! lists ([`ENV_SEED_FILE`]), which stay the list until the swarm has one. A
|
||||
//! file rendered from the swarm's list starts with [`SWARM_MARKER`]. The queue
|
||||
//! not answering for the bucket does not show that the swarm withdrew its list,
|
||||
//! so a file with the marker is left alone then.
|
||||
//! The names `services.hyperhive.squid.allowedDomains` lists ([`ENV_SEED_FILE`])
|
||||
//! are always allowed: the swarm-wide names written are those plus the swarm's,
|
||||
//! with the per-agent rules on top. Until the swarm has a list the files hold
|
||||
//! just those names. A file from the swarm's list starts with [`SWARM_MARKER`];
|
||||
//! the queue not answering for the bucket is no withdrawal, so a file with the
|
||||
//! marker is left alone then.
|
||||
//!
|
||||
//! Every entry is checked again with
|
||||
//! [`swarm_wire::egress_allow::normalize_domain`], since the names end up in
|
||||
//! squid's configuration, and skipped when it fails. An entry that
|
||||
//! [`ENV_ALWAYS_ALLOWED`] or another entry of its list covers is dropped,
|
||||
//! because squid ignores the narrower of two overlapping names in one ACL.
|
||||
//!
|
||||
//! A failed read leaves both files as they are.
|
||||
//! because squid ignores the narrower of two overlapping names in one ACL. A
|
||||
//! failed read leaves both files as they are.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
|
@ -54,7 +54,7 @@ pub const ENV_ALLOWED_FILE: &str = "HIVE_SQUID_ALLOWED_FILE";
|
|||
pub const ENV_AGENT_ALLOW_FILE: &str = "HIVE_SQUID_AGENT_ALLOW_FILE";
|
||||
|
||||
/// The file of names `services.hyperhive.squid.allowedDomains` lists, one per
|
||||
/// line, which the files hold until the swarm has a list.
|
||||
/// line, which every rendering of the swarm-wide names includes.
|
||||
pub const ENV_SEED_FILE: &str = "HIVE_SQUID_ALLOWED_SEED_FILE";
|
||||
|
||||
/// The names, separated by spaces, that squid allows every authenticated user
|
||||
|
|
@ -191,7 +191,7 @@ fn sync_with(
|
|||
) -> Result<Source> {
|
||||
let always: Vec<&str> = always.split_whitespace().collect();
|
||||
let rendered = match read {
|
||||
Read::Current(list) => render(&list, agents, &always),
|
||||
Read::Current(list) => render(&list, agents, &always, &seed_names(seed)?),
|
||||
Read::NotSet => {
|
||||
if super::squid_users::read_or_empty(files.allowed)?.starts_with(SWARM_MARKER) {
|
||||
return Ok(Source::Kept);
|
||||
|
|
@ -251,18 +251,27 @@ struct Rendered {
|
|||
counts: Option<Counts>,
|
||||
}
|
||||
|
||||
/// The files for the seed: its names as they are, and no per-agent rules.
|
||||
fn seeded(seed: Option<&Path>) -> Result<Rendered> {
|
||||
let names = match seed {
|
||||
/// The names in the seed file, trimmed, without blank lines and comments. The
|
||||
/// file is this hive's own configuration, so its names are not re-checked.
|
||||
fn seed_names(seed: Option<&Path>) -> Result<Vec<String>> {
|
||||
let text = match seed {
|
||||
Some(path) => super::squid_users::read_or_empty(path)?,
|
||||
None => String::new(),
|
||||
};
|
||||
Ok(text
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty() && !line.starts_with('#'))
|
||||
.map(str::to_owned)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// The files for the seed: its names as they are, and no per-agent rules.
|
||||
fn seeded(seed: Option<&Path>) -> Result<Rendered> {
|
||||
let mut allowed = SEED_MARKER.to_owned();
|
||||
for line in names.lines().map(str::trim) {
|
||||
if !line.is_empty() && !line.starts_with('#') {
|
||||
allowed.push_str(line);
|
||||
allowed.push('\n');
|
||||
}
|
||||
for name in seed_names(seed)? {
|
||||
allowed.push_str(&name);
|
||||
allowed.push('\n');
|
||||
}
|
||||
Ok(Rendered {
|
||||
allowed,
|
||||
|
|
@ -283,8 +292,9 @@ fn reduced(mut names: Vec<String>) -> Vec<String> {
|
|||
names
|
||||
}
|
||||
|
||||
/// The files for `list`, for the agents this hive runs.
|
||||
fn render(list: &AllowList, agents: &[String], always: &[&str]) -> Rendered {
|
||||
/// The files for `list` and the seed's `own` names, for the agents this hive
|
||||
/// runs.
|
||||
fn render(list: &AllowList, agents: &[String], always: &[&str], own: &[String]) -> Rendered {
|
||||
let mut skipped = Vec::new();
|
||||
let mut swarm_wide = Vec::new();
|
||||
let mut per_agent: std::collections::BTreeMap<&str, Vec<String>> =
|
||||
|
|
@ -305,6 +315,7 @@ fn render(list: &AllowList, agents: &[String], always: &[&str]) -> Rendered {
|
|||
}
|
||||
}
|
||||
}
|
||||
swarm_wide.extend(own.iter().map(|name| name.to_ascii_lowercase()));
|
||||
swarm_wide.retain(|d| !covered_by(d, always));
|
||||
let swarm_wide = reduced(swarm_wide);
|
||||
|
||||
|
|
@ -366,7 +377,7 @@ mod tests {
|
|||
const ALWAYS: &[&str] = &["api.anthropic.com", ".claude.ai", "cache.nixos.org"];
|
||||
|
||||
fn rendered(entries: &[(&str, Option<&str>)], mine: &[&str]) -> Rendered {
|
||||
render(&list(entries), &agents(mine), ALWAYS)
|
||||
render(&list(entries), &agents(mine), ALWAYS, &[])
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -464,6 +475,7 @@ mod tests {
|
|||
&list(&[("api.anthropic.com", None), ("a.claude.ai", None)]),
|
||||
&agents(&["atlas"]),
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
assert_eq!(
|
||||
bare.allowed,
|
||||
|
|
@ -545,18 +557,74 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// The seed is the hive's own additive list: a swarm list, empty or not,
|
||||
/// renders the seed's names with it.
|
||||
#[test]
|
||||
fn a_swarm_list_replaces_the_seed_even_when_empty() {
|
||||
fn a_swarm_list_adds_to_the_seed_even_when_empty() {
|
||||
let d = dir("forge.example\n");
|
||||
sync(&d, Read::NotSet).expect("seeded");
|
||||
assert!(text(&d.allowed()).contains("forge.example"), "control");
|
||||
let empty = Read::Current(AllowList::default());
|
||||
assert_eq!(sync(&d, empty).expect("renders"), Source::Swarm);
|
||||
assert_eq!(text(&d.allowed()), SWARM_MARKER);
|
||||
let named = Read::Current(list(&[("github.com", None), ("pypi.org", Some("atlas"))]));
|
||||
assert_eq!(text(&d.allowed()), "# source: swarm\nforge.example\n");
|
||||
let named = Read::Current(list(&[("github.com", None)]));
|
||||
sync(&d, named).expect("renders");
|
||||
assert_eq!(
|
||||
text(&d.allowed()),
|
||||
"# source: swarm\nforge.example\ngithub.com\n"
|
||||
);
|
||||
assert_eq!(text(&d.agent_allow()), SWARM_MARKER);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_per_agent_entry_renders_on_top_of_the_seed() {
|
||||
let d = dir("forge.example\n");
|
||||
let read = Read::Current(list(&[("pypi.org", Some("atlas"))]));
|
||||
sync(&d, read).expect("renders");
|
||||
assert_eq!(text(&d.allowed()), "# source: swarm\nforge.example\n");
|
||||
assert_eq!(
|
||||
text(&d.agent_allow()),
|
||||
"# source: swarm\n\
|
||||
acl agent_allow_atlas proxy_auth atlas\n\
|
||||
acl agent_allow_atlas_dst dstdomain pypi.org\n\
|
||||
http_access allow agent_allow_atlas agent_allow_atlas_dst\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_seed_name_the_swarm_repeats_or_squid_allows_anyway_is_written_once() {
|
||||
// `cache.nixos.org` is always allowed; `.example.com` covers the
|
||||
// swarm's `api.example.com`.
|
||||
let d = dir("Forge.Example\n.example.com\ncache.nixos.org\n");
|
||||
let read = Read::Current(list(&[
|
||||
("forge.example", None),
|
||||
("api.example.com", None),
|
||||
("github.com", None),
|
||||
]));
|
||||
sync(&d, read).expect("renders");
|
||||
assert_eq!(
|
||||
text(&d.allowed()),
|
||||
"# source: swarm\n.example.com\nforge.example\ngithub.com\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_seed_file_is_no_seed_names() {
|
||||
let d = dir("");
|
||||
std::fs::remove_file(d.seed()).expect("remove");
|
||||
let read = Read::Current(list(&[("github.com", None)]));
|
||||
sync_with(
|
||||
read,
|
||||
&agents(&["atlas"]),
|
||||
None,
|
||||
"",
|
||||
&Files {
|
||||
allowed: &d.allowed(),
|
||||
agent_allow: &d.agent_allow(),
|
||||
},
|
||||
)
|
||||
.expect("renders");
|
||||
assert_eq!(text(&d.allowed()), "# source: swarm\ngithub.com\n");
|
||||
assert!(text(&d.agent_allow()).contains("acl agent_allow_atlas proxy_auth atlas"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue