Prepare supervised email-connect SMTP foundation and projection
Some checks failed
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
CCR intake gate / gate (push) Has been cancelled

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
This commit is contained in:
codex 2026-10-31 12:00:57 +01:00
parent f7c01d4000
commit e77f3989b4
8 changed files with 914 additions and 3 deletions

View file

@ -222,7 +222,7 @@
| task | RPF-WP-0067-T02 | done | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md |
| task | RPF-WP-0067-T03 | wait | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md |
| task | RPF-WP-0068-T01 | done | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
| task | RPF-WP-0068-T02 | todo | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
| task | RPF-WP-0068-T02 | done | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
| task | RPF-WP-0068-T03 | wait | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
| task | RPF-WP-0068-T04 | todo | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
| intake | RPF-IN-0001 | closed | — | intakes/intakes.md |

View file

@ -4,7 +4,9 @@ CCR-2026-0056 proposes one additional reader of the existing platform SMTP entry
RPF-WP-0068 tracks preparation, foundation/binding proof, attended admission and
native consumer delivery. The parent requirement is CSOC-WP-0015-T02.
**Source preparation is complete; execution is not ready.** The dedicated service
**The complete controller is now prepared** in
[the foundation packet](email-connect-smtp-foundation-review-2026-10-31.md).
Its41 SMTP tests pass. **Execution still awaits review/approval.** The dedicated service
account/store/projection have only passed server dry-run. Native binding is not
confirmed, the CCR is proposed, and no new policy or role has been installed.

View file

@ -0,0 +1,128 @@
# Email-connect platform SMTP foundation review
Prepared for CCR-2026-0056 and RPF-WP-0068, supporting CSOC-WP-0015-T02.
This is a concrete review packet, not evidence of live installation.
## Decision scope
Admit email-connect as a third reader of the existing platform SMTP mailbox,
alongside telemetry and Authelia. Create exactly one service account, namespace
SecretStore and ExternalSecret in email-connect. The scoped Kubernetes role reads
the whole exact OpenBao KV record at
`platform/data/workloads/railiance-telemetry/smtp`; ESO projects only SMTP_PASSWORD
into the new Secret's `password` key. OpenBao does not enforce field-level access.
The role has no default policy, a maximum 15-minute lifetime and only the exact
service-account/namespace binding with audience openbao.
This does not change the email-connect deployment or sender, create a caller,
send email, scan a mailbox, activate backup monitoring, rotate the shared password,
or grant suppression authority. Those live obligations remain in CSOC-WP-0015.
The user already selected platform-root, with Bernd as human operator, for replies
and bounces. This new reader requires its own admission review.
The source-backed foundation proposal is:
`sha256:f8da6c831160e1c6354ce54f936809d2d4fb48eb45eb23484dc0bcdfe0bb52f0`
Reproduce it without credentials or network operations:
```bash
python3 /home/worsch/railiance-platform/scripts/email_connect_smtp_foundation.py
```
The digest binds the controller, all three resources including server defaults,
cluster/namespace/application identities, fixed kubeconfig path and admission
digest. The latter binds the CCR's functional scope, policy and seven dependency
files. Changes to execution code or scope require a new proposal. Approval and
native binding evidence do not change functional scope.
## Preconditions and approval boundary
CCR-2026-0056 remains **proposed**, with native binding unconfirmed. The
[credential workflow](credential-change-approval.md) states: “Only `approved`
requests may be applied.” Its shared-reader lane requires platform review;
automatic single-reader approval does not apply. Its client workflow also states:
“The **founder** resolves the State Hub decision” and “Clients do not run
`sync-decision` on their own CCRs.” No review, approval or sync is impersonated here.
The completed packet is ready for that platform/consumer review and founder
decision. After the owner workflow has made the exact CCR approved, the founder
can run the attended command below. The controller creates and verifies the
dedicated service account, then uses canonical confirm-binding itself before
admitting the OpenBao role. The operator need not falsely confirm an absent SA.
## Attended command after approval
Run in the founder's terminal; this is not an agent execution instruction.
The OpenBao relay must be available at port18200 and the fixed primary kubeconfig
must be present. `warden plan` resolves this operation to
openbao-platform-admin-login with one interactive OIDC/MFA login.
```bash
mkdir -m 700 /home/worsch/email-connect-smtp-admission-20261031
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/email_connect_smtp_foundation.py \
--execute \
--expect-digest sha256:f8da6c831160e1c6354ce54f936809d2d4fb48eb45eb23484dc0bcdfe0bb52f0 \
--receipt /home/worsch/email-connect-smtp-admission-20261031/receipt.jsonl
```
The directory creation is intentionally exclusive. If it already exists, inspect
its contents and prior receipts before proceeding; do not remove or overwrite
evidence. Do not run the second command if the first failed. The helper separately
requires a private caller-owned directory and a new receipt path.
The child must be silent because Warden rejects child output. It records a private
mode0600 append-only JSON Lines journal, fsynced before changes. Share only the
last complete receipt and Warden outcome; never login tokens or provider values.
The expected final child status is `foundation_admitted_projection_ready`.
Warden must also report successful session revocation and cleanup.
## What execution verifies
Before mutation, pinned OpenBao and Kubernetes cluster/namespace identities,
platform-admin without root, current deployment identity/generation and absence
of all three new objects and the target Secret are required. Objects are created
exclusively; none is applied over an existing object. No automatic rollback or
retry follows a partial result.
The new service account disables automount. Its new short-lived JWT passes native
TokenReview for its UID, full service-account name and openbao audience. Only then
does canonical confirm-binding run. Guarded admission preserves the shared mailbox,
telemetry/Authelia roles and policies, and existing agent denial. It authenticates
to SMTP using certificate-verified STARTTLS without submitting mail.
Actual workload OpenBao login must have only the dedicated policy and bounded TTL,
read its exact record, and deny metadata, siblings and mutation capabilities.
Wrong service account and wrong audience must fail. The workload token is revoked
and lookup must fail afterward. This does not claim an independently exercised
same-name/wrong-namespace fixture; exact namespace binding is checked in role
metadata and the positive namespace identity is pinned.
Both store and projection must report Ready with exact reviewed specs; the new
Secret must name the new ExternalSecret as controller owner. Secret checks use
PartialObjectMetadata content negotiation through a private Unix-socket proxy,
with no full-object fallback. Secret values are neither requested nor journaled.
Existing application/configuration/old projection fingerprints must remain equal.
If execution fails, retain the journal and inspect its last phase and native state.
There may be a created SA, admitted role or projection even if no final success
was written. Do not rerun blindly, revoke unrelated readers, rotate the mailbox or
roll out the application while outcome is uncertain.
## Validation and remaining work
41 synthetic SMTP tests pass, including12 controller cases and15 admission cases.
They exercise ordering, drift refusal, target collisions, binding refusal, partial
creation, privacy/exclusive journals, interruption, native token capability and
revocation checks, and metadata-only response enforcement/proxy cleanup.
All three resources passed a fresh native server dry-run. A read-only native
metadata query confirmed the target Secret absent. No credential was fetched by
the agent and no production resource was installed.
Evidence: `docs/evidence/email-connect-smtp-foundation-preparation-2026-10-31.json`.
After the attended return, verify receipt and native state, record CCR verification
and rotation inventory, and hand the custody result to CSOC-WP-0015-T02. The sender
rollout, actual delivered-message authentication, inbox access, authoritative return
handling, suppression admission and retention remain separate unfinished work.

