the-custodian/docs/changes/netkingdom-target-2026-10-17/sops-fd-r2/source.patch
codex 6f29868361
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Has been cancelled
docs: preserve reviewed NetKingdom session artifacts
2026-11-01 23:35:28 +01:00

82 lines
3.9 KiB
Diff

--- a/tests/test_sops_private.py
+++ b/tests/test_sops_private.py
@@ -10,6 +10,40 @@
ROOT = Path(__file__).resolve().parents[1]
SOPS = shutil.which('sops')
pytestmark = pytest.mark.skipif(SOPS is None, reason='SOPS required for real crypto regression')
+
+
+@pytest.mark.parametrize('wrong_recipient', [False, True])
+def test_real_sops_decrypt_with_only_sealed_descriptor(case, wrong_recipient):
+ import fcntl
+ original = case[1].read_bytes()
+ material = (subprocess.run(['age-keygen'], capture_output=True, check=True).stdout
+ if wrong_recipient else original)
+ fd = os.memfd_create('generated-sops-custody', os.MFD_ALLOW_SEALING)
+ try:
+ os.fchmod(fd, 0o600); os.write(fd, material); os.lseek(fd, 0, os.SEEK_SET)
+ fcntl.fcntl(fd, fcntl.F_ADD_SEALS,
+ fcntl.F_SEAL_WRITE | fcntl.F_SEAL_GROW | fcntl.F_SEAL_SHRINK | fcntl.F_SEAL_SEAL)
+ case[1].unlink()
+ env = {**case[2], 'NETKINGDOM_CUSTODY_AGE_KEY_FD': str(fd)}
+ env.pop('NETKINGDOM_CUSTODY_AGE_KEY_FILE')
+ if wrong_recipient:
+ # A correct ambient key must never rescue the wrong explicit FD.
+ env['SOPS_AGE_KEY'] = original.decode()
+ result = subprocess.run(['make', '-s', 'sops-decrypt', 'FILE=' + str(case[3]),
+ 'OUTPUT=' + str(case[4])], cwd=case[0], env=env,
+ pass_fds=(fd,), capture_output=True, text=True, timeout=15)
+ assert not case[1].exists()
+ assert 'synthetic-secret-payload' not in result.stdout + result.stderr
+ if wrong_recipient:
+ assert result.returncode != 0
+ assert not case[4].exists()
+ else:
+ assert result.returncode == 0
+ assert json.loads(case[4].read_text()) == {'credential': 'synthetic-secret-payload'}
+ assert case[4].stat().st_mode & 0o077 == 0
+ assert not list(case[4].parent.glob('.netkingdom-sops-*'))
+ finally:
+ os.close(fd)
@pytest.fixture
--- a/tests/test_sops_write.py
+++ b/tests/test_sops_write.py
@@ -11,6 +11,36 @@
SOPS=shutil.which('sops')
pytestmark=pytest.mark.skipif(SOPS is None,reason='real SOPS required')
PLAIN='synthetic-sops-write-value'
+
+
+@pytest.mark.parametrize('action', ['rotate', 'edit'])
+def test_real_sops_write_with_only_sealed_descriptor(case, action):
+ import fcntl
+ fd = os.memfd_create('generated-sops-write-custody', os.MFD_ALLOW_SEALING)
+ try:
+ os.fchmod(fd, 0o600); os.write(fd, case['key'].read_bytes()); os.lseek(fd, 0, os.SEEK_SET)
+ fcntl.fcntl(fd, fcntl.F_ADD_SEALS,
+ fcntl.F_SEAL_WRITE | fcntl.F_SEAL_GROW | fcntl.F_SEAL_SHRINK | fcntl.F_SEAL_SEAL)
+ case['key'].unlink()
+ env = {**case['env'], 'NETKINGDOM_CUSTODY_AGE_KEY_FD': str(fd)}
+ env.pop('NETKINGDOM_CUSTODY_AGE_KEY_FILE'); env.pop('SOPS_AGE_KEY_FILE')
+ before = case['cipher'].read_bytes()
+ result = subprocess.run(['make', '-s', 'sops-' + action,
+ 'FILE=' + str(case['cipher']), 'INPUT=' + str(case['input'])],
+ cwd=case['repo'], env=env, pass_fds=(fd,),
+ capture_output=True, text=True, timeout=15)
+ assert result.returncode == 0
+ assert PLAIN not in result.stdout + result.stderr
+ assert not case['key'].exists()
+ assert before != case['cipher'].read_bytes()
+ clear = subprocess.run([SOPS, '--decrypt', str(case['cipher'])],
+ env={**env, 'SOPS_AGE_KEY_FILE': '/proc/self/fd/' + str(fd)},
+ pass_fds=(fd,), capture_output=True, check=True)
+ assert json.loads(clear.stdout) == {'credential': PLAIN + ('-edited' if action == 'edit' else '')}
+ assert case['cipher'].stat().st_mode & 0o077 == 0
+ assert not list(case['cipher'].parent.glob('.netkingdom-sops-write-*'))
+ finally:
+ os.close(fd)
@pytest.fixture