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
|
|
@ -14,6 +14,7 @@ import sys
|
|||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / 'scripts'))
|
||||
from service_assurance import read_contract, admission
|
||||
from recovery_evidence import recovery_signals
|
||||
|
||||
|
||||
def query(args, allowed_codes=(0,)):
|
||||
|
|
@ -94,7 +95,8 @@ def capture():
|
|||
add('eso.ready', 'unavailable')
|
||||
add('eso.refresh', 'unavailable')
|
||||
# No token, Secret, application data/logs or seal/unseal mutation.
|
||||
# Native restore and offsite receipts remain separate attended evidence.
|
||||
# Receipt timestamps are preserved; reads never renew recovery evidence.
|
||||
signals.update(recovery_signals(datetime.now(timezone.utc)))
|
||||
return {'schema': 'railiance-platform.observation.v1', 'cluster_uid': uid,
|
||||
'captured_at': datetime.now(timezone.utc).isoformat(), 'signals': signals}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,63 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
# Silent owner command for the governed openbao-platform-admin-login lane.
|
||||
# Warden rejects any child output and self-revokes the attended session after
|
||||
# this command exits. The role payload contains no secret values.
|
||||
|
||||
# Silent child for the governed attended login; no role or token output.
|
||||
set -euo pipefail
|
||||
|
||||
ROLE_PATH="auth/netkingdom/role/platform-admin"
|
||||
CALLBACK_URI="http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback"
|
||||
PAYLOAD="$(mktemp "${TMPDIR:-/tmp}/openbao-platform-admin-role.XXXXXX.json")"
|
||||
READBACK="$(mktemp "${TMPDIR:-/tmp}/openbao-platform-admin-readback.XXXXXX.json")"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$PAYLOAD" "$READBACK"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
chmod 0600 "$PAYLOAD" "$READBACK"
|
||||
|
||||
command -v bao >/dev/null 2>&1
|
||||
command -v python3 >/dev/null 2>&1
|
||||
|
||||
cat >"$PAYLOAD" <<'ROLE_JSON'
|
||||
{
|
||||
"role_type": "oidc",
|
||||
"user_claim": "sub",
|
||||
"groups_claim": "groups",
|
||||
"oidc_scopes": ["openid", "profile", "email", "groups"],
|
||||
"allowed_redirect_uris": [
|
||||
"http://localhost:8250/oidc/callback",
|
||||
"http://127.0.0.1:8250/oidc/callback",
|
||||
"http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback",
|
||||
"https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback",
|
||||
"https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback"
|
||||
],
|
||||
"bound_claims": {
|
||||
"groups": ["net-kingdom-admins"]
|
||||
},
|
||||
"claim_mappings": {
|
||||
"email": "email",
|
||||
"preferred_username": "username"
|
||||
},
|
||||
"policies": ["platform-admin"],
|
||||
"ttl": "1h"
|
||||
}
|
||||
ROLE_JSON
|
||||
|
||||
bao write "$ROLE_PATH" @"$PAYLOAD" >/dev/null 2>&1
|
||||
bao read -format=json "$ROLE_PATH" >"$READBACK" 2>/dev/null
|
||||
python3 - "$READBACK" "$CALLBACK_URI" <<'PY' >/dev/null 2>&1
|
||||
import json
|
||||
import sys
|
||||
|
||||
path, callback = sys.argv[1:]
|
||||
with open(path, encoding="utf-8") as handle:
|
||||
role = json.load(handle).get("data") or {}
|
||||
|
||||
if callback not in role.get("allowed_redirect_uris", []):
|
||||
raise SystemExit(1)
|
||||
if role.get("role_type") != "oidc":
|
||||
raise SystemExit(1)
|
||||
if "platform-admin" not in role.get("token_policies", role.get("policies", [])):
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
exec python3 "$(dirname "$0")/openbao_operator_loopback_callback.py" "$@" >/dev/null 2>&1
|
||||
|
|
|
|||
49
scripts/openbao_operator_loopback_callback.py
Normal file
49
scripts/openbao_operator_loopback_callback.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Silent contained callback update; preserve the existing administrator role."""
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROLE = 'auth/netkingdom/role/platform-admin'
|
||||
CALLBACK = 'http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback'
|
||||
|
||||
|
||||
def read_role():
|
||||
result = subprocess.run(['bao', 'read', '-format=json', ROLE],
|
||||
capture_output=True, check=True, timeout=30)
|
||||
role = json.loads(result.stdout)['data']
|
||||
if (role.get('role_type') != 'oidc'
|
||||
or 'platform-admin' not in role.get('token_policies', role.get('policies', []))
|
||||
or not isinstance(role.get('allowed_redirect_uris'), list)
|
||||
or not all(isinstance(uri, str) for uri in role['allowed_redirect_uris'])):
|
||||
raise ValueError('unexpected role')
|
||||
return role
|
||||
|
||||
|
||||
def update(read=read_role, write=None):
|
||||
original = read()
|
||||
if CALLBACK in original['allowed_redirect_uris']:
|
||||
return False
|
||||
desired = dict(original, allowed_redirect_uris=original['allowed_redirect_uris'] + [CALLBACK])
|
||||
if read() != original:
|
||||
raise ValueError('role changed before write')
|
||||
# The endpoint has no CAS: this detects observed drift, not an atomic lock.
|
||||
if write is None:
|
||||
subprocess.run(['bao', 'write', ROLE, '-'], input=json.dumps(desired).encode(),
|
||||
capture_output=True, check=True, timeout=30)
|
||||
else:
|
||||
write(desired)
|
||||
if read() != desired:
|
||||
raise ValueError('role readback differs')
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
if sys.argv[1:] == ['--check-only']:
|
||||
sys.exit(0 if CALLBACK in read_role()['allowed_redirect_uris'] else 3)
|
||||
if sys.argv[1:]:
|
||||
sys.exit(2)
|
||||
update()
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
44
scripts/recovery_evidence.py
Normal file
44
scripts/recovery_evidence.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""Hash-pinned native recovery receipts, with original completion timestamps."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from service_assurance import timestamp
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def recovery_signals(now, root=ROOT):
|
||||
index = json.loads((root / 'assurance/recovery-evidence.json').read_text())
|
||||
if index['schema'] != 'railiance-platform.recovery-evidence.v1':
|
||||
raise ValueError('unknown recovery index')
|
||||
signals = {}
|
||||
for entry in index['receipts']:
|
||||
signal = entry['signal']
|
||||
if signal in signals or signal not in ('apps-pg.restore', 'forgejo-db.restore'):
|
||||
raise ValueError('unexpected recovery signal')
|
||||
sample = {'result': 'unavailable', 'observed_at': now.isoformat()}
|
||||
try:
|
||||
path = (root / entry['path']).resolve()
|
||||
if not path.is_relative_to((root / 'docs/evidence').resolve()):
|
||||
raise ValueError('receipt outside evidence directory')
|
||||
raw = path.read_bytes()
|
||||
if hashlib.sha256(raw).hexdigest() != entry['sha256']:
|
||||
raise ValueError('receipt drift')
|
||||
receipt = json.loads(raw)
|
||||
cell = signal.removesuffix('.restore')
|
||||
if (receipt['schema'] != 'platform.scaleway-primary-restore.v1'
|
||||
or receipt['primary_destination'] != f's3://railiance-platform-pg-backup/platform-pg/{cell}/'
|
||||
or receipt['source'] != 'Scaleway Barman base backup and WAL'
|
||||
or receipt['stage'] != 'database_acceptance'
|
||||
or receipt['status'] != 'verified'
|
||||
or receipt['cleanup'] is not True
|
||||
or receipt['production_ready'] is not True):
|
||||
raise ValueError('receipt not accepted')
|
||||
completed = timestamp(receipt['finished_at'])
|
||||
if not timestamp(receipt['started_at']) <= completed <= now:
|
||||
raise ValueError('invalid receipt chronology')
|
||||
sample = {'result': 'pass', 'observed_at': receipt['finished_at']}
|
||||
except (OSError, ValueError, KeyError, TypeError):
|
||||
pass
|
||||
signals[signal] = sample
|
||||
return signals
|
||||
Loading…
Add table
Add a link
Reference in a new issue