record whether an agent's config sets privateUsers in its closure record
This commit is contained in:
parent
7412a0f1bd
commit
3ca660e558
3 changed files with 178 additions and 6 deletions
|
|
@ -422,12 +422,14 @@ async fn handle_meta(
|
|||
out_path,
|
||||
system,
|
||||
cached,
|
||||
private_users,
|
||||
} => tracing::info!(
|
||||
agent = %job.agent,
|
||||
meta_rev = %job.meta_rev,
|
||||
%out_path,
|
||||
%system,
|
||||
cached,
|
||||
private_users,
|
||||
"agent meta build built"
|
||||
),
|
||||
FromMetaReply::Failed { error_tail } => tracing::warn!(
|
||||
|
|
@ -641,6 +643,7 @@ mod tests {
|
|||
out_path: "/nix/store/x".to_owned(),
|
||||
system: "s".to_owned(),
|
||||
cached: false,
|
||||
private_users: false,
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
|
@ -656,6 +659,7 @@ mod tests {
|
|||
out_path: "/nix/store/x".to_owned(),
|
||||
system: "s".to_owned(),
|
||||
cached: false,
|
||||
private_users: false,
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ impl Builder {
|
|||
out_path,
|
||||
system: self.system.clone(),
|
||||
cached: false,
|
||||
private_users: self.private_users(job, token).await,
|
||||
},
|
||||
Reply::Failed { error_tail } => FromMetaReply::Failed { error_tail },
|
||||
},
|
||||
|
|
@ -170,12 +171,39 @@ impl Builder {
|
|||
/// the hives build. No input is overridden: the committed `flake.lock`
|
||||
/// decides all of them.
|
||||
fn meta_attr(&self, job: &BuildFromMeta) -> String {
|
||||
format!("{}.system.build.toplevel", self.meta_config(job))
|
||||
}
|
||||
|
||||
/// The agent's evaluated NixOS config in `internal/meta` at the commit.
|
||||
fn meta_config(&self, job: &BuildFromMeta) -> String {
|
||||
format!(
|
||||
"git+{}/{META_REPO_PATH}.git?rev={}#nixosConfigurations.{}.config.system.build.toplevel",
|
||||
"git+{}/{META_REPO_PATH}.git?rev={}#nixosConfigurations.{}.config",
|
||||
self.forge_url, job.meta_rev, job.agent
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether the agent's config sets `services.hyperhive.agent.user.privateUsers`,
|
||||
/// read by `nix eval` of the flake the build just evaluated, in this
|
||||
/// sandbox. Anything but a clean `true` is false: an agent whose module
|
||||
/// predates the option, a failed eval and a timeout all keep the agent out
|
||||
/// of a user namespace.
|
||||
async fn private_users(&self, job: &BuildFromMeta, token: &str) -> bool {
|
||||
let mut cmd = self.nix_command(token);
|
||||
cmd.args(EVAL_FLAGS)
|
||||
.arg(format!("{}.{PRIVATE_USERS_OPTION}", self.meta_config(job)));
|
||||
match run_raw(cmd, self.deadline).await {
|
||||
Ok((status, out, _)) if status.success() => parse_flag(&out),
|
||||
Ok((status, _, err)) => {
|
||||
tracing::info!(agent = %job.agent, %status, error = %tail(&String::from_utf8_lossy(&err), status), "privateUsers not read; the agent stays out of a user namespace");
|
||||
false
|
||||
}
|
||||
Err(why) => {
|
||||
tracing::warn!(agent = %job.agent, %why, "privateUsers not read; the agent stays out of a user namespace");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The config commit as a flake reference, fetched through the PR's head
|
||||
/// ref.
|
||||
fn agent_url(&self, job: &BuildAgent) -> String {
|
||||
|
|
@ -199,6 +227,26 @@ const BUILD_FLAGS: [&str; 8] = [
|
|||
"--print-build-logs",
|
||||
];
|
||||
|
||||
/// What every `nix eval` of an option starts with: JSON out, nothing written to
|
||||
/// the flake's lock, the flake's own `nix.conf` settings refused.
|
||||
const EVAL_FLAGS: [&str; 7] = [
|
||||
"eval",
|
||||
"--json",
|
||||
"--no-write-lock-file",
|
||||
"--option",
|
||||
"accept-flake-config",
|
||||
"false",
|
||||
"--quiet",
|
||||
];
|
||||
|
||||
/// The agent option the controller records, relative to the evaluated config.
|
||||
const PRIVATE_USERS_OPTION: &str = "services.hyperhive.agent.user.privateUsers";
|
||||
|
||||
/// `nix eval --json` of a bool: true only for exactly `true`.
|
||||
fn parse_flag(out: &[u8]) -> bool {
|
||||
out.trim_ascii() == b"true"
|
||||
}
|
||||
|
||||
/// `nix flake lock` of the flake in the working directory. The flake's config
|
||||
/// is not trusted, and nothing is built.
|
||||
const LOCK_ARGS: &[&str] = &["flake", "lock", "--option", "accept-flake-config", "false"];
|
||||
|
|
@ -484,6 +532,47 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// The reply carries what the evaluated config says about the option, and
|
||||
/// anything but a clean `true` reads as false. The `true` arm is the
|
||||
/// control for the others.
|
||||
#[tokio::test]
|
||||
async fn a_meta_build_reports_the_agents_private_users_option() {
|
||||
for (i, (answer, want)) in [
|
||||
("echo true", true),
|
||||
("echo false", false),
|
||||
("echo null", false),
|
||||
("echo true; exit 1", false),
|
||||
("echo error: attribute missing >&2; exit 1", false),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let nix = fake_nix(
|
||||
&format!("private-users-{i}"),
|
||||
&format!("if [ \"$1\" = eval ]; then {answer}; else echo /nix/store/x; fi\n"),
|
||||
);
|
||||
let b = Builder { nix, ..builder() };
|
||||
let reply = b.build_from_meta(&meta_job(), "t").await;
|
||||
assert!(
|
||||
matches!(&reply, FromMetaReply::Built { private_users, .. } if *private_users == want),
|
||||
"{answer}: {reply:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_option_is_read_from_the_evaluated_config_not_the_toplevel() {
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}.{PRIVATE_USERS_OPTION}",
|
||||
builder().meta_config(&meta_job())
|
||||
),
|
||||
"git+https://forge.example/internal/meta.git\
|
||||
?rev=0123456789abcdef0123456789abcdef01234567\
|
||||
#nixosConfigurations.atlas.config.services.hyperhive.agent.user.privateUsers"
|
||||
);
|
||||
assert!(parse_flag(b"true\n") && !parse_flag(b"false\n") && !parse_flag(b""));
|
||||
}
|
||||
#[test]
|
||||
fn a_meta_job_that_would_bend_the_reference_is_refused() {
|
||||
for bad in [
|
||||
|
|
@ -520,6 +609,7 @@ mod tests {
|
|||
out_path: format!("{} {}", BUILD_FLAGS.join(" "), b.meta_attr(&meta_job())),
|
||||
system: "x86_64-linux".to_owned(),
|
||||
cached: false,
|
||||
private_users: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,7 +208,10 @@ pub async fn cache_built(
|
|||
let (
|
||||
Some(cache),
|
||||
FromMetaReply::Built {
|
||||
out_path, system, ..
|
||||
out_path,
|
||||
system,
|
||||
private_users,
|
||||
..
|
||||
},
|
||||
) = (cache, &reply)
|
||||
else {
|
||||
|
|
@ -234,6 +237,7 @@ pub async fn cache_built(
|
|||
out_path: out_path.clone(),
|
||||
system: system.clone(),
|
||||
cached: true,
|
||||
private_users: *private_users,
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
|
|
@ -258,6 +262,7 @@ mod tests {
|
|||
out_path: OUT.to_owned(),
|
||||
system: "x86_64-linux".to_owned(),
|
||||
cached: false,
|
||||
private_users: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue