Build local product stack with Vault-managed secrets (#42)
This commit is contained in:
1 parent
8e2ef7aa88
commit
184a3be413
32 files changed
+1191
-10
No files matched your search
@@ -0,0 +1,72 @@
|
||||
"""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')
|
||||
Reference in new issue
Block a user