82 lines
3.9 KiB
Diff
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
|