73 lines
4.1 KiB
Python
73 lines
4.1 KiB
Python
"""Integration checks against this project's running stack; never print secrets."""
|
|
import json
|
|
import ssl
|
|
import urllib.request
|
|
|
|
from bootstrap import compose, mongo
|
|
from vault import STATE
|
|
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
raise RuntimeError(message)
|
|
|
|
|
|
def database_access(vault):
|
|
for role, database, forbidden in [('bff', 'sxchnge', 'sxchnge_notifications'),
|
|
('notifications', 'sxchnge_notifications', 'sxchnge')]:
|
|
lease = vault.call('database/creds/' + role)
|
|
credentials = lease['data']
|
|
auth = 'db.getSiblingDB("admin").auth(' + json.dumps(credentials['username']) + ',' + json.dumps(credentials['password']) + ');'
|
|
try:
|
|
result = compose('exec', '-T', 'mongo', 'mongosh', '--quiet', '--file', '/dev/stdin', capture=True,
|
|
input=auth + 'const own=db.getSiblingDB(' + json.dumps(database) + '); own.local_smoke.insertOne({_id:"vault-check"}); own.local_smoke.deleteOne({_id:"vault-check"});\n'
|
|
'let denied=false; try { db.getSiblingDB(' + json.dumps(forbidden) + ').runCommand({find:"local_smoke",filter:{}}); } catch(e) { denied=e.code===13; } if(!denied) throw new Error("cross-database access allowed"); print("scope-ok");')
|
|
require('scope-ok' in result, 'Mongo service scope check failed')
|
|
renewed = vault.call('sys/leases/renew', {'lease_id': lease['lease_id'], 'increment': '1h'})
|
|
require(renewed['lease_duration'] > 0, 'Mongo lease renewal failed')
|
|
finally:
|
|
vault.call('sys/leases/revoke', {'lease_id': lease['lease_id']})
|
|
admin = vault.kv('bootstrap/mongo')['password']
|
|
result = mongo('print(admin.getUser(' + json.dumps(credentials['username']) + ')===null)', admin)
|
|
require('true' in result, 'Revoked Mongo user still exists')
|
|
print('PASS: database-plugin issuance, separate database permissions, lease renewal and revocation')
|
|
|
|
|
|
def policies(vault):
|
|
for service in ['bff', 'notifications', 'nats']:
|
|
root = STATE / 'agents' / service
|
|
login = vault.call('auth/approle/login', {'role_id': (root / 'role-id').read_text(),
|
|
'secret_id': (root / 'secret-id').read_text()})
|
|
token = login['auth']['client_token']
|
|
try:
|
|
result = vault.call('sys/capabilities', {'token': token, 'paths': ['kv/data/bootstrap/mongo', 'sys/init']})['data']
|
|
require(all(result[path] == ['deny'] for path in ['kv/data/bootstrap/mongo', 'sys/init']), 'Service can access bootstrap credentials')
|
|
finally:
|
|
vault.call('auth/token/revoke', {'token': token})
|
|
print('PASS: service identities cannot read bootstrap credentials')
|
|
|
|
|
|
def infrastructure(vault):
|
|
status = compose('ps', '--format', 'json', capture=True)
|
|
rows = [json.loads(line) for line in status.splitlines() if line.strip()]
|
|
infra = {r['Service']: r for r in rows if r['Service'] in {'vault', 'mongo', 'nats'}}
|
|
require(set(infra) == {'vault', 'mongo', 'nats'}, 'Expected Vault, Mongo and NATS')
|
|
require(all(r['Health'] == 'healthy' for r in infra.values()), 'Infrastructure is unhealthy')
|
|
print('PASS: Vault, Mongo replica set and NATS JetStream are healthy')
|
|
policies(vault)
|
|
database_access(vault)
|
|
|
|
|
|
def verify(vault):
|
|
status = compose('ps', '--format', 'json', capture=True)
|
|
rows = [json.loads(line) for line in status.splitlines() if line.strip()]
|
|
require({r['Service'] for r in rows} == {'vault', 'mongo', 'nats', 'notifications', 'bff', 'frontend'}, 'Expected six running services')
|
|
require(all(r['Health'] == 'healthy' for r in rows), 'Some services are unhealthy')
|
|
context = ssl.create_default_context(cafile=str(STATE / 'ca.pem'))
|
|
for path in ['/', '/api/v1/health']:
|
|
with urllib.request.urlopen('https://localhost:5126' + path, context=context, timeout=10) as response:
|
|
require(response.status == 200, 'Frontend or BFF health failed')
|
|
print('PASS: all six services healthy; frontend and proxied BFF use verified TLS')
|
|
infrastructure(vault)
|
|
print('Resend credentials are configured; no live email sent by this check')
|