View file

@ -0,0 +1,111 @@
{
"schema": "platform.email-connect-smtp-foundation-preparation.v1",
"prepared_at": "2026-10-31T10:59:04.381919+00:00",
"proposal": {
"proposal_digest": "sha256:f8da6c831160e1c6354ce54f936809d2d4fb48eb45eb23484dc0bcdfe0bb52f0",
"proposal": {
"schema": "platform.email-connect-smtp-foundation-proposal.v1",
"foundation": {
"schema": "platform.email-connect-smtp-foundation.v1",
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
"namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319",
"deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72",
"deployment_generation": 74,
"resources": [
{
"apiVersion": "v1",
"automountServiceAccountToken": false,
"kind": "ServiceAccount",
"metadata": {
"name": "email-connect-platform-smtp-eso",
"namespace": "email-connect"
}
},
{
"apiVersion": "external-secrets.io/v1",
"kind": "SecretStore",
"metadata": {
"name": "openbao-email-connect-platform-smtp",
"namespace": "email-connect"
},
"spec": {
"provider": {
"vault": {
"auth": {
"kubernetes": {
"mountPath": "kubernetes",
"role": "email-connect-platform-smtp-eso",
"serviceAccountRef": {
"audiences": [
"openbao"
],
"name": "email-connect-platform-smtp-eso"
}
}
},
"path": "platform",
"server": "http://openbao.openbao.svc:8200",
"version": "v2"
}
}
}
},
{
"apiVersion": "external-secrets.io/v1",
"kind": "ExternalSecret",
"metadata": {
"name": "email-connect-platform-smtp",
"namespace": "email-connect"
},
"spec": {
"data": [
{
"remoteRef": {
"conversionStrategy": "Default",
"decodingStrategy": "None",
"key": "workloads/railiance-telemetry/smtp",
"metadataPolicy": "None",
"property": "SMTP_PASSWORD"
},
"secretKey": "password"
}
],
"refreshInterval": "1h",
"secretStoreRef": {
"kind": "SecretStore",
"name": "openbao-email-connect-platform-smtp"
},
"target": {
"creationPolicy": "Owner",
"deletionPolicy": "Retain",
"name": "email-connect-platform-smtp",
"template": {
"engineVersion": "v2",
"mergePolicy": "Replace",
"type": "Opaque"
}
}
}
}
]
},
"admission_digest": "sha256:14a051abc35d94b9fbfbc8b47f19f9ba7bc95c779ea98fbb9083072386e72160",
"kubeconfig": "/home/worsch/.kube/config-railiance01",
"controller_sha256": "4f1355c0cd48368fa1ae4dcb0138296b3880359329a4ace35dee51783b0ff13f"
},
"ccr_status": "proposed"
},
"validation": {
"smtp_tests_passed": 41,
"smtp_tests_failed": 0,
"server_dry_run_resources": 3,
"native_target_secret_absent": true,
"target_metadata_only_api": true
},
"test_source_sha256": "afdb7877637ced8a1d755bea1b609378b9b74603f3561762ed1dcb38ddd8d82c",
"approved": false,
"executed": false,
"resources_installed": false,
"email_sent": false,
"secrets_read_by_agent": false
}

