use std::path::PathBuf;
use std::process::{Command, Stdio};
use crate::pushrefs::PushRef;
pub const FORMAT: &str = "amont-attest-v2";
pub const NOTES_REF: &str = "amont-attest";
pub const NOTES_FULL_REF: &str = "refs/notes/amont-attest";
pub const NAMESPACE: &str = "amont-attest";
pub const PUSH_GUARD: &str = "AMONT_ATTEST_PUSH";
const TOGGLE: &str = "amont.attest";
const KEY_CONFIG: &str = "amont.attestKey";
const KEY_DEFAULT: &str = ".ssh/amont-attest";
pub fn push_guard_active() -> bool {
std::env::var_os(PUSH_GUARD).is_some()
}
pub fn enabled() -> bool {
crate::config::boolean_or(TOGGLE, false)
}
fn key_path() -> Option<PathBuf> {
if let Some(k) = crate::git::stdout(&["config", "--get", KEY_CONFIG]) {
if !k.is_empty() {
return Some(PathBuf::from(k));
}
}
let home = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE"))?;
Some(PathBuf::from(home).join(KEY_DEFAULT))
}
pub fn platform() -> String {
format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)
}
pub fn payload(tree: &str, gates: &[String]) -> String {
format!(
"{FORMAT}\ntree {tree}\ngates {}\nplatform {}\namont {}\n",
gates.join(" "),
platform(),
env!("CARGO_PKG_VERSION")
)
}
fn sign(payload: &str, key: &std::path::Path) -> Option<String> {
use std::io::Write;
let mut child = Command::new("ssh-keygen")
.args(["-Y", "sign", "-n", NAMESPACE, "-f"])
.arg(key)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.ok()?;
child.stdin.take()?.write_all(payload.as_bytes()).ok()?;
let out = child.wait_with_output().ok()?;
if !out.status.success() {
return None;
}
let sig = String::from_utf8_lossy(&out.stdout).trim().to_string();
sig.starts_with("-----BEGIN SSH SIGNATURE-----")
.then_some(sig)
}
pub fn verify(
payload: &str,
sig: &str,
allowed_signers: &std::path::Path,
principal: &str,
) -> bool {
use std::io::Write;
let sig_file = std::env::temp_dir().join(format!(
"amont-attest-verify-{}-{:p}.sig",
std::process::id(),
&sig
));
if std::fs::write(&sig_file, format!("{sig}\n")).is_err() {
return false;
}
let ok = (|| {
let mut child = Command::new("ssh-keygen")
.args(["-Y", "verify", "-n", NAMESPACE, "-I", principal, "-f"])
.arg(allowed_signers)
.arg("-s")
.arg(&sig_file)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.ok()?;
child.stdin.take()?.write_all(payload.as_bytes()).ok()?;
child.wait().ok().map(|s| s.success())
})()
.unwrap_or(false);
let _ = std::fs::remove_file(&sig_file);
ok
}
pub fn attest_push(remote: &str, refs: &[PushRef], gates: &[String]) {
if gates.is_empty() || remote.is_empty() || !enabled() {
return;
}
let Some(key) = key_path() else { return };
if !key.exists() {
crate::config::complain(
TOGGLE,
&format!("signing key {} does not exist", key.display()),
"no attestation (CI will run the tests)",
);
return;
}
let mut noted = false;
for r in refs {
if is_zero(&r.local_oid) {
continue; }
let spec = format!("{}^{{tree}}", r.local_oid);
let Some(tree) = crate::git::stdout(&["rev-parse", &spec]) else {
continue;
};
let body = match sign(&payload(&tree, gates), &key) {
Some(sig) => format!("{}\n{sig}", payload(&tree, gates)),
None => continue,
};
if crate::git::succeeds(&[
"notes",
"--ref",
NOTES_REF,
"add",
"-f",
"-m",
&body,
&r.local_oid,
]) {
noted = true;
}
}
if noted && push_notes(remote) {
crate::say!(
"{} attested {} for CI ({})",
crate::ui::valid_sign(),
crate::ui::highlight(&gates.join(" ")),
NOTES_REF,
);
}
}
pub fn covered(
signers: &std::path::Path,
principal: &str,
require_platform: Option<&str>,
) -> Option<String> {
let refspec = format!("+{NOTES_FULL_REF}:{NOTES_FULL_REF}");
let _ = crate::git::succeeds(&["fetch", "origin", &refspec]);
let head_tree = crate::git::stdout(&["rev-parse", "HEAD^{tree}"])?;
for candidate in ["HEAD", "HEAD^2"] {
let Some(commit) = crate::git::stdout(&["rev-parse", "--verify", candidate]) else {
continue;
};
let Some(body) = crate::git::stdout(&["notes", "--ref", NOTES_REF, "show", &commit]) else {
continue;
};
let Some((payload, sig)) = split_note(&body) else {
continue;
};
let mut lines = payload.lines();
if lines.next() != Some(FORMAT) {
continue;
}
let field = |name: &str| {
payload
.lines()
.find_map(|l| l.strip_prefix(name).and_then(|r| r.strip_prefix(' ')))
.map(str::trim)
};
let (Some(tree), Some(gates), Some(ran_on)) =
(field("tree"), field("gates"), field("platform"))
else {
continue;
};
if tree != head_tree || gates.is_empty() {
continue; }
if require_platform.is_some_and(|want| want != ran_on) {
continue;
}
if verify(&payload, &sig, signers, principal) {
return Some(gates.to_string());
}
}
None
}
pub fn default_signers() -> Option<PathBuf> {
let root = crate::git::stdout(&["rev-parse", "--show-toplevel"]).map(PathBuf::from);
[".forgejo/allowed_signers", ".github/allowed_signers"]
.into_iter()
.map(|rel| match &root {
Some(root) => root.join(rel),
None => PathBuf::from(rel),
})
.find(|p| p.exists())
}
pub fn first_principal(signers: &std::path::Path) -> Option<String> {
let body = std::fs::read_to_string(signers).ok()?;
body.lines()
.map(str::trim)
.find(|l| !l.is_empty() && !l.starts_with('#'))
.and_then(|l| l.split_whitespace().next())
.map(str::to_string)
}
fn split_note(body: &str) -> Option<(String, String)> {
let (payload, sig) = body.split_once("\n\n")?;
if !sig.starts_with("-----BEGIN SSH SIGNATURE-----") {
return None;
}
Some((format!("{payload}\n"), sig.to_string()))
}
fn push_notes(remote: &str) -> bool {
let refspec = format!("{NOTES_FULL_REF}:{NOTES_FULL_REF}");
Command::new("git")
.args(["push", remote, &refspec])
.env(PUSH_GUARD, "1")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn is_zero(oid: &str) -> bool {
!oid.is_empty() && oid.bytes().all(|b| b == b'0')
}
pub fn forget() -> bool {
crate::git::succeeds(&["update-ref", "-d", NOTES_FULL_REF])
}
pub fn forget_in(repo: &std::path::Path) -> bool {
crate::git::succeeds_in(repo, &["update-ref", "-d", NOTES_FULL_REF])
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn dir(name: &str) -> PathBuf {
let d = std::env::temp_dir().join(format!("attest-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
d
}
fn git(dir: &Path, args: &[&str]) -> String {
let out = std::process::Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.output()
.expect("git");
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn repo(name: &str) -> PathBuf {
let d = dir(name);
git(&d, &["init", "-q", "--template=", "."]);
git(&d, &["config", "user.email", "t@t.test"]);
git(&d, &["config", "user.name", "t"]);
d
}
fn keypair(d: &Path) -> (PathBuf, PathBuf) {
let key = d.join("attest_key");
let ok = std::process::Command::new("ssh-keygen")
.args(["-q", "-t", "ed25519", "-N", "", "-C", "test", "-f"])
.arg(&key)
.status()
.expect("ssh-keygen must exist for these tests")
.success();
assert!(ok, "keygen failed");
let pubkey = std::fs::read_to_string(key.with_extension("pub")).unwrap();
let signers = d.join("allowed_signers");
std::fs::write(
&signers,
format!("t@t.test namespaces=\"{NAMESPACE}\" {pubkey}"),
)
.unwrap();
(key, signers)
}
fn in_repo<T>(dir: &Path, f: impl FnOnce() -> T) -> T {
let _guard = crate::TEST_CWD.lock().unwrap_or_else(|p| p.into_inner());
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(dir).unwrap();
let r = f();
std::env::set_current_dir(prev).unwrap();
r
}
#[test]
fn the_payload_is_the_documented_contract() {
let p = payload(
"abc123",
&["pre-push-pytest".into(), "pre-push-cargo-test".into()],
);
let lines: Vec<&str> = p.lines().collect();
assert_eq!(lines[0], FORMAT);
assert_eq!(lines[1], "tree abc123");
assert_eq!(lines[2], "gates pre-push-pytest pre-push-cargo-test");
assert_eq!(lines[3], format!("platform {}", platform()));
assert_eq!(lines[4], format!("amont {}", env!("CARGO_PKG_VERSION")));
assert!(
p.ends_with('\n'),
"CI reconstructs these bytes; the trailing newline is part of them"
);
}
#[test]
fn sign_verify_roundtrip_and_tamper_rejection() {
let d = dir("roundtrip");
let (key, signers) = keypair(&d);
let p = payload("deadbeef", &["pre-push-pytest".into()]);
let sig = sign(&p, &key).expect("signing with a real key succeeds");
assert!(verify(&p, &sig, &signers, "t@t.test"));
let tampered = payload("deadbeee", &["pre-push-pytest".into()]);
assert!(!verify(&tampered, &sig, &signers, "t@t.test"));
assert!(!verify(&p, &sig, &signers, "someone@else.test"));
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn a_missing_key_signs_nothing() {
assert!(sign("anything", Path::new("/nonexistent/key")).is_none());
}
#[test]
fn an_enabled_push_leaves_a_verifiable_note_on_the_remote() {
let d = dir("e2e");
let (key, signers) = keypair(&d);
let remote = d.join("remote.git");
std::fs::create_dir_all(&remote).unwrap();
git(&remote, &["init", "-q", "--bare", "--template=", "."]);
let work = repo("e2e-work");
git(
&work,
&["remote", "add", "origin", remote.to_str().unwrap()],
);
git(&work, &["config", "amont.attest", "true"]);
git(&work, &["config", "amont.attestKey", key.to_str().unwrap()]);
std::fs::write(work.join("a.ts"), "x").unwrap();
git(&work, &["add", "a.ts"]);
git(&work, &["commit", "-qm", "chore: a"]);
let head = git(&work, &["rev-parse", "HEAD"]);
let tree = git(&work, &["rev-parse", "HEAD^{tree}"]);
let push_ref = PushRef {
local_ref: "refs/heads/main".into(),
local_oid: head.clone(),
remote_ref: "refs/heads/main".into(),
remote_oid: "0".repeat(40),
};
in_repo(&work, || {
attest_push("origin", &[push_ref], &["pre-push-run-tests-js".into()]);
});
let body = git(&remote, &["notes", "--ref", NOTES_REF, "show", &head]);
assert!(!body.is_empty(), "no note reached the remote");
let (p, sig) = body
.split_once("\n\n")
.expect("payload, blank line, signature");
let p = format!("{p}\n"); assert!(p.starts_with(FORMAT));
assert!(
p.contains(&format!("tree {tree}")),
"attests the pushed tree"
);
assert!(
verify(&p, sig, &signers, "t@t.test"),
"the remote copy verifies"
);
let _ = std::fs::remove_dir_all(&d);
let _ = std::fs::remove_dir_all(&work);
}
#[test]
fn no_opt_in_means_no_note() {
let d = dir("optout");
let (key, _) = keypair(&d);
let remote = d.join("remote.git");
std::fs::create_dir_all(&remote).unwrap();
git(&remote, &["init", "-q", "--bare", "--template=", "."]);
let work = repo("optout-work");
git(
&work,
&["remote", "add", "origin", remote.to_str().unwrap()],
);
git(&work, &["config", "amont.attestKey", key.to_str().unwrap()]);
std::fs::write(work.join("a.ts"), "x").unwrap();
git(&work, &["add", "a.ts"]);
git(&work, &["commit", "-qm", "chore: a"]);
let head = git(&work, &["rev-parse", "HEAD"]);
let push_ref = PushRef {
local_ref: "refs/heads/main".into(),
local_oid: head.clone(),
remote_ref: "refs/heads/main".into(),
remote_oid: "0".repeat(40),
};
in_repo(&work, || {
attest_push("origin", &[push_ref], &["pre-push-run-tests-js".into()]);
});
assert!(
git(&remote, &["notes", "--ref", NOTES_REF, "list"]).is_empty(),
"an un-opted-in repo attested something"
);
let _ = std::fs::remove_dir_all(&d);
let _ = std::fs::remove_dir_all(&work);
}
#[test]
fn deletions_and_empty_gates_attest_nothing() {
let d = dir("nothing");
let (key, _) = keypair(&d);
let remote = d.join("remote.git");
std::fs::create_dir_all(&remote).unwrap();
git(&remote, &["init", "-q", "--bare", "--template=", "."]);
let work = repo("nothing-work");
git(
&work,
&["remote", "add", "origin", remote.to_str().unwrap()],
);
git(&work, &["config", "amont.attest", "true"]);
git(&work, &["config", "amont.attestKey", key.to_str().unwrap()]);
std::fs::write(work.join("a.ts"), "x").unwrap();
git(&work, &["add", "a.ts"]);
git(&work, &["commit", "-qm", "chore: a"]);
let head = git(&work, &["rev-parse", "HEAD"]);
let deletion = PushRef {
local_ref: "(delete)".into(),
local_oid: "0".repeat(40),
remote_ref: "refs/heads/gone".into(),
remote_oid: head.clone(),
};
let real = PushRef {
local_ref: "refs/heads/main".into(),
local_oid: head,
remote_ref: "refs/heads/main".into(),
remote_oid: "0".repeat(40),
};
in_repo(&work, || {
attest_push("origin", &[deletion], &["pre-push-pytest".into()]);
attest_push("origin", &[real], &[]);
});
assert!(git(&remote, &["notes", "--ref", NOTES_REF, "list"]).is_empty());
let _ = std::fs::remove_dir_all(&d);
let _ = std::fs::remove_dir_all(&work);
}
#[test]
fn covered_answers_in_a_fresh_clone_and_rejects_drift_and_forgery() {
let d = dir("covered");
let (key, signers) = keypair(&d);
let remote = d.join("remote.git");
std::fs::create_dir_all(&remote).unwrap();
git(&remote, &["init", "-q", "--bare", "--template=", "."]);
git(&remote, &["symbolic-ref", "HEAD", "refs/heads/main"]);
let work = repo("covered-work");
git(
&work,
&["remote", "add", "origin", remote.to_str().unwrap()],
);
git(&work, &["config", "amont.attest", "true"]);
git(&work, &["config", "amont.attestKey", key.to_str().unwrap()]);
std::fs::write(work.join("a.ts"), "x").unwrap();
git(&work, &["add", "a.ts"]);
git(&work, &["commit", "-qm", "chore: a"]);
git(&work, &["push", "-q", "origin", "HEAD:main"]);
let head = git(&work, &["rev-parse", "HEAD"]);
let push_ref = PushRef {
local_ref: "refs/heads/main".into(),
local_oid: head.clone(),
remote_ref: "refs/heads/main".into(),
remote_oid: "0".repeat(40),
};
in_repo(&work, || {
attest_push("origin", &[push_ref], &["pre-push-pytest".into()]);
});
let clone = d.join("ci-checkout");
git(
&d,
&[
"clone",
"-q",
"--template=",
remote.to_str().unwrap(),
clone.to_str().unwrap(),
],
);
in_repo(&clone, || {
assert_eq!(
covered(&signers, "t@t.test", Some(&platform())).as_deref(),
Some("pre-push-pytest"),
"a fresh clone verifies the attestation and reads the gates"
);
assert_eq!(
covered(&signers, "t@t.test", None).as_deref(),
Some("pre-push-pytest"),
"`any` covers a platform-independent suite"
);
assert_eq!(
covered(&signers, "t@t.test", Some("s390x-aix")),
None,
"a pass on one platform is not evidence about another"
);
assert_eq!(
covered(&signers, "someone@else.test", None),
None,
"an unlisted principal covers nothing"
);
});
std::fs::write(clone.join("b.ts"), "y").unwrap();
git(&clone, &["config", "user.email", "t@t.test"]);
git(&clone, &["config", "user.name", "t"]);
git(&clone, &["add", "b.ts"]);
git(&clone, &["commit", "-qm", "chore: b"]);
in_repo(&clone, || {
assert_eq!(covered(&signers, "t@t.test", None), None, "drifted tree");
});
git(
&work,
&[
"notes", "--ref", NOTES_REF, "add", "-f", "-m", "garbage", &head,
],
);
git(
&work,
&[
"push",
"-q",
"origin",
&format!("+{NOTES_FULL_REF}:{NOTES_FULL_REF}"),
],
);
git(&clone, &["reset", "-q", "--hard", &head]);
in_repo(&clone, || {
assert_eq!(
covered(&signers, "t@t.test", None),
None,
"a foreign note is not a stamp"
);
});
let _ = std::fs::remove_dir_all(&d);
let _ = std::fs::remove_dir_all(&work);
}
#[test]
fn default_signers_is_found_from_a_subdirectory() {
let work = repo("signers-subdir");
std::fs::create_dir_all(work.join(".forgejo")).unwrap();
std::fs::write(work.join(".forgejo/allowed_signers"), "t@t.test x\n").unwrap();
let sub = work.join("packages").join("thing");
std::fs::create_dir_all(&sub).unwrap();
in_repo(&sub, || {
let found = default_signers().expect("resolved from the repo root, not the cwd");
assert!(found.ends_with(".forgejo/allowed_signers"));
assert!(found.exists(), "the path it returns must be usable as-is");
assert_eq!(
first_principal(&found).as_deref(),
Some("t@t.test"),
"and readable from there"
);
});
let _ = std::fs::remove_dir_all(&work);
}
#[test]
fn zero_oids_of_any_width_are_zero() {
assert!(is_zero(&"0".repeat(40)));
assert!(is_zero(&"0".repeat(64)));
assert!(!is_zero("0a0000"));
assert!(!is_zero(""));
}
#[test]
fn forget_removes_the_local_ref() {
let work = repo("forget");
std::fs::write(work.join("a.ts"), "x").unwrap();
git(&work, &["add", "a.ts"]);
git(&work, &["commit", "-qm", "chore: a"]);
git(
&work,
&["notes", "--ref", NOTES_REF, "add", "-m", "x", "HEAD"],
);
in_repo(&work, forget);
assert!(git(&work, &["notes", "--ref", NOTES_REF, "list"]).is_empty());
let _ = std::fs::remove_dir_all(&work);
}
}