From 3d43a9b1a338a35dcc4f40013669f8f24e25cd3e Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 28 Sep 2026 11:11:34 +0200 Subject: [PATCH] Prepare password-free telemetry SMTP entry for attended custody Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e6f1-443f-7783-9920-a16b2ffc467f --- docs/telemetry-smtp-custody.md | 30 ++++++++++++++++ scripts/telemetry_smtp_entry.py | 55 ++++++++++++++++++++++++++++++ tests/test_telemetry_smtp_entry.py | 32 +++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 docs/telemetry-smtp-custody.md create mode 100644 scripts/telemetry_smtp_entry.py create mode 100644 tests/test_telemetry_smtp_entry.py diff --git a/docs/telemetry-smtp-custody.md b/docs/telemetry-smtp-custody.md new file mode 100644 index 0000000..6c3b4ab --- /dev/null +++ b/docs/telemetry-smtp-custody.md @@ -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. diff --git a/scripts/telemetry_smtp_entry.py b/scripts/telemetry_smtp_entry.py new file mode 100644 index 0000000..630c48f --- /dev/null +++ b/scripts/telemetry_smtp_entry.py @@ -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) diff --git a/tests/test_telemetry_smtp_entry.py b/tests/test_telemetry_smtp_entry.py new file mode 100644 index 0000000..fc2076c --- /dev/null +++ b/tests/test_telemetry_smtp_entry.py @@ -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)