View file

@ -0,0 +1,84 @@
{
"schema": "platform.email-connect-smtp-foundation.v1",
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
"namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319",
"deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72",
"deployment_generation": 74,
"resources": [
{
"apiVersion": "v1",
"automountServiceAccountToken": false,
"kind": "ServiceAccount",
"metadata": {
"name": "email-connect-platform-smtp-eso",
"namespace": "email-connect"
}
},
{
"apiVersion": "external-secrets.io/v1",
"kind": "SecretStore",
"metadata": {
"name": "openbao-email-connect-platform-smtp",
"namespace": "email-connect"
},
"spec": {
"provider": {
"vault": {
"auth": {
"kubernetes": {
"mountPath": "kubernetes",
"role": "email-connect-platform-smtp-eso",
"serviceAccountRef": {
"audiences": [
"openbao"
],
"name": "email-connect-platform-smtp-eso"
}
}
},
"path": "platform",
"server": "http://openbao.openbao.svc:8200",
"version": "v2"
}
}
}
},
{
"apiVersion": "external-secrets.io/v1",
"kind": "ExternalSecret",
"metadata": {
"name": "email-connect-platform-smtp",
"namespace": "email-connect"
},
"spec": {
"data": [
{
"remoteRef": {
"conversionStrategy": "Default",
"decodingStrategy": "None",
"key": "workloads/railiance-telemetry/smtp",
"metadataPolicy": "None",
"property": "SMTP_PASSWORD"
},
"secretKey": "password"
}
],
"refreshInterval": "1h",
"secretStoreRef": {
"kind": "SecretStore",
"name": "openbao-email-connect-platform-smtp"
},
"target": {
"creationPolicy": "Owner",
"deletionPolicy": "Retain",
"name": "email-connect-platform-smtp",
"template": {
"engineVersion": "v2",
"mergePolicy": "Replace",
"type": "Opaque"
}
}
}
}
]
}

View file

