Prepare password-free telemetry SMTP entry for attended custody
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e6f1-443f-7783-9920-a16b2ffc467f
This commit is contained in:
parent
c3607fffea
commit
3d43a9b1a3
3 changed files with 117 additions and 0 deletions
30
docs/telemetry-smtp-custody.md
Normal file
30
docs/telemetry-smtp-custody.md
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Telemetry SMTP entry
|
||||||
|
|
||||||
|
Existing RTEL-WP-0002-T04; no new task/workplan. Founder requested entry creation
|
||||||
|
on 2026-09-28 after creating platform@coulomb.social, and will add the password.
|
||||||
|
|
||||||
|
KV v2 mount: platform. Entry: workloads/railiance-telemetry/smtp.
|
||||||
|
Full CLI path: platform/workloads/railiance-telemetry/smtp.
|
||||||
|
Initial fields: SMTP_HOST=smtp.ionos.de, SMTP_PORT=587,
|
||||||
|
SMTP_USERNAME=platform@coulomb.social, SMTP_FROM=platform@coulomb.social,
|
||||||
|
SMTP_STARTTLS=true. SMTP_PASSWORD is deliberately absent until founder update.
|
||||||
|
Preserve the existing fields when saving that new version.
|
||||||
|
|
||||||
|
Attended founder command (requires existing local OpenBao forwarding):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 warden access openbao-platform-admin-login --exec -- python3 /home/worsch/railiance-platform/scripts/telemetry_smtp_entry.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The helper is silent. CAS=0 never overwrites any existing path/version. Existing
|
||||||
|
version returns without reading values or writing. Four tests cover first
|
||||||
|
creation, existing version preservation, permission errors and CAS conflict.
|
||||||
|
Exit 20 invalid metadata; 21 metadata access failure; 22 creation failure;
|
||||||
|
23 unexpected write version; 24 verification failure; 25 contained failure.
|
||||||
|
A successful rerun on an existing path only proves presence, not its contents.
|
||||||
|
|
||||||
|
Read Warden's printed completion line: login-failed means no child ran;
|
||||||
|
completed-but-revocation-unconfirmed means creation ran but session revocation
|
||||||
|
requires attention. Native creation is pending until that attended result.
|
||||||
|
This helper does not grant ESO access, copy another mailbox's credential, or
|
||||||
|
activate SMTP. Scoped workload delivery remains the existing telemetry task.
|
||||||
55
scripts/telemetry_smtp_entry.py
Normal file
55
scripts/telemetry_smtp_entry.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Silent attended creation of a password-free telemetry SMTP KV entry.
|
||||||
|
|
||||||
|
Never overwrite an existing version, including a password subsequently added by
|
||||||
|
its owner. Run inside warden's attended OpenBao platform-admin login envelope.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PATH = 'platform/workloads/railiance-telemetry/smtp'
|
||||||
|
FIELDS = {'SMTP_HOST': 'smtp.ionos.de', 'SMTP_PORT': '587',
|
||||||
|
'SMTP_USERNAME': 'platform@coulomb.social',
|
||||||
|
'SMTP_FROM': 'platform@coulomb.social', 'SMTP_STARTTLS': 'true'}
|
||||||
|
|
||||||
|
|
||||||
|
def command(args):
|
||||||
|
return subprocess.run(['bao', *args], capture_output=True, timeout=30)
|
||||||
|
|
||||||
|
|
||||||
|
def run(invoke=command):
|
||||||
|
# Read only metadata before CAS=0 creation. No existing credential value is
|
||||||
|
# needed to refuse an overwrite. The owner can safely rerun after adding it.
|
||||||
|
before = invoke(['kv', 'metadata', 'get', '-format=json', PATH])
|
||||||
|
if before.returncode == 0:
|
||||||
|
metadata = json.loads(before.stdout)['data']
|
||||||
|
if type(metadata.get('current_version')) is int and metadata['current_version'] >= 1:
|
||||||
|
return 0
|
||||||
|
return 20
|
||||||
|
# Never infer absence from arbitrary errors or an unauthenticated connection.
|
||||||
|
if b'No value found at ' not in before.stderr + before.stdout:
|
||||||
|
return 21
|
||||||
|
created = invoke(['kv', 'put', '-format=json', '-cas=0', PATH,
|
||||||
|
*[key + '=' + value for key, value in FIELDS.items()]])
|
||||||
|
if created.returncode:
|
||||||
|
return 22
|
||||||
|
if json.loads(created.stdout).get('data', {}).get('version') != 1:
|
||||||
|
return 23
|
||||||
|
after = invoke(['kv', 'metadata', 'get', '-format=json', PATH])
|
||||||
|
if after.returncode or json.loads(after.stdout)['data'].get('current_version') != 1:
|
||||||
|
return 24
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# The attended lane refuses all child output, including exceptions.
|
||||||
|
with open(os.devnull, 'w') as sink:
|
||||||
|
os.dup2(sink.fileno(), 1)
|
||||||
|
os.dup2(sink.fileno(), 2)
|
||||||
|
try:
|
||||||
|
status = run()
|
||||||
|
except Exception:
|
||||||
|
status = 25
|
||||||
|
sys.exit(status)
|
||||||
32
tests/test_telemetry_smtp_entry.py
Normal file
32
tests/test_telemetry_smtp_entry.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
spec=importlib.util.spec_from_file_location('smtp_entry',Path(__file__).resolve().parents[1]/'scripts/telemetry_smtp_entry.py')
|
||||||
|
entry=importlib.util.module_from_spec(spec);spec.loader.exec_module(entry)
|
||||||
|
|
||||||
|
class EntryTests(unittest.TestCase):
|
||||||
|
def test_create_without_password_and_verify(self):
|
||||||
|
calls=[]
|
||||||
|
results=[subprocess.CompletedProcess([],2,b'',b'No value found at platform/metadata/workloads/railiance-telemetry/smtp'),
|
||||||
|
subprocess.CompletedProcess([],0,b'{"data":{"version":1}}',b''),
|
||||||
|
subprocess.CompletedProcess([],0,b'{"data":{"current_version":1}}',b'')]
|
||||||
|
def invoke(args): calls.append(args);return results.pop(0)
|
||||||
|
self.assertEqual(entry.run(invoke),0)
|
||||||
|
self.assertIn('-cas=0',calls[1]);self.assertFalse(any('PASSWORD' in a for a in calls[1]))
|
||||||
|
self.assertIn('SMTP_USERNAME=platform@coulomb.social',calls[1])
|
||||||
|
def test_existing_password_version_never_read_or_overwritten(self):
|
||||||
|
calls=[]
|
||||||
|
def invoke(args):
|
||||||
|
calls.append(args);return subprocess.CompletedProcess([],0,b'{"data":{"current_version":2}}',b'')
|
||||||
|
self.assertEqual(entry.run(invoke),0);self.assertEqual(len(calls),1)
|
||||||
|
self.assertEqual(calls[0][:3],['kv','metadata','get'])
|
||||||
|
def test_permission_error_never_triggers_write(self):
|
||||||
|
calls=[]
|
||||||
|
def invoke(args): calls.append(args);return subprocess.CompletedProcess([],2,b'',b'permission denied')
|
||||||
|
self.assertEqual(entry.run(invoke),21);self.assertEqual(len(calls),1)
|
||||||
|
def test_cas_conflict_is_not_success(self):
|
||||||
|
results=[subprocess.CompletedProcess([],2,b'',b'No value found at x'),subprocess.CompletedProcess([],2,b'',b'CAS mismatch')]
|
||||||
|
self.assertEqual(entry.run(lambda _:results.pop(0)),22)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue