From 3edfcf5656c5a56a43d65d7b71df3ea01b4e1d64 Mon Sep 17 00:00:00 2001 From: Anish Lakhwara Date: Sun, 1 Nov 2026 22:37:35 -0700 Subject: [PATCH] asusmini: resolve agenix rules explicitly during cutover --- scripts/upgrade-knot.sh | 3 ++- tests/test_agenix_rules.py | 47 ++++++++++++++++++++++++++++++++++++++ tests/test_upgrade_knot.py | 4 ++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/test_agenix_rules.py diff --git a/scripts/upgrade-knot.sh b/scripts/upgrade-knot.sh index a8b7c57..a67938b 100644 --- a/scripts/upgrade-knot.sh +++ b/scripts/upgrade-knot.sh @@ -151,7 +151,8 @@ trap 'exit 143' TERM umask 077 runtime=$(mktemp -d "$runtime_template") -agenix -d "$repo/secrets/knot-env.age" -i "$ssh_key" > "$runtime/knot.env" +AGENIX_RULES="$repo/secrets/secrets.nix" \ + agenix -d knot-env.age -i "$ssh_key" > "$runtime/knot.env" chmod 0400 "$runtime/knot.env" master_key="" export -n master_key diff --git a/tests/test_agenix_rules.py b/tests/test_agenix_rules.py new file mode 100644 index 0000000..da26ef6 --- /dev/null +++ b/tests/test_agenix_rules.py @@ -0,0 +1,47 @@ +import os +import shutil +import subprocess +import tempfile +import unittest +from pathlib import Path + + +@unittest.skipUnless( + all(shutil.which(tool) for tool in ("agenix", "age", "ssh-keygen")), + "requires agenix, age, and ssh-keygen on PATH", +) +class AgenixRulesTests(unittest.TestCase): + def test_decrypt_from_outside_rules_directory(self): + with tempfile.TemporaryDirectory(prefix="agenix-rules-test-") as raw: + root = Path(raw) + secrets = root / "secrets" + secrets.mkdir() + elsewhere = root / "elsewhere" + elsewhere.mkdir() + key = root / "identity" + subprocess.run( + ["ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", str(key)], + check=True, + ) + public_key = key.with_suffix(".pub").read_text().strip() + rules = secrets / "secrets.nix" + rules.write_text( + '{ "knot-env.age".publicKeys = [ "' + public_key + '" ]; }\n' + ) + plaintext = b"KNOT_MASTER_KEY=" + b"A" * 43 + b"=\n" + encrypted = subprocess.run( + ["age", "-r", public_key], input=plaintext, capture_output=True, check=True + ) + (secrets / "knot-env.age").write_bytes(encrypted.stdout) + result = subprocess.run( + ["agenix", "-d", "knot-env.age", "-i", str(key)], + cwd=elsewhere, + env={**os.environ, "AGENIX_RULES": str(rules)}, + capture_output=True, + check=True, + ) + self.assertEqual(result.stdout, plaintext) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_upgrade_knot.py b/tests/test_upgrade_knot.py index bfea9f4..646deac 100644 --- a/tests/test_upgrade_knot.py +++ b/tests/test_upgrade_knot.py @@ -97,6 +97,9 @@ elif name == "ssh-keyscan": elif name == "nix-env": pass elif name == "agenix": + rules = os.environ.get("AGENIX_RULES", "") + if not Path(rules).is_file() or args[:2] != ["-d", "knot-env.age"]: + raise SystemExit("agenix needs explicit rules and a rules-relative secret name") value = (b"KNOT_MASTER_KEY=" + (b"B" * 43 + b"=\n" if not fail("BAD_KEY") else b"bad\n")) sys.stdout.buffer.write(value) target = os.environ.get("AGENIX_TARGET") @@ -195,6 +198,7 @@ class UpgradeKnotTests(unittest.TestCase): self.repo = self.tmp / "repo" (self.repo / "secrets").mkdir(parents=True) (self.repo / "secrets" / "knot-env.age").write_text("not a secret used by fixture\n") + (self.repo / "secrets" / "secrets.nix").write_text("{}\n") self.source = self.tmp / "git" self.source.mkdir() (self.source / "knotserver.db").write_text("fixture db\n")