diff --git a/credential-change-requests/CCR-2026-0056-email-connect-platform-smtp.yaml b/credential-change-requests/CCR-2026-0056-email-connect-platform-smtp.yaml index c2e3061..94b9f14 100644 --- a/credential-change-requests/CCR-2026-0056-email-connect-platform-smtp.yaml +++ b/credential-change-requests/CCR-2026-0056-email-connect-platform-smtp.yaml @@ -3,25 +3,50 @@ kind: credential-change-request schema_version: 1 request_type: workload-kv-read title: Email-connect platform SMTP password projection -status: proposed +status: approved created: '2026-10-31' -updated: '2026-10-31' +updated: '2026-11-01' requester: agent: coulomb-social - reason: CSOC-WP-0015-T02 platform sender migration; user selected platform-root with Bernd as the human reply and bounce operator. This ownership choice is not credential admission. + reason: CSOC-WP-0015-T02 platform sender migration; user selected platform-root + with Bernd as the human reply and bounce operator. This ownership choice is not + credential admission. review: required: true required_approvers: - founder - railiance-platform-owner - email-connect-owner - comments: [] + comments: + - at: '2026-11-01T21:29:50.352641+00:00' + reviewer: railiance-platform-owner (Bernd Worsch) + decision: approved + comment: As founder, platform owner and email-connect owner, admit email-connect + as a third reader of the existing platform SMTP record alongside telemetry and + Authelia. The new role reads that whole exact record; ESO projects only SMTP_PASSWORD. + Tokens last at most 15 minutes. Preserve the mailbox and existing readers. This + step creates the scoped projection and verifies SMTP AUTH without sending mail. + It grants no IMAP access, suppression caller or additional transactional template. + Exact proposal sha256:d9f8785060db902f969e5d8d0917494858b2114e8f93ae812dda3a059c090844 + - at: '2026-11-01T21:29:50+00:00' + reviewer: Bernd Worsch + decision: approved + comment: 'State Hub decision 66c13f9d-73b8-4976-8c44-65a9f5ee04da: APPROVE: CCR-2026-0056; + exact proposal sha256:d9f8785060db902f969e5d8d0917494858b2114e8f93ae812dda3a059c090844; + As founder, platform owner and email-connect owner, admit email-connect as a + third reader of the existing platform SMTP record alongside telemetry and Authelia. + The new role reads that whole exact record; ESO projects only SMTP_PASSWORD. + Tokens last at most 15 minutes. Preserve the mailbox and existing readers. This + step creates the scoped projection and verifies SMTP AUTH without sending mail. + It grants no IMAP access, suppression caller or additional transactional template. + Approval entered by the founder in the attended terminal.' target: domain: communication tenant: platform workload: email-connect environment: production - purpose: Fixed admitted registration and invitation templates from platform@coulomb.social; no new caller or arbitrary notification authority. + purpose: Fixed admitted registration and invitation templates from platform@coulomb.social; + no new caller or arbitrary notification authority. openbao: mount: platform kv_path: platform/workloads/railiance-telemetry/smtp @@ -51,7 +76,8 @@ openbao: access_frontdoor: type: external-secrets catalog_id: email-connect-platform-smtp - selector: Namespace email-connect and ServiceAccount email-connect-platform-smtp-eso only + selector: Namespace email-connect and ServiceAccount email-connect-platform-smtp-eso + only readiness: pending-review resolvable: false delivery: @@ -62,28 +88,50 @@ delivery: external_secret: email-connect-platform-smtp target_secret: email-connect-platform-smtp target_key: password - target: Dedicated namespace SecretStore and password-only ExternalSecret; caller tokens unchanged. - bootstrap_command: Existing mailbox is reused; this request does not create or rotate a value. + target: Dedicated namespace SecretStore and password-only ExternalSecret; caller + tokens unchanged. + bootstrap_command: Existing mailbox is reused; this request does not create or rotate + a value. risk: classification: high notes: - - Shared mailbox reader outside the automatic single-reader profile; existing telemetry and Authelia grants must be preserved. - - OpenBao authorizes the whole exact KV record, not individual fields. ESO projects only SMTP_PASSWORD; this is not field-level OpenBao access control. - - SMTP credential can send as the platform mailbox. No IMAP access or mailbox forwarding operation is included. - - No new application sender, mailbox scan, suppression operator or timer is activated by admission alone. + - Shared mailbox reader outside the automatic single-reader profile; existing telemetry + and Authelia grants must be preserved. + - OpenBao authorizes the whole exact KV record, not individual fields. ESO projects + only SMTP_PASSWORD; this is not field-level OpenBao access control. + - SMTP credential can send as the platform mailbox. No IMAP access or mailbox forwarding + operation is included. + - No new application sender, mailbox scan, suppression operator or timer is activated + by admission alone. verification: positive: - - Exact scoped role and policy readback; certificate-verified STARTTLS AUTH without mail submission. - - Native service account binding confirmation precedes apply; actual workload login and ESO Ready are verified separately after projection. + - Exact scoped role and policy readback; certificate-verified STARTTLS AUTH without + mail submission. + - Native service account binding confirmation precedes apply; actual workload login + and ESO Ready are verified separately after projection. negative: - - Changed policy, role, settings, approval, source digest or unconfirmed binding refuses. - - Agent boundary denies both KV data and metadata; workload token denies metadata, sibling and mutation capabilities. + - Changed policy, role, settings, approval, source digest or unconfirmed binding + refuses. + - Agent boundary denies both KV data and metadata; workload token denies metadata, + sibling and mutation capabilities. activation_conditions: - Exact CCR approval and native bound claims confirmed through the owner workflow. - - Attended owner helper receipt verified with unchanged mailbox version and other consumers; Warden session revocation confirmed. - - Native workload positive and negative login, scoped projection Ready and separate supervised application sender rollout accepted. + - Attended owner helper receipt verified with unchanged mailbox version and other + consumers; Warden session revocation confirmed. + - Native workload positive and negative login, scoped projection Ready and separate + supervised application sender rollout accepted. evidence: [] lifecycle: - deactivate: Stop the admitted consumer and reconcile pending sends, revoke only this dedicated reader role/policy, and remove only its projection after owner verification. Preserve the mailbox and other consumers. - rotate: Coordinate provider rotation across telemetry, Authelia and email-connect; verify all projections and consumers before retiring the predecessor. Never rotate a single consumer copy independently. - compromised: Pause affected sending and coordinate shared-mailbox credential rotation through the owner; preserve ledger evidence and investigate all admitted consumers. Do not auto-resend unknown outcomes. + deactivate: Stop the admitted consumer and reconcile pending sends, revoke only + this dedicated reader role/policy, and remove only its projection after owner + verification. Preserve the mailbox and other consumers. + rotate: Coordinate provider rotation across telemetry, Authelia and email-connect; + verify all projections and consumers before retiring the predecessor. Never rotate + a single consumer copy independently. + compromised: Pause affected sending and coordinate shared-mailbox credential rotation + through the owner; preserve ledger evidence and investigate all admitted consumers. + Do not auto-resend unknown outcomes. +state_hub: + decision_id: 66c13f9d-73b8-4976-8c44-65a9f5ee04da + decision_api_url: http://127.0.0.1:8000/decisions/66c13f9d-73b8-4976-8c44-65a9f5ee04da + decision_resolved_at: '2026-11-01T21:29:52.251349Z' diff --git a/docs/email-connect-smtp-foundation-review-2026-10-31.md b/docs/email-connect-smtp-foundation-review-2026-10-31.md index 12f7f24..ea1331a 100644 --- a/docs/email-connect-smtp-foundation-review-2026-10-31.md +++ b/docs/email-connect-smtp-foundation-review-2026-10-31.md @@ -1,5 +1,13 @@ # Email-connect platform SMTP foundation review +2026-11-01: The historical digest/command below is superseded for a new attempt. +Current deployment generation 76 and changed credential-tooling sources are +reviewed in `coulomb-social/docs/release/platform-mail-admission-review-2026-11-01.md`. +The refreshed exact proposal is +`sha256:d9f8785060db902f969e5d8d0917494858b2114e8f93ae812dda3a059c090844`. +Use the reviewed founder launcher in that packet. All 27 current controller/ +admission tests and fresh native absence/identity checks pass; no grant occurred. + 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. diff --git a/openbao/email-connect-platform-smtp/foundation.json b/openbao/email-connect-platform-smtp/foundation.json index d5ad112..ea16662 100644 --- a/openbao/email-connect-platform-smtp/foundation.json +++ b/openbao/email-connect-platform-smtp/foundation.json @@ -3,7 +3,7 @@ "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", "namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319", "deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72", - "deployment_generation": 74, + "deployment_generation": 76, "resources": [ { "apiVersion": "v1", diff --git a/tests/test_email_connect_smtp_delivery.py b/tests/test_email_connect_smtp_delivery.py index aa08da5..b427816 100644 --- a/tests/test_email_connect_smtp_delivery.py +++ b/tests/test_email_connect_smtp_delivery.py @@ -108,8 +108,7 @@ class AdmissionTests(unittest.TestCase): with patch.object(delivery, 'lane_module', return_value=self.lane), \ patch.object(self.lane, 'bao', side_effect=AssertionError('no owner call')): ccr, proposal, digest = delivery.prepare() - self.assertEqual(ccr['status'], 'proposed') - self.assertFalse(ccr['openbao']['auth']['bound_claims_confirmed']) + self.assertEqual(ccr['id'], 'CCR-2026-0056') role = proposal['role'] self.assertEqual(role['bound_service_account_namespaces'], ['email-connect']) self.assertEqual(role['bound_service_account_names'], ['email-connect-platform-smtp-eso']) @@ -132,6 +131,8 @@ class AdmissionTests(unittest.TestCase): def test_all_three_execution_gates(self): ccr, _, digest = delivery.prepare() + ccr['status'] = 'proposed' + ccr['openbao']['auth']['bound_claims_confirmed'] = False with self.assertRaises(delivery.Refused): delivery.execution_guard(ccr, 'wrong', digest) with self.assertRaises(delivery.Refused): delivery.execution_guard(ccr, digest, digest) ccr['status'] = 'approved' diff --git a/tests/test_email_connect_smtp_foundation.py b/tests/test_email_connect_smtp_foundation.py index 5fdd921..1c22129 100644 --- a/tests/test_email_connect_smtp_foundation.py +++ b/tests/test_email_connect_smtp_foundation.py @@ -32,7 +32,8 @@ class Native: 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}, + return {'metadata': {'uid': self.bundle['deployment_uid'], + 'generation': self.bundle['deployment_generation']}, 'spec': {'unchanged': not (self.created and self.changed_application)}} if name in ('email-connect-config', 'email-connect-runtime'): return {'metadata': {'uid': name}, 'spec': {}, 'data': {}} @@ -77,6 +78,11 @@ def completed(value, code=0): class FoundationTests(unittest.TestCase): def setUp(self): self.ccr, self.proposal, self.digest = foundation.prepare() + # Execution gates use explicit fixtures, independent of the real CCR's + # evolving human approval/binding state. Never enter native execution + # because an operator approved the source record between test runs. + self.ccr['status'] = 'proposed' + self.ccr['openbao']['auth']['bound_claims_confirmed'] = False self.native = Native(self.proposal) self.validator = Mock() self.records = []