@ -0,0 +1,314 @@
#!/usr/bin/env python3
"""Silent attended CCR-2026-0056 foundation, admission and projection.
Default output is an offline proposal. Never changes the application sender.
An incomplete attempt requires inspection, not an automatic retry or rollback.
"""
from __future__ import annotations
import argparse
import hashlib
import http.client
import json
import os
from pathlib import Path
import socket
import subprocess
import sys
import tempfile
import time
from datetime import datetime, timezone
import email_connect_smtp_delivery as admission
from state_hub_preflight_lane import command, data, bao, capabilities
ROOT = admission.ROOT
BUNDLE = ROOT / 'openbao/email-connect-platform-smtp/foundation.json'
NS = 'email-connect'
SA = admission.LANE['service_account']
KUBECONFIG = '/home/worsch/.kube/config-railiance01'
require = admission.require
def digest(value):
return 'sha256:' + hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
def prepare():
ccr, owner, owner_digest = admission.prepare()
bundle = json.loads(BUNDLE.read_text())
proposal = dict(schema='platform.email-connect-smtp-foundation-proposal.v1',
foundation=bundle, admission_digest=owner_digest,
kubeconfig=KUBECONFIG,
controller_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest())
return ccr, proposal, digest(proposal)
class Native:
def __init__(self):
self.kube = ['kubectl', '--kubeconfig', KUBECONFIG]
def get(self, kind, name, namespace=NS):
args = self.kube + ['get', kind, name, '--ignore-not-found', '-o', 'json']
if namespace:
args += ['-n', namespace]
result = command(args)
return data(result) if result.stdout.strip() else None
def target_metadata(self, name):
# Kubernetes content negotiation excludes Secret values at the server.
# Use a private local socket, one fixed GET path and no full-object fallback.
require(name == 'email-connect-platform-smtp', 'target_secret_scope_drift')
path = '/api/v1/namespaces/email-connect/secrets/'+name
with tempfile.TemporaryDirectory(prefix='email-connect-metadata-') as directory:
sockpath = str(Path(directory)/'api.sock')
proc = subprocess.Popen(self.kube + ['proxy', '--unix-socket='+sockpath,
'--accept-paths=^'+path+'$', '--reject-methods=^(POST|PUT|PATCH|DELETE)$'],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
conn = None
try:
for _ in range(50):
if Path(sockpath).exists():
break
require(proc.poll() is None, 'metadata_proxy_failed')
time.sleep(0.1)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(10)
try:
sock.connect(sockpath)
except BaseException:
sock.close()
raise
conn = http.client.HTTPConnection('localhost', timeout=10)
conn.sock = sock
conn.request('GET', path, headers={'Accept': 'application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1'})
response = conn.getresponse()
if response.status == 404:
return None
require(response.status == 200, 'metadata_query_failed')
value = json.load(response)
require(value.get('kind') == 'PartialObjectMetadata' and
value.get('apiVersion') == 'meta.k8s.io/v1' and 'data' not in value,
'metadata_only_response_required')
return value['metadata']
finally:
if conn:
conn.close()
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill(); proc.wait(timeout=5)
def create(self, resource):
return data(command(self.kube + ['create', '-f', '-', '-o', 'json'], payload=resource))
def jwt(self, service_account=SA, audience='openbao'):
return command(self.kube + ['-n', NS, 'create', 'token', service_account,
'--audience='+audience, '--duration=10m']).stdout.decode().strip()
def token_review(self, jwt):
return data(command(self.kube + ['create', '-f', '-', '-o', 'json'], payload={
'apiVersion': 'authentication.k8s.io/v1', 'kind': 'TokenReview',
'spec': {'token': jwt, 'audiences': ['openbao']}}))['status']
def wait(self, resource):
command(self.kube + ['-n', NS, 'wait', '--for=condition=Ready',
resource['kind']+'/'+resource['metadata']['name'], '--timeout=45s'])
def exact_resource(actual, wanted):
require(actual is not None, 'resource_missing')
require(actual['apiVersion'] == wanted['apiVersion'] and actual['kind'] == wanted['kind'], 'resource_drift')
require(all(actual['metadata'].get(k) == v for k, v in wanted['metadata'].items()), 'resource_identity_drift')
if wanted['kind'] == 'ServiceAccount':
require(actual.get('automountServiceAccountToken') is False
and not actual.get('secrets') and not actual.get('imagePullSecrets'), 'service_account_drift')
else:
require(actual['spec'] == wanted['spec'], 'resource_spec_drift')
require(not actual['metadata'].get('deletionTimestamp'), 'resource_deleting')
def protected_application(native, bundle):
deployment = native.get('deployment', 'email-connect')
require(deployment['metadata']['uid'] == bundle['deployment_uid']
and deployment['metadata']['generation'] == bundle['deployment_generation'], 'application_changed')
values = []
for kind, name in [('deployment', 'email-connect'), ('configmap', 'email-connect-config'),
('externalsecret', 'email-connect-runtime')]:
obj = deployment if kind == 'deployment' else native.get(kind, name)
values.append({'uid': obj['metadata']['uid'], 'spec': obj.get('spec'), 'data': obj.get('data')})
return digest(values)
def verify_login(native):
def login(sa=SA, audience='openbao', negative=False):
result = bao(['write', '-format=json', 'auth/kubernetes/login', '-'],
payload={'role': SA, 'jwt': native.jwt(sa, audience)}, allow_failure=negative)
if negative:
if result.returncode == 0:
revoke(data(result)['auth']['client_token'])
raise admission.Refused('negative_login_succeeded')
require(b'400' in result.stderr or b'403' in result.stderr, 'negative_login_inconclusive')
return None
return data(result)['auth']
def revoke(token):
bao(['write', '-f', 'auth/token/revoke-self'], token=token)
check = bao(['token', 'lookup', '-format=json'], token=token, allow_failure=True)
require(check.returncode != 0 and b'403' in check.stderr, 'revocation_unconfirmed')
auth = login()
token = auth['client_token']
try:
require(auth['token_policies'] == [SA] and auth.get('policies', [SA]) == [SA]
and 0 < auth['lease_duration'] <= 900, 'effective_policy_or_ttl_drift')
path = admission.LANE['kv_path']
paths = [path, path.replace('/data/', '/metadata/', 1), path+'-unrelated',
'auth/token/create', 'auth/token/lookup-self', 'auth/token/revoke-self']
expected = [['read'], ['deny'], ['deny'], ['deny'], ['read'], ['update']]
require(capabilities(token, paths) == dict(zip(paths, expected)), 'capability_drift')
mailbox = data(bao(['read', '-format=json', path], token=token))['data']
admission.lane_module().validate_entry(mailbox)
finally:
revoke(token)
login('default', negative=True)
login(audience='wrong-audience', negative=True)
return dict(native_consumer_auth_verified=True, exact_read_verified=True,
mutation_sibling_metadata_denied=True, bounded_ttl=True,
other_service_account_denied=True, wrong_audience_denied=True,
workload_token_revocation_verified=True)
def run(proposal, receipt, checkpoint, native=None):
native = native or Native()
bundle = proposal['foundation']
status = data(bao(['status', '-format=json']))
require(status.get('cluster_id') == admission.OPENBAO_CLUSTER_ID and
status.get('initialized') is True and status.get('sealed') is False, 'openbao_instance_mismatch')
policies = data(bao(['token', 'lookup', '-format=json']))['data']['policies']
require('platform-admin' in policies and 'root' not in policies, 'attended_platform_admin_required')
for name, uid in [('kube-system', bundle['cluster_uid']), (NS, bundle['namespace_uid'])]:
obj = native.get('namespace', name, namespace=None)
require(obj and obj['metadata']['uid'] == uid and not obj['metadata'].get('deletionTimestamp'),
'cluster_or_namespace_identity_mismatch')
before = protected_application(native, bundle)
resources = bundle['resources']
require([x['kind'] for x in resources] == ['ServiceAccount', 'SecretStore', 'ExternalSecret'], 'foundation_contract_drift')
for x in resources:
require(native.get(x['kind'], x['metadata']['name']) is None, 'target_exists_requires_inspection')
target = resources[-1]['spec']['target']['name']
require(native.target_metadata(target) is None, 'target_secret_exists_requires_inspection')
receipt.update(phase='creating_service_account', mutation_outcome='unconfirmed')
checkpoint(receipt)
account = native.create(resources[0])
exact_resource(account, resources[0])
review = native.token_review(native.jwt())
require(review.get('authenticated') is True and review.get('audiences') == ['openbao']
and review['user']['username'] == 'system:serviceaccount:'+NS+':'+SA
and review['user']['uid'] == account['metadata']['uid'], 'native_binding_unproven')
receipt.update(phase='binding_verified', service_account_uid=account['metadata']['uid'],
native_binding_verified=True)
checkpoint(receipt)
validator = admission.module('email_connect_foundation_ccr', ROOT/'scripts/credential-change.py')
validator.confirm_binding(admission.CCR, reviewer='attended-platform-operator',
comment='CCR-2026-0056 foundation: pinned cluster and namespace, exact new service account and native audience TokenReview verified; receipt '+receipt['proposal_digest'])
ccr, _, owner_digest = admission.prepare()
admission.execution_guard(ccr, proposal['admission_digest'], owner_digest)
owner_receipt = {}
def owner_checkpoint(value):
receipt.update(phase='owner_admission', owner_admission=dict(value))
checkpoint(receipt)
admission.admit(owner_receipt, owner_checkpoint)
receipt.update(phase='native_login_verification')
checkpoint(receipt)
receipt.update(verify_login(native))
for wanted in resources[1:]:
receipt.update(phase='creating_'+wanted['kind'].lower())
checkpoint(receipt)
created = native.create(wanted)
exact_resource(created, wanted)
native.wait(wanted)
live = native.get(wanted['kind'], wanted['metadata']['name'])
exact_resource(live, wanted)
require(live['metadata']['uid'] == created['metadata']['uid'] and
any(c.get('type') == 'Ready' and c.get('status') == 'True'
for c in live.get('status', {}).get('conditions', [])), 'projection_not_ready')
if wanted['kind'] == 'ExternalSecret':
require(bool(live['status'].get('refreshTime')), 'projection_refresh_unproven')
metadata = native.target_metadata(target)
require(metadata and any(o.get('uid') == live['metadata']['uid']
and o.get('controller') is True for o in metadata.get('ownerReferences', [])),
'projection_ownership_unproven')
receipt['projection_refresh_time'] = live['status']['refreshTime']
require(protected_application(native, bundle) == before, 'application_changed')
receipt.update(status='foundation_admitted_projection_ready', phase='completed',
mutation_outcome='verified', projection_ready=True, application_sender_changed=False)
checkpoint(receipt)
def execute(ccr, proposal, actual, expected, path, runner=run):
require(actual == expected, 'reviewed_digest_required')
require(ccr['status'] == 'approved', 'approved_ccr_required')
info = path.parent.lstat()
require(path.is_absolute() and path.parent.is_dir() and not path.parent.is_symlink()
and info.st_uid == os.getuid() and not info.st_mode & 0o077, 'private_receipt_parent_required')
fd = os.open(path, os.O_WRONLY | os.O_APPEND | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
receipt = dict(schema='platform.email-connect-smtp-foundation.v1', status='started',
phase='preflight', proposal_digest=actual, started_at=datetime.now(timezone.utc).isoformat(),
mutation_outcome='not_started', email_sent=False, application_sender_changed=False)
def checkpoint(value):
raw = (json.dumps(value, sort_keys=True)+'\n').encode()
while raw:
n = os.write(fd, raw)
if n <= 0:
raise OSError('receipt_write_failed')
raw = raw[n:]
os.fsync(fd)
try:
checkpoint(receipt)
parent = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(parent)
finally:
os.close(parent)
runner(proposal, receipt, checkpoint)
require(receipt['status'] == 'foundation_admitted_projection_ready', 'success_receipt_required')
result = 0
except Exception:
receipt.update(status='failed_requires_inspection', reason='contained_foundation_failure')
result = 1
finally:
key = 'interrupted_at' if receipt['status'] == 'started' else 'completed_at'
receipt[key] = datetime.now(timezone.utc).isoformat()
try:
checkpoint(receipt)
finally:
os.close(fd)
return result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--execute', action='store_true')
parser.add_argument('--expect-digest')
parser.add_argument('--receipt', type=Path)
args = parser.parse_args()
ccr, proposal, value = prepare()
if not args.execute:
print(json.dumps(dict(proposal_digest=value, proposal=proposal, ccr_status=ccr['status']), indent=2))
return 0
require(args.receipt is not None, 'private_receipt_required')
return execute(ccr, proposal, value, args.expect_digest, args.receipt)
if __name__ == '__main__':
if '--execute' in sys.argv:
with open(os.devnull, 'w') as sink:
os.dup2(sink.fileno(), 1); os.dup2(sink.fileno(), 2)
try:
code = main()
except Exception:
code = 1
sys.exit(code)
sys.exit(main())

View file

@ -0,0 +1,262 @@
"""Exercise the attended controller against synthetic Kubernetes/Bao boundaries."""
import copy
import io
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch, Mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
import email_connect_smtp_foundation as foundation
class Native:
def __init__(self, proposal):
self.bundle = proposal['foundation']
self.created = []
self.objects = {}
self.bad_cluster = False
self.bad_binding = False
self.target_exists = False
self.fail_kind = None
self.not_ready = False
self.drift = False
self.changed_application = False
self.bad_ownership = False
def get(self, kind, name, namespace=foundation.NS):
if kind == 'namespace':
key = 'cluster_uid' if name == 'kube-system' else 'namespace_uid'
return {'metadata': {'uid': 'wrong' if self.bad_cluster else self.bundle[key]}}
if kind == 'deployment':
return {'metadata': {'uid': self.bundle['deployment_uid'], 'generation': 74},
'spec': {'unchanged': not (self.created and self.changed_application)}}
if name in ('email-connect-config', 'email-connect-runtime'):
return {'metadata': {'uid': name}, 'spec': {}, 'data': {}}
return copy.deepcopy(self.objects.get((kind, name)))
def target_metadata(self, name):
if self.target_exists:
return {'uid': 'foreign'}
if 'ExternalSecret' in self.created:
return {'ownerReferences': [{'uid': 'wrong' if self.bad_ownership else 'uid-ExternalSecret', 'controller': True}]}
return None
def create(self, wanted):
kind = wanted['kind']
self.created.append(kind)
if self.fail_kind == kind:
raise RuntimeError('synthetic provider secret text')
obj = copy.deepcopy(wanted)
obj['metadata']['uid'] = 'uid-'+kind
obj['status'] = {'conditions': [{'type': 'Ready', 'status': 'False' if self.not_ready else 'True'}],
'refreshTime': '2026-10-31T12:00:00Z'}
if self.drift and kind == 'SecretStore':
obj['spec']['provider']['vault']['path'] = 'wrong'
self.objects[(kind, wanted['metadata']['name'])] = obj
return copy.deepcopy(obj)
def jwt(self, *args):
return 'fixture-jwt'
def token_review(self, jwt):
return {'authenticated': not self.bad_binding, 'audiences': ['openbao'],
'user': {'username': 'system:serviceaccount:email-connect:'+foundation.SA, 'uid': 'uid-ServiceAccount'}}
def wait(self, resource):
pass
def completed(value, code=0):
return subprocess.CompletedProcess([], code, json.dumps(value).encode(), b'403 permission denied' if code else b'')
class FoundationTests(unittest.TestCase):
def setUp(self):
self.ccr, self.proposal, self.digest = foundation.prepare()
self.native = Native(self.proposal)
self.validator = Mock()
self.records = []
self.owner_calls = []
def owner(self, args, **kwargs):
if args[0] == 'status':
return completed({'cluster_id': foundation.admission.OPENBAO_CLUSTER_ID, 'initialized': True, 'sealed': False})
return completed({'data': {'policies': ['platform-admin']}})
def run_controller(self):
approved = copy.deepcopy(self.ccr)
approved['status'] = 'approved'
approved['openbao']['auth']['bound_claims_confirmed'] = True
def admit(row, checkpoint):
self.owner_calls.append('admit')
row.update(status='reader_admitted_verified'); checkpoint(row)
receipt = {'proposal_digest': self.digest, 'status': 'started'}
with patch.object(foundation, 'bao', side_effect=self.owner), \
patch.object(foundation.admission, 'module', return_value=self.validator), \
patch.object(foundation.admission, 'prepare', return_value=(approved, {}, self.proposal['admission_digest'])), \
patch.object(foundation.admission, 'admit', side_effect=admit), \
patch.object(foundation, 'verify_login', return_value={'native_consumer_auth_verified': True}):
foundation.run(self.proposal, receipt, lambda r: self.records.append(copy.deepcopy(r)), self.native)
return receipt
def test_offline_proposal_and_live_default_refusal(self):
self.assertEqual(self.ccr['status'], 'proposed')
self.assertEqual(len(self.proposal['foundation']['resources']), 3)
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp)/'receipt'
with self.assertRaises(foundation.admission.Refused):
foundation.execute(self.ccr, self.proposal, self.digest, self.digest, path)
self.assertFalse(path.exists())
def test_complete_controller_requires_binding_before_admission_and_ready_after(self):
result = self.run_controller()
self.assertEqual(self.native.created, ['ServiceAccount', 'SecretStore', 'ExternalSecret'])
self.validator.confirm_binding.assert_called_once()
self.assertEqual(result['status'], 'foundation_admitted_projection_ready')
self.assertFalse(result['application_sender_changed'])
phases = [r['phase'] for r in self.records]
self.assertLess(phases.index('binding_verified'), phases.index('owner_admission'))
self.assertLess(phases.index('native_login_verification'), phases.index('creating_secretstore'))
def test_wrong_cluster_and_existing_targets_precede_mutation(self):
for mode in ('cluster', 'target', 'existing'):
with self.subTest(mode=mode):
self.native = Native(self.proposal)
self.native.bad_cluster = mode == 'cluster'
self.native.target_exists = mode == 'target'
if mode == 'existing':
self.native.objects[('SecretStore', 'openbao-email-connect-platform-smtp')] = {'foreign': True}
with self.assertRaises(foundation.admission.Refused):
self.run_controller()
self.assertFalse(self.native.created)
self.assertFalse(self.owner_calls)
def test_bad_tokenreview_never_confirms_or_admits(self):
self.native.bad_binding = True
with self.assertRaises(foundation.admission.Refused): self.run_controller()
self.assertEqual(self.native.created, ['ServiceAccount'])
self.validator.confirm_binding.assert_not_called()
self.assertFalse(self.owner_calls)
def test_projection_drift_not_ready_foreign_owner_and_application_changes_refuse(self):
for field in ('drift', 'not_ready', 'bad_ownership', 'changed_application'):
with self.subTest(field=field):
self.native = Native(self.proposal); setattr(self.native, field, True)
with self.assertRaises(foundation.admission.Refused): self.run_controller()
self.assertFalse(any(r.get('status') == 'foundation_admitted_projection_ready' for r in self.records))
def test_creation_failure_keeps_checkpoint_and_does_not_rollback_or_retry(self):
self.native.fail_kind = 'SecretStore'
with self.assertRaises(RuntimeError): self.run_controller()
self.assertEqual(self.native.created, ['ServiceAccount', 'SecretStore'])
self.assertEqual(self.records[-1]['phase'], 'creating_secretstore')
self.assertEqual(self.records[-1]['mutation_outcome'], 'unconfirmed')
def test_journal_failure_silent_private_and_exclusive(self):
self.ccr['status'] = 'approved'
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp)/'receipt'
def fail(proposal, receipt, checkpoint):
receipt.update(phase='creating_service_account', mutation_outcome='unconfirmed')
checkpoint(receipt)
raise RuntimeError('synthetic private provider text')
self.assertEqual(foundation.execute(self.ccr, self.proposal, self.digest, self.digest, path, fail), 1)
raw = path.read_bytes()
rows = [json.loads(line) for line in raw.splitlines()]
self.assertEqual(rows[-1]['status'], 'failed_requires_inspection')
self.assertEqual(rows[-1]['phase'], 'creating_service_account')
self.assertNotIn(b'private provider', raw)
self.assertEqual(path.stat().st_mode & 0o777, 0o600)
with self.assertRaises(FileExistsError):
foundation.execute(self.ccr, self.proposal, self.digest, self.digest, path, fail)
self.assertEqual(path.read_bytes(), raw)
def test_interruption_cannot_be_success(self):
self.ccr['status'] = 'approved'
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp)/'receipt'
def interrupt(*args): raise SystemExit(9)
with self.assertRaises(SystemExit):
foundation.execute(self.ccr, self.proposal, self.digest, self.digest, path, interrupt)
last = json.loads(path.read_text().splitlines()[-1])
self.assertEqual(last['status'], 'started')
self.assertIn('interrupted_at', last)
def test_native_login_scope_denials_and_all_tokens_revoked(self):
calls = []
def bao(args, **kw):
calls.append((args, kw))
if args[:2] == ['write', '-format=json']:
# The fake JWT identifies the requested service account/audience.
if kw['payload']['jwt'] != foundation.SA+':openbao': return completed({}, 1)
return completed({'auth': {'client_token': 'fixture', 'token_policies': [foundation.SA], 'lease_duration': 900}})
if args[0] == 'read':
return completed({'data': {'data': dict(foundation.admission.lane_module().FIELDS, SMTP_PASSWORD='fixture'), 'metadata': {'version': 2}}})
if args[0] == 'write': return completed({})
return completed({}, 1)
def caps(token, paths):
return dict(zip(paths, [['read'], ['deny'], ['deny'], ['deny'], ['read'], ['update']]))
native = Mock()
native.jwt.side_effect = lambda sa=foundation.SA, audience='openbao': sa+':'+audience
with patch.object(foundation, 'bao', side_effect=bao), patch.object(foundation, 'capabilities', side_effect=caps):
result = foundation.verify_login(native)
self.assertTrue(result['other_service_account_denied'])
self.assertTrue(result['workload_token_revocation_verified'])
self.assertEqual(sum(a == ['write', '-f', 'auth/token/revoke-self'] for a, _ in calls), 1)
def test_wrong_digest_and_cli_refusal_are_before_receipt_or_owner_calls(self):
self.ccr['status'] = 'approved'
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp)/'receipt'
with self.assertRaises(foundation.admission.Refused):
foundation.execute(self.ccr, self.proposal, self.digest, 'wrong', path)
result = subprocess.run([sys.executable, str(Path(foundation.__file__)), '--execute',
'--expect-digest', 'wrong', '--receipt', str(path)], capture_output=True, timeout=10)
self.assertNotEqual(result.returncode, 0)
self.assertEqual((result.stdout, result.stderr), (b'', b''))
self.assertFalse(path.exists())
def test_metadata_query_negotiates_partial_object_and_cleans_proxy(self):
for status, payload, refuses in [
(404, {}, False),
(200, {'kind': 'PartialObjectMetadata', 'apiVersion': 'meta.k8s.io/v1', 'metadata': {'uid': 'fixture'}}, False),
(200, {'kind': 'Secret', 'apiVersion': 'v1', 'data': {'fixture': 'must-not-be-accepted'}}, True),
(403, {}, True),
]:
with self.subTest(status=status, refuses=refuses):
response = io.BytesIO(json.dumps(payload).encode()); response.status = status
process = Mock(); process.poll.return_value = None
conn = Mock(); conn.getresponse.return_value = response
with patch.object(foundation.subprocess, 'Popen', return_value=process), \
patch.object(foundation.socket, 'socket'), \
patch.object(foundation.http.client, 'HTTPConnection', return_value=conn), \
patch.object(foundation.time, 'sleep'):
if refuses:
with self.assertRaises(foundation.admission.Refused):
foundation.Native().target_metadata('email-connect-platform-smtp')
else:
result = foundation.Native().target_metadata('email-connect-platform-smtp')
self.assertEqual(result, None if status == 404 else {'uid': 'fixture'})
self.assertEqual(conn.request.call_args.kwargs['headers']['Accept'],
'application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1')
process.terminate.assert_called_once(); process.wait.assert_called_once()
conn.close.assert_called_once()
def test_native_scope_failure_still_revokes_workload_token(self):
calls = []
def bao(args, **kwargs):
calls.append(args)
if args[:2] == ['write', '-format=json']:
return completed({'auth': {'client_token': 'fixture', 'token_policies': ['too-broad'], 'lease_duration': 900}})
if args[:2] == ['write', '-f']: return completed({})
return completed({}, 1)
with patch.object(foundation, 'bao', side_effect=bao):
with self.assertRaises(foundation.admission.Refused): foundation.verify_login(Mock())
self.assertIn(['write', '-f', 'auth/token/revoke-self'], calls)
if __name__ == '__main__': unittest.main()

View file

@ -39,7 +39,7 @@ Evidence: docs/evidence/email-connect-smtp-admission-preparation-2026-10-31.json
```task
id: RPF-WP-0068-T02
status: todo
status: done
priority: high
state_hub_task_id: "de0ec1f9-cc11-593f-88c5-5a3972969f01"
```
@ -54,6 +54,16 @@ readback after admission without printing values or reading Kubernetes Secrets.
Use coulomb-social/docs/release/platform-mail-sender-2026-10-31/candidate.json.
Do not deploy the application sender here or reuse an existing consumer's grant.
Preparation completed 2026-10-31: email_connect_smtp_foundation.py binds the
server-defaulted three-object bundle and pinned primary/namespace/application
identities. It performs native TokenReview before canonical binding confirmation,
guarded owner admission, positive/negative workload login with token revocation,
exclusive projection creation and Ready/ownership checks. PartialObjectMetadata
queries avoid Secret values; fresh native query confirms target absent. All41
SMTP tests pass (12 foundation cases). No live creation/admission executed.
Native execution results belong to T03/T04, not this preparation completion.
Exact packet: docs/email-connect-smtp-foundation-review-2026-10-31.md.
## T03 — Obtain exact admission and execute through the attended owner lane
```task