flex-auth/tools/exercise_sitting_review_policy.py

94 lines
4.1 KiB
Python
Raw Normal View History

import json, time, copy, subprocess, tempfile
from pathlib import Path
import argparse
p = argparse.ArgumentParser()
p.add_argument('--binary', required=True)
p.add_argument('--receipt', type=Path, required=True)
args = p.parse_args()
r = Path(__file__).resolve().parents[1] / 'examples/informed-decision-sitting'
records = json.loads((r / 'records.json').read_text())
results = []
with tempfile.TemporaryDirectory() as temp:
request_path = Path(temp) / 'request.json'
def check(name, request, expected):
request_path.write_text(json.dumps(request))
result = subprocess.run(
[args.binary, 'check', '--registry', str(r / 'registry.json'),
'--policy', str(r / 'policy.md'), '--request', str(request_path)],
capture_output=True, text=True, check=True)
d = json.loads(result.stdout)
assert d['effect'] == expected, (name, d)
results.append({'check': name, 'effect': d['effect']})
return d
for memo, record in records.items():
request = {
'id': 'local-regression',
'tenant': 'tenant:platform',
'subject': {
'id': 'synthetic-reviewer',
'type': 'human',
'tenant': 'tenant:platform',
'attributes': {
'groups': ['net-kingdom-admins'],
'roles': [],
'tenant_source': 'registration-supplied',
'principal_type_source': 'authentication-derived',
'assurance': {
'level': 'aal2',
'mfa': True,
'methods': ['pwd', 'otp'],
'source': 'key-cape',
'at': int(time.time()),
},
},
},
'resource': {
'id': memo,
'type': 'decision-memo',
'system': 'informed-decision',
'tenant': 'tenant:platform',
},
'action': 'accept',
'context': {
'memo_version': 1,
'approval_id': record['approval_id'],
'approval_binding_digest': record['binding_digest'],
},
'policy_version': 'v1',
}
label = record['label']
for action in ['read', 'acknowledge', 'accept', 'return', 'discuss', 'decline']:
check(label + ':' + action, request | {'action': action}, 'allow')
for name, path, value in [
('wrong-group', ['subject', 'attributes', 'groups'], ['net-kingdom-users']),
('no-group', ['subject', 'attributes', 'groups'], []),
('service', ['subject', 'type'], 'service'),
('stale-mfa', ['subject', 'attributes', 'assurance', 'at'], int(time.time()) - 901),
('future-mfa', ['subject', 'attributes', 'assurance', 'at'], int(time.time()) + 300),
('no-mfa', ['subject', 'attributes', 'assurance', 'mfa'], False),
('forged-human-route', ['subject', 'attributes', 'principal_type_source'], 'registration-supplied'),
('wrong-tenant', ['subject', 'tenant'], 'tenant:other'),
('other-memo', ['resource', 'id'], 'memo:other'),
('omitted-c01', ['resource', 'id'], 'memo:infd-20260914-c01'),
('t03-memo', ['resource', 'id'], 'memo:SECRETS-WP-0010-T03-apply'),
('changed-version', ['context', 'memo_version'], 2),
('changed-approval', ['context', 'approval_id'], 'other'),
('changed-digest', ['context', 'approval_binding_digest'], 'sha256:' + '0' * 64),
('consume', ['action'], 'consume'),
]:
candidate = copy.deepcopy(request)
target = candidate
for key in path[:-1]:
target = target[key]
target[path[-1]] = value
check(label + ':' + name, candidate, 'deny')
args.receipt.write_text(json.dumps({
'scope': 'local actual evaluator with synthetic identity; no live human approvals; T03 package untouched',
'checks': results,
}, indent=2) + '\n')
print(len(results), 'policy checks passed')