Advance blocked assurance and operator callback work
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
This commit is contained in:
parent
a3ca4b708f
commit
445f1361dc
16 changed files with 505 additions and 144 deletions
|
|
@ -1,78 +1,62 @@
|
|||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import copy
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
spec = importlib.util.spec_from_file_location('callback', Path(__file__).resolve().parents[1] / 'scripts/openbao_operator_loopback_callback.py')
|
||||
m = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(m)
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPT = REPO_ROOT / "scripts/openbao-apply-operator-loopback-callback.sh"
|
||||
CALLBACK = "http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback"
|
||||
def role():
|
||||
return {'role_type': 'oidc', 'token_policies': ['platform-admin'],
|
||||
'allowed_redirect_uris': ['https://existing.example/callback'],
|
||||
'token_ttl': 900, 'bound_claims': {'groups': ['custom-admins']},
|
||||
'claim_mappings': {'email': 'email'}, 'token_max_ttl': 1800}
|
||||
|
||||
|
||||
def _fake_bao(tmp_path: Path) -> tuple[Path, Path]:
|
||||
capture = tmp_path / "written-role.json"
|
||||
executable = tmp_path / "bao"
|
||||
executable.write_text(
|
||||
"""#!/bin/sh
|
||||
set -eu
|
||||
if [ "$1" = "write" ]; then
|
||||
cp "${3#@}" "$BAO_CAPTURE"
|
||||
exit 0
|
||||
fi
|
||||
if [ "$1" = "read" ]; then
|
||||
if [ "${BAO_FAKE_MISSING_CALLBACK:-false}" = "true" ]; then
|
||||
printf '%s\\n' '{"data":{"role_type":"oidc","token_policies":["platform-admin"],"allowed_redirect_uris":[]}}'
|
||||
else
|
||||
printf '%s\\n' '{"data":{"role_type":"oidc","token_policies":["platform-admin"],"allowed_redirect_uris":["http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback"]}}'
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
exit 2
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
def test_preserves_all_other_settings():
|
||||
current = role()
|
||||
original = copy.deepcopy(current)
|
||||
writes = []
|
||||
def write(value):
|
||||
writes.append(value)
|
||||
current.update(value)
|
||||
assert m.update(lambda: copy.deepcopy(current), write)
|
||||
assert len(writes) == 1
|
||||
assert current == dict(original, allowed_redirect_uris=original['allowed_redirect_uris'] + [m.CALLBACK])
|
||||
assert not m.update(lambda: copy.deepcopy(current), write)
|
||||
assert len(writes) == 1
|
||||
|
||||
|
||||
def test_observed_concurrent_change_prevents_write():
|
||||
reads = iter([role(), dict(role(), token_ttl=300)])
|
||||
with pytest.raises(ValueError):
|
||||
m.update(lambda: next(reads), lambda _: pytest.fail('must not write'))
|
||||
|
||||
|
||||
def test_failed_readback_is_not_success():
|
||||
with pytest.raises(ValueError):
|
||||
m.update(role, lambda _: None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mutation', [{'role_type': 'jwt'}, {'token_policies': ['other']}, {'allowed_redirect_uris': 'bad'}])
|
||||
def test_unexpected_live_role_refused(monkeypatch, mutation):
|
||||
import json
|
||||
import subprocess
|
||||
payload = dict(role(), **mutation)
|
||||
monkeypatch.setattr(m.subprocess, 'run', lambda *a, **kw: subprocess.CompletedProcess(a, 0, json.dumps({'data': payload}).encode()))
|
||||
with pytest.raises(ValueError):
|
||||
m.read_role()
|
||||
|
||||
|
||||
def test_silent_entrypoint_on_command_failure(tmp_path):
|
||||
import os
|
||||
import subprocess
|
||||
executable = tmp_path / 'bao'
|
||||
executable.write_text('#!/bin/sh\necho SECRET_CANARY >&2\nexit 1\n')
|
||||
executable.chmod(0o755)
|
||||
return executable, capture
|
||||
|
||||
|
||||
def _run(tmp_path: Path, *, missing_callback: bool = False) -> tuple[subprocess.CompletedProcess[str], Path]:
|
||||
_, capture = _fake_bao(tmp_path)
|
||||
env = dict(os.environ)
|
||||
env.update(
|
||||
{
|
||||
"PATH": f"{tmp_path}:{env['PATH']}",
|
||||
"TMPDIR": str(tmp_path),
|
||||
"BAO_CAPTURE": str(capture),
|
||||
"BAO_FAKE_MISSING_CALLBACK": str(missing_callback).lower(),
|
||||
}
|
||||
)
|
||||
result = subprocess.run(
|
||||
[str(SCRIPT)],
|
||||
cwd=REPO_ROOT,
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
return result, capture
|
||||
|
||||
|
||||
def test_contained_command_is_silent_and_writes_exact_role(tmp_path: Path) -> None:
|
||||
result, capture = _run(tmp_path)
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
assert result.stderr == ""
|
||||
|
||||
role = json.loads(capture.read_text(encoding="utf-8"))
|
||||
assert CALLBACK in role["allowed_redirect_uris"]
|
||||
assert role["policies"] == ["platform-admin"]
|
||||
assert role["bound_claims"] == {"groups": ["net-kingdom-admins"]}
|
||||
assert not list(tmp_path.glob("openbao-platform-admin-*.json"))
|
||||
|
||||
|
||||
def test_verification_failure_remains_silent_and_nonzero(tmp_path: Path) -> None:
|
||||
result, _ = _run(tmp_path, missing_callback=True)
|
||||
result = subprocess.run([str(Path(m.__file__).with_name('openbao-apply-operator-loopback-callback.sh'))],
|
||||
env=dict(os.environ, PATH=str(tmp_path) + ':' + os.environ['PATH']), capture_output=True)
|
||||
assert result.returncode != 0
|
||||
assert result.stdout == ""
|
||||
assert result.stderr == ""
|
||||
assert not list(tmp_path.glob("openbao-platform-admin-*.json"))
|
||||
assert result.stdout == result.stderr == b''
|
||||
|
|
|
|||
43
tests/test_recovery_evidence.py
Normal file
43
tests/test_recovery_evidence.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
from datetime import datetime, timezone, timedelta
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import pytest
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
|
||||
from recovery_evidence import recovery_signals, ROOT
|
||||
from service_assurance import evaluate
|
||||
|
||||
NOW = datetime(2026, 9, 6, 12, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def test_real_receipts_preserve_completion_and_eventually_expire():
|
||||
signals = recovery_signals(NOW)
|
||||
assert all(s['result'] == 'pass' for s in signals.values())
|
||||
assert signals['apps-pg.restore']['observed_at'] == '2026-09-05T22:30:45.208512+00:00'
|
||||
contract = {'cluster_uid': 'test', 'capture_max_age_seconds': 900,
|
||||
'signals': {key: {'owner': 'platform', 'max_age_seconds': 2592000} for key in signals}}
|
||||
later = NOW + timedelta(days=31)
|
||||
result = evaluate(contract, {'schema': 'railiance-platform.observation.v1',
|
||||
'cluster_uid': 'test', 'captured_at': later.isoformat(), 'signals': recovery_signals(later)}, later)
|
||||
assert all(s['state'] == 'stale' for s in result['signals'].values())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('change', ['hash', 'cleanup', 'provider', 'missing_time', 'future', 'naive'])
|
||||
def test_invalid_receipt_is_unavailable(tmp_path, change):
|
||||
index = json.loads((ROOT / 'assurance/recovery-evidence.json').read_text())
|
||||
index['receipts'] = index['receipts'][:1]
|
||||
entry = index['receipts'][0]
|
||||
receipt = json.loads((ROOT / entry['path']).read_text())
|
||||
if change == 'cleanup': receipt['cleanup'] = False
|
||||
if change == 'provider': receipt['primary_destination'] = 's3://other/'
|
||||
if change == 'missing_time': del receipt['finished_at']
|
||||
if change == 'future': receipt['finished_at'] = '2027-01-01T00:00:00Z'
|
||||
if change == 'naive': receipt['finished_at'] = '2026-09-05T23:00:00'
|
||||
path = tmp_path / entry['path']
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_text(json.dumps(receipt))
|
||||
if change != 'hash': entry['sha256'] = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
(tmp_path / 'assurance').mkdir()
|
||||
(tmp_path / 'assurance/recovery-evidence.json').write_text(json.dumps(index))
|
||||
assert recovery_signals(NOW, tmp_path)['apps-pg.restore']['result'] == 'unavailable'
|
||||
|
|
@ -147,7 +147,11 @@ class CollectorTests(unittest.TestCase):
|
|||
baseline = json.loads((ROOT / 'assurance/admission-baseline.json').read_text())
|
||||
with patch.object(self.collector, 'query', side_effect=query), patch.object(self.collector, 'admission', return_value=baseline):
|
||||
observation = self.collector.capture()
|
||||
for sample in observation['signals'].values(): self.assertEqual(sample['result'], 'unavailable')
|
||||
for name, sample in observation['signals'].items():
|
||||
if name not in ('apps-pg.restore', 'forgejo-db.restore'):
|
||||
self.assertEqual(sample['result'], 'unavailable')
|
||||
# Recorded recovery evidence is independent of failed live status reads.
|
||||
self.assertEqual(observation['signals']['apps-pg.restore']['result'], 'pass')
|
||||
self.assertNotIn('secret', [str(a).lower() for call in calls for a in call])
|
||||
result = m.evaluate(contract, observation, datetime.now(timezone.utc))
|
||||
self.assertFalse(result['healthy'])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue