agents: pull the forge token from bao; drop tea-login
forge-token.nix fetches swarm/agents/<agent>/forge-token under the agent's own store identity into /run/hive-agent-forge-token/token, and re-fetches on a timer so a rotation lands. hive-forge, the git credential helper, hive-forge-notify, forge-avatar-sync and the web UI read that file first and fall back to <state>/forge-token. tea-login is deleted: it copied the token into ~/.config/tea, which docs/swarm/credentials.md forbids for a store secret. hive-forge covers the same verbs. swarmctl gains agent mint-forge-token. Refs #3782
This commit is contained in:
parent
52c8c0b0de
commit
dd32a395f7
16 changed files with 501 additions and 156 deletions
|
|
@ -29,7 +29,23 @@ fn scratch_state_dir(tag: &str) -> PathBuf {
|
|||
/// `state_dir`, feeding `request` as the git credential-protocol
|
||||
/// request body on stdin. Returns `(exit success, stdout, stderr)`.
|
||||
fn run_get(base_url: &str, state_dir: &Path, request: &str) -> (bool, String, String) {
|
||||
let mut child = Command::new(env!("CARGO_BIN_EXE_hive-forge"))
|
||||
run_get_with(base_url, state_dir, None, request)
|
||||
}
|
||||
|
||||
/// [`run_get`], with `HIVE_FORGE_TOKEN_FILE` set to `fetched` when given and
|
||||
/// removed otherwise, so the caller's own environment never leaks in.
|
||||
fn run_get_with(
|
||||
base_url: &str,
|
||||
state_dir: &Path,
|
||||
fetched: Option<&Path>,
|
||||
request: &str,
|
||||
) -> (bool, String, String) {
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_hive-forge"));
|
||||
match fetched {
|
||||
Some(p) => cmd.env("HIVE_FORGE_TOKEN_FILE", p),
|
||||
None => cmd.env_remove("HIVE_FORGE_TOKEN_FILE"),
|
||||
};
|
||||
let mut child = cmd
|
||||
.arg("credential-helper")
|
||||
.arg("get")
|
||||
.env("HIVE_FORGE_URL", base_url)
|
||||
|
|
@ -111,3 +127,46 @@ fn credential_helper_get_still_works_with_no_host_line() {
|
|||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// The token the agent fetched from the swarm secret store wins over the
|
||||
/// state-dir file the hive used to write, so a rotated token takes effect
|
||||
/// even while the old file is still on disk.
|
||||
#[test]
|
||||
fn the_fetched_token_wins_over_the_state_file() {
|
||||
let dir = scratch_state_dir("fetched");
|
||||
let fetched = dir.join("fetched-token");
|
||||
std::fs::write(&fetched, "fetched-token-value\n").expect("seed fetched token");
|
||||
let (ok, stdout, stderr) = run_get_with(
|
||||
"http://forge.internal.example",
|
||||
&dir,
|
||||
Some(&fetched),
|
||||
"protocol=https\nhost=forge.internal.example\n",
|
||||
);
|
||||
assert!(ok, "stderr={stderr}");
|
||||
assert!(
|
||||
stdout.contains("password=fetched-token-value"),
|
||||
"stdout={stdout}"
|
||||
);
|
||||
assert!(!stdout.contains(TOKEN), "stdout={stdout}");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// An agent whose token was not minted yet has the variable set and no file
|
||||
/// behind it; it keeps working off the state-dir file.
|
||||
#[test]
|
||||
fn a_fetched_token_that_is_not_there_falls_back_to_the_state_file() {
|
||||
let dir = scratch_state_dir("fallback");
|
||||
let missing = dir.join("never-fetched");
|
||||
let (ok, stdout, stderr) = run_get_with(
|
||||
"http://forge.internal.example",
|
||||
&dir,
|
||||
Some(&missing),
|
||||
"protocol=https\nhost=forge.internal.example\n",
|
||||
);
|
||||
assert!(ok, "stderr={stderr}");
|
||||
assert!(
|
||||
stdout.contains(&format!("password={TOKEN}")),
|
||||
"stdout={stdout}"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue