79 lines
4 KiB
Python
79 lines
4 KiB
Python
"""Verify the installed Tea client in a disposable configuration, without tokens in output."""
|
|
import argparse
|
|
import csv
|
|
import io
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--isolation-only', action='store_true')
|
|
args = parser.parse_args()
|
|
tea = '/home/worsch/bin/tea'
|
|
report = {'native_changes': False, 'client': 'installed Tea 0.9.2',
|
|
'profile': 'existing routed workstation development credential; no independent agent identity claim',
|
|
'original_tea_configuration_used': False, 'credential_values_emitted': False}
|
|
secret = os.environ.pop('API_TOKEN', '')
|
|
with tempfile.TemporaryDirectory(prefix='wp80-tea-proof-') as temporary:
|
|
os.chmod(temporary, 0o700)
|
|
environment = dict(os.environ)
|
|
for key in ['GITEA_SERVER_TOKEN', 'GITEA_SERVER_PASSWORD', 'GITEA_SERVER_USER', 'GITEA_SERVER_URL']:
|
|
environment.pop(key, None)
|
|
# Tea documents this child-only configuration location in its own help.
|
|
environment['XDG_CONFIG_HOME'] = temporary
|
|
|
|
def call(arguments):
|
|
start = time.monotonic()
|
|
result = subprocess.run([tea, *arguments], cwd=temporary, env=environment,
|
|
capture_output=True, text=True, timeout=30)
|
|
return result, round(time.monotonic() - start, 3)
|
|
|
|
listing, _ = call(['logins', 'list', '-o', 'csv'])
|
|
rows = list(csv.DictReader(io.StringIO(listing.stdout)))
|
|
if listing.returncode or rows:
|
|
raise SystemExit('Tea configuration isolation failed; no login was written')
|
|
report['configuration_isolation_verified'] = True
|
|
if args.isolation_only:
|
|
report['mode'] = 'configuration-isolation-only; no credential or API request'
|
|
else:
|
|
if not secret:
|
|
raise SystemExit('Use the owner-routed --exec transport; API_TOKEN missing')
|
|
environment['GITEA_SERVER_TOKEN'] = secret
|
|
login, seconds = call(['logins', 'add', '--name', 'wp80-current', '--url', 'https://forgejo.coulomb.social'])
|
|
environment.pop('GITEA_SERVER_TOKEN', None)
|
|
secret = ''
|
|
report['login_exit_code'] = login.returncode
|
|
report['login_seconds'] = seconds
|
|
if login.returncode == 0:
|
|
for path in Path(temporary).rglob('*'):
|
|
if path.is_file():
|
|
path.chmod(0o600)
|
|
listing, _ = call(['logins', 'list', '-o', 'csv'])
|
|
rows = list(csv.DictReader(io.StringIO(listing.stdout)))
|
|
if listing.returncode or len(rows) != 1 or rows[0].get('URL') != 'https://forgejo.coulomb.social':
|
|
raise SystemExit('Unexpected current-host configuration; raw output discarded')
|
|
user = rows[0].get('User', '')
|
|
if not user or len(user) > 64 or not all(c.isalnum() or c in '._-' for c in user):
|
|
raise SystemExit('Unexpected identity metadata; raw output discarded')
|
|
report['api_user'] = user
|
|
who, seconds = call(['whoami'])
|
|
report['whoami_exit_code'] = who.returncode
|
|
report['whoami_seconds'] = seconds
|
|
report['returned_identity_matches'] = user in who.stdout
|
|
repo, seconds = call(['repos', '--login', 'wp80-current', '--fields', 'owner,name,permission',
|
|
'--output', 'csv', 'coulomb/the-custodian'])
|
|
report['repository_read_exit_code'] = repo.returncode
|
|
report['repository_read_seconds'] = seconds
|
|
report['expected_repository_returned'] = 'the-custodian' in repo.stdout and 'coulomb' in repo.stdout
|
|
report['passed'] = (who.returncode == repo.returncode == 0 and
|
|
report['returned_identity_matches'] and report['expected_repository_returned'])
|
|
else:
|
|
report['passed'] = False
|
|
report['reason'] = 'Current-host token login failed; raw child output discarded'
|
|
report['temporary_configuration_removed'] = True
|
|
print(json.dumps(report, indent=2))
|
|
if not args.isolation_only and not report.get('passed'):
|
|
raise SystemExit(1)
|