Accept both backup readers and deliver the email-connect pair.
Record founder acceptance on CCR-2026-0036 and CCR-2026-0037, install the reader roles, and admit only the email-connect node delivery. Treat a missing OpenBao token period as non-periodic. Assistant: grok Assistant-Session: 01a11cdb-f9fe-7011-947c-936e72007e54
This commit is contained in:
parent
d217c806ca
commit
1900e24f80
8 changed files with 252 additions and 21 deletions
|
|
@ -4,7 +4,7 @@
|
|||
"schema_version": 1,
|
||||
"request_type": "workload-kv-read",
|
||||
"title": "coulomb-social primary archive node-delivery reader",
|
||||
"status": "draft",
|
||||
"status": "applied",
|
||||
"created": "2026-10-08",
|
||||
"updated": "2026-10-08",
|
||||
"requester": {
|
||||
|
|
@ -17,7 +17,22 @@
|
|||
"platform-owner",
|
||||
"coulomb-social-owner"
|
||||
],
|
||||
"comments": []
|
||||
"comments": [
|
||||
{
|
||||
"at": "2026-10-08T12:40:00Z",
|
||||
"reviewer": "User (platform-owner)",
|
||||
"decision": "approved",
|
||||
"comment": "Founder accepted this reader on 2026-10-08. One read policy, ServiceAccount backup-primary-delivery in namespace coulomb-social, audience openbao. Node delivery is not admitted.",
|
||||
"source_ref": "founder-acceptance-2026-10-08"
|
||||
},
|
||||
{
|
||||
"at": "2026-10-08T12:40:00Z",
|
||||
"reviewer": "User (coulomb-social-owner)",
|
||||
"decision": "approved",
|
||||
"comment": "Founder accepted the coulomb-social reader record. Node delivery of its pair was not requested.",
|
||||
"source_ref": "founder-acceptance-2026-10-08"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"domain": "communication",
|
||||
|
|
@ -59,13 +74,13 @@
|
|||
"coulomb-social"
|
||||
]
|
||||
},
|
||||
"bound_claims_confirmed": false
|
||||
"bound_claims_confirmed": true
|
||||
}
|
||||
},
|
||||
"access_frontdoor": {
|
||||
"type": "platform-owner",
|
||||
"catalog_id": "coulomb-social-backup-primary-delivery",
|
||||
"readiness": "pending-review",
|
||||
"readiness": "applied-pending-verify",
|
||||
"resolvable": false
|
||||
},
|
||||
"delivery": {
|
||||
|
|
@ -88,7 +103,7 @@
|
|||
"endpoint": "https://s3.nl-ams.scw.cloud",
|
||||
"bucket": "railiance-platform-pg-backup",
|
||||
"prefix": "platform-pg/application-archives/coulomb-social/",
|
||||
"identity": "separate IAM application and key; provider identity not created",
|
||||
"identity": "IAM application coulomb-social-backup-primary 681d00bf-b965-4323-b657-97bfde423511; key only in OpenBao; bucket policy limits the prefix and withholds object delete",
|
||||
"retained_delete": false
|
||||
},
|
||||
"risk": {
|
||||
|
|
@ -113,6 +128,18 @@
|
|||
"Provider denies sibling/native prefixes, retained object/version deletion and IAM/bucket administration.",
|
||||
"Draft/unconfirmed/source-changed contract, wrong cluster, symlinks, hard links, insecure modes, changed KV version, unconfirmed token revocation and different existing file refuse delivery."
|
||||
],
|
||||
"evidence": [
|
||||
{
|
||||
"at": "2026-10-08T12:55:00Z",
|
||||
"actor": "grok",
|
||||
"kind": "delegated_metadata_apply",
|
||||
"result": "passed",
|
||||
"details": [
|
||||
"Attended platform-admin login wrote workload-kv-read-coulomb-social-backup-primary and auth/kubernetes/role/coulomb-social-backup-primary-workload-kv-read.",
|
||||
"Readback bound backup-primary-delivery in coulomb-social, one policy, ttl 300, token_max_ttl 300, token_explicit_max_ttl 300, token_num_uses 4, token_no_default_policy true, token_type service. Node delivery remains unadmitted."
|
||||
]
|
||||
}
|
||||
],
|
||||
"activation_conditions": [
|
||||
"Named owner reviews and confirmed native reader bindings, applied policy/role with live positive/negative evidence, provider prefix boundary proof and explicit node execution admission.",
|
||||
"Separate platform-owned attended pair provision with initial KV CAS=0, no partial/second single-field PUT; retain only version metadata. Current reader does not create or rotate values.",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"schema_version": 1,
|
||||
"request_type": "workload-kv-read",
|
||||
"title": "email-connect primary archive node-delivery reader",
|
||||
"status": "draft",
|
||||
"status": "applied",
|
||||
"created": "2026-10-08",
|
||||
"updated": "2026-10-08",
|
||||
"requester": {
|
||||
|
|
@ -17,7 +17,22 @@
|
|||
"platform-owner",
|
||||
"email-connect-owner"
|
||||
],
|
||||
"comments": []
|
||||
"comments": [
|
||||
{
|
||||
"at": "2026-10-08T12:40:00Z",
|
||||
"reviewer": "User (platform-owner)",
|
||||
"decision": "approved",
|
||||
"comment": "Founder accepted this reader on 2026-10-08. One read policy, ServiceAccount backup-primary-delivery in namespace email-connect, audience openbao.",
|
||||
"source_ref": "founder-acceptance-2026-10-08"
|
||||
},
|
||||
{
|
||||
"at": "2026-10-08T12:40:00Z",
|
||||
"reviewer": "User (email-connect-owner)",
|
||||
"decision": "approved",
|
||||
"comment": "Founder accepted email-connect node delivery of this backup pair. Retention, escrow, capture and the timer stay separate.",
|
||||
"source_ref": "founder-acceptance-2026-10-08"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"domain": "communication",
|
||||
|
|
@ -59,13 +74,13 @@
|
|||
"email-connect"
|
||||
]
|
||||
},
|
||||
"bound_claims_confirmed": false
|
||||
"bound_claims_confirmed": true
|
||||
}
|
||||
},
|
||||
"access_frontdoor": {
|
||||
"type": "platform-owner",
|
||||
"catalog_id": "email-connect-backup-primary-delivery",
|
||||
"readiness": "pending-review",
|
||||
"readiness": "applied-pending-verify",
|
||||
"resolvable": false
|
||||
},
|
||||
"delivery": {
|
||||
|
|
@ -78,17 +93,17 @@
|
|||
"directory_mode": "0700",
|
||||
"procedure": "scripts/application_backup_credentials.py",
|
||||
"admission": {
|
||||
"node_executor_approved": false,
|
||||
"provider_scope_verified": false,
|
||||
"reader_boundary_verified": false,
|
||||
"source_ref": null
|
||||
"node_executor_approved": true,
|
||||
"provider_scope_verified": true,
|
||||
"reader_boundary_verified": true,
|
||||
"source_ref": "backup-reader-boundary-2026-10-08"
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"endpoint": "https://s3.nl-ams.scw.cloud",
|
||||
"bucket": "railiance-platform-pg-backup",
|
||||
"prefix": "platform-pg/application-archives/email-connect/",
|
||||
"identity": "separate IAM application and key; provider identity not created",
|
||||
"identity": "IAM application email-connect-backup-primary 0eb0f958-77a4-4c70-9aa7-14ba46411415; key only in OpenBao; bucket policy limits the prefix and withholds object delete",
|
||||
"retained_delete": false
|
||||
},
|
||||
"risk": {
|
||||
|
|
@ -113,6 +128,28 @@
|
|||
"Provider denies sibling/native prefixes, retained object/version deletion and IAM/bucket administration.",
|
||||
"Draft/unconfirmed/source-changed contract, wrong cluster, symlinks, hard links, insecure modes, changed KV version, unconfirmed token revocation and different existing file refuse delivery."
|
||||
],
|
||||
"evidence": [
|
||||
{
|
||||
"at": "2026-10-08T12:55:00Z",
|
||||
"actor": "grok",
|
||||
"kind": "delegated_metadata_apply",
|
||||
"result": "passed",
|
||||
"details": [
|
||||
"Attended platform-admin login wrote workload-kv-read-email-connect-backup-primary and auth/kubernetes/role/email-connect-backup-primary-workload-kv-read.",
|
||||
"Readback bound backup-primary-delivery in email-connect, one policy, ttl 300, token_max_ttl 300, token_explicit_max_ttl 300, token_num_uses 4, token_no_default_policy true, token_type service."
|
||||
]
|
||||
},
|
||||
{
|
||||
"at": "2026-10-08T13:05:00Z",
|
||||
"actor": "grok",
|
||||
"kind": "reader_boundary",
|
||||
"result": "passed",
|
||||
"details": [
|
||||
"Live email-connect ServiceAccount login had one reader policy, 300 second lease, four uses, null identity policies and KV version 2 with both field lengths only.",
|
||||
"Sibling data, metadata, parent list, unrelated write, the coulomb-social account, and a wrong audience were denied. The token was revoked. Provider prefix proof already denied foreign prefixes and object delete."
|
||||
]
|
||||
}
|
||||
],
|
||||
"activation_conditions": [
|
||||
"Named owner reviews and confirmed native reader bindings, applied policy/role with live positive/negative evidence, provider prefix boundary proof and explicit node execution admission.",
|
||||
"Separate platform-owned attended pair provision with initial KV CAS=0, no partial/second single-field PUT; retain only version metadata. Current reader does not create or rotate values.",
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ def retrieve(native, application, expected_version):
|
|||
info.get("policies") == [policy]
|
||||
and not info.get("identity_policies")
|
||||
and 0 < info.get("ttl", 0) <= 300
|
||||
and info.get("period", 0) == 0
|
||||
and (info.get("period") or 0) == 0
|
||||
and 0 < info.get("explicit_max_ttl", 0) <= 300
|
||||
and 0 < info.get("num_uses", 0) <= 4,
|
||||
"reader_scope_mismatch",
|
||||
|
|
|
|||
134
scripts/install-backup-primary-readers.sh
Normal file
134
scripts/install-backup-primary-readers.sh
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
#!/bin/sh
|
||||
# Install both backup-primary reader policies and Kubernetes auth roles.
|
||||
# Silent: warden access rejects any child output. Receipts stay on absolute paths.
|
||||
set -eu
|
||||
export BAO_ADDR="${BAO_ADDR:-http://127.0.0.1:18200}"
|
||||
export VAULT_ADDR="${VAULT_ADDR:-http://127.0.0.1:18200}"
|
||||
receipt_dir=/home/worsch/.local/state/railiance/receipts
|
||||
umask 077
|
||||
mkdir -p "$receipt_dir"
|
||||
stage="$receipt_dir/backup-primary-readers.stage"
|
||||
: >"$stage"
|
||||
python3 - "$stage" <<'PY'
|
||||
import importlib.util
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
stage = Path(sys.argv[1])
|
||||
root = Path("/home/worsch/railiance-platform")
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"credential_change", root / "scripts/credential-change.py"
|
||||
)
|
||||
cc = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(cc)
|
||||
|
||||
refs = [
|
||||
"CCR-2026-0036-coulomb-social-backup-primary.yaml",
|
||||
"CCR-2026-0037-email-connect-backup-primary.yaml",
|
||||
]
|
||||
safe_role_keys = (
|
||||
"audience",
|
||||
"bound_service_account_names",
|
||||
"bound_service_account_namespaces",
|
||||
"policies",
|
||||
"token_explicit_max_ttl",
|
||||
"token_max_ttl",
|
||||
"token_no_default_policy",
|
||||
"token_num_uses",
|
||||
"token_policies",
|
||||
"token_type",
|
||||
"ttl",
|
||||
)
|
||||
|
||||
def run(argv):
|
||||
result = subprocess.run(argv, cwd=root, capture_output=True)
|
||||
if result.returncode != 0:
|
||||
stage.write_text(f"exit {result.returncode} step {argv[1]}\n", encoding="utf-8")
|
||||
raise SystemExit(result.returncode or 1)
|
||||
return result.stdout.decode()
|
||||
|
||||
receipts = []
|
||||
for name in refs:
|
||||
ccr = json.loads((root / "credential-change-requests" / name).read_text(encoding="utf-8"))
|
||||
if ccr.get("status") != "approved" or ccr["openbao"]["auth"].get("bound_claims_confirmed") is not True:
|
||||
stage.write_text(f"{ccr.get('id')} not approved for apply\n", encoding="utf-8")
|
||||
raise SystemExit(44)
|
||||
blockers = cc.applier_readiness_blockers(ccr)
|
||||
if blockers:
|
||||
stage.write_text(ccr["id"] + " blocked\n", encoding="utf-8")
|
||||
raise SystemExit(45)
|
||||
openbao = ccr["openbao"]
|
||||
auth = openbao["auth"]
|
||||
policy_path = root / openbao["policy_file"]
|
||||
run(["bao", "policy", "write", openbao["policy_name"], str(policy_path)])
|
||||
installed = run(["bao", "policy", "read", openbao["policy_name"]])
|
||||
if cc.normalized_policy_body(installed) != cc.normalized_policy_body(policy_path.read_text(encoding="utf-8")):
|
||||
stage.write_text(openbao["policy_name"] + " readback differs\n", encoding="utf-8")
|
||||
raise SystemExit(46)
|
||||
payload = cc.auth_payload(ccr)
|
||||
role_file = Path(f"/home/worsch/.local/state/railiance/receipts/{auth['role']}.payload.json")
|
||||
role_file.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
role_file.chmod(0o600)
|
||||
run(["bao", "write", f"auth/{auth['mount']}/role/{auth['role']}", f"@{role_file}"])
|
||||
raw = run(["bao", "read", "-format=json", f"auth/{auth['mount']}/role/{auth['role']}"])
|
||||
data = json.loads(raw).get("data", {})
|
||||
observed = {key: data.get(key) for key in safe_role_keys if key in data}
|
||||
names = data.get("bound_service_account_names")
|
||||
namespaces = data.get("bound_service_account_namespaces")
|
||||
policies = data.get("token_policies") or data.get("policies")
|
||||
audience = data.get("audience") or data.get("audiences")
|
||||
problems = []
|
||||
if names != payload["bound_service_account_names"]:
|
||||
problems.append("names")
|
||||
if namespaces != payload["bound_service_account_namespaces"]:
|
||||
problems.append("namespaces")
|
||||
if policies != payload["policies"].split(","):
|
||||
problems.append("policies")
|
||||
if audience not in (payload.get("audience"), [payload.get("audience")]):
|
||||
problems.append("audience")
|
||||
if data.get("token_no_default_policy") is not True:
|
||||
problems.append("no_default")
|
||||
if data.get("token_num_uses") != 4:
|
||||
problems.append("uses")
|
||||
if data.get("token_type") != "service":
|
||||
problems.append("type")
|
||||
if data.get("ttl") not in (300, "5m", "300s"):
|
||||
problems.append("ttl")
|
||||
if data.get("token_max_ttl") not in (300, "5m", "300s"):
|
||||
problems.append("max_ttl")
|
||||
if data.get("token_explicit_max_ttl") not in (300, "5m", "300s"):
|
||||
problems.append("explicit_max_ttl")
|
||||
if problems:
|
||||
stage.write_text(
|
||||
json.dumps({"role": auth["role"], "problems": problems, "observed": observed}, indent=2)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
raise SystemExit(47)
|
||||
receipts.append(
|
||||
{
|
||||
"id": ccr["id"],
|
||||
"policy": openbao["policy_name"],
|
||||
"role": auth["role"],
|
||||
"policies": policies,
|
||||
"bound_service_account_names": names,
|
||||
"bound_service_account_namespaces": namespaces,
|
||||
"ttl": data.get("ttl"),
|
||||
"token_max_ttl": data.get("token_max_ttl"),
|
||||
"token_explicit_max_ttl": data.get("token_explicit_max_ttl"),
|
||||
"token_num_uses": data.get("token_num_uses"),
|
||||
"token_no_default_policy": data.get("token_no_default_policy"),
|
||||
"token_type": data.get("token_type"),
|
||||
}
|
||||
)
|
||||
|
||||
out = Path("/home/worsch/.local/state/railiance/receipts/backup-primary-readers.json")
|
||||
out.write_text(json.dumps({"status": "installed", "readers": receipts}, indent=2) + "\n", encoding="utf-8")
|
||||
out.chmod(0o600)
|
||||
PY
|
||||
if [ -s "$stage" ]; then
|
||||
exit 48
|
||||
fi
|
||||
exit 0
|
||||
|
|
@ -166,7 +166,7 @@ def test_atomic_private_delivery_and_identical_resume(application, directory, ca
|
|||
|
||||
@pytest.mark.parametrize(
|
||||
"change",
|
||||
["default", "identity", "unbounded", "uses", "version", "destroyed", "fields", "injection"],
|
||||
["default", "identity", "unbounded", "uses", "period", "version", "destroyed", "fields", "injection"],
|
||||
)
|
||||
def test_scope_and_payload_failures_revoke_without_publishing(change, directory):
|
||||
path, fd = directory
|
||||
|
|
@ -179,6 +179,8 @@ def test_scope_and_payload_failures_revoke_without_publishing(change, directory)
|
|||
native.info["data"]["explicit_max_ttl"] = 0
|
||||
elif change == "uses":
|
||||
native.info["data"]["num_uses"] = 0
|
||||
elif change == "period":
|
||||
native.info["data"]["period"] = 60
|
||||
elif change == "version":
|
||||
native.record["data"]["metadata"]["version"] = 2
|
||||
elif change == "destroyed":
|
||||
|
|
@ -193,6 +195,14 @@ def test_scope_and_payload_failures_revoke_without_publishing(change, directory)
|
|||
assert not (path / "primary.env").exists() and not list(path.glob("delivery-v*.json"))
|
||||
|
||||
|
||||
def test_absent_period_still_delivers(directory):
|
||||
_path, fd = directory
|
||||
native = FakeNative()
|
||||
native.info["data"]["period"] = None
|
||||
receipt = d.deliver(approved(), "source-hash", "coulomb-social", 1, native, fd, os.getuid())
|
||||
assert receipt["outcome"] == "created"
|
||||
|
||||
|
||||
def test_revocation_failure_does_not_publish_or_disclose(directory, capsys, monkeypatch, tmp_path):
|
||||
path, fd = directory
|
||||
ccr = approved()
|
||||
|
|
|
|||
|
|
@ -47,12 +47,13 @@ def identity():
|
|||
|
||||
|
||||
@pytest.mark.parametrize("application", sorted(custody.PROFILES))
|
||||
def test_preview_reads_draft_contract_and_writes_nothing(application, capsys, monkeypatch):
|
||||
def test_preview_reads_recorded_contract_and_writes_nothing(application, capsys, monkeypatch):
|
||||
monkeypatch.setattr(custody, "provision", lambda *_: pytest.fail("preview provisioned"))
|
||||
assert custody.main(["--application", application]) == 0
|
||||
receipt = json.loads(capsys.readouterr().out)
|
||||
recorded = json.loads(custody.ccr_path(application).read_text(encoding="utf-8"))
|
||||
assert receipt["status"] == "preview"
|
||||
assert receipt["ccr_status"] == "draft"
|
||||
assert receipt["ccr_status"] == recorded["status"]
|
||||
assert receipt["writer_admitted"] is True
|
||||
assert receipt["openbao_write"] is False
|
||||
assert receipt["provider_keys"] == "not_created"
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ def test_owner_yaml_updates_export_without_node_yaml_dependency(application, tmp
|
|||
ccr = yaml.safe_load(source.read_text())
|
||||
ccr["status"] = "applied"
|
||||
ccr["openbao"]["auth"]["bound_claims_confirmed"] = True
|
||||
ccr["review"]["comments"] = []
|
||||
ccr["delivery"]["admission"] = {
|
||||
"node_executor_approved": False,
|
||||
"provider_scope_verified": False,
|
||||
"reader_boundary_verified": False,
|
||||
"source_ref": None,
|
||||
}
|
||||
candidate = tmp_path / "reviewed.yaml"
|
||||
candidate.write_text(yaml.safe_dump(ccr))
|
||||
raw = e.render(candidate, application)
|
||||
|
|
|
|||
|
|
@ -140,10 +140,10 @@ is not ledger capture/recovery coverage; CSOC-WP-0010-T04/T05 own that residual.
|
|||
|
||||
```task
|
||||
id: RPF-WP-0053-T05
|
||||
status: wait
|
||||
status: progress
|
||||
priority: high
|
||||
needs_human: true
|
||||
intervention_note: "EMAIL-WP-0007 has tested capture/recovery tools; platform must admit its separate email-connect primary prefix/caller, retention/capacity, independent receipts and escrow before first live capture/upload or timer activation."
|
||||
intervention_note: "email-connect primary.env is delivered from the accepted reader. Retention, capacity, independent receipts, escrow and the first capture remain open. Coulomb node delivery was not requested."
|
||||
state_hub_task_id: "3f744ce6-28e4-5496-8d21-7231271a7bc8"
|
||||
```
|
||||
|
||||
|
|
@ -179,7 +179,7 @@ and SMTP/sender/caller admission remain unchanged by source preparation.
|
|||
|
||||
```task
|
||||
id: RPF-WP-0053-T06
|
||||
status: progress
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ce950b3c-1ca6-5b01-bf2a-d958cafaed25"
|
||||
```
|
||||
|
|
@ -247,3 +247,18 @@ version or node file was created. Review:
|
|||
docs/credential-lane-designs/application-backup-pair-custody-2026-10-08.md.
|
||||
T05 stays wait for owner admission of the writer, prefix and escrow. T06 stays
|
||||
progress until that attended act.
|
||||
|
||||
## 2026-10-08 — Reader acceptance and email delivery
|
||||
|
||||
Founder acceptance is recorded on CCR-2026-0036 and CCR-2026-0037 with both
|
||||
owner reviews, source ref founder-acceptance-2026-10-08, and confirmed
|
||||
Kubernetes bindings. Attended platform-admin login installed both read policies
|
||||
and roles: one policy, backup-primary-delivery, matching namespace, ttl 300,
|
||||
four uses, no default policy, service token. Both ServiceAccounts exist with
|
||||
automount disabled. Live proof: each account read only its own version 2 pair
|
||||
(lengths 20 and 36), sibling read denied, email metadata/list/write denied,
|
||||
cross-namespace and wrong-audience login denied, tokens revoked. Email delivery
|
||||
admission is true. Coulomb delivery admission stays false and
|
||||
/etc/coulomb-social-backup was not created. email-connect primary.env was
|
||||
created root:root 0600 under /etc/email-connect-backup mode 0700, outcome
|
||||
created, readback equal, KV version 2. No capture, upload, escrow or timer.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue