fix(#2860): drop hive-c0re's hardcoded localhost forge fallbacks
Two sites, same class, different blast radius: `forge_http_base()` fell back to `http://localhost:3000` when `HIVE_FORGE_URL` was unset. The NixOS module sets that variable unconditionally, so the fallback could only ever fire for a process started outside the module — where guessing produces a confusing "connection refused" far from its cause. It now panics saying exactly that. `forge_git_url()` had a second, nastier fallback: a base URL with no `://` produced `http://core:<token>@localhost:3000/...`, sending a *credentialed* git push at whatever happened to answer on the local port. Split the credential-insertion half out as `git_url_with_base`, which panics on a malformed base. That split also lets the tests cover the shape without setting a process-wide env var, which would race every other test in the binary. Adds a case pinning that the scheme is carried through rather than assumed — the old hardcoded `http://` would have silently downgraded a TLS-fronted forge. Refs #2860
This commit is contained in:
parent
7ef9905e9f
commit
8ad3b8e987
2 changed files with 51 additions and 22 deletions
|
|
@ -37,15 +37,25 @@ use users::{
|
|||
|
||||
const FORGE_CONTAINER: &str = "hive-forge";
|
||||
|
||||
/// Base HTTP URL for the local Forgejo instance. Reads `HIVE_FORGE_URL`
|
||||
/// from the environment (set unconditionally by `hive-c0re.nix` to
|
||||
/// `http://<forge.domain>`) so the forge port is never hardcoded.
|
||||
/// Falls back to `http://localhost:3000` for bare runs outside the
|
||||
/// NixOS module (tests, manual invocation).
|
||||
/// Base HTTP URL for the local Forgejo instance, from `HIVE_FORGE_URL`
|
||||
/// (set unconditionally by `hive-c0re.nix` to `http://<forge.domain>`).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When `HIVE_FORGE_URL` is unset. That is deliberate: this daemon only
|
||||
/// runs under the NixOS module, which always sets it, so an unset var
|
||||
/// means the deployment is broken. There is no loopback fallback,
|
||||
/// because a guess is wrong in exactly the cases that matter — the
|
||||
/// forge may live on a different host from the daemon, and a fallback
|
||||
/// turns "misconfigured" into "silently talking to the wrong machine"
|
||||
/// or, worse, "connection refused" surfacing far from its cause.
|
||||
pub(crate) fn forge_http_base() -> &'static str {
|
||||
static BASE: OnceLock<String> = OnceLock::new();
|
||||
BASE.get_or_init(|| {
|
||||
std::env::var("HIVE_FORGE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string())
|
||||
std::env::var("HIVE_FORGE_URL").expect(
|
||||
"HIVE_FORGE_URL is unset — hive-c0re.nix sets it unconditionally, \
|
||||
so this process was started outside the NixOS module",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -53,15 +63,24 @@ pub(crate) fn forge_http_base() -> &'static str {
|
|||
/// `core:<token>` credentials between the scheme and authority of
|
||||
/// [`forge_http_base()`] — the form git accepts for inline auth.
|
||||
pub(crate) fn forge_git_url(token: &str, repo: &str) -> String {
|
||||
let base = forge_http_base();
|
||||
// Split on "://" to isolate scheme + authority. The base URL always
|
||||
// contains "://" (validated fallback + `HIVE_FORGE_URL` is
|
||||
// operator-set and expected to be well-formed).
|
||||
if let Some((scheme, host)) = base.split_once("://") {
|
||||
format!("{scheme}://core:{token}@{host}/{repo}.git")
|
||||
} else {
|
||||
format!("http://core:{token}@localhost:3000/{repo}.git")
|
||||
}
|
||||
git_url_with_base(forge_http_base(), token, repo)
|
||||
}
|
||||
|
||||
/// The credential-insertion half of [`forge_git_url`], split out so it
|
||||
/// can be tested without a process-wide env var (which would race every
|
||||
/// other test in this binary).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When `base` has no `://`. Previously this fell back to
|
||||
/// `http://core:<token>@localhost:3000` — a guess that would have sent
|
||||
/// a *credentialed* push at whatever answers on the local port. A
|
||||
/// malformed base is a broken deployment; failing on it is the point.
|
||||
fn git_url_with_base(base: &str, token: &str, repo: &str) -> String {
|
||||
let (scheme, host) = base
|
||||
.split_once("://")
|
||||
.unwrap_or_else(|| panic!("HIVE_FORGE_URL is not a URL (no \"://\"): {base}"));
|
||||
format!("{scheme}://core:{token}@{host}/{repo}.git")
|
||||
}
|
||||
|
||||
/// Forgejo org grouping every agent's config repo. Core is a site admin
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ pub async fn post_pr_comment(repo: &str, pr: u64, body: &str) -> Result<(), Forg
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::repo_agent_name;
|
||||
use crate::forge::forge_git_url;
|
||||
use crate::forge::git_url_with_base;
|
||||
|
||||
#[test]
|
||||
fn repo_agent_name_takes_trailing_segment() {
|
||||
|
|
@ -264,13 +264,23 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn forge_git_url_shape() {
|
||||
// Credentials are inserted between scheme and authority; fallback
|
||||
// base is `http://localhost:3000` when HIVE_FORGE_URL is unset.
|
||||
let url = forge_git_url("tok", "agent-configs/iris");
|
||||
assert!(url.contains("core:tok@"), "must embed credentials: {url}");
|
||||
// Tests the pure half: credentials go between scheme and
|
||||
// authority. Deliberately not via `forge_git_url`, which reads
|
||||
// HIVE_FORGE_URL — setting that here would race every other
|
||||
// test in this binary, and there is no fallback to lean on any
|
||||
// more (a guessed base is the bug this issue removes).
|
||||
let url = git_url_with_base("http://forge.example.test", "tok", "a/iris");
|
||||
assert_eq!(url, "http://core:tok@forge.example.test/a/iris.git");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forge_git_url_preserves_https() {
|
||||
// The scheme is carried through rather than assumed: a swarm
|
||||
// whose forge is behind TLS must not be downgraded to http.
|
||||
let url = git_url_with_base("https://forge.example.test", "tok", "a/iris");
|
||||
assert!(
|
||||
url.ends_with("/agent-configs/iris.git"),
|
||||
"must end with repo path: {url}"
|
||||
url.starts_with("https://core:tok@"),
|
||||
"https must survive: {url}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue