Compare commits
422 commits
csoc-authe
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88071e79e2 | ||
|
|
0e98465c6e | ||
|
|
72672c66d6 | ||
|
|
aeb21434bd | ||
|
|
371f787493 | ||
|
|
7f20b20b64 | ||
|
|
a3851f6a18 | ||
|
|
cd52b13502 | ||
|
|
ecce45284a | ||
|
|
edc6bf4dd6 | ||
|
|
c90d37e27f | ||
|
|
7b979eb39c | ||
|
|
3ddef4cedc | ||
|
|
3a68eaca8a | ||
|
|
22cfdb72e4 | ||
|
|
2422226128 | ||
|
|
645b529a3c | ||
|
|
2fb7f951eb | ||
|
|
b0b6c0cf46 | ||
|
|
bb08f98d14 | ||
|
|
ac206fa2e8 | ||
|
|
22ff06638c | ||
|
|
56200e617b | ||
|
|
86eb45647c | ||
|
|
2a1587fc9b | ||
|
|
cfe3042987 | ||
|
|
dad91e8c44 | ||
|
|
8d574be1bf | ||
|
|
17fdc27465 | ||
|
|
b2ca83176a | ||
|
|
a7392437e1 | ||
|
|
7624cfcdca | ||
|
|
76daa8c8b2 | ||
|
|
02d75dcc74 | ||
|
|
430c6eea07 | ||
|
|
233ec5f586 | ||
|
|
617c292594 | ||
|
|
9d576f05f2 | ||
|
|
fb22d69130 | ||
|
|
0c36d7c596 | ||
|
|
edd8687ea6 | ||
|
|
d8a0575727 | ||
|
|
efb17b4984 | ||
|
|
4277088e1c | ||
|
|
016c147976 | ||
|
|
90f859f395 | ||
|
|
2bb8c37fbc | ||
|
|
f3984e9b1d | ||
|
|
fe3ef34626 | ||
|
|
3b1a55c04e | ||
|
|
3035f87d7f | ||
|
|
98e2cad1cc | ||
|
|
d41c5ace0a | ||
|
|
c02c3977f9 | ||
|
|
28d5d1875c | ||
|
|
de13931c75 | ||
|
|
fbc4830aa0 | ||
|
|
e9a2f237b3 | ||
|
|
d6ee8ba8c4 | ||
|
|
1df1345ba5 | ||
|
|
ca7c46b775 | ||
|
|
5b0a2a06c4 | ||
|
|
814469944c | ||
|
|
363e63562b | ||
|
|
7c38de3309 | ||
|
|
8d4d285643 | ||
|
|
2aab7f6f2b | ||
|
|
962aa9e262 | ||
|
|
8cb5b13c77 | ||
|
|
c89ac18d24 | ||
|
|
0fb2305def | ||
|
|
a274938232 | ||
|
|
7173956e12 | ||
|
|
96d97d8fa0 | ||
|
|
30e8b5f321 | ||
|
|
4023e6c3bd | ||
|
|
16b4e33a16 | ||
|
|
681feb3de1 | ||
|
|
fd0fcf94a1 | ||
|
|
497656c4ff | ||
|
|
f71a028903 | ||
|
|
7c43f547ad | ||
|
|
ba445d7aed | ||
|
|
50e51e0fd8 | ||
|
|
356d84a095 | ||
|
|
6858bef858 | ||
|
|
7bdf1996f0 | ||
|
|
702ca22069 | ||
|
|
b4bafb0df8 | ||
|
|
678998f5b9 | ||
|
|
831a0f9c98 | ||
|
|
98a8b08bf6 | ||
|
|
c9b68ea4e4 | ||
|
|
d0ff5349db | ||
|
|
c20bf329fd | ||
|
|
331c4af2bf | ||
|
|
2f69a4443d | ||
|
|
69cc133e9d | ||
|
|
b70ba57a4e | ||
|
|
88bafd8848 | ||
|
|
8ac066f299 | ||
|
|
dff35c15d8 | ||
|
|
2023a94408 | ||
|
|
03a74e839c | ||
|
|
8b57d270de | ||
|
|
c5f9770c97 | ||
|
|
2ac97865da | ||
|
|
0f012dbe28 | ||
|
|
93e84dbd34 | ||
|
|
3be4311faf | ||
|
|
e77f3989b4 | ||
|
|
f7c01d4000 | ||
|
|
1524c54570 | ||
|
|
ca2b5e3385 | ||
|
|
04d9dc87ac | ||
|
|
8cab281ffc | ||
|
|
2d4a3838bf | ||
|
|
674a7132d0 | ||
|
|
5d87acd184 | ||
|
|
d20ebe7bca | ||
|
|
5dd1d6875b | ||
|
|
bcfbe29b24 | ||
|
|
bae2df0d31 | ||
|
|
96d4d319be | ||
|
|
5d2f43d6d9 | ||
|
|
a715bfa468 | ||
|
|
112c04170d | ||
|
|
09f4fd33cb | ||
|
|
e1918b3f88 | ||
|
|
3e8155878b | ||
|
|
3c98ca96bb | ||
|
|
b16f8bab8c | ||
|
|
7e0b7d9062 | ||
|
|
849f3947dc | ||
|
|
ccc0f8b578 | ||
|
|
61a82ac74c | ||
|
|
12f2056272 | ||
|
|
87a8a5cbdf | ||
|
|
7aa38fcacd | ||
|
|
bd8ca57dc9 | ||
|
|
2d8a554c10 | ||
|
|
1eca6d00c7 | ||
|
|
86c92792b2 | ||
|
|
f00298cd44 | ||
|
|
95b5a19898 | ||
|
|
42c06d8db4 | ||
|
|
3727f8bc7a | ||
|
|
a2da4085e0 | ||
|
|
05ff9f8678 | ||
|
|
8016f56f18 | ||
|
|
e3bc1ab32b | ||
|
|
bedab6f5ab | ||
|
|
87a488cc66 | ||
|
|
4583b22fd6 | ||
|
|
98b1190765 | ||
|
|
565815006c | ||
|
|
2bbf1ffd83 | ||
|
|
8f3b4a7e85 | ||
|
|
bca66d1d2e | ||
|
|
2126457a16 | ||
|
|
bab4f2ca6a | ||
|
|
7084b57d57 | ||
|
|
624843e122 | ||
|
|
1701a3fa2a | ||
|
|
fb0b50234d | ||
|
|
39bf724a14 | ||
|
|
a61a07cf15 | ||
|
|
7f67c9c244 | ||
|
|
0cc3281ab3 | ||
|
|
76b45e0e16 | ||
|
|
3894dc7c54 | ||
|
|
1f8fbc7584 | ||
|
|
e594350eff | ||
|
|
bd6660a916 | ||
|
|
c27f50b00e | ||
|
|
d0d0db7c52 | ||
|
|
8bf107eb85 | ||
|
|
7fbba919c5 | ||
|
|
30c48cb993 | ||
|
|
4a91b8579d | ||
|
|
51d5247156 | ||
|
|
2c5d3b327e | ||
|
|
c67fb49723 | ||
|
|
cfd9fa57ae | ||
|
|
1a6d83ace9 | ||
|
|
36050b100f | ||
|
|
b3f12b2ffa | ||
|
|
c03fce06e2 | ||
|
|
15a931b84d | ||
|
|
20db368aee | ||
|
|
1228cd8407 | ||
|
|
fd2f808cd8 | ||
|
|
8761da9027 | ||
|
|
b64bb3f1fe | ||
|
|
3e6f2807cf | ||
|
|
4b2b72a81c | ||
|
|
5a8cd4dd2b | ||
|
|
07c6d30aea | ||
|
|
e415da97f3 | ||
|
|
9d5fc4275c | ||
|
|
ded592239e | ||
|
|
567d2c671b | ||
|
|
acd1ade59a | ||
|
|
bfdd443861 | ||
|
|
d70422cc41 | ||
|
|
72bba8e6b4 | ||
|
|
918656f9f4 | ||
|
|
e7bc778784 | ||
|
|
5d2cb0431c | ||
|
|
f8ce8df044 | ||
|
|
141609f96f | ||
|
|
53d34bc4f4 | ||
|
|
b06d1f0b37 | ||
|
|
79fbb4bdb5 | ||
|
|
7e7ca8be51 | ||
|
|
04f18c8c73 | ||
|
|
45c8d9370e | ||
|
|
102668f089 | ||
|
|
7caac13b9c | ||
|
|
8545bf8e4c | ||
|
|
3d971178d8 | ||
|
|
10fddbc98f | ||
|
|
b6148da443 | ||
|
|
4398f89e02 | ||
|
|
d35c0f9d4b | ||
|
|
104ed90f42 | ||
|
|
0a9617a62d | ||
|
|
08c1f036f2 | ||
|
|
84c529f239 | ||
|
|
a3e8eb64ba | ||
|
|
7be8b6e76d | ||
|
|
76fdc0b861 | ||
|
|
b9eaa20060 | ||
|
|
d870596cc7 | ||
|
|
5f30ddd90c | ||
|
|
8164c57467 | ||
|
|
e2b832b5d4 | ||
|
|
d042d468df | ||
|
|
4021ca5e79 | ||
|
|
d79079bb21 | ||
|
|
99fd21ee37 | ||
|
|
69a792c070 | ||
|
|
59d1441cf8 | ||
|
|
3b6e647aa0 | ||
|
|
6e24b09eaa | ||
|
|
ce5344da69 | ||
|
|
19b13f6f36 | ||
|
|
4f054046dc | ||
|
|
7ec54d59e0 | ||
|
|
d5a86b74d1 | ||
|
|
643006aa25 | ||
|
|
061d98b66e | ||
|
|
2951b69fc5 | ||
|
|
b6c4410a82 | ||
|
|
352aeea5e9 | ||
|
|
cc3c7d883b | ||
|
|
635daec6b4 | ||
|
|
6e8955fcd0 | ||
|
|
a570bc2f5b | ||
|
|
748fe6c7db | ||
|
|
6fc2dae86a | ||
|
|
0f0282a436 | ||
|
|
bb6838f588 | ||
|
|
e9cf0d8884 | ||
|
|
b797594a21 | ||
|
|
dd003c9429 | ||
|
|
3e904b750f | ||
|
|
048362e3ef | ||
|
|
a55cfbe116 | ||
|
|
133628c573 | ||
|
|
64292531ce | ||
|
|
8655857888 | ||
|
|
516bd2a49c | ||
|
|
02c8114568 | ||
|
|
02101df7d1 | ||
|
|
88b4aed83e | ||
|
|
be514f6d74 | ||
|
|
044df6fe5b | ||
|
|
c8d45d585f | ||
|
|
b8a5b80e33 | ||
|
|
311aa86eef | ||
|
|
badb91c71a | ||
|
|
8cfbf6410d | ||
|
|
6152aa9e86 | ||
|
|
ce8286dff9 | ||
|
|
aee6fe79d7 | ||
|
|
63df822f6c | ||
|
|
a86dfe2220 | ||
|
|
d303c99e3b | ||
|
|
1b2f7ead59 | ||
|
|
cea70051d8 | ||
|
|
6323db59ba | ||
|
|
5ea8b2dcd3 | ||
|
|
60101b0932 | ||
|
|
b98d17c09f | ||
|
|
7c79293831 | ||
|
|
3001a8148f | ||
|
|
c9b56a084e | ||
|
|
1dc1d2f3a1 | ||
|
|
4d2bb19f8a | ||
|
|
725d126c77 | ||
|
|
5a1a7ee5c1 | ||
|
|
4f233bb028 | ||
|
|
c45c058283 | ||
|
|
bd8931f92a | ||
|
|
3d19215801 | ||
|
|
4258fbecf3 | ||
|
|
2366c1125f | ||
|
|
24d3384b33 | ||
|
|
73f7f55e5c | ||
|
|
12ae540434 | ||
|
|
567ff5db25 | ||
|
|
d156a60f53 | ||
|
|
e3a0e7ba1c | ||
|
|
cc2d639262 | ||
|
|
5f46e2c3c1 | ||
|
|
bf4c52a901 | ||
|
|
cda974e817 | ||
|
|
7d662b17e3 | ||
|
|
fac2f28bcd | ||
|
|
390894f4da | ||
|
|
30d2290888 | ||
|
|
9460f6f200 | ||
|
|
5ae70642a7 | ||
|
|
7adbcb492b | ||
|
|
d7583e1e48 | ||
|
|
f814c744fd | ||
|
|
62bbdee811 | ||
|
|
93ac156adf | ||
|
|
b0e9a1e7a3 | ||
|
|
aa3dbda733 | ||
|
|
6bb406eb15 | ||
|
|
431df1de24 | ||
|
|
4e8fa6e831 | ||
|
|
4ed50501f6 | ||
|
|
d264e68436 | ||
|
|
33aed8a5c9 | ||
|
|
46f5c956b5 | ||
|
|
c0797e40cb | ||
|
|
83890db963 | ||
|
|
e50a0d1890 | ||
|
|
8647587ca2 | ||
|
|
dc73e9cbf6 | ||
|
|
2fe072a866 | ||
|
|
d47cbd05a5 | ||
|
|
9ec57c0dff | ||
|
|
f69e461cdb | ||
|
|
4493360317 | ||
|
|
5b9c8262be | ||
|
|
c771a17269 | ||
|
|
6b1c1c700d | ||
|
|
8c152d012a | ||
|
|
64658a354b | ||
|
|
9110dcdb40 | ||
|
|
6a394c86de | ||
|
|
b3d3297b3d | ||
|
|
dc1bbb5ad5 | ||
|
|
7b75823095 | ||
|
|
64d2543151 | ||
|
|
d4e34e096a | ||
|
|
d0e1a58bfe | ||
|
|
444f773dd9 | ||
|
|
d14c4bfd14 | ||
|
|
aa71e3c2a7 | ||
|
|
24573edd88 | ||
|
|
983891307f | ||
|
|
7ee2fa5a1b | ||
|
|
b96b86753c | ||
|
|
21b8af32d2 | ||
|
|
c924ff2279 | ||
|
|
6f77484785 | ||
|
|
b328d36a20 | ||
|
|
c8022c0798 | ||
|
|
26693a890f | ||
|
|
c921aa0aed | ||
|
|
7d498c8547 | ||
|
|
87234a2c04 | ||
|
|
93a4bc2a9f | ||
|
|
117e837329 | ||
|
|
ff9a236f62 | ||
|
|
a595db9f7f | ||
|
|
857be4695f | ||
|
|
60435ab53e | ||
|
|
0c03513f5b | ||
|
|
6871bc71fd | ||
|
|
47912db30f | ||
|
|
ce1c1a6338 | ||
|
|
64ae5946d9 | ||
|
|
bed91ceaf2 | ||
|
|
2b14d41d0a | ||
|
|
b77cb41275 | ||
|
|
1b36e147e3 | ||
|
|
03d7f2f6bb | ||
|
|
5e67a425c4 | ||
|
|
0b2e7ddb5f | ||
|
|
2e7701f155 | ||
|
|
1900e24f80 | ||
|
|
d217c806ca | ||
|
|
dfb3e105fc | ||
|
|
31c679f212 | ||
|
|
9b658e3f19 | ||
|
|
acbc78ffea | ||
|
|
953e168308 | ||
|
|
1cc1b14488 | ||
|
|
2a338f06b9 | ||
|
|
715b0064ce | ||
|
|
d0525c836d | ||
|
|
8b3bb395b4 | ||
|
|
a8fc1a3438 | ||
|
|
59f973c2c5 | ||
|
|
5a19434c7f | ||
|
|
22651d851f | ||
|
|
005c204219 | ||
|
|
483b955f15 | ||
|
|
720277bd39 | ||
|
|
614d70fb8a | ||
|
|
6ad9d34738 | ||
|
|
53e91ab2d2 | ||
|
|
26dd803410 | ||
|
|
f437396337 | ||
|
|
d8e912e526 | ||
|
|
71607d277c |
759 changed files with 92529 additions and 1019 deletions
|
|
@ -12,7 +12,7 @@ make apps-pg-status # shared apps-pg cluster health (read-only)
|
|||
make pg-status # confirms retired Bitnami postgresql-ha is absent
|
||||
make valkey-status # confirms Valkey is absent
|
||||
make consumption-preflight CONSUMING_ENTITY=entity:<slug> ESTIMATE_EUR=<eur>
|
||||
make db-deploy | apps-pg-deploy # production-touching
|
||||
make forgejo-db-deploy | apps-pg-deploy # production-touching
|
||||
```
|
||||
|
||||
`pg-deploy` and `valkey-deploy` are fail-closed. `*-status` / `*-logs`
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
||||
# Custodian Brief — railiance-platform
|
||||
|
||||
**Domain:** financials
|
||||
**Last synced:** 2026-10-06 12:27 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
||||
### Coordinate audit-core temporary custody and recovery exercises
|
||||
Progress: 2/4 done | workplan_id: `f4640325-e89c-591d-b58e-ec6b087900ac`
|
||||
|
||||
**Open tasks:**
|
||||
- ! T02 — Define the runtime database lease recovery exercise `fda4262a`
|
||||
*(wait: Value-safe lease revoke/expire exercise must be coordinated with rapp-postgres and audit-core.)*
|
||||
- ! T03 — Define the coordinated railiance01 reboot exercise `ba729d18`
|
||||
*(wait: Single-node outage exercise needs operator access and a declared restart order coordinated with railiance-infra, railiance-cluster, audit-core and the OpenBao package owner.)*
|
||||
|
||||
### ArgoCD phase B: adopt the four existing Applications on railiance01
|
||||
Progress: 2/8 done | workplan_id: `98140775-3b9a-5cf9-9af6-722502d487dc`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Adopt openbao-secretstore (lowest risk) `6d5fc801`
|
||||
*(wait: Adopted 2026-09-21; the follow-on step (issue-core adoption, then automation) waits on the founder's go-ahead.)*
|
||||
- ! Adopt issue-core (from rapp-issue-core) `0e619658`
|
||||
*(wait: Founder go-ahead plus rapp-issue-core's agreement (hub message) that ArgoCD owns production and make deploy stops being a production path.)*
|
||||
- ! Adopt target-revenue `d418068a`
|
||||
*(wait: Adopted 2026-09-21; remaining preconditions and the next step wait on the founder's go-ahead.)*
|
||||
- ! Adopt external-secrets (highest risk) `4ca54868`
|
||||
*(wait: Founder go-ahead for the ESO adoption after adding CRD Prune=false,Delete=false and re-running the diff.)*
|
||||
- ! Turn the root back to the production lane `844ff115`
|
||||
*(wait: Founder go-ahead to restore automated prune/selfHeal on railiance-apps-root once T03-T06 are proven.)*
|
||||
- ! Plan phase C: retire coulombcore's ArgoCD `55d1382f`
|
||||
*(wait: Planning only; needs an operator read-only check of coulombcore's ArgoCD, outside agent session scope.)*
|
||||
|
||||
### Close Forgejo primary backup coverage on Scaleway
|
||||
Progress: 3/4 done | workplan_id: `7beec1a7-aa82-5a36-9a66-6b60008a2455`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Establish primary full-archive delivery and application recovery `a4807df0`
|
||||
*(wait: Operator chooses the independent archive prefix and confirms retention/caller contract and bounded worker credential delivery before the primary archive retrieval.)*
|
||||
|
||||
### Coordinate KeyCape live Secret exposure recovery
|
||||
Progress: 4/6 done | workplan_id: `b2c25a01-4a80-55c1-90cf-8538000f7e0e`
|
||||
|
||||
**Open tasks:**
|
||||
- ! T03 — Collect exact owner acknowledgements `714ae011`
|
||||
*(wait: Attended resolver run: postchecks outstanding; NetKingdom asked to package the complete sequence as one receipt-producing command for the next attended session.)*
|
||||
- ! T06 — Publish the Railiance/OpenBao custody handoff `3b9748c4`
|
||||
*(wait: Platform/OpenBao owner must publish the non-secret routing receipt for both lanes through the attended handoff; values are never inferred.)*
|
||||
|
||||
### Close S3 service assurance and ownership gaps
|
||||
Progress: 4/7 done | workplan_id: `ca639c3d-3a87-5fa4-ad13-6f2e014b0c84`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Make backup freshness and recurring recovery evidence checkable `d64446fb`
|
||||
*(wait: activity-core replied (not accepted yet): role acceptance is the founder decision ACTIVITY-WP-0042-T01 (D2); then ACTIVITY-WP-0042 bounded operation, S3 OpenBao credential route, repaired openbao-activity-core store and per-run approval-engine approval.)*
|
||||
- ! Produce S3 signals and prove their delivery to the evidence owner `5351e0e4`
|
||||
*(wait: Needs a concrete receiving contract from railiance-telemetry (RTEL-WP-0002) before health transport is accepted; unmonitored state retained meanwhile.)*
|
||||
- ! Obtain acceptance for compatibility assets and derived-record cleanup `3b74f79c`
|
||||
*(wait: Owner-ready handoffs to railiance-forge, rapp-openbao and the ArgoCD bootstrap owner await acceptance; S3 custody contracts stay here until then.)*
|
||||
|
||||
### Adopt activity-core application runtime into railiance01 GitOps
|
||||
Progress: 2/3 done | workplan_id: `b8cf2fc2-60c2-5d4e-a60a-55f1304d9f54`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Observe adoption and enforce bounded promotion readiness `63b44949`
|
||||
*(wait: 24h soak after adoption, then founder confirmation of the scoped identity and checks before automated sync; ACTIVITY-WP-0041 owns publication and the promotion guard.)*
|
||||
|
||||
### Prepare generated attended windows and explicit value-custody choice
|
||||
Progress: 3/4 done | workplan_id: `0c7e3b0c-1c36-568c-af51-b06f912a0f1d`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Bind a real dated window and owner receipt manifests `1ae5a6b8`
|
||||
*(wait: Platform preparation and founder design choice are complete. Independent informed-decision runtime source/publication, KeyCape custody/registration/issuance, secrets-engine consumer custody coordinates and provider setup-link handoff remain; dependency-requirements.md names the exact owner deliveries.)*
|
||||
|
||||
### Implement reviewed credential lanes with separate owner gates
|
||||
Progress: 3/9 done | workplan_id: `975db491-5412-5e27-8e34-14a2417bb039`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Accept and provision secrets-engine service JWT login `e0c82ea9`
|
||||
*(wait: KEY-WP-0037 now owns protected custody, real registration and native issuance. Platform issuer-reader source CCR-2026-0033 is prepared. secrets-engine must accept exact consumer host/file/delivery/rotation coordinates before the separate custody child is finalized; KeyCape exact-primary identity repair and separate live go remain.)*
|
||||
- ! Implement the platform operator-write CCR contract and Fluid lane `f47e5bc5`
|
||||
*(wait: MASON-WP-0005-T02 source contract is done (ops-mason message 79570eb8). The attended policy/schema apply remains with the open provisioning decision.)*
|
||||
- ! Admit the separate approval client-side readers `68bff751`
|
||||
*(wait: INFD-WP-0001-T07/T08 terminal; remaining gate is human approval of the scoped delivery proof; front door stays non-resolvable.)*
|
||||
- ! Dispose of the ungoverned whynot-design npm duplicate `0dd7c9a5`
|
||||
*(wait: Founder HOLD (2026-09-21) on the legacy whynot-design npm pointer; custody disposition is an attended decision.)*
|
||||
- ! Admit the two factory audit-sender custody lanes `0ef52c26`
|
||||
*(wait: Provisioned and delivered 2026-09-11 (CCR-2026-0021/0022 applied); do not repeat. Receiver rollout with evidence_kind and native producer acceptance passed 2026-09-11. Remaining human acceptance: independent archive readback and revocation of the interim audit bearer tokens.)*
|
||||
- ! Founder approval of the CCR-2026-0033 custody child `7c753be6`
|
||||
|
||||
### Onboard rapp-policy-nexus to the ArgoCD production lane
|
||||
Progress: 3/5 done | workplan_id: `ec41a4bd-df18-5b07-9b63-ccb80d9f001c`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Adopt policy-nexus (live, founder go-ahead) `8638fa73`
|
||||
*(wait: Founder go-ahead: the merge is a live production change (CONSTRUCT @ railiance-platform, activation=APPROVED); also needs the root automation restored in RPF-WP-0044-T07.)*
|
||||
- ! Prepare the Application and the dry-run evidence `2afb1aac`
|
||||
*(wait: Draft is prepared. The attended read-only helm history / kubectl diff remains. This session did not touch the cluster.)*
|
||||
|
||||
### Coordinate KeyCape live Secret exposure recovery
|
||||
Progress: 4/6 done | workplan_id: `038bc3c0-4492-5b91-95eb-ae515ca205df`
|
||||
|
||||
**Open tasks:**
|
||||
- ! T03 — Collect exact owner acknowledgements `e019c166`
|
||||
*(wait: Canonical RPF-WP-0027-T03: complete platform custody acknowledgements remain missing.)*
|
||||
- ! T06 — Publish the Railiance/OpenBao custody handoff `69326850`
|
||||
*(wait: Canonical RPF-WP-0027-T06: complete field/auth/expiry/handoff contract absent; September 15 operator block remains in force.)*
|
||||
|
||||
### Coordinate audit-core temporary custody and recovery exercises
|
||||
Progress: 2/4 done | workplan_id: `88c4ef7f-0af8-580e-90dc-a2bae2675a4d`
|
||||
|
||||
**Open tasks:**
|
||||
- ! T02 — Define the runtime database lease recovery exercise `caba7fcd`
|
||||
*(wait: Canonical RPF-WP-0015-T02: approved sender identity, fresh bounded attended window, named abort operator and live recovery receipt required.)*
|
||||
- ! T03 — Define the coordinated railiance01 reboot exercise `09cf4065`
|
||||
*(wait: Canonical RPF-WP-0015-T03: fresh outage approval, off-host snapshot, quorum/access evidence and owner execution required.)*
|
||||
|
||||
### Retract public OpenBao listener behind operator-only access
|
||||
Progress: 2/3 done | workplan_id: `6f8a6cbc-c076-5f0a-ade2-281a7ec71360`
|
||||
|
||||
**Open tasks:**
|
||||
- ! T03 — Complete the attended operator cutover `99f8b41f`
|
||||
|
||||
## Inbox Hygiene
|
||||
|
||||
**Missing thread_id:** 1 unread message(s) lack supersession chains.
|
||||
|
||||
---
|
||||
## MCP Orientation (when available)
|
||||
|
||||
If the state-hub MCP server is reachable, call:
|
||||
`get_domain_summary("financials")`
|
||||
This provides richer cross-domain context.
|
||||
If the MCP call fails, use this file as your orientation source.
|
||||
43
.forgejo/workflows/ccr-intake.yaml
Normal file
43
.forgejo/workflows/ccr-intake.yaml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# RPF-WP-0061-T03: self-service CCR intake gate.
|
||||
# ccr/* branches: exactly one new in-profile CCR (+ its new policy file) from a
|
||||
# listed submitter, nothing else changed. main: post-merge id uniqueness.
|
||||
# Source comes from the exact-revision archive, as in keycape-factor-acceptance.
|
||||
name: CCR intake gate
|
||||
on:
|
||||
push:
|
||||
branches: ["ccr/**", main]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
gate:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: python:3.12-slim@sha256:d764629ce0ddd8c71fd371e9901efb324a95789d2315a47db7e4d27e78f1b0e9
|
||||
steps:
|
||||
- name: Fetch exact revision and main
|
||||
run: |
|
||||
set -eu
|
||||
python3 - <<'PYCODE'
|
||||
import os,tarfile,tempfile,urllib.request
|
||||
from pathlib import Path
|
||||
base='https://forgejo.coulomb.social/'+os.environ['GITHUB_REPOSITORY']+'/archive/'
|
||||
for name,ref in (('branch',os.environ['GITHUB_SHA']),('main','main')):
|
||||
root=Path('/tmp/ccr-intake')/name;root.mkdir(parents=True)
|
||||
with urllib.request.urlopen(base+ref+'.tar.gz',timeout=30) as response,tempfile.TemporaryFile() as archive:
|
||||
archive.write(response.read());archive.seek(0)
|
||||
with tarfile.open(fileobj=archive,mode='r:gz') as tar:tar.extractall(root,filter='data')
|
||||
PYCODE
|
||||
pip install --quiet --disable-pip-version-check --root-user-action=ignore --timeout 60 pyyaml==6.0.2 jsonschema==4.23.0
|
||||
- name: Gate ccr branch
|
||||
if: startsWith(github.ref, 'refs/heads/ccr/')
|
||||
run: |
|
||||
set -eu
|
||||
cd /tmp/ccr-intake/branch/*
|
||||
main_root=$(echo /tmp/ccr-intake/main/*)
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_ccr_intake.py'
|
||||
python3 scripts/credential-change.py check-branch --branch "$GITHUB_REF" --main-root "$main_root"
|
||||
- name: Check CCR ids on main
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
set -eu
|
||||
cd /tmp/ccr-intake/branch/*
|
||||
python3 scripts/credential-change.py check-ids
|
||||
1
.generated-work-records.json
Normal file
1
.generated-work-records.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":1,"repo_slug":"railiance-platform"}
|
||||
8
.generated-work-records.mk
Normal file
8
.generated-work-records.mk
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Explicit opt-in recipe; no environment files or external make includes.
|
||||
CUSTODIAN_SOURCE ?= /home/worsch/the-custodian
|
||||
GENERATED_REPO_SLUG ?= railiance-platform
|
||||
.PHONY: generated-work-records generated-work-records-check
|
||||
generated-work-records:
|
||||
python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_SLUG)"
|
||||
generated-work-records-check:
|
||||
python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_SLUG)" --check
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -13,6 +13,7 @@ helm/*.yaml
|
|||
!helm/*-probe.yaml
|
||||
!helm/vergabe-demo-company-role.yaml
|
||||
!helm/vergabe-demo-company-database.yaml
|
||||
!helm/playwright-consumer-access.yaml
|
||||
|
||||
# ArgoCD repository credentials — encrypt locally, never commit
|
||||
argocd/repositories/*.repository.sops.yaml
|
||||
|
|
@ -32,3 +33,6 @@ tools/vendor/bin/kubectl
|
|||
|
||||
# Derived local Repo Manager cache
|
||||
.repo-manager/
|
||||
|
||||
# Generated workstation orientation; keep local, regenerate from State Hub.
|
||||
/.custodian-brief.md
|
||||
|
|
|
|||
158
Makefile
158
Makefile
|
|
@ -58,18 +58,10 @@ CREDENTIAL_HELPER_PURPOSE ?= flex-auth-openbao-smoke
|
|||
|
||||
##@ CloudNative PG (cnpg) — primary database operator
|
||||
|
||||
db-deploy: ## Apply Gitea cnpg Cluster (creates gitea-db in databases namespace)
|
||||
$(KUBECTL) apply -f helm/gitea-db-cluster.yaml
|
||||
|
||||
db-status: ## Show cnpg cluster health
|
||||
$(KUBECTL) cnpg status gitea-db -n databases 2>/dev/null || \
|
||||
$(KUBECTL) get cluster gitea-db -n databases -o wide
|
||||
|
||||
db-shell: ## Open psql shell on gitea-db primary
|
||||
$(KUBECTL) cnpg psql gitea-db -n databases -- -U gitea gitea
|
||||
|
||||
db-logs: ## Tail gitea-db primary logs
|
||||
$(KUBECTL) logs -n databases -l cnpg.io/cluster=gitea-db -f --tail=50
|
||||
db-deploy db-status db-shell db-logs: ## Retired Gitea-era gitea-db targets; use forgejo-db-*
|
||||
@echo "gitea-db was the CoulombCore-era Gitea database and does not exist on railiance01." >&2
|
||||
@echo "Use forgejo-db-deploy / forgejo-db-status / forgejo-db-shell (RAPPS-WP-0019-T05)." >&2
|
||||
@exit 2
|
||||
|
||||
##@ Forgejo database (railiance01 production forge)
|
||||
|
||||
|
|
@ -207,24 +199,23 @@ openbao-dry-run: openbao-repo ## Render the OpenBao Helm release without applyin
|
|||
-f $(OPENBAO_VALUES) \
|
||||
--dry-run
|
||||
|
||||
openbao-overlay-apply: ## Apply KeyCape login overlay gateway and assets
|
||||
OPENBAO_UI_OVERLAY_DIR=$(OPENBAO_UI_OVERLAY_DIR) \
|
||||
OPENBAO_UI_OVERLAY_K8S=$(OPENBAO_UI_OVERLAY_K8S) \
|
||||
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
|
||||
scripts/openbao-ui-overlay-apply.sh
|
||||
# The OpenBao package (Helm values, UI overlay, gateway) is owned by
|
||||
# rapp-openbao. The copies here are retained for reference only; two deployable
|
||||
# copies drifted apart and one deploy undid the other's changes (RPF-WP-0057).
|
||||
# These targets refuse.
|
||||
RAPP_OPENBAO_REFUSAL = echo '$@ is owned by rapp-openbao; the copy in railiance-platform is retained only and must not be applied.' >&2; \
|
||||
echo 'Run it from the canonical repo instead: cd ~/rapp-openbao && make $@' >&2; \
|
||||
echo 'See docs/rapp-openbao-compatibility-handoff.md.' >&2; exit 2
|
||||
|
||||
openbao-overlay-apply: ## Refuses: the overlay is applied from rapp-openbao
|
||||
@$(RAPP_OPENBAO_REFUSAL)
|
||||
|
||||
openbao-verify-login-overlay: ## Verify public KeyCape login overlay is active
|
||||
OPENBAO_UI_OVERLAY_DIR=$(OPENBAO_UI_OVERLAY_DIR) \
|
||||
scripts/openbao-verify-login-overlay.sh $(OPENBAO_VERIFY_LOGIN_OVERLAY_ARGS)
|
||||
|
||||
openbao-deploy: openbao-repo ## Deploy / upgrade OpenBao to the openbao namespace
|
||||
$(KUBECTL) create namespace $(OPENBAO_NAMESPACE) --dry-run=client -o yaml | $(KUBECTL) apply -f -
|
||||
$(HELM) upgrade --install $(OPENBAO_RELEASE) openbao/openbao \
|
||||
--version $(OPENBAO_CHART_VERSION) \
|
||||
--namespace $(OPENBAO_NAMESPACE) \
|
||||
-f $(OPENBAO_VALUES) \
|
||||
--wait --timeout 5m
|
||||
$(MAKE) openbao-overlay-apply
|
||||
openbao-deploy: ## Refuses: OpenBao is deployed from rapp-openbao
|
||||
@$(RAPP_OPENBAO_REFUSAL)
|
||||
|
||||
.PHONY: openbao-public-listener-dry-run openbao-public-listener-status openbao-public-listener-retract openbao-public-listener-rollback
|
||||
|
||||
|
|
@ -313,6 +304,23 @@ openbao-validate-emergency-evidence: ## Validate non-secret OpenBao emergency se
|
|||
OPENBAO_EMERGENCY_EVIDENCE='$(OPENBAO_EMERGENCY_EVIDENCE)' \
|
||||
scripts/openbao-validate-emergency-drill-evidence.sh
|
||||
|
||||
OPENBAO_REHEARSAL_IMAGE ?= quay.io/openbao/openbao:2.5.4
|
||||
|
||||
openbao-isolated-restore-test: ## RPF-WP-0064 offline tests: admission, v2 receipt verifier, launcher (no Docker)
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_openbao_isolated_restore
|
||||
|
||||
openbao-isolated-restore-image: ## Pull the public OpenBao image used by the synthetic rehearsal (no custody)
|
||||
docker pull -q $(OPENBAO_REHEARSAL_IMAGE)
|
||||
|
||||
openbao-isolated-restore-rehearsal: ## RPF-WP-0064-T04 native rehearsal: --network none containers, generated material only
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest -v tests.test_openbao_isolated_restore_native
|
||||
|
||||
openbao-verify-restore-receipt: ## Verify a v2 isolated-restore receipt (RECEIPT=/abs/path SOURCE_ROOT=clean checkout [NOT_AFTER=])
|
||||
python3 scripts/openbao-isolated-restore/verify-restore-receipt.py --receipt '$(RECEIPT)' \
|
||||
--source-root '$(or $(SOURCE_ROOT),$(CURDIR))' $(if $(NOT_AFTER),--not-after '$(NOT_AFTER)')
|
||||
|
||||
.PHONY: openbao-isolated-restore-test openbao-isolated-restore-image openbao-isolated-restore-rehearsal openbao-verify-restore-receipt
|
||||
|
||||
##@ Credential broker
|
||||
|
||||
credential-grants-validate: ## Validate non-secret credential grant catalog
|
||||
|
|
@ -451,6 +459,9 @@ argocd-repo-apply: ## Apply a SOPS-encrypted ArgoCD repository Secret (set ARGOC
|
|||
(echo "ERROR: set ARGOCD_REPOSITORY_SECRET=argocd/repositories/<repo>.repository.sops.yaml"; exit 1)
|
||||
sops -d $(ARGOCD_REPOSITORY_SECRET) | $(KUBECTL) apply -f -
|
||||
|
||||
argocd-pin-drift: ## Read-only: compare railiance01 child Application pins in Git with live (run before any root sync)
|
||||
python3 scripts/argocd_child_pin_drift.py
|
||||
|
||||
argocd-status: ## Show Railiance ArgoCD projects, root app, and registered repos
|
||||
$(KUBECTL) get appprojects.argoproj.io -n $(ARGOCD_NAMESPACE) \
|
||||
railiance-bootstrap railiance-tenants railiance-platform-addons
|
||||
|
|
@ -526,17 +537,82 @@ audit-database-lease-approval-check: ## Validate an approved T02 receipt without
|
|||
python3 scripts/audit-core-database-lease-recovery.py validate-approval \
|
||||
--approval "$(AUDIT_DATABASE_LEASE_APPROVAL)"
|
||||
|
||||
audit-database-lease-recovery-exercise: ## Run the separately approved attended T02 exercise
|
||||
@test -n "$(AUDIT_DATABASE_LEASE_APPROVAL)" || { echo 'set AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json' >&2; exit 2; }
|
||||
@test -n "$(AUDIT_DATABASE_LEASE_DRIVER)" || { echo 'set AUDIT_DATABASE_LEASE_DRIVER=/path/to/approved-load-driver' >&2; exit 2; }
|
||||
python3 scripts/audit-core-database-lease-recovery.py exercise \
|
||||
--approval "$(AUDIT_DATABASE_LEASE_APPROVAL)" \
|
||||
--load-driver "$(AUDIT_DATABASE_LEASE_DRIVER)" \
|
||||
ATTENDED_EXEC = python3 scripts/openbao-attended-exec.py --
|
||||
AUDIT_T02_NEED_APPROVAL = @test -n "$(AUDIT_DATABASE_LEASE_APPROVAL)" || { echo 'set AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json' >&2; exit 2; }
|
||||
AUDIT_T02_NEED_DRIVER = @test -n "$(AUDIT_DATABASE_LEASE_DRIVER)" || { echo 'set AUDIT_DATABASE_LEASE_DRIVER=/path/to/approved-load-driver' >&2; exit 2; }
|
||||
AUDIT_SYNTHETIC_SENDER_NEED_CONFIRM = @test -n "$(AUDIT_SYNTHETIC_SENDER_CONFIRM)" || { echo 'set AUDIT_SYNTHETIC_SENDER_CONFIRM to the confirm string printed by preflight' >&2; exit 2; }
|
||||
AUDIT_T02_NEED_BEARER = @test -n "$(AUDIT_T02_TOKEN_FILE)" || { echo 'set AUDIT_T02_TOKEN_FILE=/abs/path/outside/git (0700 dir)' >&2; exit 2; }
|
||||
|
||||
audit-t02-attended-window: ## One contained login: project, port-forward, T02 exercise, evidence, cleanup
|
||||
$(AUDIT_T02_NEED_APPROVAL)
|
||||
$(AUDIT_T02_NEED_DRIVER)
|
||||
$(AUDIT_SYNTHETIC_SENDER_NEED_CONFIRM)
|
||||
$(AUDIT_T02_NEED_BEARER)
|
||||
@test -n "$(AUDIT_T02_EVIDENCE_DIR)" || { echo 'set AUDIT_T02_EVIDENCE_DIR=/abs/dir/outside/git (0700)' >&2; exit 2; }
|
||||
$(ATTENDED_EXEC) scripts/t02-attended-window.py \
|
||||
--approval "$(abspath $(AUDIT_DATABASE_LEASE_APPROVAL))" \
|
||||
--load-driver "$(abspath $(AUDIT_DATABASE_LEASE_DRIVER))" \
|
||||
--bearer-file "$(AUDIT_T02_TOKEN_FILE)" --evidence-dir "$(AUDIT_T02_EVIDENCE_DIR)" \
|
||||
--confirm "$(AUDIT_SYNTHETIC_SENDER_CONFIRM)" $(if $(AUDIT_T02_PORT),--port $(AUDIT_T02_PORT)) \
|
||||
$(if $(AUDIT_T02_MODE),--mode "$(AUDIT_T02_MODE)")
|
||||
|
||||
audit-database-lease-recovery-exercise: ## Exercise step alone, inside its own contained login (prefer audit-t02-attended-window)
|
||||
$(AUDIT_T02_NEED_APPROVAL)
|
||||
$(AUDIT_T02_NEED_DRIVER)
|
||||
@test -n "$(AUDIT_T02_RESULT_FILE)" || { echo 'set AUDIT_T02_RESULT_FILE=/abs/new/file outside Git (the contained child must stay silent)' >&2; exit 2; }
|
||||
$(ATTENDED_EXEC) scripts/audit-core-database-lease-recovery.py exercise --contained --result-file "$(AUDIT_T02_RESULT_FILE)" \
|
||||
--approval "$(abspath $(AUDIT_DATABASE_LEASE_APPROVAL))" \
|
||||
--load-driver "$(abspath $(AUDIT_DATABASE_LEASE_DRIVER))" \
|
||||
--confirm RAILIANCE-WP-0024-T02:attended
|
||||
|
||||
audit-synthetic-sender-test: ## Unit tests for the synthetic sender lane and T02 window (no network)
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_audit_core_synthetic_sender_lane tests.test_t02_attended_window
|
||||
|
||||
audit-synthetic-sender-preflight: ## Read-only T02 sender gate; cluster checks only (registry metadata needs --contained)
|
||||
$(AUDIT_T02_NEED_APPROVAL)
|
||||
python3 scripts/audit_core_synthetic_sender_lane.py preflight --approval "$(AUDIT_DATABASE_LEASE_APPROVAL)" \
|
||||
$(if $(AUDIT_T02_TOKEN_FILE),--bearer-file "$(AUDIT_T02_TOKEN_FILE)") $(AUDIT_SYNTHETIC_SENDER_PREFLIGHT_ARGS)
|
||||
|
||||
audit-synthetic-sender-project: ## Projection step alone, inside its own contained login (prefer audit-t02-attended-window)
|
||||
$(AUDIT_T02_NEED_APPROVAL)
|
||||
$(AUDIT_SYNTHETIC_SENDER_NEED_CONFIRM)
|
||||
$(AUDIT_T02_NEED_BEARER)
|
||||
@test -n "$(AUDIT_T02_RESULT_FILE)" || { echo 'set AUDIT_T02_RESULT_FILE=/abs/new/file outside Git (the contained child must stay silent)' >&2; exit 2; }
|
||||
$(ATTENDED_EXEC) scripts/audit_core_synthetic_sender_lane.py project --contained --result-file "$(AUDIT_T02_RESULT_FILE)" \
|
||||
--approval "$(abspath $(AUDIT_DATABASE_LEASE_APPROVAL))" \
|
||||
--bearer-file "$(AUDIT_T02_TOKEN_FILE)" --confirm "$(AUDIT_SYNTHETIC_SENDER_CONFIRM)"
|
||||
|
||||
audit-synthetic-sender-cleanup: ## Idempotent cleanup inside a contained login (evidence or abort reason required)
|
||||
$(AUDIT_T02_NEED_APPROVAL)
|
||||
$(AUDIT_SYNTHETIC_SENDER_NEED_CONFIRM)
|
||||
@test -n "$(AUDIT_T02_EVIDENCE)$(AUDIT_SYNTHETIC_SENDER_ABORT_REASON)" || { echo 'set AUDIT_T02_EVIDENCE=/path/to/exercise-result.json or AUDIT_SYNTHETIC_SENDER_ABORT_REASON=...' >&2; exit 2; }
|
||||
@test -n "$(AUDIT_T02_RESULT_FILE)" || { echo 'set AUDIT_T02_RESULT_FILE=/abs/new/file outside Git (the contained child must stay silent)' >&2; exit 2; }
|
||||
$(ATTENDED_EXEC) scripts/audit_core_synthetic_sender_lane.py cleanup --contained --result-file "$(AUDIT_T02_RESULT_FILE)" \
|
||||
--approval "$(abspath $(AUDIT_DATABASE_LEASE_APPROVAL))" \
|
||||
--confirm "$(AUDIT_SYNTHETIC_SENDER_CONFIRM)" \
|
||||
$(if $(AUDIT_T02_TOKEN_FILE),--bearer-file "$(AUDIT_T02_TOKEN_FILE)") \
|
||||
$(if $(AUDIT_T02_EVIDENCE),--exercise-evidence "$(abspath $(AUDIT_T02_EVIDENCE))") \
|
||||
$(if $(AUDIT_SYNTHETIC_SENDER_ABORT_REASON),--abort-reason "$(AUDIT_SYNTHETIC_SENDER_ABORT_REASON)")
|
||||
|
||||
railiance01-reboot-preflight: ## Read-only gate for the coordinated single-node reboot
|
||||
python3 scripts/audit-core-recovery-preflight.py node-reboot $(RAILIANCE01_REBOOT_PREFLIGHT_ARGS)
|
||||
|
||||
.PHONY: openbao-reboot-snapshot openbao-reboot-snapshot-test
|
||||
openbao-reboot-snapshot: ## Attended Raft snapshot -> age -> verify -> offsite; silent child writes SNAPSHOT_RECEIPT
|
||||
@test -n "$(SNAPSHOT_SCENARIO_ID)" || { echo 'set SNAPSHOT_SCENARIO_ID=<scenario id, e.g. RPF-WP-0015-T03-...>' >&2; exit 2; }
|
||||
@test -n "$(SNAPSHOT_RECEIPT)" || { echo 'set SNAPSHOT_RECEIPT=/abs/new/file outside Git (the contained child must stay silent)' >&2; exit 2; }
|
||||
@test ! -e "$(SNAPSHOT_RECEIPT)" || { echo 'SNAPSHOT_RECEIPT already exists' >&2; exit 2; }
|
||||
@test -n "$(SNAPSHOT_CUSTODIAN_ESCROW_REF)" || { echo 'set SNAPSHOT_CUSTODIAN_ESCROW_REF=<where a decryption key is held outside OpenBao>' >&2; exit 2; }
|
||||
env -u OPENBAO_TOKEN -u VAULT_TOKEN -u BAO_TOKEN \
|
||||
$(ATTENDED_EXEC) scripts/openbao-reboot-snapshot.py \
|
||||
--scenario-id "$(SNAPSHOT_SCENARIO_ID)" --receipt-file "$(abspath $(SNAPSHOT_RECEIPT))" \
|
||||
$(if $(SNAPSHOT_PRIVATE_DIR),--private-dir "$(abspath $(SNAPSHOT_PRIVATE_DIR))") \
|
||||
--custodian-escrow-ref "$(SNAPSHOT_CUSTODIAN_ESCROW_REF)" \
|
||||
$(if $(SNAPSHOT_CUSTODIAN_RECIPIENT),--custodian-recipient "$(SNAPSHOT_CUSTODIAN_RECIPIENT)")
|
||||
|
||||
openbao-reboot-snapshot-test: ## Offline unit tests for the attended snapshot lane (no OpenBao, no network)
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_openbao_reboot_snapshot
|
||||
|
||||
wp0024-owner-show: ## Show one owner's hash-bound review contract (set OWNER)
|
||||
@test -n "$(OWNER)" || { echo 'set OWNER=audit-core|rapp-postgres|railiance-cluster|railiance-infra' >&2; exit 2; }
|
||||
@python3 scripts/wp0024-owner-review.py show --owner "$(OWNER)"
|
||||
|
|
@ -560,7 +636,7 @@ wp0024-t02-driver-register: ## Register committed audit-core driver source witho
|
|||
@python3 scripts/wp0024-t02-driver-candidate.py register \
|
||||
--repo "$(abspath ../audit-core)" --driver "$(DRIVER)" --reviewer "$(REVIEWER)"
|
||||
|
||||
.PHONY: audit-whitehat-e2-preflight audit-whitehat-e2-project audit-whitehat-e2-cleanup audit-database-lease-recovery-preflight audit-database-lease-approval-check audit-database-lease-recovery-exercise railiance01-reboot-preflight wp0024-owner-show wp0024-owner-verify wp0024-owner-status wp0024-t02-driver-show wp0024-t02-driver-status wp0024-t02-driver-register apps-pg-isolation-probe
|
||||
.PHONY: audit-t02-attended-window audit-synthetic-sender-test audit-synthetic-sender-preflight audit-synthetic-sender-project audit-synthetic-sender-cleanup audit-whitehat-e2-preflight audit-whitehat-e2-project audit-whitehat-e2-cleanup audit-database-lease-recovery-preflight audit-database-lease-approval-check audit-database-lease-recovery-exercise railiance01-reboot-preflight wp0024-owner-show wp0024-owner-verify wp0024-owner-status wp0024-t02-driver-show wp0024-t02-driver-status wp0024-t02-driver-register apps-pg-isolation-probe
|
||||
.PHONY: db-deploy db-status db-shell db-logs forgejo-db-deploy forgejo-db-status forgejo-db-shell apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-configure-external-secrets-issue-core openbao-configure-external-secrets-activity-core openbao-configure-external-secrets-forgejo openbao-validate-restore-evidence openbao-validate-emergency-evidence credential-grants-validate credential-change-applier-dry-run credential-change-applier-apply-plan credential-change-applier-apply credential-change-runbook credential-change-record-evidence credential-change-lifecycle-plan credential-change-lifecycle-event credential-change-import-inventory agent-high-risk-boundary-check openbao-credential-change-appliers-dry-run openbao-configure-credential-change-appliers openbao-token-grants-dry-run openbao-configure-token-grants openbao-verify-token-grants-dry-run openbao-verify-token-grants openbao-verify-token-grants-smoke credential-helper-dry-run credential-tests credential-exec-ops-warden-smoke argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup forgejo-backup forgejo-backup-dry-run forgejo-backup-status forgejo-package-prune forgejo-package-prune-dry-run reef-deployables apps-pg-evidence consumption-preflight help
|
||||
|
||||
##@ S3 service assurance (read-only)
|
||||
|
|
@ -582,3 +658,21 @@ keycape-factor-tests: ## Validate factor custody, scope, renewal and config pres
|
|||
.PHONY: keycape-factor-live-acceptance
|
||||
keycape-factor-live-acceptance: ## Verify native scope and rotation (creates bounded proof/renewal Jobs)
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 scripts/keycape_factor_acceptance.py
|
||||
|
||||
# CUST-WP-0079-T05: staged-source generation; hook uses the isolated recipe.
|
||||
.PHONY: generated-work-records generated-work-records-check
|
||||
generated-work-records:
|
||||
$(MAKE) --no-print-directory -f .generated-work-records.mk generated-work-records
|
||||
generated-work-records-check:
|
||||
$(MAKE) --no-print-directory -f .generated-work-records.mk generated-work-records-check
|
||||
|
||||
##@ Service-identity metadata (NK-WP-0054-T04, RPF-WP-0067)
|
||||
.PHONY: service-identity-metadata-tests service-identity-metadata-openbao service-identity-metadata-postgresql-sql
|
||||
service-identity-metadata-tests: ## Offline fixture tests for the value-safe identity metadata exporter
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest -v tests.test_service_identity_metadata_export
|
||||
|
||||
service-identity-metadata-openbao: ## Read-only OpenBao auth-role/policy export via the operator's bao login (OUT=file)
|
||||
python3 scripts/service_identity_metadata_export.py --output "$${OUT:-openbao-identity-metadata.json}" openbao
|
||||
|
||||
service-identity-metadata-postgresql-sql: ## Print the read-only pg_roles/pg_auth_members SELECT
|
||||
@python3 scripts/service_identity_metadata_export.py postgresql-sql
|
||||
|
|
|
|||
45
SCOPE.md
45
SCOPE.md
|
|
@ -106,6 +106,13 @@ description: S3 custody, policy and approved runtime credential delivery; packag
|
|||
keywords: [openbao, secrets, vault-compatible, secret-management, dynamic-credentials, audit, kubernetes-auth]
|
||||
```
|
||||
|
||||
```capability
|
||||
type: security
|
||||
title: Supervised agent observer access
|
||||
description: custodian-agents namespace and ServiceAccount with ClusterRole custodian-supervised-observer (curated read; no secrets, configmaps, logs, exec or writes) for founder-issued short-lived agent tokens, plus the OpenBao agt SSH role limited to permit-pty. Writes are routed through gated-executor approvals.
|
||||
keywords: [rbac, serviceaccount, supervised-agents, read-only, openbao, ssh-role]
|
||||
```
|
||||
|
||||
## Getting oriented
|
||||
|
||||
Read [AGENTS.md](AGENTS.md), [INTENT.md](INTENT.md), then
|
||||
|
|
@ -154,3 +161,41 @@ remain RPF-WP-0038-T04.
|
|||
The account cutover and archive-integrity fix do not establish that coverage.
|
||||
WP-0029 retains secondary credential invalidation/recovery; its completion
|
||||
must not be presented as full primary-backup assurance.
|
||||
|
||||
|
||||
## Supervised agent access — 2026-10-20
|
||||
|
||||
RPF-WP-0062 adds the `custodian-agents` namespace, the `custodian-agent`
|
||||
ServiceAccount and ClusterRole `custodian-supervised-observer`: curated read
|
||||
only, with no secrets, configmaps, logs, exec or writes. It is synced and
|
||||
pinned at `567d2c6`; the founder issues 24-hour tokens for it under
|
||||
CUST-WP-0073. The OpenBao `agt` SSH role now allows only the `permit-pty`
|
||||
extension (`e415da9`). `scripts/openbao-apply-ssh-engine.sh` previously never
|
||||
wrote roles, because it passed `bao` twice and exited 127; it is fixed at `4b2b72a`.
|
||||
Privileged actions for these agents go through gated-executor and not through
|
||||
this role.
|
||||
|
||||
|
||||
## Repository maintenance reuse
|
||||
|
||||
The tracked `.generated-work-records.json` adopts the Custodian staged-source
|
||||
adapter for WORK-RECORDS.md. `make generated-work-records` uses the existing
|
||||
State Hub renderer through `.generated-work-records.mk`; the shared pre-commit
|
||||
hook runs this isolated recipe only for opted-in repositories. The corresponding
|
||||
`make generated-work-records-check` is read-only. Canonical repository identity
|
||||
is explicit and does not depend on the checkout directory.
|
||||
|
||||
Unstaged source changes are preserved; conflicts and unstaged generated-file
|
||||
changes stop the commit. For `git commit --only`, generate first and include
|
||||
WORK-RECORDS.md with the selected source paths. Existing generated-only guards
|
||||
and exact-scope secret scans remain in place. This is repository maintenance,
|
||||
not a runtime service, authorization decision, deployment or publication.
|
||||
Other index generators and hub-derived briefs retain their owner boundaries.
|
||||
|
||||
|
||||
## Local orientation brief
|
||||
|
||||
`.custodian-brief.md` is a generated local orientation snapshot. It remains on
|
||||
disk for offline use and is root-ignored rather than versioned. Regenerate it
|
||||
with the State Hub brief renderer; workplans and other authored repository
|
||||
records remain authoritative. Brief refreshes must not create Git commits.
|
||||
|
|
|
|||
159
WORK-RECORDS.md
159
WORK-RECORDS.md
|
|
@ -8,7 +8,11 @@
|
|||
|
||||
| Kind | ID | Status | Lane | Source |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| workplan | RPF-WP-0015 | blocked | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
|
||||
| workplan | RPF-WP-ADHOC-2026-10-10 | finished | — | workplans/ADHOC-2026-10-10.md |
|
||||
| workplan | RPF-WP-ADHOC-2026-10-11 | finished | — | workplans/ADHOC-2026-10-11.md |
|
||||
| workplan | RPF-WP-ADHOC-2026-10-19 | finished | — | workplans/ADHOC-2026-10-19.md |
|
||||
| workplan | RPF-WP-ADHOC-2026-11-03 | finished | — | workplans/ADHOC-2026-11-03.md |
|
||||
| workplan | RPF-WP-0015 | finished | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
|
||||
| workplan | RPF-WP-0025 | finished | — | workplans/RPF-WP-0025-openbao-operator-only-access.md |
|
||||
| workplan | RPF-WP-0027 | blocked | — | workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md |
|
||||
| workplan | RPF-WP-0029 | finished | — | workplans/RPF-WP-0029-backup-credential-default-removal.md |
|
||||
|
|
@ -26,11 +30,44 @@
|
|||
| workplan | RPF-WP-0047 | finished | — | workplans/RPF-WP-0047-bao-coulomb-social-notice-page.md |
|
||||
| workplan | RPF-WP-0048 | blocked | — | workplans/RPF-WP-0048-activity-core-gitops-adoption.md |
|
||||
| workplan | RPF-WP-0049 | finished | — | workplans/RPF-WP-0049-platform-root-custody-record-delivery.md |
|
||||
| workplan | RPF-WP-0050 | blocked | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md |
|
||||
| workplan | RPF-WP-0050 | finished | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md |
|
||||
| workplan | RPF-WP-0051 | finished | — | workplans/RPF-WP-0051-informed-decision-requester-workload-delivery.md |
|
||||
| workplan | RPF-WP-0052 | finished | — | workplans/RPF-WP-0052-authelia-platform-smtp-admission.md |
|
||||
| workplan | RPF-WP-0053 | active | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
|
||||
| workplan | RPF-WP-0054 | finished | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md |
|
||||
| workplan | RPF-WP-0055 | backlog | — | workplans/RPF-WP-0055-keycape-client-secret-rotation-residual.md |
|
||||
| workplan | RPF-WP-0056 | blocked | — | workplans/RPF-WP-0056-playwright-consumer-admission.md |
|
||||
| workplan | RPF-WP-0057 | finished | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md |
|
||||
| workplan | RPF-WP-0058 | finished | — | workplans/RPF-WP-0058-reuse-private-source-reader.md |
|
||||
| workplan | RPF-WP-0059 | finished | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md |
|
||||
| workplan | RPF-WP-0060 | blocked | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md |
|
||||
| workplan | RPF-WP-0061 | active | — | workplans/RPF-WP-0061-self-service-ccr-intake.md |
|
||||
| workplan | RPF-WP-0062 | finished | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md |
|
||||
| workplan | RPF-WP-0063 | finished | — | workplans/RPF-WP-0063-wave2-resource-right-sizing.md |
|
||||
| workplan | RPF-WP-0064 | active | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| workplan | RPF-WP-0065 | proposed | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md |
|
||||
| workplan | RPF-WP-0066 | proposed | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md |
|
||||
| workplan | RPF-WP-0067 | finished | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md |
|
||||
| workplan | RPF-WP-0068 | finished | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
|
||||
| workplan | RPF-WP-0069 | active | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| workplan | RPF-WP-0070 | finished | — | workplans/RPF-WP-0070-mas-evidence-recovery.md |
|
||||
| workplan | RPF-WP-0071 | backlog | — | workplans/RPF-WP-0071-attended-credential-rotation-lane.md |
|
||||
| workplan | RPF-WP-0072 | backlog | — | workplans/RPF-WP-0072-audit-core-recovery-compatibility.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-10-T01 | done | — | workplans/ADHOC-2026-10-10.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-11-T01 | done | — | workplans/ADHOC-2026-10-11.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-11-T02 | done | — | workplans/ADHOC-2026-10-11.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-11-T03 | done | — | workplans/ADHOC-2026-10-11.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-19-T01 | done | — | workplans/ADHOC-2026-10-19.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-19-T02 | done | — | workplans/ADHOC-2026-10-19.md |
|
||||
| task | RPF-WP-ADHOC-2026-10-19-T03 | done | — | workplans/ADHOC-2026-10-19.md |
|
||||
| task | RPF-WP-ADHOC-2026-11-03-T01 | done | — | workplans/ADHOC-2026-11-03.md |
|
||||
| task | RPF-WP-ADHOC-2026-11-03-T02 | done | — | workplans/ADHOC-2026-11-03.md |
|
||||
| task | RPF-WP-ADHOC-2026-11-03-T03 | done | — | workplans/ADHOC-2026-11-03.md |
|
||||
| task | RPF-WP-ADHOC-2026-11-03-T04 | done | — | workplans/ADHOC-2026-11-03.md |
|
||||
| task | RPF-WP-ADHOC-2026-11-03-T05 | done | — | workplans/ADHOC-2026-11-03.md |
|
||||
| task | RPF-WP-0015-T01 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
|
||||
| task | RPF-WP-0015-T02 | wait | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
|
||||
| task | RPF-WP-0015-T03 | wait | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
|
||||
| task | RPF-WP-0015-T02 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
|
||||
| task | RPF-WP-0015-T03 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
|
||||
| task | RPF-WP-0015-T04 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md |
|
||||
| task | RPF-WP-0025-T01 | done | — | workplans/RPF-WP-0025-openbao-operator-only-access.md |
|
||||
| task | RPF-WP-0025-T02 | done | — | workplans/RPF-WP-0025-openbao-operator-only-access.md |
|
||||
|
|
@ -49,10 +86,15 @@
|
|||
| task | RPF-WP-0035-T03 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T04 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T05 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T06 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T06 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T07 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T08 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T09 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T09 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T10 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T11 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T12 | progress | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T13 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0035-T14 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
|
||||
| task | RPF-WP-0036-T01 | done | — | workplans/RPF-WP-0036-platform-service-assurance.md |
|
||||
| task | RPF-WP-0036-T02 | done | — | workplans/RPF-WP-0036-platform-service-assurance.md |
|
||||
| task | RPF-WP-0036-T03 | wait | — | workplans/RPF-WP-0036-platform-service-assurance.md |
|
||||
|
|
@ -80,12 +122,12 @@
|
|||
| task | RPF-WP-0043-T05 | done | — | workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md |
|
||||
| task | RPF-WP-0044-T01 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T02 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T03 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T04 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T05 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T03 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T04 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T05 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T06 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T07 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T08 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0044-T08 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
|
||||
| task | RPF-WP-0045-T01 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
|
||||
| task | RPF-WP-0045-T02 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
|
||||
| task | RPF-WP-0045-T03 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
|
||||
|
|
@ -111,11 +153,106 @@
|
|||
| task | RPF-WP-0049-T05 | done | — | workplans/RPF-WP-0049-platform-root-custody-record-delivery.md |
|
||||
| task | RPF-WP-0050-T01 | done | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md |
|
||||
| task | RPF-WP-0050-T02 | done | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md |
|
||||
| task | RPF-WP-0050-T03 | wait | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md |
|
||||
| task | RPF-WP-0050-T03 | cancel | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md |
|
||||
| task | RPF-WP-0050-T04 | done | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md |
|
||||
| task | RPF-WP-0051-T01 | done | — | workplans/RPF-WP-0051-informed-decision-requester-workload-delivery.md |
|
||||
| task | RPF-WP-0051-T02 | done | — | workplans/RPF-WP-0051-informed-decision-requester-workload-delivery.md |
|
||||
| task | RPF-WP-0051-T03 | done | — | workplans/RPF-WP-0051-informed-decision-requester-workload-delivery.md |
|
||||
| task | RPF-WP-0052-T01 | done | — | workplans/RPF-WP-0052-authelia-platform-smtp-admission.md |
|
||||
| task | RPF-WP-0052-T02 | done | — | workplans/RPF-WP-0052-authelia-platform-smtp-admission.md |
|
||||
| task | RPF-WP-0052-T03 | done | — | workplans/RPF-WP-0052-authelia-platform-smtp-admission.md |
|
||||
| task | RPF-WP-0053-T01 | done | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
|
||||
| task | RPF-WP-0053-T02 | progress | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
|
||||
| task | RPF-WP-0053-T03 | progress | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
|
||||
| task | RPF-WP-0053-T04 | todo | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
|
||||
| task | RPF-WP-0053-T05 | done | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
|
||||
| task | RPF-WP-0053-T06 | done | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
|
||||
| task | RPF-WP-0054-T01 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md |
|
||||
| task | RPF-WP-0054-T02 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md |
|
||||
| task | RPF-WP-0054-T03 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md |
|
||||
| task | RPF-WP-0054-T04 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md |
|
||||
| task | RPF-WP-0054-T05 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md |
|
||||
| task | RPF-WP-0054-T06 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md |
|
||||
| task | RPF-WP-0054-T07 | cancel | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md |
|
||||
| task | RPF-WP-0055-T01 | todo | — | workplans/RPF-WP-0055-keycape-client-secret-rotation-residual.md |
|
||||
| task | RPF-WP-0056-T01 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md |
|
||||
| task | RPF-WP-0056-T02 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md |
|
||||
| task | RPF-WP-0056-T03 | wait | — | workplans/RPF-WP-0056-playwright-consumer-admission.md |
|
||||
| task | RPF-WP-0056-T04 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md |
|
||||
| task | RPF-WP-0056-T05 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md |
|
||||
| task | RPF-WP-0056-T06 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md |
|
||||
| task | RPF-WP-0057-T01 | done | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md |
|
||||
| task | RPF-WP-0057-T02 | done | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md |
|
||||
| task | RPF-WP-0057-T03 | done | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md |
|
||||
| task | RPF-WP-0057-T04 | done | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md |
|
||||
| task | RPF-WP-0057-T05 | done | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md |
|
||||
| task | RPF-WP-0058-T01 | done | — | workplans/RPF-WP-0058-reuse-private-source-reader.md |
|
||||
| task | RPF-WP-0058-T02 | done | — | workplans/RPF-WP-0058-reuse-private-source-reader.md |
|
||||
| task | RPF-WP-0058-T03 | done | — | workplans/RPF-WP-0058-reuse-private-source-reader.md |
|
||||
| task | RPF-WP-0058-T04 | done | — | workplans/RPF-WP-0058-reuse-private-source-reader.md |
|
||||
| task | RPF-WP-0058-T05 | cancel | — | workplans/RPF-WP-0058-reuse-private-source-reader.md |
|
||||
| task | RPF-WP-0059-T01 | done | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md |
|
||||
| task | RPF-WP-0059-T02 | done | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md |
|
||||
| task | RPF-WP-0059-T03 | done | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md |
|
||||
| task | RPF-WP-0059-T04 | done | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md |
|
||||
| task | RPF-WP-0060-T01 | wait | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md |
|
||||
| task | RPF-WP-0060-T02 | done | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md |
|
||||
| task | RPF-WP-0060-T03 | wait | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md |
|
||||
| task | RPF-WP-0060-T04 | wait | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md |
|
||||
| task | RPF-WP-0061-T01 | done | — | workplans/RPF-WP-0061-self-service-ccr-intake.md |
|
||||
| task | RPF-WP-0061-T02 | done | — | workplans/RPF-WP-0061-self-service-ccr-intake.md |
|
||||
| task | RPF-WP-0061-T03 | done | — | workplans/RPF-WP-0061-self-service-ccr-intake.md |
|
||||
| task | RPF-WP-0061-T04 | done | — | workplans/RPF-WP-0061-self-service-ccr-intake.md |
|
||||
| task | RPF-WP-0061-T05 | wait | — | workplans/RPF-WP-0061-self-service-ccr-intake.md |
|
||||
| task | RPF-WP-0062-T01 | done | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md |
|
||||
| task | RPF-WP-0062-T02 | done | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md |
|
||||
| task | RPF-WP-0062-T03 | done | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md |
|
||||
| task | RPF-WP-0063-T01 | done | — | workplans/RPF-WP-0063-wave2-resource-right-sizing.md |
|
||||
| task | RPF-WP-0064-T01 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0064-T02 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0064-T03 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0064-T04 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0064-T05 | wait | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0064-T06 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0064-T07 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0064-T08 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0064-T09 | todo | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0064-T10 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md |
|
||||
| task | RPF-WP-0065-T01 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md |
|
||||
| task | RPF-WP-0065-T02 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md |
|
||||
| task | RPF-WP-0065-T03 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md |
|
||||
| task | RPF-WP-0065-T04 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md |
|
||||
| task | RPF-WP-0065-T05 | wait | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md |
|
||||
| task | RPF-WP-0066-T01 | todo | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md |
|
||||
| task | RPF-WP-0066-T02 | wait | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md |
|
||||
| task | RPF-WP-0066-T03 | todo | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md |
|
||||
| task | RPF-WP-0066-T04 | wait | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md |
|
||||
| task | RPF-WP-0066-T05 | wait | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md |
|
||||
| task | RPF-WP-0066-T06 | todo | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md |
|
||||
| task | RPF-WP-0067-T01 | done | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md |
|
||||
| task | RPF-WP-0067-T02 | done | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md |
|
||||
| task | RPF-WP-0067-T03 | done | — | 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 | done | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
|
||||
| task | RPF-WP-0068-T03 | done | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
|
||||
| task | RPF-WP-0068-T04 | done | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md |
|
||||
| task | RPF-WP-0069-T01 | done | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T02 | done | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T03 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T04 | done | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T05 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T06 | cancel | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T07 | done | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T08 | done | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0069-T09 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md |
|
||||
| task | RPF-WP-0070-T01 | done | — | workplans/RPF-WP-0070-mas-evidence-recovery.md |
|
||||
| task | RPF-WP-0070-T02 | done | — | workplans/RPF-WP-0070-mas-evidence-recovery.md |
|
||||
| task | RPF-WP-0070-T03 | done | — | workplans/RPF-WP-0070-mas-evidence-recovery.md |
|
||||
| task | RPF-WP-0070-T04 | done | — | workplans/RPF-WP-0070-mas-evidence-recovery.md |
|
||||
| task | RPF-WP-0070-T05 | done | — | workplans/RPF-WP-0070-mas-evidence-recovery.md |
|
||||
| task | RPF-WP-0071-T01 | todo | — | workplans/RPF-WP-0071-attended-credential-rotation-lane.md |
|
||||
| task | RPF-WP-0071-T02 | todo | — | workplans/RPF-WP-0071-attended-credential-rotation-lane.md |
|
||||
| task | RPF-WP-0072-T01 | todo | — | workplans/RPF-WP-0072-audit-core-recovery-compatibility.md |
|
||||
| intake | RPF-IN-0001 | closed | — | intakes/intakes.md |
|
||||
| intake | RPF-IN-0002 | open | — | intakes/intakes.md |
|
||||
| intake | RPF-IN-0003 | open | — | intakes/intakes.md |
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
# Static notice for bao.coulomb.social (RPF-WP-0047). Serves one page; proxies nothing.
|
||||
server {
|
||||
listen 8080;
|
||||
server_name _;
|
||||
server_tokens off;
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
add_header Content-Security-Policy "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
add_header Cache-Control "no-store" always;
|
||||
add_header X-Robots-Tag "noindex, nofollow" always;
|
||||
|
||||
location = / {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
location = /robots.txt {
|
||||
default_type text/plain;
|
||||
return 200 "User-agent: *\nDisallow: /\n";
|
||||
}
|
||||
location = /healthz {
|
||||
access_log off;
|
||||
default_type text/plain;
|
||||
return 200 "ok\n";
|
||||
}
|
||||
# Any other path (e.g. /v1/sys/health, /ui/) is 404 with the same notice, never 200.
|
||||
location / {
|
||||
error_page 404 /index.html;
|
||||
return 404;
|
||||
}
|
||||
location = /index.html {
|
||||
internal;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>Not publicly available</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; max-width: 36rem; margin: 15vh auto; padding: 0 1rem; color: #222; background: #fff; line-height: 1.5; }
|
||||
@media (prefers-color-scheme: dark) { body { color: #ddd; background: #111; } }
|
||||
h1 { font-size: 1.4rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Not publicly available</h1>
|
||||
<p>This address belongs to an internal service that is not offered publicly.</p>
|
||||
<p>There is nothing to sign in to here. If you were sent to this address, please contact the person who gave it to you.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# RPF-WP-0047. Read only by argocd/railiance01/applications/bao-notice.application.yaml.
|
||||
|
||||
resources:
|
||||
- manifests.yaml
|
||||
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: false
|
||||
|
||||
configMapGenerator:
|
||||
- name: bao-notice-conf
|
||||
namespace: bao-notice
|
||||
files: [default.conf]
|
||||
- name: bao-notice-html
|
||||
namespace: bao-notice
|
||||
files: [index.html]
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
# RPF-WP-0047: static "not publicly available" notice for bao.coulomb.social.
|
||||
# OpenBao stays non-public (RMASTER-WP-0020-T09). This namespace has no route
|
||||
# to OpenBao: ingress only from Traefik, no egress at all.
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: bao-notice
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-platform
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: bao-notice-isolation
|
||||
namespace: bao-notice
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes: [Ingress, Egress]
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
ports:
|
||||
- {protocol: TCP, port: 8080}
|
||||
egress: []
|
||||
---
|
||||
# cert-manager HTTP-01 solver pods run in this namespace on 8089 during
|
||||
# issuance and renewal; Traefik must reach them or the challenge gets 502.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: bao-notice-acme-solver
|
||||
namespace: bao-notice
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
acme.cert-manager.io/http01-solver: "true"
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
ports:
|
||||
- {protocol: TCP, port: 8089}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bao-notice
|
||||
namespace: bao-notice
|
||||
labels:
|
||||
app.kubernetes.io/name: bao-notice
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: bao-notice
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: bao-notice
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginxinc/nginx-unprivileged@sha256:65e3e85dbaed8ba248841d9d58a899b6197106c23cb0ff1a132b7bfe0547e4c0
|
||||
ports:
|
||||
- {name: http, containerPort: 8080}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
resources:
|
||||
requests: {cpu: 5m, memory: 16Mi}
|
||||
limits: {cpu: 100m, memory: 64Mi}
|
||||
readinessProbe:
|
||||
httpGet: {path: /healthz, port: http}
|
||||
periodSeconds: 20
|
||||
livenessProbe:
|
||||
httpGet: {path: /healthz, port: http}
|
||||
periodSeconds: 60
|
||||
volumeMounts:
|
||||
- {name: conf, mountPath: /etc/nginx/conf.d, readOnly: true}
|
||||
- {name: html, mountPath: /usr/share/nginx/html, readOnly: true}
|
||||
- {name: tmp, mountPath: /tmp}
|
||||
volumes:
|
||||
- name: conf
|
||||
configMap:
|
||||
name: bao-notice-conf
|
||||
items: [{key: default.conf, path: default.conf}]
|
||||
- name: html
|
||||
configMap:
|
||||
name: bao-notice-html
|
||||
items: [{key: index.html, path: index.html}]
|
||||
- name: tmp
|
||||
emptyDir: {sizeLimit: 16Mi}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: bao-notice
|
||||
namespace: bao-notice
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: bao-notice
|
||||
ports:
|
||||
- {name: http, port: 80, targetPort: http}
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: redirect-https
|
||||
namespace: bao-notice
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: bao-notice
|
||||
namespace: bao-notice
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts: [bao.coulomb.social]
|
||||
secretName: bao-notice-tls
|
||||
rules:
|
||||
- host: bao.coulomb.social
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service: {name: bao-notice, port: {number: 80}}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: bao-notice-http-redirect
|
||||
namespace: bao-notice
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
traefik.ingress.kubernetes.io/router.middlewares: bao-notice-redirect-https@kubernetescrd
|
||||
traefik.ingress.kubernetes.io/router.priority: "1"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: bao.coulomb.social
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service: {name: bao-notice, port: {number: 80}}
|
||||
53
argocd/platform-addons/custodian-agents/clusterrole.yaml
Normal file
53
argocd/platform-addons/custodian-agents/clusterrole.yaml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Curated read-only observer (founder decision 2026-10-20, CUST-WP-0073-T02).
|
||||
# Verbs: get/list/watch only. Explicitly excluded: secrets, configmaps,
|
||||
# pods/log, pods/exec, pods/attach, pods/portforward, serviceaccounts/token,
|
||||
# nodes/proxy, any write verb, any wildcard. Privileged actions go through the
|
||||
# separate gated executor; nothing here grants writes.
|
||||
# Enforced offline by tests/test_custodian_agent_rbac.py.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: custodian-supervised-observer
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: custodian-agent-identity
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- namespaces
|
||||
- nodes
|
||||
- pods
|
||||
- services
|
||||
- endpoints
|
||||
- events
|
||||
- persistentvolumeclaims
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["apps"]
|
||||
resources:
|
||||
- deployments
|
||||
- statefulsets
|
||||
- daemonsets
|
||||
- replicasets
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["discovery.k8s.io"]
|
||||
resources: ["endpointslices"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["events.k8s.io"]
|
||||
resources: ["events"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["argoproj.io"]
|
||||
resources:
|
||||
- applications
|
||||
- appprojects
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["postgresql.cnpg.io"]
|
||||
resources:
|
||||
- clusters
|
||||
- backups
|
||||
- scheduledbackups
|
||||
verbs: ["get", "list", "watch"]
|
||||
- nonResourceURLs: ["/version", "/healthz", "/livez", "/readyz"]
|
||||
verbs: ["get"]
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: custodian-agent-supervised-observer
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: custodian-agent-identity
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: custodian-supervised-observer
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: custodian-agent
|
||||
namespace: custodian-agents
|
||||
14
argocd/platform-addons/custodian-agents/kustomization.yaml
Normal file
14
argocd/platform-addons/custodian-agents/kustomization.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# RPF-WP-0062 / CUST-WP-0073-T02. Read only by
|
||||
# argocd/railiance01/applications/custodian-agents.application.yaml.
|
||||
# Read-only observer identity for supervised custodian agents. No Secret, no
|
||||
# static token, no write verb. Credentials are short-lived TokenRequest tokens
|
||||
# issued by the supervisor (see workplans/RPF-WP-0062-custodian-agent-observer-identity.md).
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- serviceaccount.yaml
|
||||
- clusterrole.yaml
|
||||
- clusterrolebinding.yaml
|
||||
7
argocd/platform-addons/custodian-agents/namespace.yaml
Normal file
7
argocd/platform-addons/custodian-agents/namespace.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: custodian-agents
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: custodian-agent-identity
|
||||
11
argocd/platform-addons/custodian-agents/serviceaccount.yaml
Normal file
11
argocd/platform-addons/custodian-agents/serviceaccount.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# No token Secret is declared for this ServiceAccount, and none may be added.
|
||||
# Tokens are issued per session by the supervisor via TokenRequest.
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: custodian-agent
|
||||
namespace: custodian-agents
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: custodian-agent-identity
|
||||
automountServiceAccountToken: false
|
||||
99
argocd/platform-addons/custodian-allocation-review/README.md
Normal file
99
argocd/platform-addons/custodian-allocation-review/README.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# custodian-allocation-review (CUST-WP-0071-T05) — prepared, not deployed
|
||||
|
||||
Weekly workload-demand and allocation assessment for railiance01, selected by
|
||||
the founder on 2026-10-21 (the-custodian
|
||||
`docs/decisions/2026-10-21-cust-wp-0071-delivery-interview.md`). Not
|
||||
referenced by any Application. The draft Application is in
|
||||
`argocd/railiance01/drafts/`. The CronJob is `suspend: true`.
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Executor owner | railiance-platform (platform-addons, ArgoCD `railiance-platform-addons` project) |
|
||||
| Assessment/report owner | the-custodian (CUST-WP-0071-T05); code in `the-custodian/scripts/weekly_allocation_incluster.py` |
|
||||
| Schedule | `0 8 * * 1`, `timeZone: Europe/Berlin` (Monday 08:00), `concurrencyPolicy: Forbid`, `startingDeadlineSeconds: 21600` |
|
||||
| Telemetry window | 7d ending Monday 06:00 UTC of the run's ISO week (DST-independent, so consecutive weeks are comparable) |
|
||||
| Image | `python:3.12-slim` pinned by digest (same as eso-token-renewer); code and data come from a ConfigMap |
|
||||
| Identity | SA `custodian-assessment/custodian-allocation-review`, ClusterRole `custodian-allocation-review-reader` |
|
||||
| RBAC | get/list on core `nodes`, `pods`, `persistentvolumeclaims`, `persistentvolumes`; `storage.k8s.io/storageclasses`; `metrics.k8s.io/nodes,pods`. No watch, no writes, no secrets, configmaps, logs, exec or proxy |
|
||||
| Prometheus | `http://telemetry-prometheus.telemetry.svc:9090`, plain HTTP, no credential |
|
||||
| Delivery | One State Hub message per newly retained week, `custodian-allocation-review` → `the-custodian`, sent to `STATE_HUB_URL=http://state-hub.state-hub.svc:8000`. The message id is recorded in `receipts.jsonl` |
|
||||
| Network | `networkpolicy.yaml`: the namespace denies all ingress and egress by default. Egress is allowed only to DNS (kube-system/kube-dns 53 UDP/TCP), kube-apiserver (10.43.0.1:443 and k3s endpoint 92.205.62.239:6443), Prometheus (telemetry, 9090) and the State Hub API (state-hub `app=state-hub`, 8000) |
|
||||
| Storage | PVC `custodian-allocation-review-data` (local-path, 1Gi, ArgoCD `Prune=false,Delete=false`): `reports/<YYYY>-W<ww>.json`, `runs/<week>/<attempt>/` inputs, append-only `receipts.jsonl` |
|
||||
| Resources | requests 20m/96Mi, limits 500m/256Mi, `activeDeadlineSeconds: 900`, `backoffLimit: 2` |
|
||||
|
||||
## Behaviour
|
||||
|
||||
- Writes only to the PVC and the one hub message per retained week. It never
|
||||
resizes anything or creates tasks. The next Custodian session reviews the
|
||||
message and records owner-repo tasks (ADR-001).
|
||||
- Idempotency: an existing report for the week means the run does nothing and
|
||||
succeeds. A failed attempt writes a `failed` receipt and no report, so the Job
|
||||
retry or a manual Job collects again.
|
||||
- Missed weeks: each retained receipt lists `missed_weeks` since the previous
|
||||
report. Comparison across a gap stays unknown.
|
||||
- Delivery is idempotent. A week that already has a message id is never
|
||||
reposted. A failed post exits 1, and the Job retry posts for the
|
||||
already-retained week.
|
||||
- Missed and failed runs are caught by the-custodian's daily security flow
|
||||
(`scripts/weekly_allocation_check.py`). It flags a failed latest receipt, or
|
||||
a missing report for the due ISO week after Monday 12:00 Europe/Berlin. It
|
||||
stays inactive until `CUSTODIAN_ALLOCATION_RECEIPTS=hub` is set.
|
||||
- Workload rows remain `investigate`. The queries are namespace-level because
|
||||
kube-state-metrics does not export `kube_pod_labels`.
|
||||
|
||||
## Vendored sources
|
||||
|
||||
`src/` is generated by `scripts/vendor_custodian_allocation_review.py` from
|
||||
clean owner commits, and `vendor.lock.json` records each commit and sha256.
|
||||
`--check` verifies them, and `tests/test_custodian_allocation_review.py`
|
||||
enforces the check offline. To change the job code, commit in the owner repo
|
||||
and re-vendor. Never edit `src/` by hand.
|
||||
|
||||
## Activation (founder go-ahead required; after CUST-WP-0071-T04 completes)
|
||||
|
||||
1. Review this directory and the vendored sources. Re-vendor if the owner
|
||||
repositories have moved on, and keep `--check` passing.
|
||||
2. rapp-telemetry:
|
||||
- Merge `helm/values-railiance01-retention.pending.yaml` (10d / 10GB) into
|
||||
`helm/values-railiance01.yaml` and run the attended helm upgrade.
|
||||
- Apply `manifests/custodian-assessment-prometheus-ingress.pending.yaml`.
|
||||
Without it, `telemetry-isolation` blocks port 9090 and every query
|
||||
records a transport error.
|
||||
3. Widen the ArgoCD project. This commit already adds `PersistentVolumeClaim`
|
||||
and `networking.k8s.io/NetworkPolicy` to the `namespaceResourceWhitelist` in
|
||||
`argocd/railiance01/bootstrap/02-railiance-platform-addons-project.yaml`.
|
||||
The bootstrap is applied by hand, so apply that AppProject during the
|
||||
change window.
|
||||
4. Set `targetRevision` in the draft Application to the reviewed commit. Move
|
||||
the draft to `argocd/railiance01/applications/`, run
|
||||
`make argocd-pin-drift`, then hand-sync `railiance-apps-root` and then
|
||||
`custodian-allocation-review`.
|
||||
5. Acceptance run, still suspended:
|
||||
`kubectl -n custodian-assessment create job --from=cronjob/custodian-allocation-review accept-1`.
|
||||
Check that `receipts.jsonl` shows `retained`, `failed_queries: []`,
|
||||
`delivery: posted` with a `message_id`, and that the hub message reached
|
||||
`the-custodian`. Run it on a Monday, because it claims that ISO week's
|
||||
report slot.
|
||||
6. Activate the daily missed/failed-run check: add
|
||||
`Environment=CUSTODIAN_ALLOCATION_RECEIPTS=hub` to the-custodian
|
||||
`infra/security-flow/custodian-security-flow.service`. Exercise it once by
|
||||
leaving a week without a report, or by pointing `--allocation-receipts` at a
|
||||
failed receipt.
|
||||
7. Unsuspend through a reviewed commit (`suspend: false`) and a new
|
||||
`targetRevision`, not a live patch.
|
||||
|
||||
## Rollback
|
||||
|
||||
- Stop: commit `suspend: true` and sync. In an emergency, run
|
||||
`kubectl -n custodian-assessment patch cronjob custodian-allocation-review -p '{"spec":{"suspend":true}}'`,
|
||||
then commit the same change.
|
||||
- Remove: delete the Application (it has no finalizer, so the resources stay),
|
||||
then delete the CronJob, RBAC, NetworkPolicies and ConfigMap. Keep the PVC unless the history
|
||||
is explicitly abandoned, because local-path deletes the data with the claim.
|
||||
|
||||
## Still open before T05 can be accepted
|
||||
|
||||
These items are not provided here: an explicit owner acknowledgment beyond
|
||||
the hub message being read, how owner reviews reach `/data/reviews.json`, and
|
||||
workload-level metrics. Reports stay at namespace level; the founder declined a
|
||||
pod-label allowlist for now.
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
# CUST-WP-0071-T05: weekly workload-demand and allocation assessment.
|
||||
# Founder decision 2026-10-21 (the-custodian
|
||||
# docs/decisions/2026-10-21-cust-wp-0071-delivery-interview.md): in-cluster
|
||||
# CronJob, Monday 08:00 Europe/Berlin, scoped read-only ServiceAccount,
|
||||
# persisted weekly aggregates. suspend: true until review and T04 completion;
|
||||
# unsuspend only by a reviewed commit (see README.md).
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: custodian-allocation-review
|
||||
namespace: custodian-assessment
|
||||
labels:
|
||||
app.kubernetes.io/name: custodian-allocation-review
|
||||
app.kubernetes.io/part-of: railiance-platform
|
||||
spec:
|
||||
schedule: '0 8 * * 1'
|
||||
timeZone: Europe/Berlin
|
||||
suspend: true
|
||||
concurrencyPolicy: Forbid
|
||||
# Late starts lose coverage at the 7-day retention edge; after 6h the week
|
||||
# is left to the next run, whose receipt lists it under missed_weeks.
|
||||
startingDeadlineSeconds: 21600
|
||||
successfulJobsHistoryLimit: 4
|
||||
failedJobsHistoryLimit: 4
|
||||
jobTemplate:
|
||||
spec:
|
||||
activeDeadlineSeconds: 900
|
||||
backoffLimit: 2
|
||||
ttlSecondsAfterFinished: 1209600
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: custodian-allocation-review
|
||||
spec:
|
||||
serviceAccountName: custodian-allocation-review
|
||||
# Token needed for the read-only API calls; the SA itself does not
|
||||
# automount, so only this pod template receives it.
|
||||
automountServiceAccountToken: true
|
||||
restartPolicy: Never
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65534
|
||||
runAsGroup: 65534
|
||||
fsGroup: 65534
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: assess
|
||||
image: python:3.12-slim@sha256:d764629ce0ddd8c71fd371e9901efb324a95789d2315a47db7e4d27e78f1b0e9
|
||||
command: [python3, /app/weekly_allocation_incluster.py]
|
||||
env:
|
||||
- {name: PYTHONDONTWRITEBYTECODE, value: '1'}
|
||||
- {name: ASSESSMENT_DATA_DIR, value: /data}
|
||||
- {name: ASSESSMENT_SOURCE_DIR, value: /app}
|
||||
- {name: ASSESSMENT_OBSERVER, value: /app/observe_cluster_resources.py}
|
||||
- {name: ASSESSMENT_QUERIES, value: /app/weekly-allocation-queries.json}
|
||||
- {name: ASSESSMENT_OWNERSHIP, value: /app/namespace-ownership.json}
|
||||
- {name: ASSESSMENT_INVENTORY, value: /app/reef-railiance-k3s.json}
|
||||
- {name: ASSESSMENT_REVIEWS, value: /data/reviews.json}
|
||||
- {name: ASSESSMENT_EXECUTOR, value: railiance-platform/custodian-assessment/cronjob/custodian-allocation-review}
|
||||
- {name: PROMETHEUS_URL, value: 'http://telemetry-prometheus.telemetry.svc:9090'}
|
||||
# One message per newly retained week (custodian-allocation-review -> the-custodian).
|
||||
- {name: STATE_HUB_URL, value: 'http://state-hub.state-hub.svc:8000'}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
resources:
|
||||
requests: {cpu: 20m, memory: 96Mi}
|
||||
limits: {cpu: 500m, memory: 256Mi}
|
||||
volumeMounts:
|
||||
- {name: source, mountPath: /app, readOnly: true}
|
||||
- {name: data, mountPath: /data}
|
||||
- {name: tmp, mountPath: /tmp}
|
||||
volumes:
|
||||
- name: source
|
||||
configMap: {name: custodian-allocation-review-source, defaultMode: 0444}
|
||||
- name: data
|
||||
persistentVolumeClaim: {claimName: custodian-allocation-review-data}
|
||||
- name: tmp
|
||||
emptyDir: {sizeLimit: 16Mi}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# CUST-WP-0071-T05. NOT referenced by any Application yet: the draft lives in
|
||||
# argocd/railiance01/drafts/custodian-allocation-review.application.yaml.
|
||||
# src/ is vendored from owner repos (the-custodian, railiance-cluster,
|
||||
# resource-control); vendor.lock.json pins the source commits and sha256.
|
||||
# Refresh with tools/vendor_custodian_allocation_review.py, never by hand.
|
||||
|
||||
namespace: custodian-assessment
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- rbac.yaml
|
||||
- pvc.yaml
|
||||
- networkpolicy.yaml
|
||||
- cronjob.yaml
|
||||
|
||||
configMapGenerator:
|
||||
- name: custodian-allocation-review-source
|
||||
files:
|
||||
- src/weekly_allocation_incluster.py
|
||||
- src/weekly_allocation_report.py
|
||||
- src/reconcile_railiance_allocation.py
|
||||
- src/observe_cluster_resources.py
|
||||
- src/weekly-allocation-queries.json
|
||||
- src/namespace-ownership.json
|
||||
- src/reef-railiance-k3s.json
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: custodian-assessment
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: custodian-allocation-review
|
||||
pod-security.kubernetes.io/enforce: restricted
|
||||
pod-security.kubernetes.io/enforce-version: latest
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# CUST-WP-0071-T05 (founder decision 2026-10-21): default-deny egress and no
|
||||
# ingress for the custodian-assessment namespace. The job may reach only:
|
||||
# DNS kube-system/kube-dns (CoreDNS) 53 UDP/TCP
|
||||
# kube-apiserver 10.43.0.1:443 (the `kubernetes` Service) and its k3s
|
||||
# endpoint 92.205.62.239:6443 (same pair as rapp-telemetry's
|
||||
# telemetry-isolation, since k3s may match either side of DNAT)
|
||||
# Prometheus telemetry/prometheus pods 9090
|
||||
# State Hub API state-hub/state-hub pods 8000 (the Service listens on 8000)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: custodian-assessment-default-deny
|
||||
namespace: custodian-assessment
|
||||
labels:
|
||||
app.kubernetes.io/name: custodian-allocation-review
|
||||
app.kubernetes.io/part-of: railiance-platform
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes: [Ingress, Egress]
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: custodian-allocation-review-egress
|
||||
namespace: custodian-assessment
|
||||
labels:
|
||||
app.kubernetes.io/name: custodian-allocation-review
|
||||
app.kubernetes.io/part-of: railiance-platform
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: custodian-allocation-review
|
||||
policyTypes: [Egress]
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- {protocol: UDP, port: 53}
|
||||
- {protocol: TCP, port: 53}
|
||||
- to:
|
||||
- ipBlock: {cidr: 10.43.0.1/32}
|
||||
ports:
|
||||
- {protocol: TCP, port: 443}
|
||||
- to:
|
||||
- ipBlock: {cidr: 92.205.62.239/32}
|
||||
ports:
|
||||
- {protocol: TCP, port: 6443}
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: telemetry
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: prometheus
|
||||
ports:
|
||||
- {protocol: TCP, port: 9090}
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: state-hub
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: state-hub
|
||||
ports:
|
||||
- {protocol: TCP, port: 8000}
|
||||
21
argocd/platform-addons/custodian-allocation-review/pvc.yaml
Normal file
21
argocd/platform-addons/custodian-allocation-review/pvc.yaml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Weekly reports, per-attempt inputs and receipts.jsonl outlive the 7-day
|
||||
# Prometheus retention here. local-path reclaimPolicy is Delete, so ArgoCD must
|
||||
# never prune or delete this claim: removing it destroys the history.
|
||||
# Requires PersistentVolumeClaim in the railiance-platform-addons AppProject
|
||||
# namespaceResourceWhitelist before the Application can sync (activation step).
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: custodian-allocation-review-data
|
||||
namespace: custodian-assessment
|
||||
labels:
|
||||
app.kubernetes.io/name: custodian-allocation-review
|
||||
app.kubernetes.io/part-of: railiance-platform
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-options: Prune=false,Delete=false
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
49
argocd/platform-addons/custodian-allocation-review/rbac.yaml
Normal file
49
argocd/platform-addons/custodian-allocation-review/rbac.yaml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# CUST-WP-0071-T05 (founder decision 2026-10-21): read-only, scoped to exactly
|
||||
# the reads made by weekly_allocation_incluster.py (see its API_PATHS and
|
||||
# METRICS_PATHS). Verbs get/list only; no watch, no write verb, no wildcard,
|
||||
# no secrets, configmaps, logs, exec, proxy or token subresources. Prometheus
|
||||
# access is network-level (rapp-telemetry NetworkPolicy), not RBAC.
|
||||
# Enforced offline by tests/test_custodian_allocation_review.py.
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: custodian-allocation-review
|
||||
namespace: custodian-assessment
|
||||
labels:
|
||||
app.kubernetes.io/name: custodian-allocation-review
|
||||
app.kubernetes.io/part-of: railiance-platform
|
||||
automountServiceAccountToken: false
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: custodian-allocation-review-reader
|
||||
labels:
|
||||
app.kubernetes.io/name: custodian-allocation-review
|
||||
app.kubernetes.io/part-of: railiance-platform
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: [nodes, pods, persistentvolumeclaims, persistentvolumes]
|
||||
verbs: [get, list]
|
||||
- apiGroups: [storage.k8s.io]
|
||||
resources: [storageclasses]
|
||||
verbs: [get, list]
|
||||
- apiGroups: [metrics.k8s.io]
|
||||
resources: [nodes, pods]
|
||||
verbs: [get, list]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: custodian-allocation-review-reader
|
||||
labels:
|
||||
app.kubernetes.io/name: custodian-allocation-review
|
||||
app.kubernetes.io/part-of: railiance-platform
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: custodian-allocation-review-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: custodian-allocation-review
|
||||
namespace: custodian-assessment
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
{
|
||||
"host": {
|
||||
"cluster_resource_id": "resource:railiance:reef-railiance:k3s",
|
||||
"owner": "railiance-cluster",
|
||||
"resource_id": "resource:hosteurope:railiance01",
|
||||
"same_cpus_as_cluster": true
|
||||
},
|
||||
"namespaces": {
|
||||
"activity-core": {
|
||||
"owner": "activity-core",
|
||||
"service": "activity-core",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"approval-engine": {
|
||||
"owner": "approval-engine",
|
||||
"service": "approval-engine",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"argocd": {
|
||||
"owner": "railiance-enablement",
|
||||
"service": "argocd",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"audit-core": {
|
||||
"owner": "audit-core",
|
||||
"service": "audit-core",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"bao-notice": {
|
||||
"owner": "railiance-platform",
|
||||
"service": "bao-notice",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"canned-prompts": {
|
||||
"owner": "canned-prompts",
|
||||
"service": "canned-prompts",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"cert-manager": {
|
||||
"owner": "railiance-cluster",
|
||||
"service": "cert-manager",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"cnpg-system": {
|
||||
"owner": "rapp-postgres",
|
||||
"service": "cloudnative-pg",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"core-hub": {
|
||||
"owner": "core-hub",
|
||||
"service": "core-hub",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"coulomb": {
|
||||
"owner": "unknown",
|
||||
"service": "ihp-railiance-probe",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"coulomb-social": {
|
||||
"owner": "coulomb-social",
|
||||
"service": "coulomb-social",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"databases": {
|
||||
"owner": "rapp-postgres",
|
||||
"service": "apps-pg",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"email-connect": {
|
||||
"owner": "email-connect",
|
||||
"service": "email-connect",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"external-secrets": {
|
||||
"owner": "railiance-platform",
|
||||
"service": "external-secrets",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"flex-auth": {
|
||||
"owner": "flex-auth",
|
||||
"service": "flex-auth",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"forgejo": {
|
||||
"owner": "railiance-forge",
|
||||
"service": "forgejo",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"informed-decision": {
|
||||
"owner": "informed-decision",
|
||||
"service": "informed-decision",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"issue-core": {
|
||||
"owner": "issue-core",
|
||||
"service": "issue-core",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"knative-serving": {
|
||||
"owner": "rail-knative",
|
||||
"service": "knative-serving",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"kourier-system": {
|
||||
"owner": "rail-knative",
|
||||
"service": "kourier",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"kube-system": {
|
||||
"owner": "railiance-cluster",
|
||||
"service": "k3s-control-plane",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"mas": {
|
||||
"owner": "mail-address-semantics",
|
||||
"service": "mas-live-test",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"mfa": {
|
||||
"owner": "net-kingdom",
|
||||
"service": "lldap-mfa",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"openbao": {
|
||||
"owner": "railiance-platform",
|
||||
"service": "openbao",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"platform-pg-drill": {
|
||||
"owner": "rapp-postgres",
|
||||
"service": "platform-pg-drill",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"playwright": {
|
||||
"owner": "railiance-platform",
|
||||
"service": "rapp-playwright",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"policy-nexus": {
|
||||
"owner": "policy-nexus",
|
||||
"service": "policy-nexus",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"rapp-qonto": {
|
||||
"owner": "rapp-qonto",
|
||||
"service": "qonto",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"rapp-qonto-egress": {
|
||||
"owner": "rapp-qonto",
|
||||
"service": "qonto-egress",
|
||||
"tenant": "tenant:friendly:binky"
|
||||
},
|
||||
"rein-aharness": {
|
||||
"owner": "rein-aharness",
|
||||
"service": "rein-aharness",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"reuse": {
|
||||
"owner": "reuse-surface",
|
||||
"service": "reuse-surface",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"sbom-nexus": {
|
||||
"owner": "sbom-nexus",
|
||||
"service": "sbom-nexus",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"sso": {
|
||||
"owner": "net-kingdom",
|
||||
"service": "keycape-authelia",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"state-hub": {
|
||||
"owner": "state-hub",
|
||||
"service": "state-hub",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"target-revenue": {
|
||||
"owner": "target-revenue",
|
||||
"service": "target-revenue",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"telemetry": {
|
||||
"owner": "rapp-telemetry",
|
||||
"service": "prometheus-grafana",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"tenant-engine": {
|
||||
"owner": "tenant-engine",
|
||||
"service": "tenant-engine",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"user-engine": {
|
||||
"owner": "user-engine",
|
||||
"service": "user-engine",
|
||||
"tenant": "unknown"
|
||||
},
|
||||
"vergabe-demo-company": {
|
||||
"owner": "railiance-apps",
|
||||
"service": "vergabe-teilnahme",
|
||||
"tenant": "unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Capture non-secret cluster capacity, utilization, and allocation evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
CPU_FACTORS = {"n": 0.000001, "u": 0.001, "m": 1.0, "": 1000.0}
|
||||
BYTE_FACTORS = {
|
||||
"": 1,
|
||||
"Ki": 1024,
|
||||
"Mi": 1024**2,
|
||||
"Gi": 1024**3,
|
||||
"Ti": 1024**4,
|
||||
"K": 1000,
|
||||
"M": 1000**2,
|
||||
"G": 1000**3,
|
||||
"T": 1000**4,
|
||||
}
|
||||
|
||||
|
||||
def cpu_millicores(value: str | None) -> int:
|
||||
match = re.fullmatch(r"([0-9]+(?:\.[0-9]+)?)(n|u|m)?", str(value or "0"))
|
||||
if not match:
|
||||
raise ValueError(f"unsupported CPU quantity: {value}")
|
||||
return round(float(match.group(1)) * CPU_FACTORS[match.group(2) or ""])
|
||||
|
||||
|
||||
def bytes_value(value: str | None) -> int:
|
||||
match = re.fullmatch(r"([0-9]+(?:\.[0-9]+)?)(Ki|Mi|Gi|Ti|K|M|G|T)?", str(value or "0"))
|
||||
if not match:
|
||||
raise ValueError(f"unsupported byte quantity: {value}")
|
||||
return round(float(match.group(1)) * BYTE_FACTORS[match.group(2) or ""])
|
||||
|
||||
|
||||
def workload_name(pod: dict[str, Any]) -> str:
|
||||
metadata = pod.get("metadata") or {}
|
||||
labels = metadata.get("labels") or {}
|
||||
for key in ("app.kubernetes.io/part-of", "app.kubernetes.io/name", "app"):
|
||||
if labels.get(key):
|
||||
return str(labels[key])
|
||||
owners = metadata.get("ownerReferences") or []
|
||||
if owners and owners[0].get("name"):
|
||||
return str(owners[0]["name"])
|
||||
return re.sub(r"-[a-f0-9]{6,}(?:-[a-z0-9]{5})?$", "", str(metadata.get("name", "unknown")))
|
||||
|
||||
|
||||
def pod_effective_requests(pod: dict[str, Any]) -> tuple[int, int]:
|
||||
"""Pod request is max(sum(containers), max(init containers))."""
|
||||
spec = pod.get("spec") or {}
|
||||
containers = spec.get("containers") or []
|
||||
inits = spec.get("initContainers") or []
|
||||
|
||||
def req(container: dict[str, Any], key: str) -> str | None:
|
||||
return ((container.get("resources") or {}).get("requests") or {}).get(key)
|
||||
|
||||
cpu = sum(cpu_millicores(req(c, "cpu")) for c in containers)
|
||||
mem = sum(bytes_value(req(c, "memory")) for c in containers)
|
||||
init_cpu = max((cpu_millicores(req(c, "cpu")) for c in inits), default=0)
|
||||
init_mem = max((bytes_value(req(c, "memory")) for c in inits), default=0)
|
||||
return max(cpu, init_cpu), max(mem, init_mem)
|
||||
|
||||
|
||||
def parse_top(lines: str | None, *, pods: bool) -> dict[tuple[str, str] | str, dict[str, int]]:
|
||||
result: dict[tuple[str, str] | str, dict[str, int]] = {}
|
||||
for line in (lines or "").splitlines():
|
||||
fields = line.split()
|
||||
if pods and len(fields) >= 4:
|
||||
result[(fields[0], fields[1])] = {
|
||||
"cpu_millicores": cpu_millicores(fields[2]),
|
||||
"memory_bytes": bytes_value(fields[3]),
|
||||
}
|
||||
elif not pods and len(fields) >= 4:
|
||||
result[fields[0]] = {
|
||||
"cpu_millicores": cpu_millicores(fields[1]),
|
||||
# kubectl top nodes: NAME CPU CPU% MEMORY MEMORY%
|
||||
"memory_bytes": bytes_value(fields[3]),
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def build_observation(snapshots: dict[str, Any], captured_at: str) -> dict[str, Any]:
|
||||
gaps: list[str] = []
|
||||
node_top = parse_top(snapshots.get("node_top", ""), pods=False)
|
||||
pod_top = parse_top(snapshots.get("pod_top", ""), pods=True)
|
||||
if snapshots.get("node_top") is None:
|
||||
gaps.append("node metrics unavailable")
|
||||
if snapshots.get("pod_top") is None:
|
||||
gaps.append("pod metrics unavailable")
|
||||
|
||||
nodes = []
|
||||
alloc_cpu = alloc_memory = alloc_pods = 0
|
||||
for node in snapshots["nodes"].get("items") or []:
|
||||
meta = node.get("metadata") or {}
|
||||
status = node.get("status") or {}
|
||||
alloc = status.get("allocatable") or {}
|
||||
labels = meta.get("labels") or {}
|
||||
name = str(meta.get("name"))
|
||||
topology = {
|
||||
"region": labels.get("topology.kubernetes.io/region"),
|
||||
"zone": labels.get("topology.kubernetes.io/zone"),
|
||||
"hostname": labels.get("kubernetes.io/hostname"),
|
||||
"provider_id": (node.get("spec") or {}).get("providerID"),
|
||||
}
|
||||
if not topology["region"] or not topology["zone"]:
|
||||
gaps.append(f"node {name} lacks independent region/zone labels")
|
||||
row = {
|
||||
"name": name,
|
||||
"ready": any(c.get("type") == "Ready" and c.get("status") == "True" for c in status.get("conditions") or []),
|
||||
"allocatable": {
|
||||
"cpu_millicores": cpu_millicores(alloc.get("cpu")),
|
||||
"memory_bytes": bytes_value(alloc.get("memory")),
|
||||
"ephemeral_storage_bytes": bytes_value(alloc.get("ephemeral-storage")),
|
||||
"pods": int(alloc.get("pods") or 0),
|
||||
},
|
||||
"topology": topology,
|
||||
"observed": node_top.get(name),
|
||||
}
|
||||
nodes.append(row)
|
||||
alloc_cpu += row["allocatable"]["cpu_millicores"]
|
||||
alloc_memory += row["allocatable"]["memory_bytes"]
|
||||
alloc_pods += row["allocatable"]["pods"]
|
||||
|
||||
workloads: dict[tuple[str, str], dict[str, Any]] = {}
|
||||
pending_unscheduled: list[dict[str, Any]] = []
|
||||
node_allocated: dict[str, dict[str, int]] = {
|
||||
node["name"]: {"cpu_millicores": 0, "memory_bytes": 0} for node in nodes
|
||||
}
|
||||
scheduled_cpu = scheduled_memory = 0
|
||||
for pod in snapshots["pods"].get("items") or []:
|
||||
meta = pod.get("metadata") or {}
|
||||
spec = pod.get("spec") or {}
|
||||
status = pod.get("status") or {}
|
||||
phase = str(status.get("phase") or "")
|
||||
if phase in {"Succeeded", "Failed"}:
|
||||
continue
|
||||
namespace = str(meta.get("namespace", "default"))
|
||||
pod_name = str(meta.get("name"))
|
||||
cpu, mem = pod_effective_requests(pod)
|
||||
node_name = spec.get("nodeName")
|
||||
if phase == "Pending" and not node_name:
|
||||
pending_unscheduled.append({
|
||||
"namespace": namespace,
|
||||
"name": pod_name,
|
||||
"cpu_millicores": cpu,
|
||||
"memory_bytes": mem,
|
||||
})
|
||||
continue
|
||||
if node_name in node_allocated:
|
||||
node_allocated[node_name]["cpu_millicores"] += cpu
|
||||
node_allocated[node_name]["memory_bytes"] += mem
|
||||
scheduled_cpu += cpu
|
||||
scheduled_memory += mem
|
||||
name = workload_name(pod)
|
||||
key = (namespace, name)
|
||||
row = workloads.setdefault(key, {
|
||||
"namespace": namespace,
|
||||
"workload": name,
|
||||
"pods": 0,
|
||||
"requests": {"cpu_millicores": 0, "memory_bytes": 0},
|
||||
"limits": {"cpu_millicores": 0, "memory_bytes": 0},
|
||||
"observed": {"cpu_millicores": 0, "memory_bytes": 0} if snapshots.get("pod_top") is not None else None,
|
||||
})
|
||||
row["pods"] += 1
|
||||
row["requests"]["cpu_millicores"] += cpu
|
||||
row["requests"]["memory_bytes"] += mem
|
||||
for container in spec.get("containers") or []:
|
||||
limits = (container.get("resources") or {}).get("limits") or {}
|
||||
row["limits"]["cpu_millicores"] += cpu_millicores(limits.get("cpu"))
|
||||
row["limits"]["memory_bytes"] += bytes_value(limits.get("memory"))
|
||||
metric = pod_top.get((namespace, pod_name))
|
||||
if metric and row["observed"] is not None:
|
||||
row["observed"]["cpu_millicores"] += metric["cpu_millicores"]
|
||||
row["observed"]["memory_bytes"] += metric["memory_bytes"]
|
||||
|
||||
storage_classes = []
|
||||
for item in snapshots["storageclasses"].get("items") or []:
|
||||
storage_classes.append({
|
||||
"name": (item.get("metadata") or {}).get("name"),
|
||||
"provisioner": item.get("provisioner"),
|
||||
"reclaim_policy": item.get("reclaimPolicy"),
|
||||
"volume_binding_mode": item.get("volumeBindingMode"),
|
||||
"default": (item.get("metadata") or {}).get("annotations", {}).get("storageclass.kubernetes.io/is-default-class") == "true",
|
||||
})
|
||||
pv_capacity = sum(bytes_value(((item.get("spec") or {}).get("capacity") or {}).get("storage")) for item in snapshots["pvs"].get("items") or [])
|
||||
pvc_rows = []
|
||||
pvc_requested = 0
|
||||
for item in snapshots["pvcs"].get("items") or []:
|
||||
meta = item.get("metadata") or {}
|
||||
spec = item.get("spec") or {}
|
||||
requested = bytes_value(((spec.get("resources") or {}).get("requests") or {}).get("storage"))
|
||||
pvc_requested += requested
|
||||
pvc_rows.append({
|
||||
"namespace": meta.get("namespace"),
|
||||
"name": meta.get("name"),
|
||||
"storage_class": spec.get("storageClassName"),
|
||||
"requested_bytes": requested,
|
||||
"phase": (item.get("status") or {}).get("phase"),
|
||||
})
|
||||
|
||||
workload_rows = sorted(workloads.values(), key=lambda row: (row["namespace"], row["workload"]))
|
||||
requested_cpu = scheduled_cpu
|
||||
requested_memory = scheduled_memory
|
||||
pending_cpu = sum(item["cpu_millicores"] for item in pending_unscheduled)
|
||||
pending_memory = sum(item["memory_bytes"] for item in pending_unscheduled)
|
||||
preflight_nodes = []
|
||||
for node in nodes:
|
||||
allocated = node_allocated.get(node["name"], {"cpu_millicores": 0, "memory_bytes": 0})
|
||||
preflight_nodes.append({
|
||||
"name": node["name"],
|
||||
"ready": node["ready"],
|
||||
"unschedulable": False,
|
||||
"allocatable_cpu_m": node["allocatable"]["cpu_millicores"],
|
||||
"allocatable_memory_bytes": node["allocatable"]["memory_bytes"],
|
||||
"allocated_cpu_m": allocated["cpu_millicores"],
|
||||
"allocated_memory_bytes": allocated["memory_bytes"],
|
||||
})
|
||||
return {
|
||||
"schema_version": "railiance.cluster-resource-observation.v1",
|
||||
"record_type": "usage_observation",
|
||||
"resource_id": "resource:railiance:reef-railiance:k3s",
|
||||
"source": "railiance-cluster",
|
||||
"workplan_id": "RCLUSTER-WP-0014",
|
||||
"reef": "reef-railiance",
|
||||
"captured_at": captured_at,
|
||||
"capacity": {
|
||||
"nodes": nodes,
|
||||
"totals": {"cpu_millicores": alloc_cpu, "memory_bytes": alloc_memory, "pods": alloc_pods, "pv_bytes": pv_capacity},
|
||||
},
|
||||
"utilization": {"workloads": workload_rows},
|
||||
"storage": {"classes": storage_classes, "claims": pvc_rows},
|
||||
"failure_domain": {
|
||||
"current_contract": "single Ready node on railiance01; local storage is correlated with node loss",
|
||||
"independent_domains_claimed": 1 if nodes else 0,
|
||||
"threephoenix_target_is_live": False,
|
||||
},
|
||||
"allocation": {
|
||||
"method_version": "cluster-raw-drivers-v1",
|
||||
"drivers": ["cpu_requests", "memory_requests", "pvc_requested_bytes", "observed_usage"],
|
||||
"denominators": {"cpu_requested_millicores": requested_cpu, "memory_requested_bytes": requested_memory, "pvc_requested_bytes": pvc_requested},
|
||||
"pending_unscheduled": {
|
||||
"cpu_millicores": pending_cpu,
|
||||
"memory_bytes": pending_memory,
|
||||
"pods": pending_unscheduled,
|
||||
},
|
||||
"residual_capacity": {"cpu_millicores": max(alloc_cpu - requested_cpu, 0), "memory_bytes": max(alloc_memory - requested_memory, 0), "pv_bytes": max(pv_capacity - pvc_requested, 0)},
|
||||
"not_a_scheduling_guarantee": True,
|
||||
"selection_owner": "resource-control",
|
||||
},
|
||||
"state_hub_preflight_observation": {
|
||||
"schema": "state-hub.release-headroom-preflight.v1-input",
|
||||
"observed_at": captured_at,
|
||||
"freshness_seconds": 0,
|
||||
"nodes": preflight_nodes,
|
||||
"pending_unrelated": [
|
||||
{
|
||||
"name": f"{item['namespace']}/{item['name']}",
|
||||
"cpu_m": item["cpu_millicores"],
|
||||
"memory_bytes": item["memory_bytes"],
|
||||
}
|
||||
for item in pending_unscheduled
|
||||
],
|
||||
},
|
||||
"measurement_gaps": sorted(set(gaps)),
|
||||
"provenance": {
|
||||
"commands": [
|
||||
"kubectl get nodes -o json", "kubectl get pods -A -o json",
|
||||
"kubectl get pvc -A -o json", "kubectl get pv -o json",
|
||||
"kubectl get storageclass -o json", "kubectl top nodes --no-headers",
|
||||
"kubectl top pods -A --no-headers",
|
||||
],
|
||||
"secret_surfaces_read": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def run_json(command: list[str]) -> dict[str, Any]:
|
||||
return json.loads(subprocess.check_output(command, text=True))
|
||||
|
||||
|
||||
def run_optional(command: list[str]) -> str | None:
|
||||
completed = subprocess.run(command, text=True, capture_output=True, check=False)
|
||||
return completed.stdout if completed.returncode == 0 else None
|
||||
|
||||
|
||||
def collect(kubectl: str, remote: str | None = None) -> dict[str, Any]:
|
||||
base = ["ssh", "-o", "BatchMode=yes", remote, kubectl] if remote else [kubectl]
|
||||
snapshots = {
|
||||
"nodes": run_json(base + ["get", "nodes", "-o", "json"]),
|
||||
"pods": run_json(base + ["get", "pods", "-A", "-o", "json"]),
|
||||
"pvcs": run_json(base + ["get", "pvc", "-A", "-o", "json"]),
|
||||
"pvs": run_json(base + ["get", "pv", "-o", "json"]),
|
||||
"storageclasses": run_json(base + ["get", "storageclass", "-o", "json"]),
|
||||
"node_top": run_optional(base + ["top", "nodes", "--no-headers"]),
|
||||
"pod_top": run_optional(base + ["top", "pods", "-A", "--no-headers"]),
|
||||
}
|
||||
captured_at = datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||
return build_observation(snapshots, captured_at)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--kubectl", default="kubectl")
|
||||
parser.add_argument("--remote", help="Optional SSH host on which kubectl runs")
|
||||
parser.add_argument("--output-dir", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
observation = collect(args.kubectl, args.remote)
|
||||
except (OSError, subprocess.CalledProcessError, json.JSONDecodeError, ValueError) as exc:
|
||||
print(f"cluster observation failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
rendered = json.dumps(observation, indent=2) + "\n"
|
||||
if args.output_dir:
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
stamp = observation["captured_at"].replace(":", "").replace("-", "")
|
||||
destination = args.output_dir / f"{stamp}.json"
|
||||
destination.write_text(rendered, encoding="utf-8")
|
||||
(args.output_dir / "latest.json").write_text(rendered, encoding="utf-8")
|
||||
print(destination)
|
||||
else:
|
||||
sys.stdout.write(rendered)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
#!/usr/bin/env python3
|
||||
"""CUST-WP-0071-T01: join live cluster allocation with owner/service/tenant."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CLUSTER_OBS = Path.home() / "railiance-cluster" / "tools" / "observe_cluster_resources.py"
|
||||
HEADROOM = Path.home() / "state-hub" / "scripts" / "release_headroom_preflight.py"
|
||||
OWNERS = ROOT / "docs" / "evidence" / "namespace-ownership.yaml"
|
||||
|
||||
|
||||
def load_module(path: Path, name: str):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"cannot load {path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def join(observation: dict[str, Any], owners: dict[str, Any]) -> dict[str, Any]:
|
||||
mapping = owners.get("namespaces") or {}
|
||||
rows = []
|
||||
unknown = []
|
||||
zero_request = []
|
||||
for workload in observation.get("utilization", {}).get("workloads") or []:
|
||||
ns = workload["namespace"]
|
||||
meta = mapping.get(ns, {"owner": "unknown", "service": "unknown", "tenant": "unknown"})
|
||||
row = {
|
||||
"namespace": ns,
|
||||
"workload": workload["workload"],
|
||||
"pods": workload["pods"],
|
||||
"cpu_request_m": workload["requests"]["cpu_millicores"],
|
||||
"memory_request_bytes": workload["requests"]["memory_bytes"],
|
||||
"owner": meta.get("owner", "unknown"),
|
||||
"service": meta.get("service", "unknown"),
|
||||
"tenant": meta.get("tenant", "unknown"),
|
||||
}
|
||||
rows.append(row)
|
||||
if ns not in mapping:
|
||||
unknown.append(ns)
|
||||
if workload["requests"]["cpu_millicores"] == 0:
|
||||
zero_request.append(f"{ns}/{workload['workload']}")
|
||||
pending = observation.get("allocation", {}).get("pending_unscheduled") or {}
|
||||
alloc = observation.get("allocation") or {}
|
||||
capacity = observation.get("capacity", {}).get("totals") or {}
|
||||
residual = (alloc.get("residual_capacity") or {}).get("cpu_millicores", 0)
|
||||
requested = (alloc.get("denominators") or {}).get("cpu_requested_millicores", 0)
|
||||
return {
|
||||
"schema": "custodian.allocation-reconcile.v1",
|
||||
"workplan_id": "CUST-WP-0071-T01",
|
||||
"captured_at": observation.get("captured_at"),
|
||||
"cluster_observation_schema": observation.get("schema_version"),
|
||||
"count_host_and_cluster_cpus_once": True,
|
||||
"host": owners.get("host"),
|
||||
"capacity_cpu_m": capacity.get("cpu_millicores"),
|
||||
"scheduled_request_cpu_m": requested,
|
||||
"pending_unscheduled_cpu_m": pending.get("cpu_millicores", 0),
|
||||
"residual_cpu_m": residual,
|
||||
"not_a_scheduling_guarantee": True,
|
||||
"workloads": sorted(rows, key=lambda r: (-r["cpu_request_m"], r["namespace"], r["workload"])),
|
||||
"unknown_namespaces": sorted(set(unknown)),
|
||||
"zero_request_workloads": sorted(zero_request),
|
||||
"pending_unscheduled": pending.get("pods") or [],
|
||||
"measurement_gaps": observation.get("measurement_gaps") or [],
|
||||
"state_hub_preflight_observation": observation.get("state_hub_preflight_observation"),
|
||||
"signal_notes": [
|
||||
"Host resource:hosteurope:railiance01 and cluster resource:railiance:reef-railiance:k3s are the same 4 vCPU; do not add them.",
|
||||
"Prometheus namespace_cpu:kube_pod_container_resource_requests:sum omitted Forgejo 100m on 2026-09-11; Kubernetes API is the scheduler-effective source.",
|
||||
"node-exporter was disabled; host CPU usage is not a reservation and is not treated as spare capacity.",
|
||||
"Zero-request pods are demand, not zero. Missing metrics are listed as gaps, not zeros.",
|
||||
"STATE-WP-0091 preflight consumes state_hub_preflight_observation; residual millicores are not admission.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def render_markdown(report: dict[str, Any], preflight: dict[str, Any] | None) -> str:
|
||||
lines = [
|
||||
f"# Railiance allocation reconcile — {report['captured_at']}",
|
||||
"",
|
||||
"CUST-WP-0071-T01. Scheduler-effective requests from the Kubernetes API",
|
||||
"via `railiance-cluster` observe (RCLUSTER-WP-0014 / RAIL-BS-WP-0014).",
|
||||
"Host and cluster are the same 4 vCPU and are counted once.",
|
||||
"",
|
||||
f"- Capacity: {report['capacity_cpu_m']}m",
|
||||
f"- Scheduled requests: {report['scheduled_request_cpu_m']}m",
|
||||
f"- Pending unscheduled: {report['pending_unscheduled_cpu_m']}m",
|
||||
f"- Residual (not a guarantee): {report['residual_cpu_m']}m",
|
||||
f"- Unknown namespaces: {', '.join(report['unknown_namespaces']) or 'none'}",
|
||||
f"- Zero-request workloads: {len(report['zero_request_workloads'])}",
|
||||
"",
|
||||
"| Namespace | Workload | Owner | Tenant | CPU request (m) | Pods |",
|
||||
"|---|---|---|---|---:|---:|",
|
||||
]
|
||||
for row in report["workloads"]:
|
||||
lines.append(
|
||||
f"| {row['namespace']} | {row['workload']} | {row['owner']} | {row['tenant']} | {row['cpu_request_m']} | {row['pods']} |"
|
||||
)
|
||||
lines += ["", "## STATE-WP-0091 preflight", ""]
|
||||
if preflight:
|
||||
lines.append(f"- ok: `{preflight.get('ok')}`")
|
||||
lines.append(f"- remaining_cpu_m: {preflight.get('remaining_cpu_m')}")
|
||||
for reason in preflight.get("reasons") or []:
|
||||
lines.append(f"- refuse: {reason}")
|
||||
for note in preflight.get("notes") or []:
|
||||
lines.append(f"- note: {note}")
|
||||
lines += ["", "## Signal notes", ""]
|
||||
for note in report["signal_notes"]:
|
||||
lines.append(f"- {note}")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--observation", type=Path, help="Existing cluster observation JSON")
|
||||
parser.add_argument("--kubectl", default="kubectl")
|
||||
parser.add_argument("--output-dir", type=Path, default=ROOT / "docs" / "evidence")
|
||||
args = parser.parse_args()
|
||||
cluster = load_module(CLUSTER_OBS, "cluster_resources")
|
||||
if args.observation:
|
||||
observation = json.loads(args.observation.read_text())
|
||||
else:
|
||||
observation = cluster.collect(args.kubectl)
|
||||
import yaml # main only: join() stays stdlib-only for the in-cluster runner
|
||||
|
||||
owners = yaml.safe_load(OWNERS.read_text())
|
||||
report = join(observation, owners)
|
||||
preflight = None
|
||||
if observation.get("state_hub_preflight_observation"):
|
||||
headroom = load_module(HEADROOM, "release_headroom")
|
||||
preflight = headroom.evaluate(observation["state_hub_preflight_observation"])
|
||||
report["state_hub_preflight"] = preflight
|
||||
stamp = datetime.now(UTC).strftime("%Y-%m-%d")
|
||||
out_json = args.output_dir / f"{stamp}-allocation-reconcile.json"
|
||||
out_md = args.output_dir / f"{stamp}-allocation-reconcile.md"
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
out_json.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
|
||||
out_md.write_text(render_markdown(report, preflight), encoding="utf-8")
|
||||
print(out_json)
|
||||
print(out_md)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"schema_version": "0.3", "record_scope": "inventory",
|
||||
"id": "resource:railiance:reef-railiance:k3s",
|
||||
"financial_entity_id": null,
|
||||
"procuring_entity_id": "entity:railiance",
|
||||
"entity_gap": "no allocation driver; cost remains a Railiance residual",
|
||||
"description": "Single-node k3s runtime on railiance01. Cost and consumption view of reef-railiance; not a copy of IPs or kubeconfig.",
|
||||
"decision": {
|
||||
"status": "gap",
|
||||
"chosen": "Live discovered cluster; no formal procurement decision in this repo",
|
||||
"rejected": [],
|
||||
"approved_by": null,
|
||||
"approved_on": null,
|
||||
"ref": "/home/worsch/railiance-cluster/docs/rail-kubernetes-substrate-profile.md"
|
||||
},
|
||||
"operational_refs": [
|
||||
"reef:railiance/declarations/reef.yaml"
|
||||
],
|
||||
"credential_handles": [],
|
||||
"consumers": {
|
||||
"potential": [
|
||||
"rapp-postgres",
|
||||
"rapp-qonto",
|
||||
"rapp-openbao",
|
||||
"coulomb-social",
|
||||
"helix-forge",
|
||||
"railiance-platform"
|
||||
],
|
||||
"actual": [
|
||||
"rapp-postgres",
|
||||
"rapp-qonto",
|
||||
"coulomb-social",
|
||||
"helix-forge",
|
||||
"railiance-platform",
|
||||
"state-hub"
|
||||
]
|
||||
},
|
||||
"resource_class": "kubernetes_capacity",
|
||||
"status": "active", "management_model": "self_managed",
|
||||
"provider": {"name": "Railiance", "account_ref": null, "product_ref": "k3s", "provider_resource_id": "k3s://239.62.205.92.host.secureserver.net"},
|
||||
"service": {"name": "reef-railiance k3s", "service_id": "reef-railiance", "class": "single-node Kubernetes runtime", "capacity_model": "shared"},
|
||||
"location": {"region": null, "country": null, "failure_domains": ["provider:host-europe", "host:railiance01", "cluster:reef-railiance"], "residency": null},
|
||||
"capacity": [
|
||||
{"metric": "cpu", "value": 4, "unit": "vCPU", "kind": "usable", "observed_at": "2026-08-11"},
|
||||
{"metric": "memory", "value": 15.62, "unit": "GiB", "kind": "usable", "observed_at": "2026-08-11"},
|
||||
{"metric": "ephemeral_storage", "value": 183.06, "unit": "GiB", "kind": "usable", "observed_at": "2026-08-11"},
|
||||
{"metric": "pods", "value": 110, "unit": "count", "kind": "limit", "observed_at": "2026-08-11"},
|
||||
{"metric": "nodes", "value": 1, "unit": "count", "kind": "observed", "observed_at": "2026-08-11"},
|
||||
{"metric": "cpu_usage", "value": 0.564, "unit": "vCPU", "kind": "observed", "observed_at": "2026-08-11"},
|
||||
{"metric": "memory_usage", "value": 5.83, "unit": "GiB", "kind": "observed", "observed_at": "2026-08-11"}
|
||||
],
|
||||
"ownership": {"owner": "railiance-cluster", "environment": "production", "workload_ids": ["helix-forge", "coulomb-social", "rapp-qonto", "railiance-platform", "state-hub"], "tenant_id": null, "allocation": {"mode": "unattributed", "cost_attribution_key": null, "driver": null, "method_version": null}},
|
||||
"cost": {"currency": "EUR", "tax_status": "unknown", "billing_model": "allocated share of railiance01 plus cluster operations labor; method not established", "commitment_ref": null, "price_evidence": null},
|
||||
"lifecycle": {"proposed_on": null, "ordered_on": null, "commissioned_on": "2026-03-10", "renews_on": null, "cancel_by": null, "retired_on": null, "exit_path": "Deploy a replacement cluster, restore stateful services, promote workloads and ingress, verify health, then remove the old node and credentials."},
|
||||
"relationships": [{"type": "hosted_on", "resource_id": "resource:hosteurope:railiance01"}],
|
||||
"requirements": [{"kind": "service_objective", "ref": "/home/worsch/railiance-cluster/INTENT.md"}, {"kind": "recovery", "ref": "/home/worsch/railiance-cluster/workplans/RAIL-BS-WP-0007-threephoenix-ha-cluster.md"}],
|
||||
"evidence": [
|
||||
{"kind": "telemetry", "ref": "kubernetes:reef-railiance/node/239.62.205.92.host.secureserver.net@2026-08-11T09:05:25Z", "observed_at": "2026-08-11", "authority": "reef-railiance Kubernetes API"},
|
||||
{"kind": "workload", "ref": "/home/worsch/railiance-cluster/docs/rail-kubernetes-substrate-profile.md", "observed_at": "2026-08-11", "authority": "railiance-cluster"},
|
||||
{"kind": "workload", "ref": "STATE-WP-0091: state-hub MCP CPU request 50m→10m after partial telemetry (p99 2.35m, peak 30.45m, 1078 samples). 105m node remaining is not reef admission and does not authorize procurement or scaling.", "observed_at": "2026-09-14", "authority": "state-hub"},
|
||||
{"kind": "workload", "ref": "docs/evidence/2026-10-19-custodian-allocation-review.md", "observed_at": "2026-10-19", "authority": "resource-control review of Custodian8e67cfb and Vergabe11f7c8a; reservation snapshot and provisional synthetic evidence, not admission"}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"schema": "custodian.weekly-allocation-queries.v1",
|
||||
"note": "CUST-WP-0071-T05. Namespace-level aggregates proven on 2026-09-28 against telemetry/telemetry-prometheus. They populate namespace_context only; workload rows stay 'investigate' until workload-labelled queries exist (kube_pod_labels is not exported by the current kube-state-metrics). Changing any query text changes the report query_signature and breaks week-on-week comparability for one week.",
|
||||
"window": "7d",
|
||||
"step": "5m",
|
||||
"queries": {
|
||||
"cpu_p95_m": "quantile_over_time(0.95, (sum by(namespace) (rate(container_cpu_usage_seconds_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[5m])) * 1000)[7d:5m])",
|
||||
"cpu_peak_m": "max_over_time((sum by(namespace) (rate(container_cpu_usage_seconds_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[5m])) * 1000)[7d:5m])",
|
||||
"cpu_sample_count": "count_over_time((sum by(namespace) (rate(container_cpu_usage_seconds_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[5m])) * 1000)[7d:5m])",
|
||||
"memory_p95_bytes": "quantile_over_time(0.95, (sum by(namespace) (container_memory_working_set_bytes{container!=\"\",container!=\"POD\",namespace!=\"\"}))[7d:5m])",
|
||||
"memory_peak_bytes": "max_over_time((sum by(namespace) (container_memory_working_set_bytes{container!=\"\",container!=\"POD\",namespace!=\"\"}))[7d:5m])",
|
||||
"memory_sample_count": "count_over_time((sum by(namespace) (container_memory_working_set_bytes{container!=\"\",container!=\"POD\",namespace!=\"\"}))[7d:5m])",
|
||||
"cpu_throttle_ratio": "sum by(namespace) (increase(container_cpu_cfs_throttled_periods_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[7d])) / sum by(namespace) (increase(container_cpu_cfs_periods_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[7d]))",
|
||||
"restarts": "sum by(namespace) (increase(kube_pod_container_status_restarts_total[7d]))"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
#!/usr/bin/env python3
|
||||
"""CUST-WP-0071-T05 in-cluster weekly runner: read-only collection, join, retain.
|
||||
|
||||
Standard library only, so it runs on a pinned python:3.12-slim image with the
|
||||
scripts mounted from a ConfigMap. It reads the Kubernetes API with the pod's
|
||||
ServiceAccount (get/list only), queries Prometheus over plain HTTP, reuses the
|
||||
existing owner code unchanged (railiance-cluster build_observation, custodian
|
||||
reconcile join, weekly_allocation_report build_report/retain) and writes only
|
||||
under the data directory. It never resizes, creates tasks or delivers messages.
|
||||
|
||||
Week identity is the ISO week of (run time - 6h); the telemetry window always
|
||||
ends at Monday 06:00 UTC of that week, so consecutive reports are exactly
|
||||
604800 s apart regardless of DST or scheduler jitter. 06:00 UTC is at or before
|
||||
the Monday 08:00 Europe/Berlin schedule in both CET and CEST and keeps the
|
||||
window start as close as possible to the seven-day Prometheus retention edge. A week whose report is already
|
||||
retained is a no-op success (idempotent retry); a failed attempt leaves no
|
||||
report, so the Job's retry or the next manual run collects again.
|
||||
|
||||
Delivery (founder decision 2026-10-21, "Hub message + repo tasks"): after a
|
||||
week is retained, one State Hub message goes from custodian-allocation-review
|
||||
to the-custodian with a short summary, report path and sha256. The message id
|
||||
is recorded in receipts.jsonl; a week that already has a message id is never
|
||||
posted again, and a retained-but-undelivered week is posted by the next run.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import ssl
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
SA_DIR = Path("/var/run/secrets/kubernetes.io/serviceaccount")
|
||||
WEEK = re.compile(r"^(\d{4})-W(\d{2})\.json$")
|
||||
# Exactly the reads the collector performs; mirrored by the ClusterRole.
|
||||
API_PATHS = {
|
||||
"nodes": "/api/v1/nodes",
|
||||
"pods": "/api/v1/pods",
|
||||
"pvcs": "/api/v1/persistentvolumeclaims",
|
||||
"pvs": "/api/v1/persistentvolumes",
|
||||
"storageclasses": "/apis/storage.k8s.io/v1/storageclasses",
|
||||
}
|
||||
METRICS_PATHS = {
|
||||
"node_top": "/apis/metrics.k8s.io/v1beta1/nodes",
|
||||
"pod_top": "/apis/metrics.k8s.io/v1beta1/pods",
|
||||
}
|
||||
|
||||
|
||||
def load(path: Path, name: str):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"cannot load {path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def iso(value: datetime) -> str:
|
||||
return value.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||
|
||||
|
||||
WINDOW_END_HOUR = 6
|
||||
|
||||
|
||||
def week_of(now: datetime) -> tuple[str, datetime]:
|
||||
"""ISO week label and its Monday 06:00 UTC (the telemetry window end)."""
|
||||
shifted = now.astimezone(timezone.utc) - timedelta(hours=WINDOW_END_HOUR)
|
||||
year, week, weekday = shifted.isocalendar()
|
||||
monday = (shifted - timedelta(days=weekday - 1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
return f"{year}-W{week:02d}", monday + timedelta(hours=WINDOW_END_HOUR)
|
||||
|
||||
|
||||
def missed_weeks(previous: str | None, current: str) -> list[str]:
|
||||
if previous is None:
|
||||
return []
|
||||
def monday(label):
|
||||
year, week = int(label[:4]), int(label[6:8])
|
||||
return datetime.fromisocalendar(year, week, 1).replace(tzinfo=timezone.utc, hour=WINDOW_END_HOUR)
|
||||
out, cursor = [], monday(previous) + timedelta(days=7)
|
||||
while cursor < monday(current):
|
||||
out.append(week_of(cursor)[0])
|
||||
cursor += timedelta(days=7)
|
||||
return out
|
||||
|
||||
|
||||
def previous_report(reports: Path, label: str) -> Path | None:
|
||||
earlier = sorted(p for p in reports.glob("*.json") if WEEK.match(p.name) and p.stem < label)
|
||||
return earlier[-1] if earlier else None
|
||||
|
||||
|
||||
class Kube:
|
||||
"""Minimal in-cluster API reader; the token is never logged or persisted."""
|
||||
|
||||
def __init__(self, base: str, token_path: Path, ca_path: Path):
|
||||
self.base = base.rstrip("/")
|
||||
self.token_path = token_path
|
||||
self.context = ssl.create_default_context(cafile=str(ca_path))
|
||||
|
||||
def get(self, path: str):
|
||||
request = urllib.request.Request(self.base + path, headers={
|
||||
"Authorization": "Bearer " + self.token_path.read_text().strip(),
|
||||
"Accept": "application/json"})
|
||||
with urllib.request.urlopen(request, context=self.context, timeout=60) as response:
|
||||
return json.load(response)
|
||||
|
||||
|
||||
def top_lines(metrics: dict, *, pods: bool, observer) -> str:
|
||||
"""Render metrics.k8s.io lists in the `kubectl top` shape parse_top reads."""
|
||||
lines = []
|
||||
for item in metrics.get("items") or []:
|
||||
meta = item.get("metadata") or {}
|
||||
if pods:
|
||||
cpu = sum(observer.cpu_millicores((c.get("usage") or {}).get("cpu")) for c in item.get("containers") or [])
|
||||
mem = sum(observer.bytes_value((c.get("usage") or {}).get("memory")) for c in item.get("containers") or [])
|
||||
lines.append(f"{meta.get('namespace')} {meta.get('name')} {cpu}m {mem}")
|
||||
else:
|
||||
usage = item.get("usage") or {}
|
||||
cpu = observer.cpu_millicores(usage.get("cpu"))
|
||||
mem = observer.bytes_value(usage.get("memory"))
|
||||
lines.append(f"{meta.get('name')} {cpu}m 0% {mem} 0%")
|
||||
return "\n".join(lines) + ("\n" if lines else "")
|
||||
|
||||
|
||||
def collect_snapshots(get, observer) -> dict:
|
||||
snapshots = {name: get(path) for name, path in API_PATHS.items()}
|
||||
for name, path in METRICS_PATHS.items():
|
||||
try:
|
||||
snapshots[name] = top_lines(get(path), pods=name == "pod_top", observer=observer)
|
||||
except (OSError, ValueError, urllib.error.URLError):
|
||||
snapshots[name] = None # recorded by build_observation as a measurement gap
|
||||
return snapshots
|
||||
|
||||
|
||||
def prometheus_query(base: str, promql: str, at: datetime, opener=urllib.request.urlopen) -> dict:
|
||||
"""Return Prometheus' JSON response, including error bodies, never raise on 4xx/5xx."""
|
||||
url = base.rstrip("/") + "/api/v1/query?" + urllib.parse.urlencode(
|
||||
{"query": promql, "time": f"{at.timestamp():.3f}"})
|
||||
try:
|
||||
with opener(url, timeout=120) as response:
|
||||
return json.load(response)
|
||||
except urllib.error.HTTPError as exc:
|
||||
try:
|
||||
return json.loads(exc.read())
|
||||
except ValueError:
|
||||
return {"status": "error", "errorType": "http", "error": f"HTTP {exc.code}"}
|
||||
except (OSError, ValueError) as exc:
|
||||
return {"status": "error", "errorType": "transport", "error": type(exc).__name__}
|
||||
|
||||
|
||||
def write_json(path: Path, value) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_name(path.name + ".tmp")
|
||||
temporary.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n")
|
||||
os.replace(temporary, path)
|
||||
|
||||
|
||||
def append_receipt(data: Path, receipt: dict) -> None:
|
||||
data.mkdir(parents=True, exist_ok=True)
|
||||
with (data / "receipts.jsonl").open("a") as stream:
|
||||
stream.write(json.dumps(receipt, sort_keys=True) + "\n")
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
print(json.dumps(receipt, sort_keys=True))
|
||||
|
||||
|
||||
def delivered_message(data: Path, week: str) -> str | None:
|
||||
path = data / "receipts.jsonl"
|
||||
if not path.exists():
|
||||
return None
|
||||
for line in reversed(path.read_text().splitlines()):
|
||||
try:
|
||||
entry = json.loads(line)
|
||||
except ValueError:
|
||||
continue
|
||||
if entry.get("week") == week and entry.get("message_id"):
|
||||
return entry["message_id"]
|
||||
return None
|
||||
|
||||
|
||||
def summary(receipt: dict) -> dict:
|
||||
report = json.loads(Path(receipt["report"]).read_text())
|
||||
decisions: dict[str, int] = {}
|
||||
for item in report["recommendations"]:
|
||||
decisions[item["decision"]] = decisions.get(item["decision"], 0) + 1
|
||||
return {"week": receipt["week"], "report": receipt["report"], "report_sha256": receipt["report_sha256"],
|
||||
"capture_end": report["period"]["capture_end"], "workloads": len(report["workloads"]),
|
||||
"namespaces_with_metrics": len(report["namespace_context"]),
|
||||
"recommendations": dict(sorted(decisions.items())),
|
||||
"comparison_compatible": report["comparison"]["compatible"],
|
||||
"measurement_gaps": len(report["measurement_gaps"]),
|
||||
"failed_queries": sorted(n for n, q in report["queries"].items() if q.get("response_status") != "success"),
|
||||
"next_step": "Custodian session reviews and records owner-repo tasks (ADR-001); no automatic resizing."}
|
||||
|
||||
|
||||
def hub_post(hub: str, body: dict) -> str:
|
||||
request = urllib.request.Request(hub.rstrip("/") + "/messages/", data=json.dumps(body).encode(),
|
||||
headers={"Content-Type": "application/json"}, method="POST")
|
||||
with urllib.request.urlopen(request, timeout=30) as response:
|
||||
return str(json.load(response)["id"])
|
||||
|
||||
|
||||
def deliver(data: Path, receipt: dict, hub: str | None, post=hub_post) -> tuple[dict, bool]:
|
||||
"""Post one hub message per retained week; returns (receipt, ok)."""
|
||||
if receipt.get("status") not in {"retained", "already-retained"}:
|
||||
return receipt, True
|
||||
existing = delivered_message(data, receipt["week"])
|
||||
if existing:
|
||||
return {**receipt, "delivery": "already-posted", "message_id": existing}, True
|
||||
if not hub:
|
||||
return {**receipt, "delivery": "not configured"}, True
|
||||
content = summary(receipt)
|
||||
body = {"from_agent": "custodian-allocation-review", "to_agent": "the-custodian",
|
||||
"subject": f"Weekly allocation assessment {receipt['week']}",
|
||||
"body": json.dumps(content, sort_keys=True)}
|
||||
try:
|
||||
return {**receipt, "delivery": "posted", "message_id": post(hub, body)}, True
|
||||
except (OSError, ValueError, KeyError) as exc:
|
||||
return {**receipt, "delivery": f"failed: {type(exc).__name__}"}, False
|
||||
|
||||
|
||||
def run(args, *, now: datetime, get, query) -> dict:
|
||||
sources = {
|
||||
"weekly_allocation_report": args.source_dir / "weekly_allocation_report.py",
|
||||
"reconcile_railiance_allocation": args.source_dir / "reconcile_railiance_allocation.py",
|
||||
"observe_cluster_resources": args.observer,
|
||||
"weekly_allocation_incluster": Path(__file__).resolve(),
|
||||
"queries": args.queries, "ownership": args.ownership, "inventory": args.inventory,
|
||||
}
|
||||
weekly = load(sources["weekly_allocation_report"], "weekly_allocation_report")
|
||||
reconcile = load(sources["reconcile_railiance_allocation"], "reconcile_railiance_allocation")
|
||||
observer = load(sources["observe_cluster_resources"], "observe_cluster_resources")
|
||||
label, window_end = week_of(now)
|
||||
reports = args.data_dir / "reports"
|
||||
target = reports / f"{label}.json"
|
||||
receipt = {"schema": "custodian.weekly-allocation-receipt.v1", "week": label,
|
||||
"run_at": iso(now), "report": str(target), "delivery": "not emitted",
|
||||
"acknowledgment": None}
|
||||
if target.exists():
|
||||
return {**receipt, "status": "already-retained", "report_sha256": sha256(target)}
|
||||
previous_path = previous_report(reports, label)
|
||||
previous_label = previous_path.stem if previous_path else None
|
||||
|
||||
observation = observer.build_observation(collect_snapshots(get, observer), iso(now))
|
||||
owners = json.loads(args.ownership.read_text())
|
||||
allocation = reconcile.join(observation, owners)
|
||||
spec = json.loads(args.queries.read_text())
|
||||
if spec.get("schema") != "custodian.weekly-allocation-queries.v1":
|
||||
raise ValueError("unsupported query specification")
|
||||
telemetry = {"captured_at": window_end.isoformat(), "window": spec["window"], "step": spec["step"],
|
||||
"source": args.prometheus_url,
|
||||
"queries": {name: {"promql": promql, "response": query(promql, window_end)}
|
||||
for name, promql in sorted(spec["queries"].items())}}
|
||||
provenance = {
|
||||
"schema": "custodian.allocation-capture-provenance.v1", "workplan": "CUST-WP-0071-T05",
|
||||
"source_observation": observation["captured_at"], "read_only": True,
|
||||
"secret_surfaces_read": False, "executor": args.executor,
|
||||
"kubernetes_reads": sorted(API_PATHS.values()) + sorted(METRICS_PATHS.values()),
|
||||
"prometheus": args.prometheus_url, "telemetry_window_end": window_end.isoformat(),
|
||||
"inputs": [{"name": n, "path": str(p), "sha256": sha256(p)} for n, p in sorted(sources.items())],
|
||||
}
|
||||
inventory = json.loads(args.inventory.read_text())
|
||||
inputs = {"cluster": observation, "allocation": allocation, "telemetry": telemetry,
|
||||
"inventory": inventory, "provenance": provenance}
|
||||
attempt = args.data_dir / "runs" / label / iso(now).replace(":", "")
|
||||
for name, value in inputs.items():
|
||||
write_json(attempt / f"{name}.json", value)
|
||||
report = weekly.build_report(
|
||||
**inputs, as_of=iso(now), max_age_hours=args.max_age_hours,
|
||||
previous=json.loads(previous_path.read_text()) if previous_path else None,
|
||||
reviews=json.loads(args.reviews.read_text()) if args.reviews and args.reviews.exists() else None)
|
||||
weekly.retain(target, report)
|
||||
failed = sorted(n for n, q in telemetry["queries"].items() if q["response"].get("status") != "success")
|
||||
return {**receipt, "status": "retained", "report_sha256": sha256(target),
|
||||
"inputs_dir": str(attempt), "previous_week": previous_label,
|
||||
"missed_weeks": missed_weeks(previous_label, label),
|
||||
"comparison_compatible": report["comparison"]["compatible"],
|
||||
"workloads": len(report["workloads"]), "failed_queries": failed,
|
||||
"measurement_gaps": len(report["measurement_gaps"])}
|
||||
|
||||
|
||||
def parser() -> argparse.ArgumentParser:
|
||||
env = os.environ.get
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument("--data-dir", type=Path, default=Path(env("ASSESSMENT_DATA_DIR", "/data")))
|
||||
p.add_argument("--source-dir", type=Path, default=Path(env("ASSESSMENT_SOURCE_DIR", str(HERE))))
|
||||
p.add_argument("--observer", type=Path, default=Path(env("ASSESSMENT_OBSERVER",
|
||||
str(Path(env("ASSESSMENT_SOURCE_DIR", str(HERE))) / "observe_cluster_resources.py"))))
|
||||
p.add_argument("--queries", type=Path, default=Path(env("ASSESSMENT_QUERIES", str(HERE.parent / "config/weekly-allocation-queries.json"))))
|
||||
p.add_argument("--ownership", type=Path, default=env("ASSESSMENT_OWNERSHIP") and Path(env("ASSESSMENT_OWNERSHIP")))
|
||||
p.add_argument("--inventory", type=Path, default=env("ASSESSMENT_INVENTORY") and Path(env("ASSESSMENT_INVENTORY")))
|
||||
p.add_argument("--reviews", type=Path, default=env("ASSESSMENT_REVIEWS") and Path(env("ASSESSMENT_REVIEWS")))
|
||||
p.add_argument("--prometheus-url", default=env("PROMETHEUS_URL", "http://telemetry-prometheus.telemetry.svc:9090"))
|
||||
p.add_argument("--hub-url", default=env("STATE_HUB_URL"), help="State Hub API; unset disables delivery")
|
||||
p.add_argument("--executor", default=env("ASSESSMENT_EXECUTOR", "unspecified"))
|
||||
p.add_argument("--max-age-hours", type=float, default=float(env("ASSESSMENT_MAX_AGE_HOURS", "48")))
|
||||
return p
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parser().parse_args()
|
||||
now = datetime.now(timezone.utc)
|
||||
label = week_of(now)[0]
|
||||
try:
|
||||
if not args.ownership or not args.inventory:
|
||||
raise ValueError("ownership and inventory inputs must be bound")
|
||||
host = os.environ["KUBERNETES_SERVICE_HOST"]
|
||||
port = os.environ.get("KUBERNETES_SERVICE_PORT", "443")
|
||||
kube = Kube(f"https://{host}:{port}", SA_DIR / "token", SA_DIR / "ca.crt")
|
||||
receipt = run(args, now=now, get=kube.get,
|
||||
query=lambda promql, at: prometheus_query(args.prometheus_url, promql, at))
|
||||
except Exception as exc: # noqa: BLE001 - every failure must leave a receipt
|
||||
append_receipt(args.data_dir, {"schema": "custodian.weekly-allocation-receipt.v1",
|
||||
"week": label, "run_at": iso(now), "status": "failed",
|
||||
"error": f"{type(exc).__name__}: {exc}"[:500]})
|
||||
return 1
|
||||
receipt, delivered = deliver(args.data_dir, receipt, args.hub_url)
|
||||
append_receipt(args.data_dir, receipt)
|
||||
return 0 if delivered else 1 # undelivered: Job retry posts it (report already retained)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Retain CUST-WP-0071 sizing evidence; no network, resizing or task emission."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
SCHEMA = "custodian.weekly-allocation.v1"
|
||||
METRICS = (
|
||||
"cpu_p95_m", "cpu_peak_m", "memory_p95_bytes", "memory_peak_bytes",
|
||||
"cpu_throttle_ratio", "latency_p95_seconds", "errors", "request_count",
|
||||
"restarts", "oom_events", "cpu_sample_count", "memory_sample_count",
|
||||
)
|
||||
REQUIRED = tuple(m for m in METRICS if not m.endswith("sample_count"))
|
||||
|
||||
|
||||
def timestamp(value):
|
||||
result = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
if result.tzinfo is None:
|
||||
raise ValueError("timestamps must include timezone")
|
||||
return result.astimezone(timezone.utc)
|
||||
|
||||
|
||||
def duration(value):
|
||||
match = re.fullmatch(r"([1-9][0-9]*)([smhdw])", value)
|
||||
if not match:
|
||||
raise ValueError(f"unsupported duration: {value}")
|
||||
return int(match[1]) * {"s": 1, "m": 60, "h": 3600, "d": 86400, "w": 604800}[match[2]]
|
||||
|
||||
|
||||
def digest(value):
|
||||
return hashlib.sha256(json.dumps(value, sort_keys=True, allow_nan=False).encode()).hexdigest()
|
||||
|
||||
|
||||
def finite(value):
|
||||
number = float(value)
|
||||
if not math.isfinite(number) or number < 0:
|
||||
raise ValueError("metric must be finite and nonnegative")
|
||||
return number
|
||||
|
||||
|
||||
def metric_series(telemetry):
|
||||
"""Do not distribute namespace aggregates over workloads, or sum percentiles."""
|
||||
series, queries = {}, {}
|
||||
seconds = duration(telemetry["window"])
|
||||
for name, query in sorted(telemetry.get("queries", {}).items()):
|
||||
response = query.get("response", {})
|
||||
data = response.get("data", {})
|
||||
queries[name] = {
|
||||
"promql": query.get("promql"), "response_status": response.get("status"),
|
||||
"result_type": data.get("resultType"), "warnings": response.get("warnings", []),
|
||||
"error": response.get("error"), "response_sha256": digest(response),
|
||||
"evaluation_times": [],
|
||||
}
|
||||
if name not in METRICS or response.get("status") != "success" or data.get("resultType") != "vector":
|
||||
continue
|
||||
for sample in data.get("result", []):
|
||||
labels = sample.get("metric", {})
|
||||
namespace, workload = labels.get("namespace"), labels.get("workload")
|
||||
# Additional dimensions are ambiguous: do not silently collapse pod/job series.
|
||||
if not namespace or set(labels) - {"namespace", "workload", "__name__"}:
|
||||
queries[name].setdefault("rejected", []).append("unaggregated or absent identity labels")
|
||||
continue
|
||||
key = (namespace, workload)
|
||||
values = series.setdefault(key, {})
|
||||
if name in values:
|
||||
values[name] = {"value": None, "reason": "ambiguous duplicate series"}
|
||||
continue
|
||||
try:
|
||||
epoch, raw = sample["value"]
|
||||
end = datetime.fromtimestamp(finite(epoch), timezone.utc)
|
||||
number = finite(raw)
|
||||
except (KeyError, ValueError, TypeError, OverflowError):
|
||||
values[name] = {"value": None, "reason": "invalid sample"}
|
||||
continue
|
||||
queries[name]["evaluation_times"].append(end.isoformat())
|
||||
values[name] = {
|
||||
"value": number, "window_start": (end - timedelta(seconds=seconds)).isoformat(),
|
||||
"window_end": end.isoformat(), "query": name,
|
||||
"capture_alignment_seconds": (end - timestamp(telemetry["captured_at"])).total_seconds(),
|
||||
}
|
||||
return series, queries
|
||||
|
||||
|
||||
def recommendation_id(resource, namespace, workload):
|
||||
return "allocation-" + digest([resource, namespace, workload, "sizing-review"])[:24]
|
||||
|
||||
|
||||
def build_report(cluster, allocation, telemetry, inventory, provenance, *, as_of,
|
||||
previous=None, reviews=None, max_age_hours=48):
|
||||
now = timestamp(as_of)
|
||||
if cluster.get("schema_version") != "railiance.cluster-resource-observation.v1":
|
||||
raise ValueError("unsupported cluster schema")
|
||||
if allocation.get("schema") != "custodian.allocation-reconcile.v1":
|
||||
raise ValueError("unsupported allocation schema")
|
||||
resource = cluster["resource_id"]
|
||||
if inventory.get("id") != resource or allocation.get("host", {}).get("cluster_resource_id") != resource:
|
||||
raise ValueError("resource identity mismatch")
|
||||
if allocation.get("count_host_and_cluster_cpus_once") is not True:
|
||||
raise ValueError("allocation must deduplicate host and cluster CPUs")
|
||||
if cluster["captured_at"] != allocation["captured_at"]:
|
||||
raise ValueError("cluster and reconcile snapshots differ")
|
||||
if provenance.get("source_observation") != cluster["captured_at"]:
|
||||
raise ValueError("provenance does not identify cluster snapshot")
|
||||
seconds = duration(telemetry["window"])
|
||||
if seconds != 604800:
|
||||
raise ValueError("weekly report requires a seven-day telemetry window")
|
||||
step = duration(telemetry["step"])
|
||||
series, queries = metric_series(telemetry)
|
||||
captured = {"cluster": cluster["captured_at"], "telemetry": telemetry["captured_at"]}
|
||||
freshness = {}
|
||||
for name, value in captured.items():
|
||||
age = (now - timestamp(value)).total_seconds()
|
||||
freshness[name] = {"age_seconds": age, "status": "future" if age < 0 else "stale" if age > max_age_hours * 3600 else "fresh"}
|
||||
query_signature = digest({name: q["promql"] for name, q in queries.items()})
|
||||
end = timestamp(telemetry["captured_at"])
|
||||
comparison_reason = "no previous retained report"
|
||||
compatible = False
|
||||
if previous:
|
||||
prev_end = timestamp(previous["period"]["capture_end"])
|
||||
compatible = (
|
||||
previous.get("schema") == SCHEMA and previous.get("resource_id") == resource
|
||||
and previous["period"].get("seconds") == seconds
|
||||
and previous["period"].get("step_seconds") == step
|
||||
and previous.get("query_signature") == query_signature
|
||||
and abs((end - prev_end).total_seconds() - seconds) <= step
|
||||
)
|
||||
comparison_reason = "comparable consecutive capture windows" if compatible else "nonconsecutive windows or changed query/resource/step contract"
|
||||
previous_rows = {(r["namespace"], r["workload"]): r for r in (previous or {}).get("workloads", [])}
|
||||
owners = {(r["namespace"], r["workload"]): r for r in allocation.get("workloads", [])}
|
||||
if len(owners) != len(allocation.get("workloads", [])):
|
||||
raise ValueError("duplicate allocation workload identity")
|
||||
reviews = reviews or {}
|
||||
rows, recommendations = [], []
|
||||
seen = set()
|
||||
for workload in sorted(cluster.get("utilization", {}).get("workloads", []), key=lambda x: (x["namespace"], x["workload"])):
|
||||
key = workload["namespace"], workload["workload"]
|
||||
if key in seen:
|
||||
raise ValueError("duplicate cluster workload identity")
|
||||
seen.add(key)
|
||||
owner = owners.get(key, {})
|
||||
measurements = {m: series.get(key, {}).get(m, {"value": None, "reason": "no workload series"}) for m in METRICS}
|
||||
gaps = [m for m in REQUIRED if measurements[m]["value"] is None]
|
||||
metric_stale = [m for m, v in measurements.items() if v["value"] is not None and not 0 <= (now - timestamp(v["window_end"])).total_seconds() <= max_age_hours * 3600]
|
||||
metric_unaligned = [m for m, v in measurements.items() if v["value"] is not None and abs(v["capture_alignment_seconds"]) > step]
|
||||
rid = recommendation_id(resource, *key)
|
||||
reasons = []
|
||||
if gaps:
|
||||
reasons.append("missing workload metrics: " + ", ".join(gaps))
|
||||
if metric_stale or any(v["status"] != "fresh" for v in freshness.values()):
|
||||
reasons.append("stale or future observation")
|
||||
if metric_unaligned:
|
||||
reasons.append("metric evaluation windows differ from capture by more than one step")
|
||||
if owner.get("owner", "unknown") == "unknown":
|
||||
reasons.append("unknown owner")
|
||||
if workload.get("requests", {}).get("cpu_millicores") == 0:
|
||||
reasons.append("zero CPU request requires owner review")
|
||||
review = reviews.get(rid)
|
||||
decision, confidence, evidence = "investigate", "low", []
|
||||
if review:
|
||||
if review.get("decision") not in {"keep", "increase", "decrease", "investigate"}:
|
||||
raise ValueError("invalid owner review decision")
|
||||
required = ("owner", "evidence_refs", "receiving_task", "configuration", "rollback_ref", "reviewed_at", "period_end", "coverage_ref", "service_acceptance_ref", "margin_ref")
|
||||
if any(not review.get(k) for k in required):
|
||||
raise ValueError("owner review missing evidence, receiving task or configuration/rollback")
|
||||
if review["owner"] != owner.get("owner") or review["period_end"] != telemetry["captured_at"]:
|
||||
reasons.append("owner review does not match this owner/window")
|
||||
elif timestamp(review["reviewed_at"]) > now:
|
||||
reasons.append("owner review is future-dated")
|
||||
elif not reasons:
|
||||
decision, confidence, evidence = review["decision"], "owner-reviewed", review["evidence_refs"]
|
||||
else:
|
||||
reasons.append("owner proposal retained; incomplete evidence prevents accepted sizing recommendation")
|
||||
else:
|
||||
reasons.append("no owner-reviewed sizing decision")
|
||||
row = {
|
||||
"namespace": key[0], "workload": key[1], "owner": owner.get("owner", "unknown"),
|
||||
"service": owner.get("service", "unknown"), "tenant": owner.get("tenant", "unknown"),
|
||||
"pods": workload.get("pods"), "requests": workload.get("requests"),
|
||||
"limits": workload.get("limits"), "instantaneous_observed": workload.get("observed"),
|
||||
"metrics": measurements, "missing_metrics": gaps,
|
||||
"namespace_context_ref": key[0] if (key[0], None) in series else None,
|
||||
"raw_scrape_coverage": "unknown; evaluation counts do not prove source coverage",
|
||||
"recommendation_id": rid, "week_on_week": {}, "comparison_reasons": {},
|
||||
}
|
||||
prior = previous_rows.get(key)
|
||||
for metric in METRICS:
|
||||
current = measurements[metric]["value"]
|
||||
old_measurement = (prior or {}).get("metrics", {}).get(metric, {})
|
||||
old = old_measurement.get("value")
|
||||
metric_compatible = False
|
||||
reason = comparison_reason
|
||||
if compatible and current is not None and old is not None:
|
||||
try:
|
||||
old_end, old_start = timestamp(old_measurement["window_end"]), timestamp(old_measurement["window_start"])
|
||||
new_end, new_start = timestamp(measurements[metric]["window_end"]), timestamp(measurements[metric]["window_start"])
|
||||
metric_compatible = (
|
||||
(old_end - old_start).total_seconds() == seconds
|
||||
and (new_end - new_start).total_seconds() == seconds
|
||||
and abs((new_end - old_end).total_seconds() - seconds) <= step
|
||||
and abs((new_start - old_end).total_seconds()) <= step
|
||||
and abs(measurements[metric]["capture_alignment_seconds"]) <= step
|
||||
and abs((old_end - timestamp(previous["period"]["capture_end"])).total_seconds()) <= step
|
||||
)
|
||||
reason = "comparable consecutive metric windows" if metric_compatible else "metric evaluation windows are not consecutive or capture-aligned"
|
||||
except (KeyError, ValueError, TypeError):
|
||||
reason = "missing or invalid previous metric window"
|
||||
elif compatible:
|
||||
reason = "missing current or previous metric"
|
||||
row["week_on_week"][metric] = current - old if metric_compatible else None
|
||||
row["comparison_reasons"][metric] = reason
|
||||
rows.append(row)
|
||||
prior_recommendation = next((r for r in (previous or {}).get("recommendations", []) if r["id"] == rid), {})
|
||||
recommendations.append({
|
||||
"id": rid, "decision": decision, "confidence": confidence, "owner": row["owner"],
|
||||
"reasons": reasons, "evidence_refs": evidence, "owner_proposal": review,
|
||||
"receiving_task": review.get("receiving_task") if review else prior_recommendation.get("receiving_task"),
|
||||
"previous_decision": prior_recommendation.get("decision"),
|
||||
"owner_outcome": review.get("outcome") if review else None,
|
||||
"delivery": "not emitted", "acknowledgment": None,
|
||||
})
|
||||
if seen != set(owners):
|
||||
raise ValueError("cluster and allocation workload inventories differ")
|
||||
return {
|
||||
"schema": SCHEMA, "workplan": "CUST-WP-0071-T05", "as_of": as_of,
|
||||
"adapter_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||
"resource_id": resource, "period": {"capture_end": telemetry["captured_at"], "seconds": seconds, "step_seconds": step,
|
||||
"note": "Actual per-query windows are retained on samples; capture time is not a common query evaluation time."},
|
||||
"freshness": freshness, "max_age_hours": max_age_hours, "query_signature": query_signature,
|
||||
"queries": queries, "collector_provenance": provenance,
|
||||
"source_sha256": {"cluster": digest(cluster), "allocation": digest(allocation), "telemetry": digest(telemetry), "inventory": digest(inventory)},
|
||||
"node": {"capacity": cluster.get("capacity"), "allocation": cluster.get("allocation"),
|
||||
"scheduled_request_cpu_m": allocation.get("scheduled_request_cpu_m"),
|
||||
"pending_unscheduled_cpu_m": allocation.get("pending_unscheduled_cpu_m"),
|
||||
"reservation_residual_cpu_m": allocation.get("residual_cpu_m"),
|
||||
"release_preflight": allocation.get("state_hub_preflight_observation"),
|
||||
"operating_margin": None, "release_margin_acceptance": "not established by reservation residual",
|
||||
"host_cluster_counted_once": True},
|
||||
"portfolio": {k: inventory.get(k) for k in ("id", "schema_version", "ownership", "capacity", "relationships", "evidence")},
|
||||
"workloads": rows,
|
||||
"namespace_context": {ns: values for (ns, workload), values in sorted(series.items(), key=lambda x: str(x[0])) if workload is None},
|
||||
"comparison": {"compatible": compatible, "reason": comparison_reason,
|
||||
"previous_report_sha256": digest(previous) if previous else None,
|
||||
"disappeared_workloads": [list(key) for key in sorted(set(previous_rows) - seen)]},
|
||||
"measurement_gaps": cluster.get("measurement_gaps", []) + allocation.get("measurement_gaps", []),
|
||||
"recommendations": recommendations,
|
||||
"limitations": ["No representative pilot acceptance inferred from telemetry.",
|
||||
"Namespace percentiles and peaks are not workload metrics or additive node peaks.",
|
||||
"Portfolio capacity is dated evidence, not additional host capacity.",
|
||||
"No procurement, sizing changes, owner-task creation, delivery or acknowledgment performed.",
|
||||
"Installed executor, scoped reads, independent missed-run handling and first unattended receipt remain T05 gates."],
|
||||
}
|
||||
|
||||
|
||||
def retain(path, report):
|
||||
"""Never overwrite a retained week; identical retries succeed."""
|
||||
encoded = json.dumps(report, indent=2, sort_keys=True, allow_nan=False) + "\n"
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(mode="w", dir=path.parent, delete=False) as stream:
|
||||
temporary = Path(stream.name)
|
||||
stream.write(encoded)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
try:
|
||||
os.link(temporary, path)
|
||||
except FileExistsError:
|
||||
if path.is_symlink() or path.read_text() != encoded:
|
||||
raise ValueError("retained report differs; choose a new explicit revision path")
|
||||
finally:
|
||||
if temporary is not None:
|
||||
temporary.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--request", type=Path, help="Executor-owned JSON request; paths relative to its directory")
|
||||
for name in ("cluster", "allocation", "telemetry", "inventory", "provenance"):
|
||||
parser.add_argument("--" + name, type=Path)
|
||||
parser.add_argument("--previous", type=Path)
|
||||
parser.add_argument("--reviews", type=Path)
|
||||
parser.add_argument("--as-of")
|
||||
parser.add_argument("--max-age-hours", type=float, default=48)
|
||||
parser.add_argument("--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
if args.request:
|
||||
request = json.loads(args.request.read_text())
|
||||
if request.get("schema") != "custodian.weekly-allocation-request.v1":
|
||||
raise ValueError("unsupported executor request schema")
|
||||
if any(getattr(args, key) for key in ("cluster", "allocation", "telemetry", "inventory", "provenance", "previous", "reviews", "as_of", "output")):
|
||||
raise ValueError("request cannot be mixed with input/output overrides")
|
||||
for key in ("cluster", "allocation", "telemetry", "inventory", "provenance", "previous", "reviews", "output"):
|
||||
value = request.get(key)
|
||||
setattr(args, key, args.request.parent / value if value else None)
|
||||
args.as_of = request.get("as_of")
|
||||
if any(not getattr(args, key) for key in ("cluster", "allocation", "telemetry", "inventory", "provenance", "as_of", "output")):
|
||||
raise ValueError("executor input paths, as_of and retention output must be explicitly bound")
|
||||
if not math.isfinite(args.max_age_hours) or args.max_age_hours <= 0:
|
||||
raise ValueError("max-age-hours must be positive and finite")
|
||||
inputs = {name: json.loads(getattr(args, name).read_text()) for name in ("cluster", "allocation", "telemetry", "inventory", "provenance")}
|
||||
report = build_report(**inputs, as_of=args.as_of, max_age_hours=args.max_age_hours,
|
||||
previous=json.loads(args.previous.read_text()) if args.previous else None,
|
||||
reviews=json.loads(args.reviews.read_text()) if args.reviews else None)
|
||||
retain(args.output, report)
|
||||
except (OSError, ValueError, KeyError, TypeError) as exc:
|
||||
parser.exit(2, f"weekly allocation report refused: {exc}\n")
|
||||
print(json.dumps({"status": "retained", "path": str(args.output), "sha256": digest(report),
|
||||
"workloads": len(report["workloads"]), "delivery": "not emitted"}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"files": {
|
||||
"namespace-ownership.json": {
|
||||
"commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f",
|
||||
"path": "docs/evidence/namespace-ownership.yaml",
|
||||
"repo": "the-custodian",
|
||||
"source_sha256": "a3663c03f685db35d5e0eabca59e1d74810b1b56c39db0892a58903302bb771f",
|
||||
"vendored_sha256": "dd73079defcc26db460ee1ed0ef17a5eab39b04d9dbc209cb6d02064df8f3f05"
|
||||
},
|
||||
"observe_cluster_resources.py": {
|
||||
"commit": "a5948408a565d383a99f43d414145277a43ce233",
|
||||
"path": "tools/observe_cluster_resources.py",
|
||||
"repo": "railiance-cluster",
|
||||
"source_sha256": "7a613f7ba692169ec9ddf498f7250ee2a56f8fb1088585498af6df2302e15002",
|
||||
"vendored_sha256": "7a613f7ba692169ec9ddf498f7250ee2a56f8fb1088585498af6df2302e15002"
|
||||
},
|
||||
"reconcile_railiance_allocation.py": {
|
||||
"commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f",
|
||||
"path": "scripts/reconcile_railiance_allocation.py",
|
||||
"repo": "the-custodian",
|
||||
"source_sha256": "3e118023397bbb6621116f6f3009cb6ec7f894809671efeb5c165dd2c3e32a3a",
|
||||
"vendored_sha256": "3e118023397bbb6621116f6f3009cb6ec7f894809671efeb5c165dd2c3e32a3a"
|
||||
},
|
||||
"reef-railiance-k3s.json": {
|
||||
"commit": "3d9b1bd08043735e71246213e46c91f98f41d6ce",
|
||||
"path": "data/resources/reef-railiance-k3s.json",
|
||||
"repo": "resource-control",
|
||||
"source_sha256": "c97cb2c505cc52dcf1e1f394d35a8d87dfec09a9033f6cd4c0c05717111f913f",
|
||||
"vendored_sha256": "c97cb2c505cc52dcf1e1f394d35a8d87dfec09a9033f6cd4c0c05717111f913f"
|
||||
},
|
||||
"weekly-allocation-queries.json": {
|
||||
"commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f",
|
||||
"path": "config/weekly-allocation-queries.json",
|
||||
"repo": "the-custodian",
|
||||
"source_sha256": "83bde8a6e53c4b86b9624195b9a00297310eefb1e271ba4506622c1efdd7e7a9",
|
||||
"vendored_sha256": "83bde8a6e53c4b86b9624195b9a00297310eefb1e271ba4506622c1efdd7e7a9"
|
||||
},
|
||||
"weekly_allocation_incluster.py": {
|
||||
"commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f",
|
||||
"path": "scripts/weekly_allocation_incluster.py",
|
||||
"repo": "the-custodian",
|
||||
"source_sha256": "de21c18111f818bf31d9d02763e170a83d1e7581152759f7e1962c4402120d77",
|
||||
"vendored_sha256": "de21c18111f818bf31d9d02763e170a83d1e7581152759f7e1962c4402120d77"
|
||||
},
|
||||
"weekly_allocation_report.py": {
|
||||
"commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f",
|
||||
"path": "scripts/weekly_allocation_report.py",
|
||||
"repo": "the-custodian",
|
||||
"source_sha256": "93f546243331308d7e60f8db36e988d9847537a2fe46cce49ee2fffba1f7f247",
|
||||
"vendored_sha256": "93f546243331308d7e60f8db36e988d9847537a2fe46cce49ee2fffba1f7f247"
|
||||
}
|
||||
},
|
||||
"schema": "railiance-platform.vendor-lock.v1",
|
||||
"workplan": "CUST-WP-0071-T05"
|
||||
}
|
||||
29
argocd/platform-addons/ops-mason-builder/clusterrole.yaml
Normal file
29
argocd/platform-addons/ops-mason-builder/clusterrole.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Scoped ops-mason builder (founder decision 2026-10-22, MASON-IN-0006 (b)).
|
||||
# Kinds kubernetes_plane applies, plus get/list/watch on services for dependency
|
||||
# reads, plus create on self-subject access reviews so kubectl auth can-i works.
|
||||
# Excluded: pods, secrets, configmaps, serviceaccounts/token, impersonate,
|
||||
# roles, rolebindings, clusterroles, clusterrolebindings, nodes/proxy, pods/exec,
|
||||
# escalate, bind, wildcards.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: ops-mason-builder
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: ops-mason-builder
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["serviceaccounts"]
|
||||
verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["networkpolicies"]
|
||||
verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["services"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["authorization.k8s.io"]
|
||||
resources: ["selfsubjectaccessreviews", "selfsubjectrulesreviews"]
|
||||
verbs: ["create"]
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: ops-mason-builder
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: ops-mason-builder
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: ops-mason-builder
|
||||
subjects:
|
||||
- kind: User
|
||||
name: ops-mason-builder
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
11
argocd/platform-addons/ops-mason-builder/kustomization.yaml
Normal file
11
argocd/platform-addons/ops-mason-builder/kustomization.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# MASON-IN-0006 choice (b), decision 30d29060-8d14-4a8e-9cde-34aa826eb8d1.
|
||||
# Scoped builder for ops-mason. User ops-mason-builder, no ServiceAccount token,
|
||||
# no Secret. Applied by argocd/railiance01/applications/ops-mason-builder.application.yaml.
|
||||
# Manual sync. targetRevision is pinned to the commit that introduced these manifests.
|
||||
|
||||
resources:
|
||||
- clusterrole.yaml
|
||||
- clusterrolebinding.yaml
|
||||
|
|
@ -12,3 +12,8 @@ with the founder's go-ahead: no `automated` block, no finalizer, and a pinned
|
|||
`targetRevision`. A merge here plus a hand sync of the root is a live
|
||||
production change on railiance01. Drafts wait in `../drafts/`, which no root
|
||||
syncs.
|
||||
|
||||
A release that changes a child's `targetRevision` commits that pin here in the
|
||||
same change window, whether or not the live Application was patched directly.
|
||||
Before any hand sync of the root, run `make argocd-pin-drift`; any `DRIFT`
|
||||
line means the sync would roll that child back, so stop and reconcile first.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
# ACTIVITY-WP-0041 / RPF-WP-0048: founder-authorized adoption, 2026-09-27.
|
||||
# Pin = live revision of the 2026-10-20 founder-approved release
|
||||
# (activity-core docs/evidence/2026-10-20-release-health-probe-release.json).
|
||||
# Every activity-core release commits its pin here; see RPF-WP-ADHOC-2026-10-19.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
|
|
@ -10,7 +13,7 @@ spec:
|
|||
project: activity-core
|
||||
source:
|
||||
repoURL: https://forgejo.coulomb.social/coulomb/activity-core.git
|
||||
targetRevision: ce9a8f8edf4ec162bae3aa087803bd57a1b33a66
|
||||
targetRevision: 1fe2fcc0d51c086db00d5f4d8b4e2fadb1d89a1d
|
||||
path: k8s/gitops
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# railiance01 child Application (RPF-WP-0047, founder request 2026-09-23 via
|
||||
# user-engine 7d8982c2). Static notice only; OpenBao stays non-public.
|
||||
# No automated sync, no finalizer; targetRevision is the reviewed commit.
|
||||
# Manifests moved to rapp-openbao on 2026-10-23. This Application stays the
|
||||
# GitOps attachment. Sync it by hand after that commit is on the remote.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
|
|
@ -14,9 +16,9 @@ metadata:
|
|||
spec:
|
||||
project: railiance-tenants
|
||||
source:
|
||||
repoURL: https://forgejo.coulomb.social/coulomb/railiance-platform.git
|
||||
targetRevision: 9ee20967ca7b50a8926c99ae86fa027f8bfb0fa3
|
||||
path: argocd/platform-addons/bao-notice
|
||||
repoURL: https://forgejo.coulomb.social/coulomb/rapp-openbao.git
|
||||
targetRevision: a1f5851e8ad16df5689ef8c91273d77590863089
|
||||
path: manifests/bao-notice
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: bao-notice
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# railiance01 child Application (RPF-WP-0062 / CUST-WP-0073-T02, founder decision 2026-10-20).
|
||||
# Read-only custodian agent observer identity. No automated sync, no finalizer;
|
||||
# targetRevision is the reviewed commit that introduced the manifests.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: custodian-agents
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: custodian-agent-identity
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "10"
|
||||
spec:
|
||||
project: railiance-platform-addons
|
||||
source:
|
||||
repoURL: https://forgejo.coulomb.social/coulomb/railiance-platform.git
|
||||
targetRevision: 567d2c671bb0e36bef2f7f2767414ea76aa8cf9a
|
||||
path: argocd/platform-addons/custodian-agents
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: custodian-agents
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- ApplyOutOfSyncOnly=true
|
||||
- PruneLast=true
|
||||
|
|
@ -7,7 +7,7 @@ spec:
|
|||
project: informed-decision-lifecycle
|
||||
source:
|
||||
repoURL: https://forgejo.coulomb.social/coulomb/informed-decision.git
|
||||
targetRevision: 9207a5134f92904f3843c200645ced91f247af72
|
||||
targetRevision: 312ab97e876e17341bf16f9e95397e14c2ac2720
|
||||
path: deploy/lifecycle
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# DRAFT for railiance01 (RPF-WP-0044). Not synced by any root: move to
|
||||
# ../applications/ only in this app's adoption task, with the founder's go-ahead.
|
||||
# No automated sync, no finalizer. targetRevision: rapp-issue-core commit diffed 2026-09-21.
|
||||
# railiance01 issue-core (RPF-WP-0044-T04, adopted 2026-10-20 under D2 89f1058f + founder go).
|
||||
# No automated sync, no finalizer. Read-only SSH deploy key: argocd/rapp-issue-core-repository.
|
||||
# targetRevision: rapp-issue-core 11af3cb (wave-2 requests 5m/48Mi, ISSUE-WP-0008-T01; on top of 805e124 forge-http URL).
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
|
|
@ -14,8 +14,8 @@ metadata:
|
|||
spec:
|
||||
project: railiance-tenants
|
||||
source:
|
||||
repoURL: https://forgejo.coulomb.social/coulomb/rapp-issue-core.git
|
||||
targetRevision: a78f38cfd8afb29ed23d150a4ea3e3dcd1f35381
|
||||
repoURL: ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git
|
||||
targetRevision: 11af3cbf359ba850c8a807d7cc8b369c037862df
|
||||
path: manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# railiance01 child Application (MASON-IN-0006 choice (b), decision
|
||||
# 30d29060-8d14-4a8e-9cde-34aa826eb8d1). Scoped ops-mason builder identity.
|
||||
# No automated sync, no finalizer; targetRevision is the commit that
|
||||
# introduced the manifests.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ops-mason-builder
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: ops-mason-builder
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "10"
|
||||
spec:
|
||||
project: railiance-platform-addons
|
||||
source:
|
||||
repoURL: https://forgejo.coulomb.social/coulomb/railiance-platform.git
|
||||
targetRevision: bd6660a91672c76b78e017c22394705381d8767d
|
||||
path: argocd/platform-addons/ops-mason-builder
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- ApplyOutOfSyncOnly=true
|
||||
- PruneLast=true
|
||||
|
|
@ -14,7 +14,7 @@ spec:
|
|||
project: railiance-tenants
|
||||
source:
|
||||
repoURL: https://forgejo.coulomb.social/coulomb/target-revenue.git
|
||||
targetRevision: a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0
|
||||
targetRevision: f110a4ea96be42335586d2921a987db73fa6b930
|
||||
path: k8s/railiance
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ spec:
|
|||
- https://forgejo.coulomb.social/coulomb/*.git
|
||||
# Emergency rollback only (T12 Option A through 2026-08-07)
|
||||
- https://gitea.coulomb.social/coulomb/*.git
|
||||
- ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git
|
||||
destinations:
|
||||
- server: https://kubernetes.default.svc
|
||||
namespace: "*"
|
||||
|
|
|
|||
|
|
@ -46,6 +46,12 @@ spec:
|
|||
kind: Deployment
|
||||
- group: batch
|
||||
kind: CronJob
|
||||
# CUST-WP-0071-T05 (founder decision 2026-10-21): custodian-allocation-review
|
||||
# needs its retained-report claim and default-deny egress policy.
|
||||
- group: ""
|
||||
kind: PersistentVolumeClaim
|
||||
- group: networking.k8s.io
|
||||
kind: NetworkPolicy
|
||||
- group: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
- group: rbac.authorization.k8s.io
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# DRAFT — not synced by any root (drafts/ is never referenced). CUST-WP-0071-T05.
|
||||
# Activation (founder go-ahead, after CUST-WP-0071-T04 and review): add
|
||||
# PersistentVolumeClaim to the railiance-platform-addons AppProject, set
|
||||
# targetRevision to the reviewed commit, move this file to ../applications/,
|
||||
# run `make argocd-pin-drift`, then hand-sync the root and this Application.
|
||||
# See argocd/platform-addons/custodian-allocation-review/README.md.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: custodian-allocation-review
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
railiance-platform/component: custodian-allocation-review
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "10"
|
||||
spec:
|
||||
project: railiance-platform-addons
|
||||
source:
|
||||
repoURL: https://forgejo.coulomb.social/coulomb/railiance-platform.git
|
||||
targetRevision: REPLACE_WITH_REVIEWED_COMMIT_SHA
|
||||
path: argocd/platform-addons/custodian-allocation-review
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: custodian-assessment
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- ApplyOutOfSyncOnly=true
|
||||
- PruneLast=true
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# DRAFT for railiance01 (RPF-WP-0044). Not synced by any root: move to
|
||||
# ../applications/ only in this app's adoption task, with the founder's go-ahead.
|
||||
# ../applications/ only after predecessor automated proof under accepted D2.
|
||||
# No automated sync, no finalizer. CRDs survive Application pruning/deletion.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
|
|
@ -27,6 +27,15 @@ spec:
|
|||
serviceAccount:
|
||||
create: true
|
||||
name: external-secrets
|
||||
# Wave-2 requests (RPF-WP-0063-T01); mirrors helm/external-secrets-resources.values.yaml
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 64Mi}
|
||||
certController:
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 64Mi}
|
||||
webhook:
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 80Mi}
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: external-secrets
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@
|
|||
"rapp-postgres/consumers/tenant-engine.yaml": "92f36d587f001c86ff401aedd25cb49fa171d49e000b0a6ab551e182e5c11448",
|
||||
"railiance-platform/tools/verify_apps_pg_capacity.py": "b571ec63b0a7501dabba37a84af9d2cfcb7737ed891a7ea2ab05c54010b1893e",
|
||||
"railiance-platform/assurance/placement-owners.json": "e71037f868e3264d43ff5c42511558272a89926dc8dc2f7ee952e018020b4891",
|
||||
"rapp-postgres/scripts/render_consumers.py": "2dcfd68f94a3d6c7e1072c4a55a497b1697eab30acaecae53865c9b1a596773a",
|
||||
"rapp-postgres/helm/platform-pg-cluster.yaml": "461da0d43e61a4ddca29d6e98080b8b1642bd3303f3395da1b0beb28775c2c5c",
|
||||
"rapp-postgres/helm/platform-pg-2-cluster.yaml": "06b92a76189b1a33d1eee3f5c835a3e94269ad7b040383c04e8028c2c3fe766a"
|
||||
"rapp-postgres/scripts/render_consumers.py": "5d83e29e5ec675b3c78c0129d9e12213b624146f5883534a7e5c99aaaa057f61",
|
||||
"rapp-postgres/helm/platform-pg-cluster.yaml": "f3101cfed2bdaebef2732c1c13ba44940122cb057366950eed744cd1ec4e6380",
|
||||
"rapp-postgres/helm/platform-pg-2-cluster.yaml": "c8411681251cd2e48d4e251170adee79689811836156cd2f89d0e663db68ccf8"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,11 +54,11 @@
|
|||
},
|
||||
{
|
||||
"path": "scripts/openbao-verify-login-overlay.sh",
|
||||
"sha256": "57438714f1b7aac0c37b8bdf525c5387f812a6d30532bb98b395e5db5208ad6d"
|
||||
"sha256": "44435af01da60f08228a07d547a5dc8941f848eaf2a1baf9b79a83845b6defdc"
|
||||
},
|
||||
{
|
||||
"path": "docs/rapp-openbao-compatibility-handoff.md",
|
||||
"sha256": "65ff0b39ee7312c8a242d24cf3434f9d0636a830e841f1a70de560c96cd8d57f"
|
||||
"sha256": "415f08f111b98ebb4a0cdd3906acf915a0f1c9854bd364d078aac151dd9d354f"
|
||||
}
|
||||
],
|
||||
"acceptance": "Owner names canonical replacements; update all callers and prove dry-run/smoke and rollback before source removal. No external acceptance asserted."
|
||||
|
|
|
|||
30
config/playwright/authentication-config.candidate.yaml
Normal file
30
config/playwright/authentication-config.candidate.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Owner-review candidate only. Not a kubectl resource and not activated.
|
||||
# Cluster owner must merge with existing authenticators, validate natively and
|
||||
# preserve existing admin/service-account authentication and rollback access.
|
||||
apiVersion: apiserver.config.k8s.io/v1
|
||||
kind: AuthenticationConfiguration
|
||||
jwt:
|
||||
- issuer:
|
||||
url: https://kc.coulomb.social
|
||||
audiences:
|
||||
- playwright-kubernetes
|
||||
audienceMatchPolicy: MatchAny
|
||||
claimValidationRules:
|
||||
- expression: "claims.aud == 'playwright-kubernetes' || claims.aud == ['playwright-kubernetes']"
|
||||
message: dedicated Playwright audience required
|
||||
- expression: "has(claims.scope) && 'playwright:connect' in claims.scope.split(' ')"
|
||||
message: Playwright access scope required
|
||||
- expression: "claims.principal_type == 'human' || (claims.principal_type == 'service' && has(claims.roles) && 'coding-agent' in claims.roles)"
|
||||
message: identified human or registered coding agent required
|
||||
- expression: "claims.sub != '' && claims.exp > claims.iat && claims.exp - claims.iat <= 3600"
|
||||
message: nonempty subject and bounded token lifetime required
|
||||
claimMappings:
|
||||
username:
|
||||
expression: "'keycape:' + claims.principal_type + ':' + claims.sub"
|
||||
groups:
|
||||
expression: "['playwright-consumers']"
|
||||
userValidationRules:
|
||||
- expression: "!user.username.startsWith('system:')"
|
||||
message: reserved Kubernetes identity prefix refused
|
||||
- expression: "user.groups == ['playwright-consumers']"
|
||||
message: only the Playwright consumer group may be mapped
|
||||
9
config/playwright/keycape-public-client.candidate.json
Normal file
9
config/playwright/keycape-public-client.candidate.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"clientId": "playwright-cli",
|
||||
"audience": "playwright-kubernetes",
|
||||
"displayName": "Playwright platform consumer CLI",
|
||||
"redirectUris": ["http://127.0.0.1:18764/callback"],
|
||||
"allowedScopes": ["openid", "profile", "groups", "playwright:connect"],
|
||||
"grantTypes": ["authorization_code"],
|
||||
"clientType": "public"
|
||||
}
|
||||
10
config/playwright/railiance01-ca.pem
Normal file
10
config/playwright/railiance01-ca.pem
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIBeDCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
|
||||
dmVyLWNhQDE3NzMxMDM2MjAwHhcNMjYwMzEwMDA0NzAwWhcNMzYwMzA3MDA0NzAw
|
||||
WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE3NzMxMDM2MjAwWTATBgcqhkjO
|
||||
PQIBBggqhkjOPQMBBwNCAAT44CzyUUGic6xYSLOApGq8A61OcoP/v+nns47p3F1z
|
||||
M89iyNSZqtIHRpg0BxiMtTcdwXKart6aPIC8/8PfpVyAo0IwQDAOBgNVHQ8BAf8E
|
||||
BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUG/vhGaKEp4uD2KaSPDTU
|
||||
249sq2EwCgYIKoZIzj0EAwIDSQAwRgIhALUc7a0LcGfMiAI+auFTByXlL4hTH0Cr
|
||||
DxsOiRhV9D0vAiEAjTVKYP/KFPe68cAL8S76mLKdKhd564XiVAeISh5NkG4=
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -5,7 +5,7 @@ request_type: workload-kv-read
|
|||
title: Railiance offsite backup lane (Nextcloud WebDAV + age recovery)
|
||||
status: active
|
||||
created: '2026-07-07'
|
||||
updated: '2026-09-05'
|
||||
updated: '2026-10-07'
|
||||
requester:
|
||||
agent: grok
|
||||
reason: Move railiance-backup / forgejo-backup credentials from hardcoded script
|
||||
|
|
@ -20,6 +20,12 @@ review:
|
|||
decision: approved
|
||||
comment: "Approved in chat \u2014 establish backup lane credentials in OpenBao\
|
||||
\ for workstation forgejo-backup and railiance-backup tooling (Option A Nextcloud)."
|
||||
- at: '2026-10-07'
|
||||
reviewer: platform-operator (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 73fff3d3-49fd-4535-9040-b2754a3e2024 approves removing
|
||||
the retired bao.coulomb.social OIDC callback from allowed_redirect_uris and
|
||||
one delegated re-apply. Nothing else changes.
|
||||
target:
|
||||
domain: financials
|
||||
tenant: railiance
|
||||
|
|
@ -40,7 +46,6 @@ openbao:
|
|||
mount: netkingdom
|
||||
role: railiance-backup-workload-kv-read
|
||||
allowed_redirect_uris:
|
||||
- https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback
|
||||
- http://localhost:8250/oidc/callback
|
||||
- http://127.0.0.1:8250/oidc/callback
|
||||
oidc_scopes:
|
||||
|
|
@ -72,13 +77,13 @@ risk:
|
|||
classification: high
|
||||
notes:
|
||||
- NC_WEBDAV_TOKEN grants upload to the offsite backup file drop.
|
||||
- Since 2026-09-05 the file drop belongs to Backup, folder /railiance-backups,
|
||||
with create-only permissions and a server-enforced 10 GiB account quota.
|
||||
- Operator credentials remain in KVv2 operators/nextcloud/backup, fields
|
||||
BACKUP_USERNAME and BACKUP_PASSWORD; never deliver them to production.
|
||||
- Existing Bernd-owned retained backups and recovery access remain separate;
|
||||
predecessor file-drop unshared 2026-09-15 by operator attestation; age-key
|
||||
exposure remains open.
|
||||
- Since 2026-09-05 the file drop belongs to Backup, folder /railiance-backups, with
|
||||
create-only permissions and a server-enforced 10 GiB account quota.
|
||||
- Operator credentials remain in KVv2 operators/nextcloud/backup, fields BACKUP_USERNAME
|
||||
and BACKUP_PASSWORD; never deliver them to production.
|
||||
- Existing Bernd-owned retained backups and recovery access remain separate; predecessor
|
||||
file-drop unshared 2026-09-15 by operator attestation; age-key exposure remains
|
||||
open.
|
||||
- "AGE_PRIVATE_KEY decrypts all age-encrypted backup artifacts \u2014 recovery escrow\
|
||||
\ only."
|
||||
- Credentials must not be stored on production hosts with delete permission.
|
||||
|
|
@ -117,13 +122,20 @@ verification:
|
|||
kind: capabilities_safe_verify
|
||||
result: passed
|
||||
details:
|
||||
- 'WP-0026 T07: capabilities-safe re-verify on bao.coulomb.social (no secret values read).'
|
||||
- 'Positive: token with policy workload-kv-read-railiance-backup-offsite-lane → capabilities read on platform/data/workloads/railiance/backup/offsite-lane.'
|
||||
- 'Negative: default-policy token → deny on the same data path; token revoked after check.'
|
||||
- 'Agent boundary: policy agent-high-risk-boundary → deny on data path, read on metadata path.'
|
||||
- 'Field presence via JSON key inventory only (NC_WEBDAV_TOKEN, NC_WEBDAV_URL, AGE_PRIVATE_KEY); lengths recorded, values not printed.'
|
||||
- 'EXPOSED taint set: custom_metadata exposed_at=2026-07-16T00:00:00Z exposed_version=2 (WP-0026 T05).'
|
||||
- 'ops-warden catalog promoted draft→active; fetch_command pinned to NC_WEBDAV_TOKEN; resolvable=true; risk=high.'
|
||||
- 'WP-0026 T07: capabilities-safe re-verify on bao.coulomb.social (no secret values
|
||||
read).'
|
||||
- "Positive: token with policy workload-kv-read-railiance-backup-offsite-lane\
|
||||
\ \u2192 capabilities read on platform/data/workloads/railiance/backup/offsite-lane."
|
||||
- "Negative: default-policy token \u2192 deny on the same data path; token revoked\
|
||||
\ after check."
|
||||
- "Agent boundary: policy agent-high-risk-boundary \u2192 deny on data path, read\
|
||||
\ on metadata path."
|
||||
- Field presence via JSON key inventory only (NC_WEBDAV_TOKEN, NC_WEBDAV_URL,
|
||||
AGE_PRIVATE_KEY); lengths recorded, values not printed.
|
||||
- 'EXPOSED taint set: custom_metadata exposed_at=2026-07-16T00:00:00Z exposed_version=2
|
||||
(WP-0026 T05).'
|
||||
- "ops-warden catalog promoted draft\u2192active; fetch_command pinned to NC_WEBDAV_TOKEN;\
|
||||
\ resolvable=true; risk=high."
|
||||
- at: '2026-09-05'
|
||||
actor: codex
|
||||
kind: dedicated_backup_account_cutover
|
||||
|
|
@ -131,8 +143,8 @@ verification:
|
|||
details:
|
||||
- User authorized dedicated Backup account and operator custody coordinates.
|
||||
- Verified 10737418240-byte quota and Backup-owned create-only share.
|
||||
- Encrypted fixture PUT 201, owner download and escrow decryption passed;
|
||||
runtime GET and DELETE returned 405 on the actual upload endpoint.
|
||||
- Encrypted fixture PUT 201, owner download and escrow decryption passed; runtime
|
||||
GET and DELETE returned 405 on the actual upload endpoint.
|
||||
- CAS advanced workload KV version 2 to 3, preserving age escrow and other fields.
|
||||
- Evidence docs/evidence/RPF-WP-0029-backup-account-2026-09-05.json.
|
||||
- at: '2026-09-15'
|
||||
|
|
@ -144,7 +156,15 @@ verification:
|
|||
- No predecessor value, fingerprint, length or shape was recorded.
|
||||
- HTTP 401/403 probe not run; predecessor must not be reconstructed.
|
||||
- Evidence docs/evidence/2026-09-15-rpf-wp-0029-predecessor-share-invalidated.json.
|
||||
|
||||
- at: '2026-10-07T13:10:39+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-railiance-backup-offsite-lane'
|
||||
- 'Auth role metadata write: auth/netkingdom/role/railiance-backup-workload-kv-read'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: Disable ops-warden catalog entry and detach OIDC role policy; rotate
|
||||
Nextcloud file-drop token at provider.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ request_type: workload-kv-read
|
|||
title: secrets-engine client-side read of its approval client secret
|
||||
status: applied
|
||||
created: '2026-09-09'
|
||||
updated: '2026-09-13'
|
||||
updated: '2026-10-07'
|
||||
requester:
|
||||
agent: claude
|
||||
reason: RPF-WP-0035-T06 residual from the completed verifier custody (CCR-2026-0017).
|
||||
|
|
@ -55,6 +55,12 @@ review:
|
|||
not a fabricated separate owner vote or approval-engine human decision. Live
|
||||
delivery verification remains pending and the credential front door remains
|
||||
non-resolvable.
|
||||
- at: '2026-10-07'
|
||||
reviewer: platform-operator, secrets-engine-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 73fff3d3-49fd-4535-9040-b2754a3e2024 approves removing
|
||||
the retired bao.coulomb.social OIDC callback from allowed_redirect_uris and
|
||||
one delegated re-apply. Nothing else changes.
|
||||
target:
|
||||
domain: financials
|
||||
tenant: platform
|
||||
|
|
@ -75,7 +81,6 @@ openbao:
|
|||
mount: netkingdom
|
||||
role: secrets-engine-approval-client-workload-kv-read
|
||||
allowed_redirect_uris:
|
||||
- https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback
|
||||
- http://localhost:8250/oidc/callback
|
||||
- http://127.0.0.1:8250/oidc/callback
|
||||
oidc_scopes:
|
||||
|
|
@ -157,6 +162,15 @@ verification:
|
|||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-secrets-engine-approval-client'
|
||||
- 'Auth role metadata write: auth/netkingdom/role/secrets-engine-approval-client-workload-kv-read'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
- at: '2026-10-07T13:10:43+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-secrets-engine-approval-client'
|
||||
- 'Auth role metadata write: auth/netkingdom/role/secrets-engine-approval-client-workload-kv-read'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: Detach the policy from role secrets-engine-approval-client-workload-kv-read
|
||||
and disable the ops-warden catalog entry. The KeyCape verifier lane and its delivery
|
||||
|
|
|
|||
|
|
@ -28,6 +28,26 @@ review:
|
|||
decision: approved
|
||||
comment: User instructed register sitting-requester. Apply remains the attended
|
||||
helper with CAS=0 custody, exact create-only client, and no sitting POST.
|
||||
- at: '2026-11-01'
|
||||
reviewer: railiance-platform
|
||||
decision: amendment_proposed
|
||||
comment: 'key-cape 82b471f (founder decision 2026-11-01) adds approval:read to client
|
||||
informed-decision-sitting-requester for informed-decision present_existing intake.
|
||||
Text-only amendment, see amendments: custody path, field, policy, role, bound identity,
|
||||
ESO delivery and env binding are unchanged, so no new CCR and no OpenBao change.
|
||||
Needs platform-owner and key-cape-owner approval, recorded by the founder''s scope
|
||||
decision, before the live keycape-client.py remove-then-add.'
|
||||
- at: '2026-11-01'
|
||||
reviewer: platform-owner
|
||||
decision: amendment_approved
|
||||
comment: 'Approved by the founder in session 2026-11-01 (net-kingdom coordinator relayed):
|
||||
approval:read for informed-decision-sitting-requester, text-only amendment as proposed.
|
||||
The live keycape-client.py remove-then-add remains a separate founder-run step.'
|
||||
- at: '2026-11-01'
|
||||
reviewer: key-cape-owner
|
||||
decision: amendment_approved
|
||||
comment: 'Approved by the founder in session 2026-11-01 (net-kingdom coordinator relayed),
|
||||
matching key-cape 82b471f.'
|
||||
target:
|
||||
domain: financials
|
||||
tenant: platform
|
||||
|
|
@ -108,3 +128,17 @@ state_hub:
|
|||
task_id: RPF-WP-0042-T01
|
||||
related_request: CCR-2026-0027
|
||||
related_workplan: INFD-WP-0002
|
||||
amendments:
|
||||
- at: '2026-11-01'
|
||||
by: railiance-platform
|
||||
status: approved
|
||||
change: 'Scope of the client this credential authenticates widens from approval:create
|
||||
to approval:create + approval:read (key-cape 82b471f, docs/sitting-requester-read-scope.md;
|
||||
founder decision 2026-11-01). Still never approval:consume, approval:approve or
|
||||
openid. Where this record says create-only, approval:create only or no read scope,
|
||||
read: create and read, never consume, approve or openid. Residual: an exchange without
|
||||
a scope parameter yields a create+read token (key-cape token.go). The 2026-09-15
|
||||
exchange proof (scripts/prove-sitting-requester-exchange.py) stays historical
|
||||
evidence of the create-only shape; after the live apply the new shape is verified
|
||||
as in the key-cape doc (read 200 with scope exactly approval:read; consume and approve
|
||||
400). No OpenBao object, custody value or delivery changes; status stays applied.'
|
||||
|
|
|
|||
|
|
@ -27,6 +27,26 @@ review:
|
|||
decision: approved
|
||||
comment: User instructed register sitting-requester. Apply remains the attended
|
||||
helper with CAS=0 custody, exact create-only client, and no sitting POST.
|
||||
- at: '2026-11-01'
|
||||
reviewer: railiance-platform
|
||||
decision: amendment_proposed
|
||||
comment: 'key-cape 82b471f (founder decision 2026-11-01) adds approval:read to client
|
||||
informed-decision-sitting-requester for informed-decision present_existing intake.
|
||||
Text-only amendment, see amendments: custody path, field, policy, role, bound identity,
|
||||
ESO delivery and env binding are unchanged, so no new CCR and no OpenBao change.
|
||||
Needs platform-owner and key-cape-owner approval, recorded by the founder''s scope
|
||||
decision, before the live keycape-client.py remove-then-add.'
|
||||
- at: '2026-11-01'
|
||||
reviewer: platform-owner
|
||||
decision: amendment_approved
|
||||
comment: 'Approved by the founder in session 2026-11-01 (net-kingdom coordinator relayed):
|
||||
approval:read for informed-decision-sitting-requester, text-only amendment as proposed.
|
||||
The live keycape-client.py remove-then-add remains a separate founder-run step.'
|
||||
- at: '2026-11-01'
|
||||
reviewer: key-cape-owner
|
||||
decision: amendment_approved
|
||||
comment: 'Approved by the founder in session 2026-11-01 (net-kingdom coordinator relayed),
|
||||
matching key-cape 82b471f.'
|
||||
target:
|
||||
domain: financials
|
||||
tenant: platform
|
||||
|
|
@ -117,3 +137,17 @@ state_hub:
|
|||
task_id: RPF-WP-0042-T01
|
||||
related_request: CCR-2026-0026
|
||||
related_workplan: INFD-WP-0002
|
||||
amendments:
|
||||
- at: '2026-11-01'
|
||||
by: railiance-platform
|
||||
status: approved
|
||||
change: 'Scope of the client this credential authenticates widens from approval:create
|
||||
to approval:create + approval:read (key-cape 82b471f, docs/sitting-requester-read-scope.md;
|
||||
founder decision 2026-11-01). Still never approval:consume, approval:approve or
|
||||
openid. Where this record says create-only, approval:create only or no read scope,
|
||||
read: create and read, never consume, approve or openid. Residual: an exchange without
|
||||
a scope parameter yields a create+read token (key-cape token.go). The 2026-09-15
|
||||
exchange proof (scripts/prove-sitting-requester-exchange.py) stays historical
|
||||
evidence of the create-only shape; after the live apply the new shape is verified
|
||||
as in the key-cape doc (read 200 with scope exactly approval:read; consume and approve
|
||||
400). No OpenBao object, custody value or delivery changes; status stays applied.'
|
||||
|
|
|
|||
|
|
@ -3,29 +3,62 @@ kind: credential-change-request
|
|||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: KeyCape secrets-engine-openbao issuer workload reader and authoritative custody
|
||||
status: proposed
|
||||
status: applied
|
||||
created: '2026-10-04'
|
||||
updated: '2026-10-06'
|
||||
updated: '2026-10-07'
|
||||
requester:
|
||||
agent: key-cape
|
||||
reason: 'KEY-WP-0037-T02 owner packet; issuer-side read of the authoritative secrets-engine-openbao
|
||||
client secret. Revised 2026-10-06 per founder direction (the secret lives only in OpenBao)
|
||||
and ops-mason plan keycape-secrets-engine-openbao-client-custody (658cdbc, MASON-WP-0009):
|
||||
consumer file and secrets-engine D1-D4 removed.'
|
||||
client secret. Revised 2026-10-06 per founder direction (the secret lives only
|
||||
in OpenBao) and ops-mason plan keycape-secrets-engine-openbao-client-custody (658cdbc,
|
||||
MASON-WP-0009): consumer file and secrets-engine D1-D4 removed.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- key-cape-owner
|
||||
- net-kingdom-owner
|
||||
comments: []
|
||||
comments:
|
||||
- at: '2026-10-07'
|
||||
reviewer: platform-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06)
|
||||
approves CCR-2026-0033 as platform-owner. Covers A1 (ops-mason design 658cdbc),
|
||||
A2 (this CCR and CCR-2026-0034 at 4a284f0, bound claims of both readers confirmed)
|
||||
and A3 (exactly one attended custody-child provision run, cas=0, KV version
|
||||
1). Does not approve the policy/role/ESO apply, the NetKingdom env patch, the
|
||||
KeyCape registration, the restart or any rotation.
|
||||
- at: '2026-10-07'
|
||||
reviewer: key-cape-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06)
|
||||
approves CCR-2026-0033 as key-cape-owner. Covers A1 (ops-mason design 658cdbc),
|
||||
A2 (this CCR and CCR-2026-0034 at 4a284f0, bound claims of both readers confirmed)
|
||||
and A3 (exactly one attended custody-child provision run, cas=0, KV version
|
||||
1). Does not approve the policy/role/ESO apply, the NetKingdom env patch, the
|
||||
KeyCape registration, the restart or any rotation.
|
||||
- at: '2026-10-07'
|
||||
reviewer: net-kingdom-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06)
|
||||
approves CCR-2026-0033 as net-kingdom-owner. Covers A1 (ops-mason design 658cdbc),
|
||||
A2 (this CCR and CCR-2026-0034 at 4a284f0, bound claims of both readers confirmed)
|
||||
and A3 (exactly one attended custody-child provision run, cas=0, KV version
|
||||
1). Does not approve the policy/role/ESO apply, the NetKingdom env patch, the
|
||||
KeyCape registration, the restart or any rotation.
|
||||
- at: '2026-10-07'
|
||||
reviewer: platform-owner, key-cape-owner, net-kingdom-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 1fdd993b-8899-4f87-8a8e-636346bb9692 approves the token_self_lifecycle
|
||||
amendment (lookup-self and revoke-self on the ESO reader's own token) and one
|
||||
delegated re-apply of this CCR's policy and role. Nothing else changes.
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: keycape
|
||||
environment: production
|
||||
purpose: Hold the secrets-engine-openbao client secret authoritatively in platform KV and
|
||||
deliver it to the KeyCape issuer through sso-only ESO
|
||||
purpose: Hold the secrets-engine-openbao client secret authoritatively in platform
|
||||
KV and deliver it to the KeyCape issuer through sso-only ESO
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/keycape/secrets-engine-openbao
|
||||
|
|
@ -34,6 +67,7 @@ openbao:
|
|||
policy_name: workload-kv-read-keycape-secrets-engine-openbao-eso
|
||||
policy_file: openbao/policies/workload-kv-read-keycape-secrets-engine-openbao-eso.hcl
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
|
|
@ -50,7 +84,7 @@ openbao:
|
|||
- external-secrets
|
||||
service_account_namespaces:
|
||||
- external-secrets
|
||||
bound_claims_confirmed: false
|
||||
bound_claims_confirmed: true
|
||||
kv_metadata:
|
||||
cas_required: true
|
||||
max_versions: 2
|
||||
|
|
@ -73,27 +107,47 @@ risk:
|
|||
classification: high
|
||||
notes:
|
||||
- Reads the whole path holding the secrets-engine-openbao confidential value
|
||||
- Only ExternalSecrets in namespace sso may use the store; sso namespace writers are within
|
||||
this boundary
|
||||
- 'One value, two copies: KV (authoritative) and the sso Secret (issuer delivery). The consumer
|
||||
holds no copy at rest; it reads in memory through CCR-2026-0034.'
|
||||
- 'Must stay on kubernetes auth: a KeyCape-issued identity would make KeyCape startup depend
|
||||
on KeyCape.'
|
||||
- Only ExternalSecrets in namespace sso may use the store; sso namespace writers
|
||||
are within this boundary
|
||||
- 'One value, two copies: KV (authoritative) and the sso Secret (issuer delivery).
|
||||
The consumer holds no copy at rest; it reads in memory through CCR-2026-0034.'
|
||||
- 'Must stay on kubernetes auth: a KeyCape-issued identity would make KeyCape startup
|
||||
depend on KeyCape.'
|
||||
verification:
|
||||
positive:
|
||||
- Owner-reviewed native exact identity and exact path proof required
|
||||
negative:
|
||||
- Owner-reviewed wrong identity, sibling GET and parent LIST denials required
|
||||
- Metadata shows cas_required=true, max_versions=2 and the declared custom_metadata (value-free).
|
||||
- Metadata shows cas_required=true, max_versions=2 and the declared custom_metadata
|
||||
(value-free).
|
||||
activation_conditions:
|
||||
- Exact CCR approval, confirmed identity, attended custody and native receipt acceptance
|
||||
required
|
||||
evidence:
|
||||
- at: '2026-10-07T00:03:08+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-secrets-engine-openbao-eso'
|
||||
- 'Auth role metadata write: auth/kubernetes/role/external-secrets-keycape-secrets-engine-openbao'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
- at: '2026-10-07T00:10:09+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-secrets-engine-openbao-eso'
|
||||
- 'Auth role metadata write: auth/kubernetes/role/external-secrets-keycape-secrets-engine-openbao'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: Owner-reviewed delivery withdrawal and token revocation; exact rollback receipt
|
||||
required
|
||||
rotate: 'Roll-forward only: custody child writes a new version with cas=<current> under
|
||||
a standing rotation approval for this CCR (event-driven + 90d); never a read or restore
|
||||
of an old version'
|
||||
deactivate: Owner-reviewed delivery withdrawal and token revocation; exact rollback
|
||||
receipt required
|
||||
rotate: 'Roll-forward only: custody child writes a new version with cas=<current>
|
||||
under a standing rotation approval for this CCR (event-driven + 90d); never a
|
||||
read or restore of an old version'
|
||||
compromised: Owner incident process; no automatic read, reseed or cleanup
|
||||
scaffold:
|
||||
draft_only: true
|
||||
|
|
@ -107,64 +161,65 @@ state_hub:
|
|||
related_workplan: KEY-WP-0037
|
||||
delivery:
|
||||
surface: external-secrets
|
||||
target: sso/keycape-secrets-engine-openbao-client key client-secret; issuer-side delivery
|
||||
only
|
||||
target: sso/keycape-secrets-engine-openbao-client key client-secret; issuer-side
|
||||
delivery only
|
||||
review_bundle: docs/provisioning-windows/2026-10-04-1000/keycape
|
||||
consumer_delivery: none at rest; attended operator OIDC reader CCR-2026-0034 reads into
|
||||
secrets-engine process memory per command
|
||||
consumer_delivery: none at rest; attended operator OIDC reader CCR-2026-0034 reads
|
||||
into secrets-engine process memory per command
|
||||
boundary_decision:
|
||||
id: 632a8893-0d6a-4591-a50b-40bbdb798f32
|
||||
approves: custody boundary only (platform authoritative KV and least-privilege issuer delivery;
|
||||
secrets-engine-owned mode-0600 consumer file outside every worktree)
|
||||
approves: custody boundary only (platform authoritative KV and least-privilege issuer
|
||||
delivery; secrets-engine-owned mode-0600 consumer file outside every worktree)
|
||||
does_not_approve: generation, KV write, fetch, delivery, registration or deployment
|
||||
note: 'The design narrows this boundary: the secrets-engine consumer file it approved is
|
||||
withdrawn (founder direction 2026-10-06).'
|
||||
path_correction: 'Candidate path platform/services/keycape/secrets-engine-openbao from the
|
||||
KeyCape packet is outside the platform-mount applier scope (WARDEN-WP-0028: platform/workloads/
|
||||
only). Corrected to platform/workloads/keycape/secrets-engine-openbao, same field CLIENT_SECRET.
|
||||
The issuer binds to the sso Secret; the operator reader (CCR-2026-0034) reads the exact
|
||||
data path.'
|
||||
note: 'The design narrows this boundary: the secrets-engine consumer file it approved
|
||||
is withdrawn (founder direction 2026-10-06).'
|
||||
path_correction: 'Candidate path platform/services/keycape/secrets-engine-openbao
|
||||
from the KeyCape packet is outside the platform-mount applier scope (WARDEN-WP-0028:
|
||||
platform/workloads/ only). Corrected to platform/workloads/keycape/secrets-engine-openbao,
|
||||
same field CLIENT_SECRET. The issuer binds to the sso Secret; the operator reader
|
||||
(CCR-2026-0034) reads the exact data path.'
|
||||
custody_child:
|
||||
script: scripts/keycape_secrets_engine_openbao_custody.py
|
||||
tests: tests/test_keycape_secrets_engine_openbao_custody.py
|
||||
status: source-only; never executed
|
||||
requires:
|
||||
- --approval-decision <A3-decision-id>; refused when absent, malformed, equal to the boundary
|
||||
decision, or not recorded in an approved review comment of this CCR
|
||||
- this CCR and sibling CCR-2026-0034 approved with platform-owner, key-cape-owner and net-kingdom-owner
|
||||
approvals and bound_claims_confirmed true
|
||||
- --approval-decision <A3-decision-id>; refused when absent, malformed, equal to
|
||||
the boundary decision, or not recorded in an approved review comment of this CCR
|
||||
- this CCR and sibling CCR-2026-0034 approved with platform-owner, key-cape-owner
|
||||
and net-kingdom-owner approvals and bound_claims_confirmed true
|
||||
- attended warden platform-admin envelope; no BAO_TOKEN/VAULT_TOKEN in environment
|
||||
- umask 077, shell tracing off
|
||||
- --rotate for any run after the first (cas=<observed current version>); without it an existing
|
||||
entry is refused
|
||||
generation: one secrets.token_urlsafe(48) value in process memory (64 chars); never in argv,
|
||||
environment, stdout, stderr, logs, Git or the hub
|
||||
authoritative_write: KV-v2 write with options.cas=0 (absent) or cas=<observed current version>
|
||||
(rotation, --rotate); value only in process memory and the bao stdin/HTTP body; metadata-only
|
||||
preflight
|
||||
- --rotate for any run after the first (cas=<observed current version>); without
|
||||
it an existing entry is refused
|
||||
generation: one secrets.token_urlsafe(48) value in process memory (64 chars); never
|
||||
in argv, environment, stdout, stderr, logs, Git or the hub
|
||||
authoritative_write: KV-v2 write with options.cas=0 (absent) or cas=<observed current
|
||||
version> (rotation, --rotate); value only in process memory and the bao stdin/HTTP
|
||||
body; metadata-only preflight
|
||||
receipt: O_EXCL 0600 JSON with booleans, KV version, request id, CAS, metadata applied/unchanged,
|
||||
CCR/decision ids and CCR digest only
|
||||
failure_cleanup: nothing to clean locally; a written KV version is retained and recorded;
|
||||
recovery is a fresh roll-forward run with --rotate (cas=<current>), never a KV read
|
||||
metadata_write: kv metadata put of openbao.kv_metadata, compared before write against a
|
||||
metadata-only read; skipped when equal
|
||||
failure_cleanup: nothing to clean locally; a written KV version is retained and
|
||||
recorded; recovery is a fresh roll-forward run with --rotate (cas=<current>),
|
||||
never a KV read
|
||||
metadata_write: kv metadata put of openbao.kv_metadata, compared before write against
|
||||
a metadata-only read; skipped when equal
|
||||
rotation_revocation:
|
||||
generation_and_authoritative_version: railiance-platform custody child, version-guarded,
|
||||
roll-forward
|
||||
issuer_pickup: 'key-cape / net-kingdom: ESO refresh then KeyCape restart immediately (exchanges
|
||||
fail closed 401 between KV write and restart)'
|
||||
consumer_pickup: 'automatic: the next secrets-engine command reads the latest version in
|
||||
memory (CCR-2026-0034)'
|
||||
disable_on_compromise: key-cape / net-kingdom remove registration first; platform rotates
|
||||
and detaches the CCR-2026-0034 role policy
|
||||
issuer_pickup: 'key-cape / net-kingdom: ESO refresh then KeyCape restart immediately
|
||||
(exchanges fail closed 401 between KV write and restart)'
|
||||
consumer_pickup: 'automatic: the next secrets-engine command reads the latest version
|
||||
in memory (CCR-2026-0034)'
|
||||
disable_on_compromise: key-cape / net-kingdom remove registration first; platform
|
||||
rotates and detaches the CCR-2026-0034 role policy
|
||||
retire_versions: railiance-platform per separate destroy approval
|
||||
live_order:
|
||||
- 0 founder approvals A1 design, A2 this CCR + CCR-2026-0034 (three reviews, bound claims
|
||||
confirmed for both readers), A3 custody-child decision id
|
||||
- 0 founder approvals A1 design, A2 this CCR + CCR-2026-0034 (three reviews, bound
|
||||
claims confirmed for both readers), A3 custody-child decision id
|
||||
- 1 attended custody child (this repo) writes KV v1 and metadata; non-secret receipt
|
||||
- 2 platform applies policies/roles (this CCR and CCR-2026-0034) and the sso-only ClusterSecretStore
|
||||
+ ExternalSecret; Secret sso/keycape-secrets-engine-openbao-client exists (metadata receipt
|
||||
only)
|
||||
- 2 platform applies policies/roles (this CCR and CCR-2026-0034) and the sso-only
|
||||
ClusterSecretStore + ExternalSecret; Secret sso/keycape-secrets-engine-openbao-client
|
||||
exists (metadata receipt only)
|
||||
- 3 net-kingdom env patch secrets-engine-openbao-client.proposed.patch.yaml (NK-WP-0046)
|
||||
- 4 KeyCape registration merge (KEY-WP-0037-T03)
|
||||
- 5 KeyCape restart immediately after step 4
|
||||
|
|
@ -172,19 +227,19 @@ live_order:
|
|||
- 7 platform OpenBao JWT role/policy secrets-engine-openbao and login proof (RPF-WP-0035-T02)
|
||||
rollback:
|
||||
before_step_3: delete the CCR-2026-0034 OIDC role, then its policy; delete ExternalSecret
|
||||
(uid precondition), then the ESO role/policy; KV entry retained pending separate destroy
|
||||
approval
|
||||
after_step_3: net-kingdom rollout helper restores prior config/Deployment first, then the
|
||||
before_step_3 sequence
|
||||
cleanup_receipts: native, value-free, per step owner; a failed cleanup is recorded as open
|
||||
evidence
|
||||
(uid precondition), then the ESO role/policy; KV entry retained pending separate
|
||||
destroy approval
|
||||
after_step_3: net-kingdom rollout helper restores prior config/Deployment first,
|
||||
then the before_step_3 sequence
|
||||
cleanup_receipts: native, value-free, per step owner; a failed cleanup is recorded
|
||||
as open evidence
|
||||
founder_confirmation_items:
|
||||
- 'bound_claims_confirmed stays false until the founder confirms BOTH readers: this CCR (kubernetes,
|
||||
external-secrets/external-secrets, audience openbao) and sibling CCR-2026-0034 (OIDC, groups=net-kingdom-admins
|
||||
and /assurance/level=aal2).'
|
||||
- 'Approvals: A1 ops-mason design; A2 this CCR plus CCR-2026-0034 with platform-owner, key-cape-owner
|
||||
and net-kingdom-owner reviews; A3 the exact custody-child decision id (must differ from
|
||||
boundary decision 632a8893-0d6a-4591-a50b-40bbdb798f32).'
|
||||
- 'bound_claims_confirmed stays false until the founder confirms BOTH readers: this
|
||||
CCR (kubernetes, external-secrets/external-secrets, audience openbao) and sibling
|
||||
CCR-2026-0034 (OIDC, groups=net-kingdom-admins and /assurance/level=aal2).'
|
||||
- 'Approvals: A1 ops-mason design; A2 this CCR plus CCR-2026-0034 with platform-owner,
|
||||
key-cape-owner and net-kingdom-owner reviews; A3 the exact custody-child decision
|
||||
id (must differ from boundary decision 632a8893-0d6a-4591-a50b-40bbdb798f32).'
|
||||
runbook: docs/provisioning-windows/2026-10-04-1000/keycape/founder-runbook.md
|
||||
related:
|
||||
sibling_ccr: CCR-2026-0034
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ kind: credential-change-request
|
|||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: KeyCape secrets-engine-openbao attended operator reader (in-memory, per command)
|
||||
status: proposed
|
||||
status: applied
|
||||
created: '2026-10-06'
|
||||
updated: '2026-10-06'
|
||||
updated: '2026-10-07'
|
||||
requester:
|
||||
agent: railiance-platform
|
||||
reason: Founder direction 2026-10-06 - the secrets-engine-openbao client secret lives
|
||||
only in OpenBao; ops-mason plan keycape-secrets-engine-openbao-client-custody
|
||||
reason: Founder direction 2026-10-06 - the secrets-engine-openbao client secret
|
||||
lives only in OpenBao; ops-mason plan keycape-secrets-engine-openbao-client-custody
|
||||
(658cdbc, MASON-WP-0009) section 3.3. Sibling of CCR-2026-0033 (issuer ESO reader);
|
||||
one auth role per CCR is this repo's convention (cf. CCR-2026-0019). Replaces the
|
||||
superseded secrets-engine consumer file (SECRETS-WP-0012-T02 D1-D4).
|
||||
one auth role per CCR is this repo's convention (cf. CCR-2026-0019). Replaces
|
||||
the superseded secrets-engine consumer file (SECRETS-WP-0012-T02 D1-D4).
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
|
|
@ -23,12 +23,39 @@ review:
|
|||
- at: '2026-10-06'
|
||||
reviewer: key-cape (owner packet, key-cape 7d96ee4)
|
||||
decision: claim_shape_reported
|
||||
comment: 'KeyCape reports the groups claim is always emitted, so groups_claim groups
|
||||
bound to net-kingdom-admins works as in CCR-2026-0019. MFA is not pinned on the
|
||||
openbao-admin client; it holds only through the provider default requireForAll.
|
||||
KeyCape asks that the role also bind the assurance claim (/assurance/level = aal2,
|
||||
issued only after a verified factor). This is an owner report, not an approval
|
||||
and not the founder confirmation of bound claims.'
|
||||
comment: KeyCape reports the groups claim is always emitted, so groups_claim groups
|
||||
bound to net-kingdom-admins works as in CCR-2026-0019. MFA is not pinned on
|
||||
the openbao-admin client; it holds only through the provider default requireForAll.
|
||||
KeyCape asks that the role also bind the assurance claim (/assurance/level =
|
||||
aal2, issued only after a verified factor). This is an owner report, not an
|
||||
approval and not the founder confirmation of bound claims.
|
||||
- at: '2026-10-07'
|
||||
reviewer: platform-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06)
|
||||
approves CCR-2026-0034 as platform-owner. Covers A2 for this operator OIDC reader
|
||||
(groups=net-kingdom-admins and /assurance/level=aal2 confirmed). Applying the
|
||||
role and policy needs its own go.
|
||||
- at: '2026-10-07'
|
||||
reviewer: key-cape-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06)
|
||||
approves CCR-2026-0034 as key-cape-owner. Covers A2 for this operator OIDC reader
|
||||
(groups=net-kingdom-admins and /assurance/level=aal2 confirmed). Applying the
|
||||
role and policy needs its own go.
|
||||
- at: '2026-10-07'
|
||||
reviewer: net-kingdom-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06)
|
||||
approves CCR-2026-0034 as net-kingdom-owner. Covers A2 for this operator OIDC
|
||||
reader (groups=net-kingdom-admins and /assurance/level=aal2 confirmed). Applying
|
||||
the role and policy needs its own go.
|
||||
- at: '2026-10-07'
|
||||
reviewer: platform-owner, key-cape-owner, net-kingdom-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder decision 73fff3d3-49fd-4535-9040-b2754a3e2024 approves removing
|
||||
the retired bao.coulomb.social OIDC callback from allowed_redirect_uris and
|
||||
one delegated re-apply. Nothing else changes.
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
|
|
@ -51,7 +78,6 @@ openbao:
|
|||
mount: netkingdom
|
||||
role: keycape-secrets-engine-openbao-operator-workload-kv-read
|
||||
allowed_redirect_uris:
|
||||
- https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback
|
||||
- http://localhost:8250/oidc/callback
|
||||
- http://127.0.0.1:8250/oidc/callback
|
||||
oidc_scopes:
|
||||
|
|
@ -66,7 +92,7 @@ openbao:
|
|||
groups:
|
||||
- net-kingdom-admins
|
||||
/assurance/level: aal2
|
||||
bound_claims_confirmed: false
|
||||
bound_claims_confirmed: true
|
||||
policies:
|
||||
- workload-kv-read-keycape-secrets-engine-openbao-operator
|
||||
ttl: 5m
|
||||
|
|
@ -78,8 +104,8 @@ openbao:
|
|||
founder_confirmation_items:
|
||||
- bound_claims_confirmed stays false until the founder confirms BOTH readers' bindings
|
||||
(this role's groups=net-kingdom-admins plus /assurance/level=aal2, and CCR-2026-0033's
|
||||
external-secrets/external-secrets ServiceAccount). An unconfirmed binding blocks the
|
||||
applier (applier-dry-run refuses).
|
||||
external-secrets/external-secrets ServiceAccount). An unconfirmed binding blocks
|
||||
the applier (applier-dry-run refuses).
|
||||
- The aal2 binding is the only MFA guarantee on this lane; the openbao-admin client
|
||||
itself does not pin MFA (KeyCape 7d96ee4).
|
||||
access_frontdoor:
|
||||
|
|
@ -119,6 +145,25 @@ verification:
|
|||
- Founder approvals A1 (ops-mason design), A2 (this CCR and CCR-2026-0033 with three
|
||||
reviews and bound_claims_confirmed true) and a completed CCR-2026-0033 custody
|
||||
run (A3).
|
||||
evidence:
|
||||
- at: '2026-10-07T00:03:24+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-secrets-engine-openbao-operator'
|
||||
- 'Auth role metadata write: auth/netkingdom/role/keycape-secrets-engine-openbao-operator-workload-kv-read'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
- at: '2026-10-07T13:10:47+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-secrets-engine-openbao-operator'
|
||||
- 'Auth role metadata write: auth/netkingdom/role/keycape-secrets-engine-openbao-operator-workload-kv-read'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: Delete the OIDC role first, then the policy; value-free receipt.
|
||||
rotate: Not applicable to the reader; the value rotates through the CCR-2026-0033
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ request_type: workload-kv-read
|
|||
title: Authelia platform SMTP password projection
|
||||
status: active
|
||||
created: '2026-10-06'
|
||||
updated: '2026-10-06'
|
||||
updated: '2026-11-03'
|
||||
requester:
|
||||
agent: codex
|
||||
reason: CSOC-WP-0010 / NK-WP-0047 restore user-requested password reset email; existing
|
||||
|
|
@ -33,18 +33,37 @@ openbao:
|
|||
kv_path: platform/workloads/railiance-telemetry/smtp
|
||||
fields:
|
||||
- SMTP_PASSWORD
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
policy_name: authelia-smtp-eso
|
||||
# Records the policy applied by scripts/authelia_smtp_delivery.py (lane.POLICY).
|
||||
policy_file: openbao/policies/authelia-smtp-eso.hcl
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: authelia-smtp-eso
|
||||
bound_service_account_names:
|
||||
# Role payload applied via repair_eso_kubernetes_auth.role_payload.
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- authelia-smtp-eso
|
||||
service_account_namespaces:
|
||||
- sso
|
||||
bound_claims_confirmed: true
|
||||
policies:
|
||||
- authelia-smtp-eso
|
||||
bound_service_account_namespaces:
|
||||
- sso
|
||||
ttl: 15m
|
||||
audience: openbao
|
||||
max_ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: authelia-smtp
|
||||
selector: Namespace sso and ServiceAccount authelia-smtp-eso only
|
||||
readiness: ready
|
||||
resolvable: true
|
||||
delivery:
|
||||
surface: external-secrets
|
||||
namespace: sso
|
||||
service_account: authelia-smtp-eso
|
||||
secret_store: openbao-authelia-smtp
|
||||
|
|
@ -64,3 +83,34 @@ activation:
|
|||
credential_changed: false
|
||||
consumer: sso/authelia-smtp-eso
|
||||
secret_projection_ready: true
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- Shared mailbox reader; the telemetry consumer and mailbox credential are preserved
|
||||
(receipt password_modified false).
|
||||
- OpenBao authorizes the whole exact KV record, not individual fields. ESO projects
|
||||
only SMTP_PASSWORD.
|
||||
- The SMTP credential can send as the platform mailbox; no IMAP access is granted.
|
||||
- 'Schema normalization 2026-11-03 (RPF-WP-ADHOC-2026-11-03-T04): openbao.auth,
|
||||
policy_file, access_frontdoor, risk, verification and lifecycle restated from
|
||||
the applied lane source and the 2026-10-06 owner receipt. No live change.'
|
||||
verification:
|
||||
positive:
|
||||
- Scoped role and policy readback; SMTP AUTH succeeds without sending mail (receipt
|
||||
smtp_authenticated true, email_sent false).
|
||||
- Dedicated reader admitted and secret projection ready (receipt reader_admitted true).
|
||||
negative:
|
||||
- Coding-agent boundary denies the mailbox record (receipt coding_agent_denied true).
|
||||
- Mailbox credential and telemetry role and Secret unchanged (receipt password_modified
|
||||
false).
|
||||
activation_conditions:
|
||||
- Founder approval and attended owner admission through scripts/authelia_smtp_delivery.py.
|
||||
- Verified owner receipt docs/evidence/authelia-smtp-admission-2026-10-06.json.
|
||||
lifecycle:
|
||||
deactivate: Remove only the authelia-smtp-eso role, policy and sso projection after
|
||||
switching the Authelia notifier off. Preserve the mailbox and other consumers.
|
||||
rotate: Coordinate provider rotation across all mailbox consumers (telemetry, Authelia,
|
||||
email-connect); verify every projection before retiring the predecessor. Never
|
||||
rotate one consumer copy independently.
|
||||
compromised: Disable the Authelia notifier, coordinate shared-mailbox credential rotation
|
||||
through the owner and investigate all admitted consumers.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,211 @@
|
|||
{
|
||||
"id": "CCR-2026-0036",
|
||||
"kind": "credential-change-request",
|
||||
"schema_version": 1,
|
||||
"request_type": "workload-kv-read",
|
||||
"title": "coulomb-social primary archive node-delivery reader",
|
||||
"status": "applied",
|
||||
"created": "2026-10-08",
|
||||
"updated": "2026-10-27",
|
||||
"requester": {
|
||||
"agent": "codex",
|
||||
"reason": "User asked to arrange platform credential delivery; preparation under RPF-WP-0053, no live grant inferred."
|
||||
},
|
||||
"review": {
|
||||
"required": true,
|
||||
"required_approvers": [
|
||||
"platform-owner",
|
||||
"coulomb-social-owner"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"at": "2026-10-27",
|
||||
"reviewer": "User (platform-owner)",
|
||||
"decision": "approved",
|
||||
"comment": "User explicitly approved the scoped Coulomb-only node executor and governed pair delivery. Native reader/provider denials verified before delivery; independent recovery/retention and timer activation remain separately gated.",
|
||||
"source_ref": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e"
|
||||
},
|
||||
{
|
||||
"at": "2026-10-27",
|
||||
"reviewer": "User (coulomb-social-owner)",
|
||||
"decision": "approved",
|
||||
"comment": "User explicitly approved the scoped Coulomb-only node executor and governed pair delivery. Native reader/provider denials verified before delivery; independent recovery/retention and timer activation remain separately gated.",
|
||||
"source_ref": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"domain": "communication",
|
||||
"tenant": "platform",
|
||||
"workload": "coulomb-social",
|
||||
"environment": "production",
|
||||
"purpose": "Read this application backup publisher key pair into the admitted node delivery process; no other workload credential or escrow."
|
||||
},
|
||||
"openbao": {
|
||||
"mount": "platform",
|
||||
"kv_path": "platform/workloads/coulomb-social/backup-primary",
|
||||
"fields": [
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY"
|
||||
],
|
||||
"policy_name": "workload-kv-read-coulomb-social-backup-primary",
|
||||
"policy_file": "openbao/policies/workload-kv-read-coulomb-social-backup-primary.hcl",
|
||||
"metadata_read": false,
|
||||
"token_self_lifecycle": true,
|
||||
"auth": {
|
||||
"method": "kubernetes",
|
||||
"mount": "kubernetes",
|
||||
"role": "coulomb-social-backup-primary-workload-kv-read",
|
||||
"audience": "openbao",
|
||||
"ttl": "5m",
|
||||
"token_max_ttl": "5m",
|
||||
"token_explicit_max_ttl": "5m",
|
||||
"token_no_default_policy": true,
|
||||
"token_num_uses": 4,
|
||||
"token_type": "service",
|
||||
"policies": [
|
||||
"workload-kv-read-coulomb-social-backup-primary"
|
||||
],
|
||||
"bound_claims": {
|
||||
"service_account_names": [
|
||||
"backup-primary-delivery"
|
||||
],
|
||||
"service_account_namespaces": [
|
||||
"coulomb-social"
|
||||
]
|
||||
},
|
||||
"bound_claims_confirmed": true
|
||||
}
|
||||
},
|
||||
"access_frontdoor": {
|
||||
"type": "platform-owner",
|
||||
"catalog_id": "coulomb-social-backup-primary-delivery",
|
||||
"readiness": "applied-pending-verify",
|
||||
"resolvable": false
|
||||
},
|
||||
"delivery": {
|
||||
"surface": "node-private-file",
|
||||
"host": "railiance01",
|
||||
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
|
||||
"target": "/etc/coulomb-social-backup/primary.env",
|
||||
"owner": "root:root",
|
||||
"mode": "0600",
|
||||
"directory_mode": "0700",
|
||||
"procedure": "scripts/application_backup_credentials.py",
|
||||
"admission": {
|
||||
"node_executor_approved": true,
|
||||
"provider_scope_verified": true,
|
||||
"reader_boundary_verified": true,
|
||||
"source_ref": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e"
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"endpoint": "https://s3.nl-ams.scw.cloud",
|
||||
"bucket": "railiance-platform-pg-backup",
|
||||
"prefix": "platform-pg/application-archives/coulomb-social/",
|
||||
"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": {
|
||||
"classification": "high",
|
||||
"notes": [
|
||||
"Admitted node root may request the dedicated ServiceAccount JWT; no RBAC grant or mounted token for this account. Namespace writers and node administrators are inside this credential boundary.",
|
||||
"TokenRequest JWT and five-minute, four-use OpenBao token stay in process memory; only the provider pair is delivered at rest.",
|
||||
"No worker receives IAM/bucket administration, native Barman access, sibling archive access or decryption identity.",
|
||||
"Policy/role construction does not seed values or activate capture, timers, retention, deletion or a production restore."
|
||||
]
|
||||
},
|
||||
"verification": {
|
||||
"positive": [
|
||||
"Exact namespace/ServiceAccount/audience authenticates with this one policy, no default/identity policy, five-minute maximum TTL and four-use bound.",
|
||||
"One own-path KV read followed by self-revocation yields only these two fields at a positive pinned KV version.",
|
||||
"Node delivery produces root-owned 0600 file in 0700 directory; contained readback equality and metadata-only receipt; no secrets in outputs/argv.",
|
||||
"Provider caller conditional multipart publication and version-pinned readback within exact archive prefix work."
|
||||
],
|
||||
"negative": [
|
||||
"Wrong namespace, ServiceAccount and audience cannot log in.",
|
||||
"Sibling application data GET, parent LIST, metadata GET, any KV write and platform admin endpoints are denied.",
|
||||
"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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"at": "2026-10-27",
|
||||
"actor": "codex",
|
||||
"kind": "native_reader_provider_boundary_proof",
|
||||
"result": "passed",
|
||||
"details": {
|
||||
"completed_objects_created": false,
|
||||
"credential_values_printed": false,
|
||||
"kv_version": 2,
|
||||
"negative_login_denials": {
|
||||
"wrong_audience": 403,
|
||||
"wrong_serviceaccount": 403
|
||||
},
|
||||
"own_multipart_create_abort": true,
|
||||
"positive_reader_and_revoke": true,
|
||||
"provider_denials": {
|
||||
"bucket_policy": 403,
|
||||
"delete": 403,
|
||||
"iam_list": 403,
|
||||
"native_prefix": 403,
|
||||
"sibling_prefix": 403
|
||||
},
|
||||
"reader_denials": {
|
||||
"admin": 403,
|
||||
"list": 403,
|
||||
"metadata": 403,
|
||||
"sibling": 403,
|
||||
"write_cas0_existing": 403
|
||||
},
|
||||
"schema": "coulomb.backup-boundary-proof.v1",
|
||||
"status": "verified"
|
||||
},
|
||||
"source_ref": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e"
|
||||
}
|
||||
],
|
||||
"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.",
|
||||
"Pin the final reviewed CCR SHA256 and expected KV version; initialise designated root directory only after admission. Initial delivery may resume identical bytes but cannot rotate.",
|
||||
"Independent age escrow and metadata receipt custody, retention/lifecycle/capacity, first capture/recovery and timer activation remain separate RPF-WP-0053 gates."
|
||||
]
|
||||
},
|
||||
"lifecycle": {
|
||||
"deactivate": "Owner detaches this Kubernetes role first, revokes tokens, and removes only admitted local delivery through an explicit withdrawal record. Do not delete retained archives.",
|
||||
"rotate": "Separate owner-approved CAS rotation and atomic host replacement contract required. This helper refuses different existing bytes; retain old provider identity until new publication/recovery acceptance, then revoke predecessor at provider.",
|
||||
"compromised": "Fence this worker and disable its provider identity first. Owner revokes reader tokens, withdraws reader, rotates pair and reviews retained-object access under incident authority; no automated archive deletion."
|
||||
},
|
||||
"state_hub": {
|
||||
"workplan_id": "RPF-WP-0053",
|
||||
"task_id": "RPF-WP-0053-T02"
|
||||
},
|
||||
"related": {
|
||||
"consumer_workplan": "CSOC-WP-0008",
|
||||
"design": "docs/credential-lane-designs/application-backup-delivery-2026-10-08.md"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
{
|
||||
"id": "CCR-2026-0037",
|
||||
"kind": "credential-change-request",
|
||||
"schema_version": 1,
|
||||
"request_type": "workload-kv-read",
|
||||
"title": "email-connect primary archive node-delivery reader",
|
||||
"status": "applied",
|
||||
"created": "2026-10-08",
|
||||
"updated": "2026-10-08",
|
||||
"requester": {
|
||||
"agent": "codex",
|
||||
"reason": "User asked to arrange platform credential delivery; preparation under RPF-WP-0053, no live grant inferred."
|
||||
},
|
||||
"review": {
|
||||
"required": true,
|
||||
"required_approvers": [
|
||||
"platform-owner",
|
||||
"email-connect-owner"
|
||||
],
|
||||
"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",
|
||||
"tenant": "platform",
|
||||
"workload": "email-connect",
|
||||
"environment": "production",
|
||||
"purpose": "Read this application backup publisher key pair into the admitted node delivery process; no other workload credential or escrow."
|
||||
},
|
||||
"openbao": {
|
||||
"mount": "platform",
|
||||
"kv_path": "platform/workloads/email-connect/backup-primary",
|
||||
"fields": [
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY"
|
||||
],
|
||||
"policy_name": "workload-kv-read-email-connect-backup-primary",
|
||||
"policy_file": "openbao/policies/workload-kv-read-email-connect-backup-primary.hcl",
|
||||
"metadata_read": false,
|
||||
"token_self_lifecycle": true,
|
||||
"auth": {
|
||||
"method": "kubernetes",
|
||||
"mount": "kubernetes",
|
||||
"role": "email-connect-backup-primary-workload-kv-read",
|
||||
"audience": "openbao",
|
||||
"ttl": "5m",
|
||||
"token_max_ttl": "5m",
|
||||
"token_explicit_max_ttl": "5m",
|
||||
"token_no_default_policy": true,
|
||||
"token_num_uses": 4,
|
||||
"token_type": "service",
|
||||
"policies": [
|
||||
"workload-kv-read-email-connect-backup-primary"
|
||||
],
|
||||
"bound_claims": {
|
||||
"service_account_names": [
|
||||
"backup-primary-delivery"
|
||||
],
|
||||
"service_account_namespaces": [
|
||||
"email-connect"
|
||||
]
|
||||
},
|
||||
"bound_claims_confirmed": true
|
||||
}
|
||||
},
|
||||
"access_frontdoor": {
|
||||
"type": "platform-owner",
|
||||
"catalog_id": "email-connect-backup-primary-delivery",
|
||||
"readiness": "applied-pending-verify",
|
||||
"resolvable": false
|
||||
},
|
||||
"delivery": {
|
||||
"surface": "node-private-file",
|
||||
"host": "railiance01",
|
||||
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
|
||||
"target": "/etc/email-connect-backup/primary.env",
|
||||
"owner": "root:root",
|
||||
"mode": "0600",
|
||||
"directory_mode": "0700",
|
||||
"procedure": "scripts/application_backup_credentials.py",
|
||||
"admission": {
|
||||
"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": "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": {
|
||||
"classification": "high",
|
||||
"notes": [
|
||||
"Admitted node root may request the dedicated ServiceAccount JWT; no RBAC grant or mounted token for this account. Namespace writers and node administrators are inside this credential boundary.",
|
||||
"TokenRequest JWT and five-minute, four-use OpenBao token stay in process memory; only the provider pair is delivered at rest.",
|
||||
"No worker receives IAM/bucket administration, native Barman access, sibling archive access or decryption identity.",
|
||||
"Policy/role construction does not seed values or activate capture, timers, retention, deletion or a production restore."
|
||||
]
|
||||
},
|
||||
"verification": {
|
||||
"positive": [
|
||||
"Exact namespace/ServiceAccount/audience authenticates with this one policy, no default/identity policy, five-minute maximum TTL and four-use bound.",
|
||||
"One own-path KV read followed by self-revocation yields only these two fields at a positive pinned KV version.",
|
||||
"Node delivery produces root-owned 0600 file in 0700 directory; contained readback equality and metadata-only receipt; no secrets in outputs/argv.",
|
||||
"Provider caller conditional multipart publication and version-pinned readback within exact archive prefix work."
|
||||
],
|
||||
"negative": [
|
||||
"Wrong namespace, ServiceAccount and audience cannot log in.",
|
||||
"Sibling application data GET, parent LIST, metadata GET, any KV write and platform admin endpoints are denied.",
|
||||
"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.",
|
||||
"Pin the final reviewed CCR SHA256 and expected KV version; initialise designated root directory only after admission. Initial delivery may resume identical bytes but cannot rotate.",
|
||||
"Independent age escrow and metadata receipt custody, retention/lifecycle/capacity, first capture/recovery and timer activation remain separate RPF-WP-0053 gates."
|
||||
]
|
||||
},
|
||||
"lifecycle": {
|
||||
"deactivate": "Owner detaches this Kubernetes role first, revokes tokens, and removes only admitted local delivery through an explicit withdrawal record. Do not delete retained archives.",
|
||||
"rotate": "Separate owner-approved CAS rotation and atomic host replacement contract required. This helper refuses different existing bytes; retain old provider identity until new publication/recovery acceptance, then revoke predecessor at provider.",
|
||||
"compromised": "Fence this worker and disable its provider identity first. Owner revokes reader tokens, withdraws reader, rotates pair and reviews retained-object access under incident authority; no automated archive deletion."
|
||||
},
|
||||
"state_hub": {
|
||||
"workplan_id": "RPF-WP-0053",
|
||||
"task_id": "RPF-WP-0053-T05"
|
||||
},
|
||||
"related": {
|
||||
"consumer_workplan": "EMAIL-WP-0007",
|
||||
"design": "docs/credential-lane-designs/application-backup-delivery-2026-10-08.md"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
id: CCR-2026-0038
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: service-jwt-login
|
||||
title: secrets-engine-openbao KeyCape service-JWT login (isolated mount, login-only
|
||||
policy)
|
||||
status: applied
|
||||
created: '2026-10-09'
|
||||
updated: '2026-10-08'
|
||||
requester:
|
||||
agent: railiance-platform
|
||||
reason: RPF-WP-0035-T02. KEY-WP-0037 finished 2026-10-07 (registration live, issuance
|
||||
verified 10/10, key-cape e0b5fae); CCR-2026-0033/0034 applied. Design docs/credential-lane-designs/secrets-engine-service-jwt.md;
|
||||
source bundle prepared 2026-09-27. ops-mason MASON-WP-0009-T05 waits on this lane
|
||||
to recheck invariant (ii).
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- key-cape-owner
|
||||
- secrets-engine-owner
|
||||
comments:
|
||||
- at: '2026-10-08T22:42:07+00:00'
|
||||
reviewer: platform-owner, key-cape-owner, secrets-engine-owner (founder Bernd
|
||||
Worsch)
|
||||
decision: approved
|
||||
comment: 'Approve service-JWT login lane: mount keycape-services, role secrets-engine-openbao,
|
||||
policy secrets-engine-login-self (self endpoints + deny on the client-secret
|
||||
path).'
|
||||
- at: '2026-10-08T22:42:07+00:00'
|
||||
reviewer: founder
|
||||
decision: binding_confirmed
|
||||
comment: 'Bound claims match KeyCape registration: aud secrets-engine-openbao,
|
||||
sub service:secrets-engine, principal_type service, tenant tenant:platform,
|
||||
roles secrets-engine, scope openbao:login; issuer https://kc.coulomb.social
|
||||
RS256.'
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: secrets-engine
|
||||
environment: production
|
||||
purpose: Let secrets-engine log in to OpenBao with a KeyCape-issued service JWT
|
||||
and receive a 5-minute, 8-use token that can only inspect and revoke itself.
|
||||
openbao:
|
||||
policy_name: secrets-engine-login-self
|
||||
policy_file: openbao/policies/secrets-engine-login-self.hcl
|
||||
guarded_paths:
|
||||
- platform/data/workloads/keycape/secrets-engine-openbao
|
||||
- platform/metadata/workloads/keycape/secrets-engine-openbao
|
||||
auth:
|
||||
method: jwt
|
||||
mount: keycape-services
|
||||
config_file: openbao/auth/keycape-services-config.json
|
||||
role: secrets-engine-openbao
|
||||
role_file: openbao/auth/secrets-engine-jwt-role.json
|
||||
bound_claims_confirmed: true
|
||||
founder_confirmation_items:
|
||||
- bound_claims_confirmed stays false until the founder confirms the binding against
|
||||
the KeyCape registration (aud secrets-engine-openbao, sub service:secrets-engine,
|
||||
principal_type service, tenant tenant:platform, roles secrets-engine, scope openbao:login;
|
||||
issuer https://kc.coulomb.social, RS256). applier-dry-run refuses until then.
|
||||
- The isolated mount keycape-services is new (ops-mason survey 2026-10-07 found no
|
||||
auth/jwt or keycape-services mount). The applier refuses if the path exists with
|
||||
a type other than jwt.
|
||||
access_frontdoor:
|
||||
type: none
|
||||
catalog_id: none
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
delivery:
|
||||
surface: none
|
||||
target: No value is delivered. Before the login proof, the founder may write a local,
|
||||
non-secret probe input from the applied values (mount keycape-services, role
|
||||
secrets-engine-openbao, bound_issuer https://kc.coulomb.social; secrets-engine
|
||||
docs/keycape-openbao-consumer-custody.md, e94dc07). That file is probe input,
|
||||
not the platform contract. railiance-platform publishes the consumer contract
|
||||
file, or confirms the probe input as it, only after the login proof passes.
|
||||
risk:
|
||||
classification: medium
|
||||
notes:
|
||||
- Login identity only. The policy holds lookup-self, capabilities-self and revoke-self,
|
||||
plus explicit deny on the client-secret data and metadata paths (MASON-WP-0009
|
||||
invariant (ii)); no KV read, policy/auth write, SecretID or child token.
|
||||
- A JWT may be replayed while valid (15m); residual access is bounded by the 5m/8-use
|
||||
OpenBao token. In an incident, delete the role, revoke issued tokens; do not disable
|
||||
a shared mount.
|
||||
verification:
|
||||
positive:
|
||||
- Readback after apply matches source for the policy text, mount type jwt, config
|
||||
and role (done by applier-apply; prints nothing).
|
||||
- A secrets-engine --auth service-jwt run (SECRETS-WP-0012-T03 probe-openbao) logs
|
||||
in; the token's policies are exactly [secrets-engine-login-self]; revoke-self
|
||||
succeeds.
|
||||
negative:
|
||||
- sys/capabilities-self for the service token returns deny on both guarded paths
|
||||
and on sys/policies/acl/*, auth/*/role/*, auth/token/create.
|
||||
- Wrong audience, subject, tenant, principal_type, role or scope, a coding-agent
|
||||
JWT and an expired JWT are refused.
|
||||
- A failed login never falls back to bootstrap/env providers.
|
||||
activation_conditions:
|
||||
- Founder approval of this CCR (three reviews) and bound_claims_confirmed true.
|
||||
evidence:
|
||||
- at: '2026-10-08T22:42:13+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/secrets-engine-login-self'
|
||||
- 'JWT auth mount ensured: sys/auth/keycape-services (type jwt)'
|
||||
- 'Auth config write: auth/keycape-services/config'
|
||||
- 'Auth role metadata write: auth/keycape-services/role/secrets-engine-openbao'
|
||||
- Readback matched source for policy, mount type, config and role.
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: Delete auth/keycape-services/role/secrets-engine-openbao, then the policy;
|
||||
removing the exclusively owned empty mount is a later review.
|
||||
rotate: Not applicable; the client secret rotates through the CCR-2026-0033 custody
|
||||
child.
|
||||
compromised: KeyCape disables the registration; platform deletes the role and revokes
|
||||
issued tokens; tokens are 5m/8-use and self-revoked.
|
||||
state_hub:
|
||||
workplan_id: RPF-WP-0035
|
||||
task_id: RPF-WP-0035-T02
|
||||
related_workplan: KEY-WP-0037
|
||||
related:
|
||||
design: docs/credential-lane-designs/secrets-engine-service-jwt.md
|
||||
sibling_ccrs:
|
||||
- CCR-2026-0033
|
||||
- CCR-2026-0034
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
id: CCR-2026-0039
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-operator-matrix
|
||||
title: fluid-telegram hall-of-helix attended operator KV lane (per-path matrix, OIDC
|
||||
aal2)
|
||||
status: applied
|
||||
created: '2026-10-09'
|
||||
updated: '2026-10-09'
|
||||
requester:
|
||||
agent: railiance-platform
|
||||
reason: RPF-WP-0035-T03. Founder approved ops-mason plan fluid-telegram-operator-credential-lane
|
||||
2026-10-09 (approved_spec_sha256 41a0ca2746aa93b6ee9d1ca631517d680ade95c56e7a38b75a55209ef2316b73)
|
||||
and ruled the tenant segment stays coulomb (ops-mason message b72ff77d). Design
|
||||
docs/credential-lane-designs/fluid-telegram-operator-kv.md; key-cape confirmed
|
||||
groups and aal2 claims (message b2ed890f, key-cape e0b5fae).
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- fluid-telegram-owner
|
||||
- key-cape-owner
|
||||
comments:
|
||||
- at: '2026-10-09T10:29:55+00:00'
|
||||
reviewer: platform-owner, fluid-telegram-owner, key-cape-owner (founder Bernd
|
||||
Worsch)
|
||||
decision: approved
|
||||
comment: 'Approve ops-mason plan fluid-telegram-operator-credential-lane (spec
|
||||
41a0ca27): four-entry matrix at coulomb/fluid-telegram/hall-of-helix, OIDC role
|
||||
on netkingdom, agent denies.'
|
||||
- at: '2026-10-09T10:29:55+00:00'
|
||||
reviewer: founder
|
||||
decision: binding_confirmed
|
||||
comment: groups=[fluid-telegram-operators] (LLDAP, member tegwick) and /assurance/level=aal2
|
||||
match key-cape b2ed890f.
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: coulomb
|
||||
workload: fluid-telegram
|
||||
environment: production
|
||||
purpose: Let a member of fluid-telegram-operators with a verified second factor
|
||||
run the attended provisioner for campaign hall-of-helix with exactly the four-entry
|
||||
matrix.
|
||||
openbao:
|
||||
mount: platform
|
||||
campaign_path: platform/workloads/coulomb/fluid-telegram/hall-of-helix
|
||||
policy_name: workload-kv-fluid-telegram-hall-of-helix-operator
|
||||
policy_file: openbao/policies/workload-kv-fluid-telegram-hall-of-helix-operator.hcl
|
||||
boundary_overlay_file: openbao/fluid-telegram/agent-deny-overlay.hcl
|
||||
matrix:
|
||||
- entry: operator-app
|
||||
fields:
|
||||
- api_id
|
||||
- api_hash
|
||||
capabilities:
|
||||
- read
|
||||
- entry: operator-session
|
||||
fields:
|
||||
- session_b64
|
||||
capabilities:
|
||||
- create
|
||||
- read
|
||||
- update
|
||||
- entry: bot-token
|
||||
fields:
|
||||
- token
|
||||
capabilities:
|
||||
- create
|
||||
- read
|
||||
- update
|
||||
- entry: redaction-salt
|
||||
fields:
|
||||
- salt
|
||||
capabilities:
|
||||
- create
|
||||
- read
|
||||
auth:
|
||||
method: oidc
|
||||
mount: netkingdom
|
||||
role: fluid-telegram-hall-of-helix-workload-kv
|
||||
role_file: openbao/auth/fluid-telegram-hall-of-helix-oidc-role.json
|
||||
bound_claims_confirmed: true
|
||||
founder_confirmation_items:
|
||||
- bound_claims_confirmed stays false until the founder confirms groups=[fluid-telegram-operators]
|
||||
(LLDAP group, single member tegwick) and /assurance/level=aal2 against the key-cape
|
||||
confirmation b2ed890f. applier-dry-run refuses until then.
|
||||
- The matrix grants no metadata, list, patch or delete. The salt is create-only by
|
||||
ACL; first-write CAS 0 is the consumer's job (FT-WP-0002) and is verified by an
|
||||
owner-run probe, not by ACL parameters.
|
||||
- The apply appends exact coding-agent denies on all four data and metadata paths
|
||||
to the live agent-high-risk-boundary policy, preserving its other content verbatim,
|
||||
and refuses if only some of those denies are already present.
|
||||
access_frontdoor:
|
||||
type: none
|
||||
catalog_id: none
|
||||
risk:
|
||||
level: high
|
||||
notes:
|
||||
- operator-session is a full Telegram account session. Membership of fluid-telegram-operators
|
||||
is the whole access decision; adding a member widens this lane's reach.
|
||||
- Tokens are 15m (30m maximum), no default policy, num_uses 0 so MTProto session
|
||||
persistence is not interrupted; the provisioner self-revokes at completion.
|
||||
verification:
|
||||
positive:
|
||||
- Readback after apply matches source for boundary denies, policy text and role
|
||||
(done by applier-apply; prints nothing).
|
||||
- An approved member's token reports exactly the matrix capabilities on each entry.
|
||||
negative:
|
||||
- Parent, sibling workload and sibling campaign paths report deny.
|
||||
- A non-member, or a member without aal2, cannot obtain the role.
|
||||
- Coding-agent identities report deny on all four data and metadata paths.
|
||||
- Owner-run disposable probe: salt create succeeds, a second write is denied.
|
||||
activation_conditions:
|
||||
- Founder approval (three reviews) and bound_claims_confirmed true.
|
||||
- Custody moved from the infotech prefix with scripts/fluid_telegram_custody_move.py.
|
||||
evidence:
|
||||
- at: '2026-10-09T10:30:01+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Agent boundary denies ensured: sys/policies/acl/agent-high-risk-boundary (8
|
||||
exact paths)'
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-fluid-telegram-hall-of-helix-operator'
|
||||
- 'OIDC role metadata write: auth/netkingdom/role/fluid-telegram-hall-of-helix-workload-kv'
|
||||
- Readback matched source for boundary denies, policy and role.
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: Delete auth/netkingdom/role/fluid-telegram-hall-of-helix-workload-kv
|
||||
first, then the policy; KV entries stay in custody pending an explicit retention
|
||||
decision.
|
||||
rotate: Session compromise needs Telegram-side session revocation; bot-token rotation
|
||||
needs provider replacement; salt rotation needs a data-owner decision.
|
||||
compromised: Delete the role, revoke issued tokens, revoke the Telegram session.
|
||||
state_hub:
|
||||
workplan: RPF-WP-0035
|
||||
task: RPF-WP-0035-T03
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
id: CCR-2026-0040
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: custody-generate-grant
|
||||
title: Broker grant keycape/client-secret-generate (create-only, generate in custody)
|
||||
status: applied
|
||||
created: '2026-10-09'
|
||||
updated: '2026-10-10'
|
||||
requester:
|
||||
agent: railiance-platform
|
||||
reason: RPF-WP-0054-T04. net-kingdom SecurityCaseType oidc-browser-relying-party
|
||||
step mint-secret (lane keycape-client-secret-generate); ops-warden d9f889b1, ops-mason
|
||||
789feb39. Founder chose the templated create-only grant 2026-10-09 (T02 option
|
||||
A).
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- key-cape-owner
|
||||
comments:
|
||||
- at: '2026-10-09T17:18:53+00:00'
|
||||
reviewer: platform-owner, key-cape-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Approve create-only broker grant keycape/client-secret-generate (RPF-WP-0054-T02
|
||||
option A).
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: keycape
|
||||
environment: production
|
||||
purpose: Let the credential broker create a new KeyCape confidential-client secret
|
||||
at platform/workloads/keycape/<client_id> once, without any value leaving custody.
|
||||
openbao:
|
||||
grant_id: keycape/client-secret-generate
|
||||
kv_prefix: platform/workloads/keycape
|
||||
reserved_entries:
|
||||
- secrets-engine-openbao
|
||||
policy_name: keycape-client-secret-generate
|
||||
policy_file: openbao/policies/keycape-client-secret-generate.hcl
|
||||
issuer_policy: credential-broker-keycape-client-secret-generate-issuer
|
||||
issuer_policy_file: openbao/policies/credential-broker-keycape-client-secret-generate-issuer.hcl
|
||||
token_role: keycape-client-secret-generate
|
||||
token_ttl: 5m
|
||||
founder_confirmation_items:
|
||||
- This apply writes two policies and one token role. It binds the issuer policy to
|
||||
no identity, so nothing can mint the token until a separate review binds it to the
|
||||
caller named in RPF-WP-0054-T01.
|
||||
- Create-only on keycape/+ means a token can create a new client entry once and can
|
||||
never change an existing one. Its widest misuse is an unused entry for a client
|
||||
that does not exist.
|
||||
access_frontdoor:
|
||||
type: none
|
||||
catalog_id: none
|
||||
risk:
|
||||
level: medium
|
||||
notes:
|
||||
- No data read, no update, no metadata write, no list, no delete. Exact denies on
|
||||
reserved entries take precedence over the + glob.
|
||||
- The token lives at most 5m and only inside the broker process.
|
||||
verification:
|
||||
positive:
|
||||
- Readback after apply matches source for both policies and the token role (done
|
||||
by applier-apply; prints nothing).
|
||||
- Disposable probe (T05) creates probe-secret-generate-<date> at version 1.
|
||||
negative:
|
||||
- A second create on the probe path, a data read, update, delete and any path outside
|
||||
keycape/ are denied for the grant token (sys/capabilities-self).
|
||||
- The reserved entry keycape/secrets-engine-openbao reports deny on data and metadata.
|
||||
activation_conditions:
|
||||
- Founder approval (two reviews).
|
||||
- Issuer binding reviewed after RPF-WP-0054-T01; probe passed (T05).
|
||||
evidence:
|
||||
- at: '2026-10-09T17:19:06+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/keycape-client-secret-generate'
|
||||
- 'Issuer policy metadata write: sys/policies/acl/credential-broker-keycape-client-secret-generate-issuer'
|
||||
- 'Token role metadata write: auth/token/roles/keycape-client-secret-generate'
|
||||
- Readback matched source for both policies and the token role.
|
||||
- Issuer policy is not bound to any identity by this apply.
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
- at: '2026-10-10T09:15:46+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/keycape-client-secret-generate'
|
||||
- 'Issuer policy metadata write: sys/policies/acl/credential-broker-keycape-client-secret-generate-issuer'
|
||||
- 'Token role metadata write: auth/token/roles/keycape-client-secret-generate'
|
||||
- Readback matched source for both policies and the token role.
|
||||
- Issuer policy is not bound to any identity by this apply.
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: Delete auth/token/roles/keycape-client-secret-generate, then both policies;
|
||||
generated entries stay in custody with their cases.
|
||||
rotate: Not applicable; rotation is a separate lane (RPF-WP-0054-T07).
|
||||
compromised: Delete the token role and revoke outstanding tokens by accessor; review
|
||||
metadata for entries without an accepted case.
|
||||
state_hub:
|
||||
workplan: RPF-WP-0054
|
||||
task: RPF-WP-0054-T04
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
id: CCR-2026-0041
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: 'forgejo-netkingdom-oidc: keycape-reader-policy (oidc-browser-relying-party@1.0)'
|
||||
status: applied
|
||||
created: '2026-10-09'
|
||||
updated: '2026-10-09'
|
||||
requester:
|
||||
agent: ops-mason
|
||||
reason: 'MASON-WP-0010-T03 build of SecurityCase forgejo-netkingdom-oidc (oidc-browser-relying-party@1.0),
|
||||
render sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23.
|
||||
Accepted by owner, founder on 2026-10-08: key-cape docs/forgejo-oidc-client-request.yaml
|
||||
(status accepted-2026-10-08, KEY-WP-0038-T01); type acknowledged via informed-decision
|
||||
memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e. The
|
||||
case acceptance replaces a fresh executive summary.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
comments:
|
||||
- at: '2026-10-09T18:09:10+00:00'
|
||||
reviewer: platform-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Approve ESO reader for SecurityCase forgejo-netkingdom-oidc (accepted
|
||||
2026-10-08).
|
||||
- at: '2026-10-09T18:09:10+00:00'
|
||||
reviewer: founder
|
||||
decision: binding_confirmed
|
||||
comment: Bound to external-secrets/external-secrets, mirrors CCR-2026-0033.
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: keycape
|
||||
environment: production
|
||||
purpose: KeyCape reads its client secret only through ESO. No metadata, list, parent
|
||||
or sibling access.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/keycape/forgejo
|
||||
fields:
|
||||
- CLIENT_SECRET
|
||||
policy_name: workload-kv-read-keycape-forgejo-eso
|
||||
policy_file: openbao/policies/workload-kv-read-keycape-forgejo-eso.hcl
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: external-secrets-keycape-forgejo
|
||||
audience: openbao
|
||||
ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
policies:
|
||||
- workload-kv-read-keycape-forgejo-eso
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- external-secrets
|
||||
service_account_namespaces:
|
||||
- external-secrets
|
||||
bound_claims_confirmed: true
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: forgejo-keycape-client
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- Rendered from an acknowledged SecurityCaseType; shape reviewed with the type,
|
||||
names checked against live structure.
|
||||
- Exact read on one KV path; no metadata, list, parent or sibling access.
|
||||
verification:
|
||||
positive:
|
||||
- policy workload-kv-read-keycape-forgejo-eso grants read on platform/data/workloads/keycape/forgejo
|
||||
only (capabilities check)
|
||||
negative:
|
||||
- a default-only identity has no read capability on the path
|
||||
- sibling GET and parent LIST are denied
|
||||
activation_conditions:
|
||||
- case acceptance present
|
||||
- bound claims confirmed by the platform owner
|
||||
evidence:
|
||||
- at: '2026-10-09T18:09:18+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-forgejo-eso'
|
||||
- 'Auth role metadata write: auth/kubernetes/role/external-secrets-keycape-forgejo'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: delete the role, then policy workload-kv-read-keycape-forgejo-eso (case
|
||||
rollback)
|
||||
rotate: roll-forward through the credential broker (mint-secret); never a read of
|
||||
the old value
|
||||
compromised: owner incident process; no automatic read, reseed or cleanup
|
||||
security_case:
|
||||
case: forgejo-netkingdom-oidc
|
||||
type: oidc-browser-relying-party@1.0
|
||||
render_digest: sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23
|
||||
acceptance_record: key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08,
|
||||
KEY-WP-0038-T01); type acknowledged via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4,
|
||||
hub decision e418263e
|
||||
accepted_at: '2026-10-08'
|
||||
accepted_by:
|
||||
- owner
|
||||
- founder
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
id: CCR-2026-0042
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: 'forgejo-netkingdom-oidc: consumer-reader-policy (oidc-browser-relying-party@1.0)'
|
||||
status: in_flight
|
||||
created: '2026-10-09'
|
||||
updated: '2026-10-09'
|
||||
requester:
|
||||
agent: ops-mason
|
||||
reason: 'MASON-WP-0010-T03 build of SecurityCase forgejo-netkingdom-oidc (oidc-browser-relying-party@1.0), render
|
||||
sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23. Accepted by owner, founder on 2026-10-08:
|
||||
key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08, KEY-WP-0038-T01); type acknowledged
|
||||
via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e. The case acceptance
|
||||
replaces a fresh executive summary.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
comments: []
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: keycape
|
||||
environment: production
|
||||
purpose: The consuming service reads the same secret. No metadata, list, parent or sibling access.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/keycape/forgejo
|
||||
fields:
|
||||
- CLIENT_SECRET
|
||||
policy_name: workload-kv-read-keycape-forgejo-forgejo
|
||||
policy_file: openbao/policies/workload-kv-read-keycape-forgejo-forgejo.hcl
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
access_frontdoor:
|
||||
type: none
|
||||
catalog_id: forgejo-keycape-client
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- Rendered from an acknowledged SecurityCaseType; shape reviewed with the type, names checked against live structure.
|
||||
- Exact read on one KV path; no metadata, list, parent or sibling access.
|
||||
verification:
|
||||
positive:
|
||||
- policy workload-kv-read-keycape-forgejo-forgejo grants read on platform/data/workloads/keycape/forgejo only
|
||||
(capabilities check)
|
||||
negative:
|
||||
- a default-only identity has no read capability on the path
|
||||
- sibling GET and parent LIST are denied
|
||||
activation_conditions:
|
||||
- case acceptance present
|
||||
- bound claims confirmed by the platform owner
|
||||
lifecycle:
|
||||
deactivate: delete the role, then policy workload-kv-read-keycape-forgejo-forgejo (case rollback)
|
||||
rotate: roll-forward through the credential broker (mint-secret); never a read of the old value
|
||||
compromised: owner incident process; no automatic read, reseed or cleanup
|
||||
security_case:
|
||||
case: forgejo-netkingdom-oidc
|
||||
type: oidc-browser-relying-party@1.0
|
||||
render_digest: sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23
|
||||
acceptance_record: key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08, KEY-WP-0038-T01);
|
||||
type acknowledged via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e
|
||||
accepted_at: '2026-10-08'
|
||||
accepted_by:
|
||||
- owner
|
||||
- founder
|
||||
in_flight:
|
||||
missing_fields:
|
||||
- openbao.auth
|
||||
owner: railiance-apps
|
||||
blocking_reason: >-
|
||||
railiance-apps must choose and review the consumer delivery/auth identity. Native
|
||||
forgejo-gitea and forgejo-runner both use forgejo/default; that shared identity
|
||||
must not be filled as a dedicated consumer binding. RAPPS-WP-0016 intends ESO
|
||||
delivery; current type1.1 templates only the KeyCape ESO role, so a consumer
|
||||
role/delivery refinement needs explicit owner review. Historical type1.0 CCR
|
||||
provenance remains unchanged; reconcile exact current1.1 structure before
|
||||
application. See docs/evidence/2026-10-11-forgejo-first-case/owner-return.md.
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
id: CCR-2026-0043
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: broker-issuer-binding
|
||||
title: Operator OIDC role for the keycape-client-secret-generate broker issuer (net-kingdom-admins,
|
||||
aal2)
|
||||
status: applied
|
||||
created: '2026-10-10'
|
||||
updated: '2026-10-10'
|
||||
requester:
|
||||
agent: railiance-platform
|
||||
reason: RPF-WP-0054-T05. ops-warden (23985370) names the caller as the human operator
|
||||
who runs warden case run, using their own OIDC session. Founder chose net-kingdom-admins
|
||||
+ aal2 on 2026-10-10. The issuer policy is live from CCR-2026-0040.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- key-cape-owner
|
||||
comments:
|
||||
- at: '2026-10-10T08:27:32+00:00'
|
||||
reviewer: platform-owner, key-cape-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: 'Approve issuer binding for keycape-client-secret-generate: net-kingdom-admins
|
||||
+ aal2 (RPF-WP-0054).'
|
||||
- at: '2026-10-10T08:27:32+00:00'
|
||||
reviewer: founder
|
||||
decision: binding_confirmed
|
||||
comment: groups=[net-kingdom-admins] and /assurance/level=aal2, same binding as
|
||||
CCR-2026-0034.
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: credential-broker
|
||||
environment: production
|
||||
purpose: Let a net-kingdom-admins member with a verified second factor log in for
|
||||
15 minutes with only the broker issuer policy, so credential.py generate can mint
|
||||
the create-only keycape/client-secret-generate token.
|
||||
openbao:
|
||||
issuer_policy: credential-broker-keycape-client-secret-generate-issuer
|
||||
auth:
|
||||
method: oidc
|
||||
mount: netkingdom
|
||||
role: credential-broker-keycape-client-secret-generate
|
||||
role_file: openbao/auth/credential-broker-keycape-client-secret-generate-oidc-role.json
|
||||
bound_claims_confirmed: true
|
||||
founder_confirmation_items:
|
||||
- bound_claims_confirmed stays false until the founder confirms groups=[net-kingdom-admins]
|
||||
and /assurance/level=aal2, the same binding as CCR-2026-0034 (key-cape b2ed890f).
|
||||
- The role carries only the issuer policy. It cannot read or write KV; it can mint
|
||||
keycape-client-secret-generate tokens through that token role, and look up and revoke
|
||||
tokens by accessor.
|
||||
- Loopback CLI callbacks only (no UI callback); 15m with unlimited uses inside that
|
||||
lifetime (a limited-use token cannot create the child grant token), no default policy.
|
||||
access_frontdoor:
|
||||
type: none
|
||||
catalog_id: none
|
||||
risk:
|
||||
level: medium
|
||||
notes:
|
||||
- Membership of net-kingdom-admins is the access decision; that group already holds
|
||||
the KeyCape operator reader (CCR-2026-0034).
|
||||
- Worst case within 15m is unused keycape/<client_id> entries; existing entries
|
||||
cannot be overwritten (CCR-2026-0040 is create-only).
|
||||
verification:
|
||||
positive:
|
||||
- Readback after apply matches source for the role (done by applier-apply; prints
|
||||
nothing).
|
||||
- A member with aal2 logs in with role credential-broker-keycape-client-secret-generate
|
||||
and the token's policies are exactly [credential-broker-keycape-client-secret-generate-issuer].
|
||||
negative:
|
||||
- A non-member or a member without aal2 cannot obtain the role.
|
||||
- The issuer token is denied on platform/* and on token creation through any other
|
||||
role.
|
||||
activation_conditions:
|
||||
- Founder approval (two reviews) and bound_claims_confirmed true.
|
||||
- CCR-2026-0040 applied (done 2026-10-09).
|
||||
evidence:
|
||||
- at: '2026-10-10T08:27:38+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Issuer policy confirmed live and matching source: sys/policies/acl/credential-broker-keycape-client-secret-generate-issuer'
|
||||
- 'OIDC role metadata write: auth/netkingdom/role/credential-broker-keycape-client-secret-generate'
|
||||
- Readback matched source for the role.
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
- at: '2026-10-10T09:15:56+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Issuer policy confirmed live and matching source: sys/policies/acl/credential-broker-keycape-client-secret-generate-issuer'
|
||||
- 'OIDC role metadata write: auth/netkingdom/role/credential-broker-keycape-client-secret-generate'
|
||||
- Readback matched source for the role.
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: Delete auth/netkingdom/role/credential-broker-keycape-client-secret-generate.
|
||||
rotate: Not applicable (no stored credential).
|
||||
compromised: Delete the role, revoke issued tokens; review keycape/ metadata for
|
||||
entries without an accepted case.
|
||||
state_hub:
|
||||
workplan: RPF-WP-0054
|
||||
task: RPF-WP-0054-T05
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
id: CCR-2026-0044
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: Name the dedicated reuse-surface hub-writer caller
|
||||
status: applied
|
||||
created: '2026-10-14'
|
||||
updated: '2026-10-14'
|
||||
requester:
|
||||
agent: ops-mason
|
||||
reason: CUST-WP-0081 / State Hub message 08b254af. CCR-2026-0005 is active and does
|
||||
not name a caller. Entity 4a1f663c-0621-0483-edd0-f852274f4b6b carries default,
|
||||
operator-custody and platform-admin, so its token can reach the data path, the
|
||||
metadata path and siblings. Do not attach workload-kv-read-reuse-surface-runtime
|
||||
to that entity. Bind a new OIDC role to a different principal. No secret value
|
||||
is rotated or read by this request.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-operator
|
||||
- reuse-surface-owner
|
||||
comments:
|
||||
- at: '2026-10-14T16:40:00Z'
|
||||
reviewer: grok
|
||||
decision: filed_proposed
|
||||
comment: Filed from ops-mason fd2c5a5 (State Hub 3642bdc4, MASON-WP-0011). Status
|
||||
stays proposed. bound_claims_confirmed stays false until key-cape confirms group
|
||||
reuse-surface-hub-writers and a member entity other than 4a1f663c-0621-0483-edd0-f852274f4b6b.
|
||||
The caller policy file is unchanged; apply, after platform-operator and reuse-surface-owner
|
||||
approval, is what adds lookup-self and revoke-self. CCR-2026-0005 remains the
|
||||
KV and ESO authority. No KV write and no attachment to the platform-admin entity.
|
||||
- at: '2026-10-14T17:44:49+00:00'
|
||||
reviewer: platform-operator, reuse-surface-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Founder go-ahead 2026-10-14. Approves CCR-2026-0044 as platform-operator
|
||||
and reuse-surface-owner. Group reuse-surface-hub-writers, member bernd.worsch-3,
|
||||
second factor enrolled, entity f94b7c34-e151-30bb-b473-df3ccdbb9bf1. That entity
|
||||
is not 4a1f663c-0621-0483-edd0-f852274f4b6b. No KV write. Do not attach the
|
||||
policy to the admin entity.
|
||||
- at: '2026-10-14T17:44:49+00:00'
|
||||
reviewer: founder Bernd Worsch
|
||||
decision: binding_confirmed
|
||||
comment: Bound claims are groups=reuse-surface-hub-writers and /assurance/level=aal2.
|
||||
Member is bernd.worsch-3, entity f94b7c34-e151-30bb-b473-df3ccdbb9bf1. The apply
|
||||
checks that entity's policy names and stops if platform-admin or operator-custody
|
||||
is present.
|
||||
target:
|
||||
domain: financials
|
||||
tenant: reuse
|
||||
workload: reuse-surface
|
||||
environment: production
|
||||
purpose: Attended read of the existing hub writer token by one dedicated caller.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/reuse/reuse-surface/runtime-secrets
|
||||
fields:
|
||||
- REUSE_SURFACE_TOKEN
|
||||
- REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET
|
||||
policy_name: workload-kv-read-reuse-surface-runtime
|
||||
policy_file: openbao/policies/workload-kv-read-reuse-surface-runtime.hcl
|
||||
metadata_read: true
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: oidc
|
||||
mount: netkingdom
|
||||
role: reuse-surface-hub-writer-workload-kv-read
|
||||
allowed_redirect_uris:
|
||||
- http://localhost:8250/oidc/callback
|
||||
- http://127.0.0.1:8250/oidc/callback
|
||||
- http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback
|
||||
oidc_scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
user_claim: sub
|
||||
groups_claim: groups
|
||||
bound_claims_type: string
|
||||
bound_claims:
|
||||
groups:
|
||||
- reuse-surface-hub-writers
|
||||
/assurance/level: aal2
|
||||
bound_claims_confirmed: true
|
||||
policies:
|
||||
- workload-kv-read-reuse-surface-runtime
|
||||
ttl: 15m
|
||||
token_max_ttl: 30m
|
||||
token_explicit_max_ttl: 30m
|
||||
token_no_default_policy: true
|
||||
token_type: service
|
||||
access_frontdoor:
|
||||
type: ops-warden
|
||||
catalog_id: reuse-surface-hub-write-token
|
||||
selector: reuse-surface federation hub write bearer token
|
||||
command: After a passing member receipt, run warden access reuse-surface-hub-write-token
|
||||
--exec --field REUSE_SURFACE_TOKEN -- <silent helper> with the unprinted lane
|
||||
token. Do not use --fetch.
|
||||
resolvable: false
|
||||
readiness: pending-review
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- REUSE_SURFACE_TOKEN writes to the production federation hub. This request does
|
||||
not read or rotate it.
|
||||
- The platform-admin entity must not receive this policy. OpenBao merges entity
|
||||
policies into every token for that entity.
|
||||
- The ESO policy workload-kv-read-reuse-surface-runtime-eso stays on its Kubernetes
|
||||
role. This role does not attach it.
|
||||
- bound_claims_confirmed stays false until key-cape confirms group reuse-surface-hub-writers
|
||||
and a member entity other than 4a1f663c-0621-0483-edd0-f852274f4b6b.
|
||||
- Public bao.coulomb.social ingress is not a callback. Use the workstation tunnel.
|
||||
verification:
|
||||
positive:
|
||||
- A member token's policy set is exactly workload-kv-read-reuse-surface-runtime.
|
||||
- That token's capabilities are read on the exact data path and the exact metadata
|
||||
path.
|
||||
negative:
|
||||
- The same token is deny on platform/data/workloads/reuse/reuse-surface, its metadata
|
||||
parent, and platform/data/workloads/issue-core/issue-core/issue-core-runtime.
|
||||
- A principal outside reuse-surface-hub-writers is refused by the role.
|
||||
- Entity 4a1f663c-0621-0483-edd0-f852274f4b6b is not the member token's entity.
|
||||
activation_conditions:
|
||||
- platform-operator and reuse-surface-owner approve this caller binding.
|
||||
- key-cape confirms the group and that the member entity has no platform-admin or
|
||||
operator-custody identity policy.
|
||||
- Founder applies policy and role through the delegated applier. No KV write.
|
||||
- scripts/verify-reuse-surface-hub-writer.py member and non-member both pass. Receipts
|
||||
stay mode 0600 and contain no token or field value.
|
||||
evidence:
|
||||
- at: '2026-10-14T17:47:43+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-reuse-surface-runtime'
|
||||
- 'Auth role metadata write: auth/netkingdom/role/reuse-surface-hub-writer-workload-kv-read'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: delete auth/netkingdom/role/reuse-surface-hub-writer-workload-kv-read.
|
||||
Leave the KV path, the ESO role, and CCR-2026-0005 delivery in place.
|
||||
rotate: unchanged from CCR-2026-0005. This request does not rotate field values.
|
||||
compromised: detach the role, revoke outstanding tokens for the role, and follow
|
||||
the CCR-2026-0005 compromise procedure if a field value was exposed.
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
id: CCR-2026-0045
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: Let the reuse-surface hub-writer caller query its own capabilities
|
||||
status: applied
|
||||
created: '2026-10-14'
|
||||
updated: '2026-10-15'
|
||||
supersedes: CCR-2026-0044
|
||||
requester:
|
||||
agent: ops-mason
|
||||
reason: NK-WP-0051-T06 / MASON-WP-0012. The CCR-2026-0044 member proof called sys/capabilities
|
||||
with a root or platform-admin token because the lane policy has no capabilities-self
|
||||
grant. This request adds a generated sys/capabilities-self grant (update; the
|
||||
endpoint is a POST and returns capability names only) so the lane token proves
|
||||
its own reach. Policy, role, bound claims and token bounds are otherwise unchanged
|
||||
from CCR-2026-0044. Do not attach workload-kv-read-reuse-surface-runtime to entity
|
||||
4a1f663c-0621-0483-edd0-f852274f4b6b. No secret value is read, written or rotated.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-operator
|
||||
- reuse-surface-owner
|
||||
comments:
|
||||
- at: '2026-10-14T21:00:00Z'
|
||||
reviewer: claude
|
||||
decision: filed_proposed
|
||||
comment: Filed for NK-WP-0051-T06 (RPF-WP-0057). Repo policy file now carries
|
||||
the generated sys/capabilities-self grant; the live policy is unchanged until
|
||||
approval and a delegated apply. Bound claims are those confirmed under CCR-2026-0044
|
||||
(groups=reuse-surface-hub-writers, /assurance/level=aal2); this request does
|
||||
not change them.
|
||||
- at: '2026-10-15T08:55:59+00:00'
|
||||
reviewer: Bernd Worsch (founder; platform-operator and reuse-surface-owner)
|
||||
decision: approved
|
||||
comment: 'Approved in the NetKingdom NK-WP-0051 attended session on 2026-10-15,
|
||||
in response to the explicit request covering both approval roles, CCR-2026-0045
|
||||
capabilities-self metadata apply, and the prepared RPF-WP-0057 login-page deployment.
|
||||
Negative fixture selected: tegwick; founder confirms MFA enrolled and railiance-admins
|
||||
membership only.'
|
||||
target:
|
||||
domain: financials
|
||||
tenant: reuse
|
||||
workload: reuse-surface
|
||||
environment: production
|
||||
purpose: Attended read of the existing hub writer token by one dedicated caller.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/reuse/reuse-surface/runtime-secrets
|
||||
fields:
|
||||
- REUSE_SURFACE_TOKEN
|
||||
- REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET
|
||||
policy_name: workload-kv-read-reuse-surface-runtime
|
||||
policy_file: openbao/policies/workload-kv-read-reuse-surface-runtime.hcl
|
||||
metadata_read: true
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: oidc
|
||||
mount: netkingdom
|
||||
role: reuse-surface-hub-writer-workload-kv-read
|
||||
allowed_redirect_uris:
|
||||
- http://localhost:8250/oidc/callback
|
||||
- http://127.0.0.1:8250/oidc/callback
|
||||
- http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback
|
||||
oidc_scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
user_claim: sub
|
||||
groups_claim: groups
|
||||
bound_claims_type: string
|
||||
bound_claims:
|
||||
groups:
|
||||
- reuse-surface-hub-writers
|
||||
/assurance/level: aal2
|
||||
bound_claims_confirmed: true
|
||||
policies:
|
||||
- workload-kv-read-reuse-surface-runtime
|
||||
ttl: 15m
|
||||
token_max_ttl: 30m
|
||||
token_explicit_max_ttl: 30m
|
||||
token_no_default_policy: true
|
||||
token_type: service
|
||||
token_capabilities_self: true
|
||||
access_frontdoor:
|
||||
type: ops-warden
|
||||
catalog_id: reuse-surface-hub-write-token
|
||||
selector: reuse-surface federation hub write bearer token
|
||||
command: After a passing member receipt, run warden access reuse-surface-hub-write-token
|
||||
--exec --field REUSE_SURFACE_TOKEN -- <silent helper> with the unprinted lane
|
||||
token. Do not use --fetch.
|
||||
resolvable: false
|
||||
readiness: pending-review
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- sys/capabilities-self reports capability names for paths the caller names. It
|
||||
returns no secret value and grants no access.
|
||||
- The platform-admin entity must not receive this policy. OpenBao merges entity
|
||||
policies into every token for that entity.
|
||||
- The ESO policy workload-kv-read-reuse-surface-runtime-eso stays on its Kubernetes
|
||||
role and is unchanged.
|
||||
- Until this apply, the live lane policy differs from openbao/policies/workload-kv-read-reuse-surface-runtime.hcl
|
||||
by this one stanza; CCR-2026-0044 member proofs stay on the earlier operator-token
|
||||
path.
|
||||
verification:
|
||||
positive:
|
||||
- A member token's policy set is exactly workload-kv-read-reuse-surface-runtime.
|
||||
- That token's capabilities are read on the exact data path and the exact metadata
|
||||
path.
|
||||
- The member token can call sys/capabilities-self on the five proof paths without
|
||||
any operator or root token.
|
||||
negative:
|
||||
- The same token is deny on platform/data/workloads/reuse/reuse-surface, its metadata
|
||||
parent, and platform/data/workloads/issue-core/issue-core/issue-core-runtime.
|
||||
- A principal outside reuse-surface-hub-writers is refused by the role.
|
||||
- Entity 4a1f663c-0621-0483-edd0-f852274f4b6b is not the member token's entity.
|
||||
activation_conditions:
|
||||
- platform-operator and reuse-surface-owner approve this request.
|
||||
- Founder applies the policy through the delegated applier. No KV write, no role
|
||||
change beyond re-asserting the CCR-2026-0044 role.
|
||||
- ops-mason scripts/verify-reuse-surface-hub-writer.py member passes using capabilities-self
|
||||
only, and non-member records the OpenBao refusal verbatim. Receipts stay mode
|
||||
0600 with no token or field value.
|
||||
evidence:
|
||||
- at: '2026-10-15T17:13:16+00:00'
|
||||
actor: Bernd Worsch (founder), via codex conduit
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as Bernd Worsch (founder), via codex conduit
|
||||
using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-reuse-surface-runtime'
|
||||
- 'Auth role metadata write: auth/netkingdom/role/reuse-surface-hub-writer-workload-kv-read'
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
- at: '2026-10-15T17:22:52+00:00'
|
||||
actor: Bernd Worsch (founder), via codex conduit
|
||||
kind: positive_verification
|
||||
result: passed
|
||||
details:
|
||||
- Attended bernd.worsch-3 diagnosis aal2 and hub-writers group confirmed; exact
|
||||
workload policy, intended reads, parent/sibling denial and self-revocation passed
|
||||
using capabilities-self. Receipt docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-member.txt
|
||||
- at: '2026-10-15T17:22:52+00:00'
|
||||
actor: Bernd Worsch (founder), via codex conduit
|
||||
kind: negative_verification
|
||||
result: passed
|
||||
details:
|
||||
- 'Founder-selected tegwick diagnosis showed railiance-admins only and aal2 before
|
||||
the workload role refused the groups bound claim. Native sanitized receipt names
|
||||
same uid and preserves error: docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-non-member.txt'
|
||||
lifecycle:
|
||||
deactivate: Re-apply the CCR-2026-0044 policy body (drop the sys/capabilities-self
|
||||
stanza). Leave the role, KV path and ESO lane in place.
|
||||
rotate: unchanged from CCR-2026-0005. This request does not rotate field values.
|
||||
compromised: detach the role, revoke outstanding tokens for the role, and follow
|
||||
the CCR-2026-0005 compromise procedure if a field value was exposed.
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
id: CCR-2026-0046
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: Reuse Surface exact seven private-source repository reader
|
||||
status: approved
|
||||
created: '2026-10-15'
|
||||
updated: '2026-10-15'
|
||||
requester:
|
||||
agent: platform-reader-owner delegated preparation worker
|
||||
reason: CUST-WP-0081 hosted63/70; founder metadata consent excludes credential provisioning.
|
||||
Prepare separate seven-source reader.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-operator
|
||||
- railiance-forge-owner
|
||||
- reuse-surface-owner
|
||||
- railiance-apps-owner
|
||||
comments: []
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: reuse
|
||||
workload: reuse-surface
|
||||
environment: production
|
||||
purpose: Native acquisition of seven owner-consented pinned capability indexes.
|
||||
Entire private repository code becomes readable by this dedicated reader; publication
|
||||
remains only approved metadata.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/reuse/reuse-surface/forgejo-source-read
|
||||
fields:
|
||||
- FORGEJO_SOURCE_TOKEN
|
||||
- API_USER
|
||||
- API_BASE_URL
|
||||
- TOKEN_SCOPES
|
||||
- GENERATED_AT
|
||||
- CCR_ID
|
||||
policy_name: workload-kv-read-reuse-surface-runtime-eso
|
||||
policy_file: openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: reuse-surface-runtime-eso
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- reuse-surface-eso
|
||||
service_account_namespaces:
|
||||
- reuse
|
||||
bound_claims_confirmed: true
|
||||
policies:
|
||||
- workload-kv-read-reuse-surface-runtime-eso
|
||||
ttl: 15m
|
||||
audience: openbao
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: reuse-surface-forgejo-source-read
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
delivery:
|
||||
surface: external-secrets
|
||||
bootstrap_command: warden access openbao-platform-admin-login --exec -- python3
|
||||
scripts/provision-reuse-surface-forgejo-source.py --approval <approved-revision-receipt>
|
||||
--receipt <0600-durable-receipt>
|
||||
provisioning_script: scripts/provision-reuse-surface-forgejo-source.py
|
||||
target: Existing namespace-limited store openbao-reuse. Apps owner adds only proposed
|
||||
data item into ExternalSecret reuse/reuse-surface-runtime. Shared target reuse-surface-env
|
||||
remains intact.
|
||||
forgejo_identity: reuse-surface-source
|
||||
forgejo_team: reuse-surface-source-readers
|
||||
repositories:
|
||||
- coulomb/rapp-openbao
|
||||
- coulomb/rapp-policy-nexus
|
||||
- coulomb/rapp-postgres
|
||||
- coulomb/rapp-sbom-nexus
|
||||
- coulomb/rapp-telemetry
|
||||
- coulomb/rapp-tenant-engine
|
||||
- coulomb/rapp-user-engine
|
||||
token_scopes:
|
||||
- read:repository
|
||||
consumer_gate: Reader must not be delivered until reuse-surface owner supplies verified
|
||||
image digest containing BOTH authenticated redirect rejection/HTTPS enforcement
|
||||
AND runtime-controlled exact URL allowlist checked before credential lookup. Apps
|
||||
owner must deploy REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST mapping the reader environment
|
||||
to exactly fourteen consented pinned index/entry URLs and inventory existing authenticated
|
||||
sources for migration. Current image and redirect-only fix are not approved. Native
|
||||
owner helper must verify all seven pinned indexes and entry hashes after rollout.
|
||||
risk:
|
||||
classification: high
|
||||
cost_estimate_eur: 0
|
||||
notes:
|
||||
- Repository grants disclose entire source of these seven repositories to hub; PAT
|
||||
cannot restrict access to index files.
|
||||
- No all-repositories membership, unrelated private grants, admin privileges or
|
||||
repository writes.
|
||||
- Existing ESO role expands by one exact data-read path; writer OIDC caller remains
|
||||
unchanged and denied new path.
|
||||
- Current consumer accepts arbitrary authenticated source URLs and redirects, allowing
|
||||
bearer disclosure. Delivery barred until full reviewed redirect/HTTPS and runtime-controlled
|
||||
exact URL allowlist patch image plus config is verified.
|
||||
- PAT has no server-side expiry; rotate quarterly and immediately upon incident.
|
||||
- Bootstrap writes only exact new identity/team/PAT/KV. Policy apply, ESO delivery,
|
||||
consumer rollout and registrations require their separate reviewed owner steps.
|
||||
- R1 pagination defect preserved; R2 pages until empty with duplicate/count/bound
|
||||
checks. Deployed-version live complete-grant inventory remains required; code
|
||||
review is not live scope proof.
|
||||
- Quarterly rotation is a proposed manual requirement, not deployed expiry enforcement;
|
||||
due-date owner task/receipt and separate exact-token recovery approval must be
|
||||
established before activation.
|
||||
verification:
|
||||
positive:
|
||||
- All seven repositories private; exact team/code-read flags, one dedicated team
|
||||
member, exact seven memberships, effective permissions read and exactly one read:repository
|
||||
PAT.
|
||||
- Each consented index digest matches from authenticated Forgejo read; final hosted
|
||||
native owner helper verifies pinned entry hashes as well.
|
||||
- OpenBao stdin CAS=0 field write readback matches without printing or persisting
|
||||
value.
|
||||
- ESO binding and namespace condition surveyed and confirmed; native fixed consumer
|
||||
image verified before delivery; final hosted70/70 exactly once.
|
||||
negative:
|
||||
- Known excluded private rapp-core-hub reader request denied (403/404); administrative
|
||||
endpoint and Actions secrets denied.
|
||||
- No additional private effective repositories or teams; no write probing that could
|
||||
itself cause a change.
|
||||
- Scoped writer and default OpenBao identities denied new data path; ESO role denied
|
||||
unrelated siblings and wrong SA/namespace/audience login refused.
|
||||
- Missing/anonymous native authentication refused with no privileged fallback.
|
||||
activation_conditions:
|
||||
- Independent platform/Forgejo/reuse/apps owner reviews of exact artifacts and founder
|
||||
CCR provisioning disposition recorded; no approval inferred from metadata consent.
|
||||
- Confirm existing ESO Kubernetes binding before any delegated metadata apply.
|
||||
- Consumer security patch image digest verified and apps rollout artifacts reviewed/approved
|
||||
before delivery.
|
||||
- Durable bounded bootstrap and positive/negative receipts pass; owner native fetch
|
||||
proof passes all seven pinned sources.
|
||||
- Separate scoped writer registration batch followed by fresh hosted70/70 completion
|
||||
audit.
|
||||
evidence: []
|
||||
lifecycle:
|
||||
deactivate: Separate explicitly approved silent scripts/revoke-reuse-surface-forgejo-source.py
|
||||
validates reviewed single PAT ID/name, admin PATCH only dedicated user to fresh
|
||||
process-memory password, Basic-auth DELETE exact token, then reconciles complete
|
||||
inventory absence. Admin PAT/Sudo alone cannot delete. KV/shared runtime Secret
|
||||
preserved. Apps/platform owners remove only new ESO item/policy stanza through
|
||||
their reviewed path; preserve writer/webhook/public registrations. Recovery helper
|
||||
approval is separate from mint/bootstrap approval.
|
||||
rotate: Attended owner revokes old identified PAT then governed bootstrap remints
|
||||
and verifies; never overwrite differing/orphan KV through bootstrap. Explicit
|
||||
scoped CAS update plan required for rotation.
|
||||
compromised: Revoke all dedicated service-user PATs, remove delivery data item,
|
||||
roll fixed consumer with owners, retain failed sanitized evidence; investigate
|
||||
all seven private source exposure. Do not rotate unrelated credentials without
|
||||
separate scope.
|
||||
revision: 2
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
id: CCR-2026-0047
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: rapp-qonto collector exact audit sender custody and delivery
|
||||
status: applied
|
||||
created: '2026-10-15'
|
||||
updated: '2026-10-15'
|
||||
requester:
|
||||
agent: codex
|
||||
reason: NK-WP-0035 operational receiver admission requires actual Qonto delivery. This proposed new
|
||||
grant requires explicit owner approval of the concrete packet; prior timing rollout approval does
|
||||
not approve it.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-operator
|
||||
- audit-core-owner
|
||||
- rapp-qonto-owner
|
||||
- qonto-assistant-owner
|
||||
comments:
|
||||
- at: '2026-10-15T19:27:15.042944+00:00'
|
||||
reviewer: user explicit session approval (platform-operator, audit-core-owner, rapp-qonto-owner,
|
||||
qonto-assistant-owner)
|
||||
decision: approved
|
||||
comment: User selected "Approve packet and proceed" for the complete exact packet including CCR-2026-0047
|
||||
new bearer/ESO lane, compatible source/receiver upgrades and migrations, exact registry admission,
|
||||
collector and one-hour observation. This approval applies to reviewed exact scopes; prerequisites,
|
||||
attended custody and independent receipts remain required.
|
||||
target:
|
||||
domain: financials
|
||||
tenant: friendly:binky
|
||||
workload: rapp-qonto
|
||||
environment: production
|
||||
purpose: Sender rapp-qonto-collector, sole source qonto-assistant, sole tenant tenant:friendly:binky,
|
||||
may_write=true, may_read=false, secret_policy=redact. Envelope admission load-bearing; preserve
|
||||
the source declaration per-class distinction. One source heartbeat maps to audit-core.heartbeat
|
||||
covering only audit.deny. Wait 2f=7200s, effective deadline3f=10800s. No bank calls or authority
|
||||
changes.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/rapp-qonto/audit-sender
|
||||
fields:
|
||||
- AUDIT_TOKEN
|
||||
- CUSTODY_REQUEST
|
||||
policy_name: workload-kv-read-rapp-qonto-audit
|
||||
policy_file: openbao/policies/workload-kv-read-rapp-qonto-audit.hcl
|
||||
metadata_read: false
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: external-secrets-rapp-qonto-audit
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- external-secrets
|
||||
service_account_namespaces:
|
||||
- external-secrets
|
||||
bound_claims_confirmed: true
|
||||
policies:
|
||||
- workload-kv-read-rapp-qonto-audit
|
||||
ttl: 15m
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: rapp-qonto-audit
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
delivery:
|
||||
surface: external-secrets
|
||||
target: ClusterSecretStore openbao-rapp-qonto-audit restricted to namespace rapp-qonto; ExternalSecret
|
||||
and Secret rapp-qonto/rapp-qonto-audit-sender key bearer. Source manifests/rapp-qonto-audit-sender.yaml.
|
||||
No namespace is created.
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- A bearer permits append only for exact collector sender/source/tenant. Own-source reconciliation
|
||||
is available as receiver contract, cross-source and other read routes remain refused.
|
||||
- The registry update reads existing sender credentials inside the attended platform process only.
|
||||
No registry is delivered to a producer.
|
||||
- OpenBao login TTL limits the ESO reader session, not the audit bearer. Revocation must remove the
|
||||
sender token from the receiver registry and prove refusal.
|
||||
- New paths extend the existing coding-agent deny boundary. No existing high-risk grant is widened.
|
||||
verification:
|
||||
positive:
|
||||
- CAS=0 first provision; durable provenance-bound version1 resume without rotation; exact sender registry
|
||||
CAS merge preserves all existing credentials/senders/other fields.
|
||||
- ESO copies only AUDIT_TOKEN to the named bearer key, exact source/delivery readback, receiver registry
|
||||
delivery and compatible reload.
|
||||
- Actual capture, periodic real elapsed frame, per-instance reconciliation and independent observer
|
||||
acceptance remain required after provisioning.
|
||||
negative:
|
||||
- Proposed or missing owner approval refuses before native calls; drifted custody, sender or source
|
||||
hashes refuse.
|
||||
- Reader cannot read registry, sibling custody, metadata or parent listing; wrong service account/namespace
|
||||
refused; coding-agent boundary deny wins.
|
||||
- Wrong sender source/tenant and non-own-source read routes refused. Audit bearer revocation is separate
|
||||
from reader-session revoke.
|
||||
activation_conditions:
|
||||
- Explicit approval of this complete concrete packet from platform-operator, audit-core-owner, rapp-qonto-owner
|
||||
and qonto-assistant-owner.
|
||||
- Compatible immutable receiver release with cadence parser and per-instance reconciliation is deployed/ready
|
||||
before credential reads.
|
||||
- Use attended Warden platform-admin --exec envelope, no shell-exported credentials; keep nonsecret
|
||||
independent receipt and self-revoke.
|
||||
- Apply only reviewed exact policy/role/projection; retain custody on failure and resume explicitly;
|
||||
no destructive rollback or blind rotation.
|
||||
- No claim of operational completion until native positive/negative and independent observer receipts.
|
||||
evidence:
|
||||
- at: '2026-10-15T19:40:23.546597+00:00'
|
||||
actor: codex via attended user platform-admin (root execution)
|
||||
kind: cadence_receiver_custody_and_native_sender
|
||||
result: passed
|
||||
details:
|
||||
- "Native custody receipt receiver-custody-live.json: protected registry CAS15\u219216, version1\
|
||||
\ new sender custody, Approval credentials and all other senders preserved; exact readers/negative\
|
||||
\ checks/revocation, ESO values and loaded receiver registry matched."
|
||||
- Independent attended wrapper exit0/session self-revocation confirmed by root; initial rejected
|
||||
OPENBAO_TOKEN-ambient attempt made no credential mutation and session self-revocation was confirmed.
|
||||
Retry launch removed all three token aliases before OIDC.
|
||||
- 'Native collector source/tenant admission: heartbeat1 and deny1 reconciled for one exact instance;
|
||||
duplicate200, wrong-source400, wrong-tenant400, archive-read403, invalid-bearer401; pending0.
|
||||
Original202 receipts persisted across actual collector pod restart.'
|
||||
- Source receipts copied unchanged to docs/evidence/2026-10-15-cadence-receiver/. Status applied
|
||||
only; elapsed-hour periodic observation, independent protected observer readback and complete
|
||||
native matrix remain pending.
|
||||
lifecycle:
|
||||
deactivate: Stop only collector; remove only its bearer from receiver via CAS and prove refusal; detach
|
||||
exact reader role and ESO projection; retain custody versions and evidence.
|
||||
rotate: Separate reviewed overlap-first change; this helper only provisions version1 and resumes without
|
||||
rotation.
|
||||
compromised: Stop collector and revoke its exact receiver bearer first; preserve evidence and perform
|
||||
separate reviewed rotation.
|
||||
state_hub:
|
||||
related_workplan: NK-WP-0035
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
{
|
||||
"id": "CCR-2026-0048",
|
||||
"kind": "credential-change-request",
|
||||
"schema_version": 1,
|
||||
"request_type": "workload-kv-read",
|
||||
"title": "NetKingdom identity backup publisher delivery",
|
||||
"status": "verified",
|
||||
"created": "2026-10-16",
|
||||
"updated": "2026-10-16",
|
||||
"requester": {
|
||||
"agent": "codex",
|
||||
"reason": "Operator requested implementation of missing full identity backup coverage under NK-WP-0049."
|
||||
},
|
||||
"review": {
|
||||
"required": true,
|
||||
"required_approvers": [
|
||||
"platform-owner",
|
||||
"net-kingdom-owner",
|
||||
"backup-share-owner"
|
||||
],
|
||||
"comments": [
|
||||
{
|
||||
"reviewer": "User (platform-owner)",
|
||||
"decision": "approved",
|
||||
"source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971",
|
||||
"comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.",
|
||||
"reviewed_at": "2026-10-15T23:51:21.372915+00:00"
|
||||
},
|
||||
{
|
||||
"reviewer": "User (net-kingdom-owner)",
|
||||
"decision": "approved",
|
||||
"source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971",
|
||||
"comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.",
|
||||
"reviewed_at": "2026-10-15T23:51:21.372938+00:00"
|
||||
},
|
||||
{
|
||||
"reviewer": "User (backup-share-owner)",
|
||||
"decision": "approved",
|
||||
"source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971",
|
||||
"comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.",
|
||||
"reviewed_at": "2026-10-15T23:51:21.372944+00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"domain": "infotech",
|
||||
"tenant": "platform",
|
||||
"workload": "net-kingdom-identity-backup",
|
||||
"environment": "production",
|
||||
"purpose": "publisher limited to new encrypted identity bundle folder; no age private key or Backup account password"
|
||||
},
|
||||
"openbao": {
|
||||
"mount": "platform",
|
||||
"kv_path": "platform/workloads/net-kingdom/identity-backup/publisher",
|
||||
"fields": [
|
||||
"NC_WEBDAV_TOKEN"
|
||||
],
|
||||
"policy_name": "net-kingdom-backup-publisher-eso",
|
||||
"policy_file": "openbao/policies/net-kingdom-backup-publisher-eso.hcl",
|
||||
"metadata_read": false,
|
||||
"token_self_lifecycle": true,
|
||||
"auth": {
|
||||
"method": "kubernetes",
|
||||
"mount": "kubernetes",
|
||||
"role": "net-kingdom-backup-publisher-eso",
|
||||
"audience": "openbao",
|
||||
"ttl": "15m",
|
||||
"token_max_ttl": "15m",
|
||||
"token_explicit_max_ttl": "15m",
|
||||
"token_no_default_policy": true,
|
||||
"policies": [
|
||||
"net-kingdom-backup-publisher-eso"
|
||||
],
|
||||
"bound_claims": {
|
||||
"service_account_names": [
|
||||
"net-kingdom-backup-publisher-eso"
|
||||
],
|
||||
"service_account_namespaces": [
|
||||
"sso"
|
||||
]
|
||||
},
|
||||
"bound_claims_confirmed": true
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"host": "nx4069.your-storageshare.de",
|
||||
"account_owner": "Backup",
|
||||
"folder": "/railiance-backups/net-kingdom-identity",
|
||||
"share_type": 3,
|
||||
"share_permissions": 21,
|
||||
"label": "net-kingdom-identity-publisher",
|
||||
"age_private_key_delivered": false,
|
||||
"backup_account_password_delivered": false,
|
||||
"provider_added_resharing_approved": true,
|
||||
"scope_approval_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971"
|
||||
},
|
||||
"delivery": {
|
||||
"surface": "ESO-managed Secret",
|
||||
"namespace": "sso",
|
||||
"secret": "net-kingdom-backup-publisher",
|
||||
"key": "token",
|
||||
"service_account": "net-kingdom-backup-publisher-eso",
|
||||
"reader": "owner-controlled root backup publisher process on railiance01; no agent credential delivery",
|
||||
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786"
|
||||
},
|
||||
"risk": {
|
||||
"classification": "high",
|
||||
"notes": [
|
||||
"Secret-bearing capture is limited to the four reviewed identity services; source/cluster/PVC bindings and private staging are mandatory.",
|
||||
"Publisher can read/create in only this folder; retention can read/delete in only this folder. Both also include explicitly owner-approved provider-added re-sharing, but neither grants update, other folders or account administration.",
|
||||
"Compromise of the retention token can destroy backups in this folder; keep it separate from the publisher and retain independent custody/recovery points.",
|
||||
"Node root and ESO administration are inside this credential boundary. Coding agents must be denied the new data/metadata paths.",
|
||||
"KV initial CAS=0 only; preserve existing values and refuse drift. No rotation of service keys, age keys, operator credentials or existing shares.",
|
||||
"Nextcloud adds permission16 to read-enabled public links. User approved exact normalized21/25 on2026-10-16; tokens remain confined to the encrypted backup folder. Original5/9 attempt failed before credential publication."
|
||||
]
|
||||
},
|
||||
"verification": {
|
||||
"positive": [
|
||||
"Exact share path/permissions and bounded KV/ESO role readback",
|
||||
"Ciphertext create/readback/hash and exact runtime token delivery",
|
||||
"Retention protects recovered point/newest/seven daily/two weekly and uses ETag-conditional deletion"
|
||||
],
|
||||
"negative": [
|
||||
"Publisher overwrite/delete denied; retention create/overwrite denied",
|
||||
"Sibling namespace/path/audience and KV write/metadata access denied",
|
||||
"Unapproved or changed CCR/source, wrong cluster, resource drift and malformed archive refuse execution"
|
||||
],
|
||||
"activation_conditions": [
|
||||
"Explicit platform, NetKingdom and Backup-share owner approval of this exact request and source packet.",
|
||||
"Native exact namespace/ServiceAccount/audience verification and bounded role readback; no coding-agent access.",
|
||||
"First real encrypted capture, off-host readback, independent-key recovery and cleanup before enabling timers.",
|
||||
"Retention protects the independently recovered point and newest backup; no automatic deletion of pre-existing backups."
|
||||
],
|
||||
"actual_native_receipt": {
|
||||
"repository": "net-kingdom",
|
||||
"path": "docs/evidence/2026-10-16-identity-backup-live/publisher-native-delivery-verified.json",
|
||||
"source_ccr_sha256": "a7d5c356ce0c4df9de439a58ad3d36e1578f27cda327c9387e97cfc2e9dc7cdc",
|
||||
"status": "reader_and_delivery_verified",
|
||||
"kv_version": 1,
|
||||
"wrong_service_account_denied": true,
|
||||
"wrong_namespace_denied": true,
|
||||
"wrong_audience_denied": true,
|
||||
"bounded_policy_ttl": true,
|
||||
"eso_equality_verified": true,
|
||||
"temporary_reader_revoked": true
|
||||
},
|
||||
"actual_archive_and_runtime_acceptance": {
|
||||
"owner_repo": "net-kingdom",
|
||||
"archive_recovery": "docs/evidence/2026-10-16-identity-backup-live/actual-full-recovery-review.json",
|
||||
"activation": "docs/evidence/2026-10-16-identity-backup-live/activation-review.json",
|
||||
"recurring_job": "docs/evidence/2026-10-16-identity-backup-live/recurring-job-review.json",
|
||||
"independent_owner_retrieval_and_key_recovery_passed": true,
|
||||
"systemd_job_result": "success",
|
||||
"timers_enabled": true,
|
||||
"retention_pinned_and_newest_protected": true,
|
||||
"native_full_service_authentication_claimed": false
|
||||
}
|
||||
},
|
||||
"state_hub": {
|
||||
"workplan_id": "NK-WP-0049",
|
||||
"task_id": "NK-WP-0049-T03"
|
||||
},
|
||||
"access_frontdoor": {
|
||||
"type": "platform-owner",
|
||||
"catalog_id": "net-kingdom-identity-backup-publisher",
|
||||
"readiness": "pending-review",
|
||||
"resolvable": false
|
||||
},
|
||||
"lifecycle": {
|
||||
"deactivate": "Stop the corresponding timer, withdraw only this reader and disable its dedicated share under owner control; retain archives and recovery keys.",
|
||||
"rotate": "Separate owner-approved share replacement and KV CAS rotation, preserved old access until new readback and recovery pass; no silent replacement by this setup tool.",
|
||||
"compromised": "Fence this executor and revoke its dedicated share/reader. Treat retention-token compromise as possible loss of this folder; preserve independent recovery points and rotate only under incident authority."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
{
|
||||
"id": "CCR-2026-0049",
|
||||
"kind": "credential-change-request",
|
||||
"schema_version": 1,
|
||||
"request_type": "workload-kv-read",
|
||||
"title": "NetKingdom identity backup retention delivery",
|
||||
"status": "verified",
|
||||
"created": "2026-10-16",
|
||||
"updated": "2026-10-16",
|
||||
"requester": {
|
||||
"agent": "codex",
|
||||
"reason": "Operator requested implementation of missing full identity backup coverage under NK-WP-0049."
|
||||
},
|
||||
"review": {
|
||||
"required": true,
|
||||
"required_approvers": [
|
||||
"platform-owner",
|
||||
"net-kingdom-owner",
|
||||
"backup-share-owner"
|
||||
],
|
||||
"comments": [
|
||||
{
|
||||
"reviewer": "User (platform-owner)",
|
||||
"decision": "approved",
|
||||
"source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971",
|
||||
"comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.",
|
||||
"reviewed_at": "2026-10-15T23:51:21.374014+00:00"
|
||||
},
|
||||
{
|
||||
"reviewer": "User (net-kingdom-owner)",
|
||||
"decision": "approved",
|
||||
"source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971",
|
||||
"comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.",
|
||||
"reviewed_at": "2026-10-15T23:51:21.374033+00:00"
|
||||
},
|
||||
{
|
||||
"reviewer": "User (backup-share-owner)",
|
||||
"decision": "approved",
|
||||
"source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971",
|
||||
"comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.",
|
||||
"reviewed_at": "2026-10-15T23:51:21.374039+00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"domain": "infotech",
|
||||
"tenant": "platform",
|
||||
"workload": "net-kingdom-identity-backup",
|
||||
"environment": "production",
|
||||
"purpose": "retention limited to new encrypted identity bundle folder; no age private key or Backup account password"
|
||||
},
|
||||
"openbao": {
|
||||
"mount": "platform",
|
||||
"kv_path": "platform/workloads/net-kingdom/identity-backup/retention",
|
||||
"fields": [
|
||||
"NC_WEBDAV_TOKEN"
|
||||
],
|
||||
"policy_name": "net-kingdom-backup-retention-eso",
|
||||
"policy_file": "openbao/policies/net-kingdom-backup-retention-eso.hcl",
|
||||
"metadata_read": false,
|
||||
"token_self_lifecycle": true,
|
||||
"auth": {
|
||||
"method": "kubernetes",
|
||||
"mount": "kubernetes",
|
||||
"role": "net-kingdom-backup-retention-eso",
|
||||
"audience": "openbao",
|
||||
"ttl": "15m",
|
||||
"token_max_ttl": "15m",
|
||||
"token_explicit_max_ttl": "15m",
|
||||
"token_no_default_policy": true,
|
||||
"policies": [
|
||||
"net-kingdom-backup-retention-eso"
|
||||
],
|
||||
"bound_claims": {
|
||||
"service_account_names": [
|
||||
"net-kingdom-backup-retention-eso"
|
||||
],
|
||||
"service_account_namespaces": [
|
||||
"sso"
|
||||
]
|
||||
},
|
||||
"bound_claims_confirmed": true
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"host": "nx4069.your-storageshare.de",
|
||||
"account_owner": "Backup",
|
||||
"folder": "/railiance-backups/net-kingdom-identity",
|
||||
"share_type": 3,
|
||||
"share_permissions": 25,
|
||||
"label": "net-kingdom-identity-retention",
|
||||
"age_private_key_delivered": false,
|
||||
"backup_account_password_delivered": false,
|
||||
"provider_added_resharing_approved": true,
|
||||
"scope_approval_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971"
|
||||
},
|
||||
"delivery": {
|
||||
"surface": "ESO-managed Secret",
|
||||
"namespace": "sso",
|
||||
"secret": "net-kingdom-backup-retention",
|
||||
"key": "token",
|
||||
"service_account": "net-kingdom-backup-retention-eso",
|
||||
"reader": "owner-controlled root backup retention process on railiance01; no agent credential delivery",
|
||||
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786"
|
||||
},
|
||||
"risk": {
|
||||
"classification": "high",
|
||||
"notes": [
|
||||
"Secret-bearing capture is limited to the four reviewed identity services; source/cluster/PVC bindings and private staging are mandatory.",
|
||||
"Publisher can read/create in only this folder; retention can read/delete in only this folder. Both also include explicitly owner-approved provider-added re-sharing, but neither grants update, other folders or account administration.",
|
||||
"Compromise of the retention token can destroy backups in this folder; keep it separate from the publisher and retain independent custody/recovery points.",
|
||||
"Node root and ESO administration are inside this credential boundary. Coding agents must be denied the new data/metadata paths.",
|
||||
"KV initial CAS=0 only; preserve existing values and refuse drift. No rotation of service keys, age keys, operator credentials or existing shares.",
|
||||
"Nextcloud adds permission16 to read-enabled public links. User approved exact normalized21/25 on2026-10-16; tokens remain confined to the encrypted backup folder. Original5/9 attempt failed before credential publication."
|
||||
]
|
||||
},
|
||||
"verification": {
|
||||
"positive": [
|
||||
"Exact share path/permissions and bounded KV/ESO role readback",
|
||||
"Ciphertext create/readback/hash and exact runtime token delivery",
|
||||
"Retention protects recovered point/newest/seven daily/two weekly and uses ETag-conditional deletion"
|
||||
],
|
||||
"negative": [
|
||||
"Publisher overwrite/delete denied; retention create/overwrite denied",
|
||||
"Sibling namespace/path/audience and KV write/metadata access denied",
|
||||
"Unapproved or changed CCR/source, wrong cluster, resource drift and malformed archive refuse execution"
|
||||
],
|
||||
"activation_conditions": [
|
||||
"Explicit platform, NetKingdom and Backup-share owner approval of this exact request and source packet.",
|
||||
"Native exact namespace/ServiceAccount/audience verification and bounded role readback; no coding-agent access.",
|
||||
"First real encrypted capture, off-host readback, independent-key recovery and cleanup before enabling timers.",
|
||||
"Retention protects the independently recovered point and newest backup; no automatic deletion of pre-existing backups."
|
||||
],
|
||||
"actual_native_receipt": {
|
||||
"repository": "net-kingdom",
|
||||
"path": "docs/evidence/2026-10-16-identity-backup-live/retention-native-delivery-verified.json",
|
||||
"source_ccr_sha256": "d391018abbda0fbc1acec4b5a22c0f99ce65525de940431df376a7552b3746cf",
|
||||
"status": "reader_and_delivery_verified",
|
||||
"kv_version": 1,
|
||||
"wrong_service_account_denied": true,
|
||||
"wrong_namespace_denied": true,
|
||||
"wrong_audience_denied": true,
|
||||
"bounded_policy_ttl": true,
|
||||
"eso_equality_verified": true,
|
||||
"temporary_reader_revoked": true
|
||||
},
|
||||
"actual_archive_and_runtime_acceptance": {
|
||||
"owner_repo": "net-kingdom",
|
||||
"archive_recovery": "docs/evidence/2026-10-16-identity-backup-live/actual-full-recovery-review.json",
|
||||
"activation": "docs/evidence/2026-10-16-identity-backup-live/activation-review.json",
|
||||
"recurring_job": "docs/evidence/2026-10-16-identity-backup-live/recurring-job-review.json",
|
||||
"independent_owner_retrieval_and_key_recovery_passed": true,
|
||||
"systemd_job_result": "success",
|
||||
"timers_enabled": true,
|
||||
"retention_pinned_and_newest_protected": true,
|
||||
"native_full_service_authentication_claimed": false
|
||||
}
|
||||
},
|
||||
"state_hub": {
|
||||
"workplan_id": "NK-WP-0049",
|
||||
"task_id": "NK-WP-0049-T03"
|
||||
},
|
||||
"access_frontdoor": {
|
||||
"type": "platform-owner",
|
||||
"catalog_id": "net-kingdom-identity-backup-retention",
|
||||
"readiness": "pending-review",
|
||||
"resolvable": false
|
||||
},
|
||||
"lifecycle": {
|
||||
"deactivate": "Stop the corresponding timer, withdraw only this reader and disable its dedicated share under owner control; retain archives and recovery keys.",
|
||||
"rotate": "Separate owner-approved share replacement and KV CAS rotation, preserved old access until new readback and recovery pass; no silent replacement by this setup tool.",
|
||||
"compromised": "Fence this executor and revoke its dedicated share/reader. Treat retention-token compromise as possible loss of this folder; preserve independent recovery points and rotate only under incident authority."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
id: CCR-2026-0050
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: Retained flex-auth lifecycle PDP Ed25519 seed ESO read lane
|
||||
status: proposed
|
||||
created: '2026-10-16'
|
||||
updated: '2026-10-21'
|
||||
requester:
|
||||
agent: railiance-platform
|
||||
reason: CUST-WP-0080 / RPF-WP-0035-T12 / FLEX-WP-0024-T06 source proposal. Exact
|
||||
new custody/read scope requires founder review; no approval or native binding
|
||||
is inferred.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- founder
|
||||
- railiance-platform-owner
|
||||
- access-engine-owner
|
||||
- secrets-engine-owner
|
||||
- cluster-runtime-storage-owner
|
||||
comments:
|
||||
- reviewer: railiance-platform-owner (agent railiance-platform, codex)
|
||||
decision: approved
|
||||
comment: "INTENT Secure Custody/Stable Interfaces/Operable and Observable; RPF-WP-0035-T12. Approved Platform source/plan review only for frozen P4 manifest sha256:9eec861838a8e9138f2c821cff3014998fbe74bb01447642bf50d44e8b3b7cb3 and R2 manifest sha256:5be7a7f438624dcb35b7dbc96e7c9e5a8e2d5d1b1966cb4338aa5aecbe3a8b59; own bounded review artifact docs/evidence/2026-10-16-flex-auth-native-plan-review.json; sanitized supplement sha256:f02a9b595d9246a358d19b37bdad8fde158b7f0c9657a6ff088874732511a32c. Exact read policy/role/ESO proposal and one-use CAS0 writer stay separate. Identified manual Applications and recovery gates are mandatory. No founder decision, native execution, confirmed bindings or completed admission assent; current proposed status unchanged."
|
||||
- reviewer: secrets-engine-owner (agent secrets-engine, hub message e73c9ffc-8a6d-4a17-9604-84324c678dc0)
|
||||
decision: approved
|
||||
comment: "2026-10-21, consumer side only. Consumer has no KV read role and receives no seed; public trust is admitted through a founder ssh-keygen -Y signed trust file (secrets-engine ebd8e49, docs/founder-admission.md, SECRETS-WP-0014-T04, decision e4eee965). Conditions: (1) the public receipt (kid, Ed25519 public key as 43-char unpadded base64url, evaluator/mode/policy_package/policy_version/policy_package_digest) is emitted by the attended writer at creation time or derived from its public output, never from a later read of the KV document; (2) the receipt carries the KV version and source/image provenance hashes. Not founder approval, native execution or a binding."
|
||||
- reviewer: access-engine-owner (agent access-engine, hub message 6529bacf-6065-4fb9-a9a2-a41b7ed8fa83)
|
||||
decision: approved
|
||||
comment: "2026-10-21, Access scope only (retained workload/release source, Helm values, candidate image 84c78e0 / sha256:09a83b27...981f, signer delivery/mount, process/outbox fencing/rollback). Reviewed CCR sha256:66b3ea12d3f3bb870fa0aa0e7598e32abdb9bf39efe1adcfac286407abeb98e4 (before the 2026-10-21 review-comment edits), policy sha256:b9f816da25c7770863a5fc7b3b873e9da90b087e71a9052e766bf0a187ff85ef, native plan sha256:22306d50e082fc35c5ff22c31e739a53c7584176969063f7907bc01d77e506e5, R2 manifest sha256:5be7a7f438624dcb35b7dbc96e7c9e5a8e2d5d1b1966cb4338aa5aecbe3a8b59. Notes: seed read once at startup so rotation stays a fenced restart; bound_claims_confirmed needs native readback before activation; consumer policy pin comes from access-engine docs/native-signer-public-pin.json and the public receipt is prepared with access-engine tools/prepare_signer_public_receipt.py (public fields only); keep writer receipt fields kid/public_key/public_fingerprint/kv_version/status/path/source_ccr_sha256 stable. Access rollout founder go is separate: decision 4fab263d-686b-4a06-9cea-8df619c87c03 (FLEX-WP-0024-T07). Not founder approval or native execution."
|
||||
- reviewer: railiance-platform-owner (agent railiance-platform, claude)
|
||||
decision: approved
|
||||
comment: "2026-10-21 runbook review: docs/runbooks/flex-auth-envelope-signing-attended-creation.md prepared. Secrets condition 1 is met for kid/public key/fingerprint/kv_version/source/image hashes by deriving the consumer receipt from the writer receipt (no KV read); the evaluator/mode/policy_package/policy_version/policy_package_digest fields are not in the writer receipt and must come from Access's published pin. Review-scope digest no longer binds expires_at so owner reviews can precede the 120 s window. Pending: cluster-runtime-storage-owner (railiance-cluster, requested f8b4b229), founder. Nothing executed."
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: flex-auth-secrets-engine
|
||||
environment: production
|
||||
purpose: Deliver the Ed25519 seed only to the retained lifecycle PDP startup file.
|
||||
No taskpilot enablement, caller policy changes or private consumer trust discovery.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/flex-auth/envelope-signing
|
||||
fields:
|
||||
- ED25519_PRIVATE_KEY
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
policy_name: workload-kv-read-flex-auth-envelope-signing
|
||||
policy_file: openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: external-secrets-flex-auth-envelope-signing
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- flex-auth-envelope-signing-eso
|
||||
service_account_namespaces:
|
||||
- flex-auth
|
||||
bound_claims_confirmed: false
|
||||
policies:
|
||||
- workload-kv-read-flex-auth-envelope-signing
|
||||
ttl: 15m
|
||||
audience: openbao
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: flex-auth-decision-envelope-signing-key
|
||||
selector: Retained flex-auth/flex-auth-secrets-engine signer only
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
delivery:
|
||||
surface: external-secrets
|
||||
target: Namespace flex-auth; dedicated SA flex-auth-envelope-signing-eso; SecretStore
|
||||
openbao-flex-auth-envelope-signing; ExternalSecret/Secret flex-auth-secrets-engine-envelope-signing;
|
||||
seed property only -> startup file /var/run/secrets/decision-envelope-signing/seed0440,
|
||||
readOnly uid/gid/fsGroup65532; no envFrom
|
||||
bootstrap_command: Attended writer is a distinct exact-source action proposal in
|
||||
docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json; this
|
||||
read CCR authorizes neither seed writes nor any execution by itself.
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- Possession of seed can forge lifecycle PDP envelopes; independent consumer public
|
||||
trust admission and fixed policy pins remain required.
|
||||
- 'KV read is document-granular: dedicated ESO identity can read all three fields;
|
||||
only private field is projected. Signer and Secrets consumer have no KV read role.'
|
||||
- No parent/list/metadata/write/delete capability on ESO role. No token/Secret read
|
||||
or ExternalSecret mutation granted to signer/consumer.
|
||||
- Dedicated role is new scope; founder approval required. Canonical local draft
|
||||
rapp-flex-auth-secrets-engine declaration committed d52f811ed5ec0be27c9c367e71d476125b3228be
|
||||
(tree d4fbad71bdc8ef870603526ccd1f53195d0322af) resolves locally. Remote publication,
|
||||
placement and actual runtime/package join remain open; no missing-declaration waiver.
|
||||
- Controlled outage fence stops every signer pod and disables autoscaling before
|
||||
creation/rotation. Recreate single writer and retained PVC remain Access-owned
|
||||
review gates.
|
||||
- fields declares only projected ED25519_PRIVATE_KEY. The same KV document also
|
||||
has public key/kid, and OpenBao data-read is not field-filtered; do not claim
|
||||
field-level authorization.
|
||||
verification:
|
||||
positive:
|
||||
- Exact SA/namespace/audience login; native exact data read; ESO Ready/SecretSynced;
|
||||
safe metadata resourceVersion and admitted KV version correspondence.
|
||||
- All retained signer replicas restart with admitted kid/source/image and seed/public
|
||||
correspondence; signed non-effect lifecycle allow/deny with durable originals
|
||||
and independent consumer signature verification.
|
||||
negative:
|
||||
- Wrong SA/namespace/audience login refused; sibling/list/metadata/write/delete
|
||||
denied; signer/consumer TokenRequest and Secret/ExternalSecret mutation denied.
|
||||
- Unknown/old kid, wrong policy, tamper, expired/unsigned envelope, wrong caller
|
||||
and missing seed refuse; no taskpilot activation.
|
||||
- Receipt write failure/lost backend response retained unknown; no blind retry/remint;
|
||||
stale CAS cannot overwrite version.
|
||||
activation_conditions:
|
||||
- All five required approvers independently dispose the exact CCR and native writer/fence
|
||||
proposal; founder exact new custody scope approved.
|
||||
- Exact proposed candidate source Git revision 84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0 and immutable
|
||||
OCI reference forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f
|
||||
published by successful ordinary CI402; source OS-lock/64MiB/startup hardening present.
|
||||
Access reviews exact workload/Helm image/mount/outbox source and actual stopped fence;
|
||||
Platform reviews existing GitOps Application sync/freeze; cluster reviews runtime,
|
||||
headroom and new PVC1Gi/local-path actual PV retention. Local draft package d52
|
||||
remains source2b/current05a, not candidate84 adoption; remote placement/runtime join
|
||||
and native mounted-runtime acceptance remain open. Enablement only if separately
|
||||
proposed ArgoCD installation/controller configuration changes; none granted here.
|
||||
- Exact native bound claims verified without printing JWT/Secret; read grant policy
|
||||
and role native readback match source.
|
||||
- Protected child source pinned, silent attended OIDC context verified, target cluster/backend
|
||||
pinned, fence verified and durable receipt custody installed.
|
||||
- Consumer independently admits owner-authenticated public receipt and exact policy/image/trust
|
||||
pins before protected acceptance.
|
||||
evidence: []
|
||||
lifecycle:
|
||||
deactivate: Fence all retained signer processes, stop consumers, disable delivery
|
||||
role/ESO and online trust; retain private KV and audit receipts under explicit
|
||||
owner retention until reviewed destruction.
|
||||
rotate: Pre-admit successor public receipt offline; fence consumers/all signer replicas/no
|
||||
HPA; CAS exact version with new in-memory seed; wait exact ESO version, restart
|
||||
single writer, verify native allow/deny and old-key refusal before reopening.
|
||||
compromised: Immediately remove old online trust and stop all signer processes;
|
||||
forward-only CAS recovery with independently approved successor. Never restore
|
||||
compromised seed; archive historical public keys for offline audit only.
|
||||
state_hub:
|
||||
workplan_id: RPF-WP-0035
|
||||
task_id: RPF-WP-0035-T14
|
||||
decision_id: 18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508
|
||||
decision_api_url: http://127.0.0.1:8000/decisions/18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508
|
||||
152
credential-change-requests/CCR-2026-0051-mas-tem-relay-smtp.yaml
Normal file
152
credential-change-requests/CCR-2026-0051-mas-tem-relay-smtp.yaml
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
id: CCR-2026-0051
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: Temporary MAS Scaleway TEM relay SMTP ESO read lane
|
||||
status: applied
|
||||
created: '2026-10-18'
|
||||
updated: '2026-10-18'
|
||||
requester:
|
||||
agent: ops-mason
|
||||
reason: ops-mason plans/mas-tem-relay-smtp.md (MASON-WP-0014, reviewed 2026-10-18T10:13:55Z)
|
||||
for consumer mail-address-semantics MAS-WP-0003. Hub messages daa119e6, a77e5879
|
||||
(kv_path correction) and 049afa1f. No approval is implied by this filing.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- founder
|
||||
- railiance-platform-owner
|
||||
- mail-address-semantics-owner
|
||||
comments:
|
||||
- at: '2026-10-18T10:36:53+00:00'
|
||||
reviewer: railiance-platform-owner (policy-as-code ccr-standard-lane v1)
|
||||
decision: approved
|
||||
comment: 'Standard lane: passes ccr-standard-lane v1 sha256:5481be201456739b9e01b14b0b11211923d7d838a2e71e2e417af038a05aec95.
|
||||
Platform-owner source review only; founder approval and confirm-binding still
|
||||
required.'
|
||||
- at: '2026-10-18T10:42:00+00:00'
|
||||
reviewer: founder (Bernd Worsch)
|
||||
decision: approved
|
||||
comment: 'Approved 2026-10-18 for ops-mason plans/mas-tem-relay-smtp.md (MASON-WP-0014):
|
||||
build policy workload-kv-read-mas-tem-relay and kubernetes role external-secrets-mas-tem-relay
|
||||
under ops-mason-construct. Temporary; teardown with namespace mas.'
|
||||
- at: '2026-10-18T10:46:25+00:00'
|
||||
reviewer: founder
|
||||
decision: approved
|
||||
comment: 'State Hub decision e633607e-ff3e-46ec-bb6f-143e8b44f1af: APPROVE: CCR-2026-0051
|
||||
path=platform/suppliers/scaleway/mas-tem-relay policy=workload-kv-read-mas-tem-relay
|
||||
auth_role=auth/kubernetes/role/external-secrets-mas-tem-relay; rationale=temporary
|
||||
MAS TEM relay lane, read-only, namespace mas, torn down with mas'
|
||||
- at: '2026-10-18T10:58:26+00:00'
|
||||
reviewer: railiance-platform-owner (claude, native evidence)
|
||||
decision: binding_confirmed
|
||||
comment: 'Native 2026-10-18: SA mas-tem-relay-eso exists in ns mas (consumer mail-address-semantics
|
||||
deploy/railiance/manifests.yaml 2e9a074, automount false). SecretStore mas/openbao-mas-tem-relay
|
||||
references mount kubernetes, role external-secrets-mas-tem-relay, SA mas-tem-relay-eso,
|
||||
audience openbao, and validated Ready at 10:49:56Z, i.e. a successful login
|
||||
with exactly the CCR bound claims. ESO reads the path; the 10:56:56Z failure
|
||||
is consumer-side admission, not auth.'
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: mas-tem-relay
|
||||
environment: production
|
||||
purpose: Deliver Scaleway TEM SMTP relay username/password to namespace mas only.
|
||||
Temporary lane; torn down with namespace mas.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/suppliers/scaleway/mas-tem-relay
|
||||
fields:
|
||||
- username
|
||||
- password
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
policy_name: workload-kv-read-mas-tem-relay
|
||||
policy_file: openbao/policies/workload-kv-read-mas-tem-relay.hcl
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: external-secrets-mas-tem-relay
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- mas-tem-relay-eso
|
||||
service_account_namespaces:
|
||||
- mas
|
||||
bound_claims_confirmed: true
|
||||
policies:
|
||||
- workload-kv-read-mas-tem-relay
|
||||
ttl: 15m
|
||||
audience: openbao
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: mas-tem-relay-smtp
|
||||
selector: Namespace mas, SA mas-tem-relay-eso only
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
delivery:
|
||||
surface: external-secrets
|
||||
target: Namespace mas; dedicated SA mas-tem-relay-eso; ExternalSecret projecting
|
||||
username and password only.
|
||||
bootstrap_command: Not applicable. KV value already written by the founder (v2);
|
||||
this CCR creates no value and authorizes no value read.
|
||||
risk:
|
||||
classification: medium
|
||||
notes:
|
||||
- Supplier SMTP credential can send mail as the TEM sending domain; exposure is
|
||||
limited to namespace mas.
|
||||
- New read scope on a supplier path; founder approval required. No other lane reads
|
||||
this path (ops-mason live survey 2026-10-18T10:13:55Z).
|
||||
- Apply may be run by ops-mason under ops-mason-construct (MASON-IN-0005) once installed
|
||||
(MASON-WP-0015-T02) and this CCR is approved; that grant covers workload-kv-read-*
|
||||
policies and external-secrets-* roles only.
|
||||
verification:
|
||||
positive:
|
||||
- Exact SA/namespace/audience login succeeds; ESO Ready/SecretSynced; metadata version
|
||||
matches admitted KV version 2 without printing values.
|
||||
negative:
|
||||
- Wrong SA, namespace or audience login refused; sibling paths under platform/suppliers/scaleway,
|
||||
list, metadata, write and delete denied.
|
||||
activation_conditions:
|
||||
- Founder, platform owner and consumer owner approve the exact CCR.
|
||||
- Native bound claims confirmed (confirm-binding) and native policy/role readback
|
||||
matches source.
|
||||
evidence:
|
||||
- at: '2026-10-18T10:44:23+00:00'
|
||||
actor: ops-mason (claude-MASON-WP-0014-T03, ops-mason-construct)
|
||||
kind: apply
|
||||
result: success
|
||||
details:
|
||||
- '2026-10-18: policy workload-kv-read-mas-tem-relay written from openbao/policies/workload-kv-read-mas-tem-relay.hcl;
|
||||
read back byte-equal'
|
||||
- 'kubernetes role external-secrets-mas-tem-relay written: SA mas-tem-relay-eso,
|
||||
ns mas, audience openbao, token_policies [workload-kv-read-mas-tem-relay], ttl/max/explicit_max
|
||||
15m, token_no_default_policy true; read back matches CCR'
|
||||
- KV platform/suppliers/scaleway/mas-tem-relay present (v2, founder-written);
|
||||
no value read
|
||||
- 'pending: SA mas-tem-relay-eso not yet applied (bound_claims_confirmed false);
|
||||
confirm-binding and ESO sync next'
|
||||
- at: '2026-10-18T10:58:27+00:00'
|
||||
actor: railiance-platform (claude)
|
||||
kind: native-binding
|
||||
result: success
|
||||
details:
|
||||
- SecretStoreValid 2026-10-18T10:49:56Z with the exact CCR SA/namespace/audience/role;
|
||||
ESO provider read succeeded; ops-mason apply readback 102668f.
|
||||
- 'Open: ExternalSecret mas/mas-relay refused by admission policy reject-secret-last-applied
|
||||
(client-side apply annotation copied to target); fix sent to mail-address-semantics
|
||||
1a76a832. Negative checks (wrong SA/namespace/audience, sibling, list, metadata,
|
||||
write) not yet run.'
|
||||
lifecycle:
|
||||
deactivate: Delete ExternalSecret and role external-secrets-mas-tem-relay, then
|
||||
policy workload-kv-read-mas-tem-relay, when namespace mas is torn down.
|
||||
rotate: Founder writes a new KV version via UI; ESO refresh picks it up; verify
|
||||
metadata version only.
|
||||
compromised: Revoke the SMTP credential at Scaleway, disable the role, rotate the
|
||||
KV value, then re-enable after review.
|
||||
state_hub:
|
||||
decision_id: e633607e-ff3e-46ec-bb6f-143e8b44f1af
|
||||
decision_api_url: http://127.0.0.1:8000/decisions/e633607e-ff3e-46ec-bb6f-143e8b44f1af
|
||||
decision_resolved_at: '2026-10-18T10:46:17.864665Z'
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
id: CCR-2026-0052
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: NetKingdom identity-provisioner directory bind (svc-provisioner) delivery
|
||||
status: verified
|
||||
created: '2026-10-30'
|
||||
updated: '2026-10-30'
|
||||
requester:
|
||||
agent: claude-code
|
||||
reason: 'NK-WP-0056-T04 pilot: move the first LLDAP service bind out of the founder''s
|
||||
personal SOPS custody into platform custody, so later rotations need neither a
|
||||
decrypted custody copy nor a person handling the value. svc-provisioner is the
|
||||
simplest consumer: its own Secret, two keys, no embedded config.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- net-kingdom-owner
|
||||
comments:
|
||||
- at: '2026-10-30T22:04:42+00:00'
|
||||
reviewer: User (platform-owner)
|
||||
decision: approved
|
||||
comment: 'Approved as proposed: read-only svc-provisioner lane, CAS=0 from live
|
||||
value, ESO Merge of password only.'
|
||||
- at: '2026-10-30T22:04:43+00:00'
|
||||
reviewer: User (net-kingdom-owner)
|
||||
decision: approved
|
||||
comment: Approved as proposed for NK-WP-0056-T04 pilot.
|
||||
- at: '2026-10-30T22:06:09+00:00'
|
||||
reviewer: User (platform-owner)
|
||||
decision: binding_confirmed
|
||||
comment: 'Binding confirmed: ServiceAccount net-kingdom-svc-provisioner-bind-eso
|
||||
in namespace sso only, audience openbao, as declared in net-kingdom sso-mfa/k8s/identity-provisioner/bind-eso.yaml.'
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-identity-provisioner
|
||||
environment: production
|
||||
purpose: Hold the svc-provisioner LLDAP bind password in platform custody and project
|
||||
only that value into the existing Secret sso/identity-provisioner-ldap; no ld-admin,
|
||||
admin or other service bind
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/net-kingdom/directory-binds/svc-provisioner
|
||||
fields:
|
||||
- LDAP_BIND_PASSWORD
|
||||
policy_name: net-kingdom-svc-provisioner-bind-eso
|
||||
policy_file: openbao/policies/net-kingdom-svc-provisioner-bind-eso.hcl
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: net-kingdom-svc-provisioner-bind-eso
|
||||
audience: openbao
|
||||
ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
policies:
|
||||
- net-kingdom-svc-provisioner-bind-eso
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- net-kingdom-svc-provisioner-bind-eso
|
||||
service_account_namespaces:
|
||||
- sso
|
||||
bound_claims_confirmed: true
|
||||
access_frontdoor:
|
||||
type: platform-owner
|
||||
catalog_id: net-kingdom-svc-provisioner-bind
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
lifecycle:
|
||||
deactivate: Delete the ExternalSecret (deletionPolicy Retain keeps the working Secret),
|
||||
then withdraw the role and policy; the KV entry stays as custody until the founder's
|
||||
copy is confirmed current.
|
||||
rotate: Change the svc-provisioner password in LLDAP and write the new value as
|
||||
the next KV version with CAS on the current version; ESO projects it and the provisioner
|
||||
restarts. Until NK-WP-0056 executors exist this is founder-run.
|
||||
compromised: Disable or reset svc-provisioner in LLDAP first (it is in lldap_admin),
|
||||
then rotate as above and review directory changes made under that account; fence
|
||||
the reader role if its boundary is in doubt.
|
||||
delivery:
|
||||
surface: ESO-managed Secret key (Merge into existing Secret)
|
||||
namespace: sso
|
||||
secret: identity-provisioner-ldap
|
||||
key: password
|
||||
service_account: net-kingdom-svc-provisioner-bind-eso
|
||||
reader: identity-provisioner Deployment in sso (existing secretKeyRef, unchanged);
|
||||
no agent credential delivery
|
||||
manifest: 'net-kingdom: sso-mfa/k8s/identity-provisioner/bind-eso.yaml'
|
||||
cluster_uid: a553c742-0115-43d4-99a4-a5ca56fe0786
|
||||
risk:
|
||||
classification: medium
|
||||
notes:
|
||||
- 'svc-provisioner is a member of lldap_admin (founder decision 2026-10-28, exception
|
||||
recorded in the Service Identity standard): a leak grants directory administration.
|
||||
Treat the path as high-value even though the lane itself is a single-field read.'
|
||||
- Initial KV write is CAS=0 with the value already live in the Secret and the founder's
|
||||
custody; no rotation happens in this request. An existing different KV value refuses.
|
||||
- 'ESO uses creationPolicy Merge so the existing Secret (created by apply-service-secrets.py)
|
||||
keeps its username key and owner; only password is projected. deletionPolicy Retain:
|
||||
removing the ExternalSecret leaves the working Secret in place.'
|
||||
- Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary
|
||||
before the first write.
|
||||
- Node root, ESO administration and platform-admin remain inside this credential
|
||||
boundary.
|
||||
verification:
|
||||
positive:
|
||||
- Exact policy, role and bound claims read back; native reader login as the ESO
|
||||
ServiceAccount gets only this policy with a lease of at most 15m
|
||||
- KV value equals the live Secret password before ESO is applied (setup) and after
|
||||
ESO sync (verify), compared in memory only
|
||||
- ExternalSecret Ready/SecretSynced; identity-provisioner readiness (includes directory
|
||||
login) stays Ready after a restart
|
||||
negative:
|
||||
- Reader cannot read metadata, a sibling path, the other directory-bind paths or
|
||||
create tokens
|
||||
- Wrong ServiceAccount, wrong audience and wrong namespace logins are denied
|
||||
- Coding-agent boundary denies data and metadata of the path
|
||||
activation_conditions:
|
||||
- Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound
|
||||
by the setup tool).
|
||||
- Attended platform-admin (aal2) session for setup; the founder runs it, value read
|
||||
from the decrypted custody copy without display.
|
||||
- Policy, role and KV in place and equal to live before the ExternalSecret is applied.
|
||||
- Positive and negative results recorded in value-free receipts.
|
||||
evidence:
|
||||
- at: '2026-10-30T22:23:46+00:00'
|
||||
actor: founder (attended), claude-code guiding
|
||||
kind: verifier_custody_and_rollout
|
||||
result: passed
|
||||
details:
|
||||
- '2026-10-30: bind-lane.py setup (custody equal to live, coding agents fenced,
|
||||
KV v1 CAS=0, policy+role) and verify (exact policy/TTL, wrong SA and audience
|
||||
denied, metadata/sibling/token-create denied, KV equals delivered Secret, reader
|
||||
revoked) passed; identity-provisioner rolled out Ready. First ESO apply refused
|
||||
by reject-secret-last-applied, fixed with target.template.metadata {} (net-kingdom
|
||||
985cfc3). Evidence: net-kingdom docs/evidence/2026-10-30-nk56-svc-provisioner-bind/
|
||||
(87e57aa).'
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
id: CCR-2026-0053
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: NetKingdom Authelia directory bind (svc-authelia) delivery
|
||||
status: verified
|
||||
created: '2026-10-31'
|
||||
updated: '2026-10-31'
|
||||
requester:
|
||||
agent: claude-code
|
||||
reason: 'NK-WP-0056-T04, second lane after the svc-provisioner pilot (CCR-2026-0052):
|
||||
move the svc-authelia LLDAP bind out of the founder''s personal SOPS custody into
|
||||
platform custody, so its rotation needs neither a decrypted custody copy nor a
|
||||
person handling the value.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- net-kingdom-owner
|
||||
comments:
|
||||
- at: '2026-10-31T09:03:46+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: approved
|
||||
comment: 'Approved as proposed: read-only svc-authelia lane, CAS=0 from live value,
|
||||
ESO Merge of ldap_password only.'
|
||||
- at: '2026-10-31T09:03:53+00:00'
|
||||
reviewer: net-kingdom-owner
|
||||
decision: approved
|
||||
comment: Approved as proposed for NK-WP-0056-T04.
|
||||
- at: '2026-10-31T09:04:03+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: binding_confirmed
|
||||
comment: 'Binding confirmed: ServiceAccount net-kingdom-svc-authelia-bind-eso
|
||||
in namespace sso only, audience openbao, as declared in net-kingdom sso-mfa/k8s/authelia/bind-eso.yaml.'
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-authelia
|
||||
environment: production
|
||||
purpose: Hold the svc-authelia LLDAP bind password in platform custody and project
|
||||
only that value into the key ldap_password of the existing Secret sso/authelia-secrets;
|
||||
no session, storage or OIDC key, no ld-admin, admin or other service bind
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/net-kingdom/directory-binds/svc-authelia
|
||||
fields:
|
||||
- LDAP_BIND_PASSWORD
|
||||
policy_name: net-kingdom-svc-authelia-bind-eso
|
||||
policy_file: openbao/policies/net-kingdom-svc-authelia-bind-eso.hcl
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: net-kingdom-svc-authelia-bind-eso
|
||||
audience: openbao
|
||||
ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
policies:
|
||||
- net-kingdom-svc-authelia-bind-eso
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- net-kingdom-svc-authelia-bind-eso
|
||||
service_account_namespaces:
|
||||
- sso
|
||||
bound_claims_confirmed: true
|
||||
access_frontdoor:
|
||||
type: platform-owner
|
||||
catalog_id: net-kingdom-svc-authelia-bind
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
lifecycle:
|
||||
deactivate: Delete the ExternalSecret (deletionPolicy Retain keeps the working Secret),
|
||||
then withdraw the role and policy; the KV entry stays as custody until the founder's
|
||||
copy is confirmed current.
|
||||
rotate: Change the svc-authelia password in LLDAP and write the new value as the
|
||||
next KV version with CAS on the current version; ESO projects it and Authelia
|
||||
is restarted, because it reads ldap_password only at start-up. Until NK-WP-0056
|
||||
executors exist this is founder-run.
|
||||
compromised: Reset svc-authelia in LLDAP first (read-only bind, lldap_strict_readonly),
|
||||
then rotate as above; fence the reader role if its boundary is in doubt.
|
||||
delivery:
|
||||
surface: ESO-managed Secret key (Merge into existing Secret)
|
||||
namespace: sso
|
||||
secret: authelia-secrets
|
||||
key: ldap_password
|
||||
service_account: net-kingdom-svc-authelia-bind-eso
|
||||
reader: Authelia Deployment in sso (existing secret volume, file /run/secrets/authelia/ldap_password,
|
||||
unchanged); no agent credential delivery
|
||||
manifest: 'net-kingdom: sso-mfa/k8s/authelia/bind-eso.yaml'
|
||||
cluster_uid: a553c742-0115-43d4-99a4-a5ca56fe0786
|
||||
risk:
|
||||
classification: medium
|
||||
notes:
|
||||
- svc-authelia is a read-only directory bind (lldap_strict_readonly); a leak exposes
|
||||
directory attributes (users, groups, emails), not write access. Authelia's users
|
||||
filter excludes svc-* accounts from login.
|
||||
- authelia-secrets also holds the session, storage-encryption and OIDC keys. ESO
|
||||
Merge projects only ldap_password; the reader policy cannot reach any other path.
|
||||
- Initial KV write is CAS=0 with the value already live in the Secret and the founder's
|
||||
custody; no rotation happens in this request. An existing different KV value refuses.
|
||||
- Setup also refuses unless the live authelia-config binds uid=svc-authelia, so
|
||||
the projected value cannot belong to another account.
|
||||
- Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary
|
||||
before the first write.
|
||||
- Node root, ESO administration and platform-admin remain inside this credential
|
||||
boundary.
|
||||
verification:
|
||||
positive:
|
||||
- Exact policy, role and bound claims read back; native reader login as the ESO
|
||||
ServiceAccount gets only this policy with a lease of at most 15m
|
||||
- KV value equals the live Secret ldap_password before ESO is applied (setup) and
|
||||
after ESO sync (verify), compared in memory only
|
||||
- ExternalSecret Ready/SecretSynced; Authelia stays Ready and a first-factor login
|
||||
works after a restart
|
||||
negative:
|
||||
- Reader cannot read metadata, a sibling path, the other directory-bind paths or
|
||||
create tokens
|
||||
- Wrong ServiceAccount, wrong audience and wrong namespace logins are denied
|
||||
- Coding-agent boundary denies data and metadata of the path
|
||||
activation_conditions:
|
||||
- Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound
|
||||
by the setup tool).
|
||||
- Attended platform-admin (aal2) session for setup; the founder runs it, value read
|
||||
from the decrypted custody copy without display.
|
||||
- Policy, role and KV in place and equal to live before the ExternalSecret is applied.
|
||||
- Positive and negative results recorded in value-free receipts.
|
||||
evidence:
|
||||
- at: '2026-10-31T09:11:39+00:00'
|
||||
actor: founder (attended), claude-code guiding
|
||||
kind: verifier_custody_and_rollout
|
||||
result: passed
|
||||
details:
|
||||
- '2026-10-31: bind-lane.py setup (live authelia-config binds svc-authelia, custody
|
||||
equal to live, coding agents fenced, KV v1 CAS=0, policy+role) and verify (exact
|
||||
policy/TTL, wrong SA and audience denied, metadata/sibling/token-create denied,
|
||||
KV equals delivered ldap_password, reader revoked) passed; ExternalSecret Ready
|
||||
on first apply; Authelia restarted and admin signed in with OTP. Evidence: net-kingdom
|
||||
docs/evidence/2026-10-31-nk56-svc-authelia-bind/ (7f37e25).'
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
id: CCR-2026-0054
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: NetKingdom KeyCape directory bind (svc-keycape) delivery
|
||||
status: verified
|
||||
created: '2026-10-31'
|
||||
updated: '2026-10-31'
|
||||
requester:
|
||||
agent: claude-code
|
||||
reason: 'NK-WP-0056-T04, third lane (after CCR-2026-0052 svc-provisioner and CCR-2026-0053
|
||||
svc-authelia): move the svc-keycape LLDAP bind out of the founder''s personal
|
||||
SOPS custody into platform custody. KeyCape reads it from KEYCAPE_LLDAP_BIND_PW
|
||||
(key-cape KEY-WP-0046), so ESO owns a dedicated Secret instead of one field inside
|
||||
keycape-config.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- net-kingdom-owner
|
||||
comments:
|
||||
- at: '2026-10-31T09:36:13+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: approved
|
||||
comment: 'Approved as proposed: read-only svc-keycape lane, CAS=0 from live value,
|
||||
ESO-owned dedicated Secret keycape-lldap-bind.'
|
||||
- at: '2026-10-31T09:36:25+00:00'
|
||||
reviewer: net-kingdom-owner
|
||||
decision: approved
|
||||
comment: Approved as proposed for NK-WP-0056-T04, including the KeyCape image
|
||||
main-e2b5fa1.
|
||||
- at: '2026-10-31T09:36:35+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: binding_confirmed
|
||||
comment: 'Binding confirmed: ServiceAccount net-kingdom-svc-keycape-bind-eso in
|
||||
namespace sso only, audience openbao, as declared in net-kingdom sso-mfa/k8s/keycape/bind-eso.yaml.'
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-keycape
|
||||
environment: production
|
||||
purpose: Hold the svc-keycape LLDAP bind password in platform custody and deliver
|
||||
it as the dedicated Secret sso/keycape-lldap-bind (key password), read by KeyCape
|
||||
as KEYCAPE_LLDAP_BIND_PW; no signing key, client or other keycape-config value,
|
||||
no ld-admin, admin or other service bind
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/net-kingdom/directory-binds/svc-keycape
|
||||
fields:
|
||||
- LDAP_BIND_PASSWORD
|
||||
policy_name: net-kingdom-svc-keycape-bind-eso
|
||||
policy_file: openbao/policies/net-kingdom-svc-keycape-bind-eso.hcl
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: net-kingdom-svc-keycape-bind-eso
|
||||
audience: openbao
|
||||
ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
policies:
|
||||
- net-kingdom-svc-keycape-bind-eso
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- net-kingdom-svc-keycape-bind-eso
|
||||
service_account_namespaces:
|
||||
- sso
|
||||
bound_claims_confirmed: true
|
||||
access_frontdoor:
|
||||
type: platform-owner
|
||||
catalog_id: net-kingdom-svc-keycape-bind
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
lifecycle:
|
||||
deactivate: Restore bindPW in keycape-config (directory-bind-rollout.py --operation
|
||||
switch), remove the KEYCAPE_LLDAP_BIND_PW env from the Deployment, then delete
|
||||
the ExternalSecret (deletionPolicy Retain keeps the Secret) and withdraw the role
|
||||
and policy.
|
||||
rotate: Change the svc-keycape password in LLDAP and write the new value as the
|
||||
next KV version with CAS on the current version; ESO projects it and KeyCape is
|
||||
restarted. Only after bindPW is cleared from keycape-config, because KeyCape refuses
|
||||
differing values. Until NK-WP-0056 executors exist this is founder-run.
|
||||
compromised: Reset svc-keycape in LLDAP first (read-only bind, lldap_strict_readonly),
|
||||
then rotate as above; fence the reader role if its boundary is in doubt.
|
||||
delivery:
|
||||
surface: ESO-owned Secret (creationPolicy Owner, new dedicated Secret)
|
||||
namespace: sso
|
||||
secret: keycape-lldap-bind
|
||||
key: password
|
||||
service_account: net-kingdom-svc-keycape-bind-eso
|
||||
reader: KeyCape Deployment in sso, env KEYCAPE_LLDAP_BIND_PW from secretKeyRef keycape-lldap-bind/password
|
||||
(image with key-cape KEY-WP-0046); no agent credential delivery
|
||||
manifest: 'net-kingdom: sso-mfa/k8s/keycape/bind-eso.yaml'
|
||||
cluster_uid: a553c742-0115-43d4-99a4-a5ca56fe0786
|
||||
risk:
|
||||
classification: medium
|
||||
notes:
|
||||
- svc-keycape is a read-only directory bind (lldap_strict_readonly); a leak exposes
|
||||
directory attributes (users, groups, emails), not write access.
|
||||
- keycape-config also holds the signing key and client configuration; this request
|
||||
delivers none of it, and setup compares only lldap.bindDN/bindPW in memory.
|
||||
- Initial KV write is CAS=0 with the value already live in keycape-config and the
|
||||
founder's custody; no rotation happens in this request. An existing different
|
||||
KV value refuses.
|
||||
- Setup refuses unless the live config binds uid=svc-keycape. KeyCape refuses to
|
||||
start if KEYCAPE_LLDAP_BIND_PW and bindPW differ, so a wrong delivery cannot be
|
||||
used silently.
|
||||
- Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary
|
||||
before the first write.
|
||||
- Node root, ESO administration and platform-admin remain inside this credential
|
||||
boundary.
|
||||
verification:
|
||||
positive:
|
||||
- Exact policy, role and bound claims read back; native reader login as the ESO
|
||||
ServiceAccount gets only this policy with a lease of at most 15m
|
||||
- KV value equals the live keycape-config bindPW before ESO is applied (setup),
|
||||
and the delivered Secret (and bindPW while present) after ESO sync (verify), compared
|
||||
in memory only
|
||||
- ExternalSecret Ready/SecretSynced; KeyCape with the env rolls out Ready and a
|
||||
portal login works, again after bindPW is cleared from keycape-config
|
||||
negative:
|
||||
- Reader cannot read metadata, a sibling path, the other directory-bind paths or
|
||||
create tokens
|
||||
- Wrong ServiceAccount, wrong audience and wrong namespace logins are denied
|
||||
- Coding-agent boundary denies data and metadata of the path
|
||||
activation_conditions:
|
||||
- Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound
|
||||
by the setup tool).
|
||||
- Attended platform-admin (aal2) session for setup; the founder runs it, value read
|
||||
from the decrypted custody copy without display.
|
||||
- Policy, role and KV in place and equal to live before the ExternalSecret is applied;
|
||||
the KeyCape image with KEY-WP-0046 deployed before the env is added.
|
||||
- Positive and negative results recorded in value-free receipts.
|
||||
evidence:
|
||||
- at: '2026-10-31T09:52:22+00:00'
|
||||
actor: founder (attended), claude-code guiding
|
||||
kind: verifier_custody_and_rollout
|
||||
result: passed
|
||||
details:
|
||||
- '2026-10-31: bind-lane.py setup (live keycape-config binds svc-keycape, custody
|
||||
equal to bindPW, coding agents fenced, KV v1 CAS=0, policy+role) and verify
|
||||
passed with env and config, then again env-only after directory-bind-rollout.py
|
||||
clear-password; KeyCape main-e2b5fa1 (KEY-WP-0046) rolled out with KEYCAPE_LLDAP_BIND_PW;
|
||||
admin signed in with OTP before and after. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-svc-keycape-bind/
|
||||
(a656739).'
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
id: CCR-2026-0055
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: attended-service-credential-delivery
|
||||
title: NetKingdom privacyIDEA resolver directory bind (svc-privacyidea) custody
|
||||
status: verified
|
||||
created: '2026-10-31'
|
||||
updated: '2026-10-31'
|
||||
requester:
|
||||
agent: claude-code
|
||||
reason: 'NK-WP-0056-T04, fourth lane (after CCR-2026-0052/0053/0054): move the svc-privacyidea
|
||||
LLDAP bind out of the founder''s personal SOPS custody into platform custody.
|
||||
privacyIDEA stores the resolver password encrypted in its own database and accepts
|
||||
it only through its pi-admin API; an unattended writer would need pi-admin''s
|
||||
password standing in the cluster, so no workload reads it: delivery is an attended
|
||||
write-through.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- net-kingdom-owner
|
||||
comments:
|
||||
- at: '2026-10-31T10:10:37+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: approved
|
||||
comment: 'Approved as proposed: svc-privacyidea held in OpenBao, no workload reader,
|
||||
attended write-through.'
|
||||
- at: '2026-10-31T10:10:44+00:00'
|
||||
reviewer: net-kingdom-owner
|
||||
decision: approved
|
||||
comment: Approved as proposed for NK-WP-0056-T04.
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-privacyidea
|
||||
environment: production
|
||||
purpose: Hold the svc-privacyidea LLDAP bind password in platform custody so that
|
||||
its rotation needs no decrypted custody copy; privacyIDEA receives it only through
|
||||
the attended resolver write. No other privacyIDEA, LLDAP or service credential.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/net-kingdom/directory-binds/svc-privacyidea
|
||||
fields:
|
||||
- LDAP_BIND_PASSWORD
|
||||
auth:
|
||||
method: attended-oidc
|
||||
mount: netkingdom
|
||||
role: platform-admin
|
||||
runtime_access: false
|
||||
initial_cas: 0
|
||||
access_frontdoor:
|
||||
type: attended-service-config
|
||||
catalog_id: openbao-platform-admin-login
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
lifecycle:
|
||||
deactivate: Delete the KV entry only after the founder custody copy is confirmed
|
||||
current; privacyIDEA keeps its stored resolver password.
|
||||
rotate: Change the svc-privacyidea password in LLDAP, write the next KV version
|
||||
with CAS on the current version, then run realm_bootstrap.py --rotate-bind --from-openbao
|
||||
(attended pi-admin; --verify-user MFA login required) and verify with an OTP login.
|
||||
Founder-run.
|
||||
compromised: Reset svc-privacyidea in LLDAP first (read-only bind, lldap_strict_readonly),
|
||||
then rotate as above.
|
||||
delivery:
|
||||
surface: attended-service-config
|
||||
service: privacyIDEA (sso), LDAP resolver netkingdom-lldap
|
||||
target: resolver BINDPW, stored encrypted in the privacyIDEA database
|
||||
procedure: net-kingdom sso-mfa/k8s/privacyidea/realm_bootstrap.py --rotate-bind
|
||||
--from-openbao
|
||||
gate: attended platform-admin (aal2) OpenBao session, attended pi-admin password,
|
||||
and a fresh MFA login through the resolver (--verify-user) that proves the write
|
||||
risk:
|
||||
classification: medium
|
||||
notes:
|
||||
- svc-privacyidea is a read-only directory bind (lldap_strict_readonly); a leak
|
||||
exposes directory attributes, not write access.
|
||||
- No workload, Kubernetes or ESO reader exists; only the attended platform-admin
|
||||
session reads the value, in memory.
|
||||
- Initial KV write is CAS=0 from custody after the value is proven current by an
|
||||
LLDAP sign-in as svc-privacyidea; no rotation happens in this request.
|
||||
- Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary
|
||||
before the first write.
|
||||
verification:
|
||||
positive:
|
||||
- The KV value signs in to LLDAP as svc-privacyidea (current password), compared
|
||||
without display
|
||||
- An OTP login resolves the user through the resolver after any write-through
|
||||
negative:
|
||||
- Coding-agent boundary denies data and metadata of the path
|
||||
- No auth role or policy grants the path to a workload
|
||||
activation_conditions:
|
||||
- Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound
|
||||
by the setup tool).
|
||||
- Attended platform-admin (aal2) session; the founder runs setup with the decrypted
|
||||
custody copy and the write-through with pi-admin and an MFA login through the
|
||||
resolver.
|
||||
- Positive and negative results recorded in value-free receipts.
|
||||
evidence:
|
||||
- at: '2026-10-31T10:19:36+00:00'
|
||||
actor: founder (attended), claude-code guiding
|
||||
kind: verifier_custody_and_write_through
|
||||
result: passed
|
||||
details:
|
||||
- '2026-10-31: bind-lane.py setup (custody signs in to LLDAP as svc-privacyidea,
|
||||
coding agents fenced, KV v1 CAS=0, no workload role) and verify passed; realm_bootstrap.py
|
||||
--rotate-bind --from-openbao wrote BINDPW from OpenBao and admin passed token-backed
|
||||
MFA through the resolver. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/
|
||||
(ad7d1cd).'
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
id: CCR-2026-0056
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: Email-connect platform SMTP password projection
|
||||
status: active
|
||||
created: '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.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- founder
|
||||
- railiance-platform-owner
|
||||
- email-connect-owner
|
||||
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.'
|
||||
- at: '2026-11-01T22:40:19+00:00'
|
||||
reviewer: attended-platform-operator
|
||||
decision: binding_confirmed
|
||||
comment: 'CCR-2026-0056 foundation: pinned cluster and namespace, exact new service
|
||||
account and native audience TokenReview verified; receipt sha256:d9f8785060db902f969e5d8d0917494858b2114e8f93ae812dda3a059c090844'
|
||||
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.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/railiance-telemetry/smtp
|
||||
fields:
|
||||
- SMTP_PASSWORD
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
policy_name: email-connect-platform-smtp-eso
|
||||
policy_file: openbao/policies/email-connect-platform-smtp-eso.hcl
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: email-connect-platform-smtp-eso
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- email-connect-platform-smtp-eso
|
||||
service_account_namespaces:
|
||||
- email-connect
|
||||
bound_claims_confirmed: true
|
||||
policies:
|
||||
- email-connect-platform-smtp-eso
|
||||
ttl: 15m
|
||||
audience: openbao
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: email-connect-platform-smtp
|
||||
selector: Namespace email-connect and ServiceAccount email-connect-platform-smtp-eso
|
||||
only
|
||||
readiness: ready
|
||||
resolvable: true
|
||||
delivery:
|
||||
surface: external-secrets
|
||||
namespace: email-connect
|
||||
service_account: email-connect-platform-smtp-eso
|
||||
secret_store: openbao-email-connect-platform-smtp
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
evidence:
|
||||
- at: '2026-11-01T23:03:43+00:00'
|
||||
actor: codex
|
||||
kind: positive_verification
|
||||
result: passed
|
||||
details:
|
||||
- Independent private receipt and Warden exit zero verified; exact native SA binding,
|
||||
scoped workload login, projection Ready and SMTP AUTH without sending. See docs/evidence/email-connect-smtp-native-return-2026-11-01.json.
|
||||
- at: '2026-11-01T23:06:17+00:00'
|
||||
actor: codex
|
||||
kind: negative_verification
|
||||
result: passed
|
||||
details:
|
||||
- 'Native sibling, metadata and mutation access denied; other-service-account
|
||||
and wrong-audience login denied; workload token revocation and contained Warden
|
||||
exit zero verified. Separate supervised sender transition completed at generation
|
||||
79 with all six ledger tables preserved. Independent receipt: docs/evidence/email-connect-smtp-native-return-2026-11-01.json;
|
||||
consumer return: coulomb-social/docs/release/platform-sender-independent-return-2026-11-01.json.'
|
||||
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.
|
||||
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'
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
id: CCR-2026-0057
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: attended-service-credential-delivery
|
||||
title: NetKingdom synthetic journey identity (syn-journey) password and TOTP seed
|
||||
custody
|
||||
status: verified
|
||||
created: '2026-10-31'
|
||||
updated: '2026-10-31'
|
||||
requester:
|
||||
agent: claude-code
|
||||
reason: 'NK-WP-0056-T09: a synthetic, unprivileged sign-in identity whose MFA journey
|
||||
through KeyCape, Authelia and privacyIDEA replaces the founder''s sign-in as the
|
||||
check between consumers in a bounded bind rotation (NK-WP-0056-T06). Its password
|
||||
and TOTP seed are generated in memory and held only in OpenBao; LLDAP and privacyIDEA
|
||||
receive them through one attended procedure. No workload reads them until the
|
||||
executor identity exists (T03/T10), which needs its own request.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- net-kingdom-owner
|
||||
comments:
|
||||
- at: '2026-10-31T11:50:37+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: approved
|
||||
comment: Approved by the founder in session 2026-10-31 ('i accept the ccr') for
|
||||
NK-WP-0056-T09.
|
||||
- at: '2026-10-31T11:50:37+00:00'
|
||||
reviewer: net-kingdom-owner
|
||||
decision: approved
|
||||
comment: Approved by the founder in session 2026-10-31 ('i accept the ccr') for
|
||||
NK-WP-0056-T09.
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-synthetic-journey
|
||||
environment: production
|
||||
purpose: Hold the password and TOTP seed of the LLDAP user syn-journey (no groups,
|
||||
no rights) so that a synthetic journey can prove sign-in with MFA without a person.
|
||||
No other identity, directory, privacyIDEA or service credential.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/net-kingdom/synthetic-journey/syn-journey
|
||||
fields:
|
||||
- PASSWORD
|
||||
- TOTP_SEED
|
||||
auth:
|
||||
method: attended-oidc
|
||||
mount: netkingdom
|
||||
role: platform-admin
|
||||
runtime_access: false
|
||||
initial_cas: 0
|
||||
access_frontdoor:
|
||||
type: attended-service-config
|
||||
catalog_id: openbao-platform-admin-login
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
lifecycle:
|
||||
deactivate: lane.py teardown deletes the privacyIDEA token SYNJOURNEY01, the LLDAP
|
||||
user syn-journey (refused if it has gained any group) and the KV entry with all
|
||||
versions; the KeyCape client nk-synthetic-journey is removed with keycape-client.py
|
||||
--operation remove. The coding-agent fence stays.
|
||||
rotate: Tear down and set up again (new password and seed, CAS=0 on the fresh path).
|
||||
No in-place rotation path in this request.
|
||||
compromised: Delete the privacyIDEA token first (the account then cannot pass MFA),
|
||||
then tear down. The identity has no groups, so a leak grants a sign-in that reaches
|
||||
no protected service.
|
||||
delivery:
|
||||
surface: attended-service-config
|
||||
service: LLDAP (sso) user syn-journey; privacyIDEA (mfa) TOTP token SYNJOURNEY01
|
||||
in realm coulomb
|
||||
target: the LLDAP user's password (set with the pinned lldap_set_password, OPAQUE)
|
||||
and the privacyIDEA token's OTP key (stored encrypted in the privacyIDEA database)
|
||||
procedure: net-kingdom sso-mfa/k8s/synthetic-journey/lane.py setup
|
||||
gate: attended platform-admin (aal2) OpenBao session, the human admin's LLDAP password,
|
||||
the attended pi-admin password, and a passing synthetic journey (lane.py verify)
|
||||
against the live issuer
|
||||
risk:
|
||||
classification: medium
|
||||
notes:
|
||||
- The identity is new and holds no group. Authelia admits it (it is not svc-* or
|
||||
ld-admin), KeyCape issues it tokens, but no consuming service grants it anything.
|
||||
KeyCape has no per-client group gate, so every client that admits any authenticated
|
||||
user would admit it too; none of the registered clients grants rights without
|
||||
a group.
|
||||
- KeyCape marks its tokens principal_type human (key-cape token.go); the account
|
||||
is declared SERVICE in the service-identity declarations. Recorded, not changed
|
||||
here.
|
||||
- Values are generated in memory by lane.py and written CAS=0; neither is printed,
|
||||
logged or written to disk. The journey's receipts are value-free.
|
||||
- Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary
|
||||
before the first write.
|
||||
- No workload, Kubernetes or ESO reader exists; T10 needs a separate workload-kv-read
|
||||
request for the executor identity.
|
||||
verification:
|
||||
positive:
|
||||
- The KV password signs in to LLDAP as syn-journey (compared without display)
|
||||
- The synthetic journey passes against https://kc.coulomb.social at aal2 with the
|
||||
KV values (lane.py verify), with a value-free receipt
|
||||
negative:
|
||||
- Coding-agent boundary denies data and metadata of the path
|
||||
- No auth role or policy grants the path to a workload
|
||||
- setup refuses if the KV path, the LLDAP user or the privacyIDEA token already
|
||||
exist
|
||||
activation_conditions:
|
||||
- Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound
|
||||
by lane.py).
|
||||
- The KeyCape client nk-synthetic-journey registered through security-cases/nk-synthetic-journey.yaml
|
||||
before lane.py verify.
|
||||
- Attended platform-admin (aal2) session; the founder runs setup and verify.
|
||||
- Positive and negative results recorded in value-free receipts.
|
||||
evidence:
|
||||
- at: '2026-10-31T12:33:37+00:00'
|
||||
actor: founder (attended), claude-code guiding
|
||||
kind: verifier_identity_custody_and_journey
|
||||
result: passed
|
||||
details:
|
||||
- '2026-10-31: KeyCape client nk-synthetic-journey registered (SecurityCase accepted,
|
||||
net-kingdom d654f8a); lane.py setup (coding agents fenced, syn-journey with
|
||||
no groups, TOTP token SYNJOURNEY01 with a privacyIDEA-generated key confirmed
|
||||
by code, KV v1 CAS=0, no workload role) and verify (KV current by LLDAP sign-in,
|
||||
live synthetic journey passed at aal2 with otp) passed. One partial setup removed
|
||||
by lane.py teardown first. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-t09-synthetic-identity/
|
||||
(661ab6a).'
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
id: CCR-2026-0058
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-write-next-version
|
||||
title: NetKingdom bounded executor writes the next svc-authelia bind version (pilot)
|
||||
status: approved
|
||||
created: '2026-10-31'
|
||||
updated: '2026-10-31'
|
||||
requester:
|
||||
agent: claude-code
|
||||
reason: 'NK-WP-0056-T02 owner request (hub 9d08288d), for NK-WP-0056-T06: the bounded
|
||||
executor service:nk-bind-executor rotates the svc-authelia LLDAP bind unattended
|
||||
under one founder admission (Bounded Execution Admission v0.1, proposed). The
|
||||
executor must write the next KV version of the bind with CAS and read it back
|
||||
for its checks and revert. Drafted by the railiance-platform worker; not approved.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- net-kingdom-owner
|
||||
comments:
|
||||
- at: '2026-10-31T14:26:48+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: approved
|
||||
comment: Approved by the founder in session 2026-10-31 as amended (window-scoped
|
||||
role, bind self-rotation) for NK-WP-0056-T06.
|
||||
- at: '2026-10-31T14:27:33+00:00'
|
||||
reviewer: net-kingdom-owner
|
||||
decision: approved
|
||||
comment: Approved by the founder in session 2026-10-31 as amended (window-scoped
|
||||
role, bind self-rotation) for NK-WP-0056-T06. Recorded by hand in the approve
|
||||
tool's format; the tool refuses the second approval for this request type once
|
||||
the first sets status approved.
|
||||
- at: '2026-10-31T15:46:26+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: approved
|
||||
comment: Re-approved by the founder in session 2026-10-31 with the corrected policy
|
||||
(required_parameters removed; NK-WP-0056-T12 synthetic pilot evidence).
|
||||
- at: '2026-10-31T15:46:26+00:00'
|
||||
reviewer: net-kingdom-owner
|
||||
decision: approved
|
||||
comment: Re-approved by the founder in session 2026-10-31 with the corrected policy
|
||||
(required_parameters removed; NK-WP-0056-T12 synthetic pilot evidence).
|
||||
- at: '2026-10-31T18:38:22+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: binding_confirmed
|
||||
comment: 'Binding confirmed: ServiceAccount nk-bind-executor in namespace sso
|
||||
only (uid bd15569c-8a85-4625-b001-d9f799cb857b), audience openbao, from rapp-bounded-executor.'
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-bind-executor
|
||||
environment: production
|
||||
purpose: Let only the executor ServiceAccount sso/nk-bind-executor read and write
|
||||
the next version of platform/workloads/net-kingdom/directory-binds/svc-authelia,
|
||||
with CAS on the current version; no create, metadata, delete, destroy, list or
|
||||
sibling path
|
||||
admission:
|
||||
standard: net-kingdom canon/standards/bounded-execution-admission_v0.1.md (proposed)
|
||||
executor_identity: service:nk-bind-executor
|
||||
example: net-kingdom examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml
|
||||
note: The OpenBao role does not evaluate the admission. The admission (approval-engine
|
||||
consume, window, plan digest) is enforced by the executor and its owners; this
|
||||
request bounds what a compromised or unadmitted executor can reach to one value.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/net-kingdom/directory-binds/svc-authelia
|
||||
fields:
|
||||
- LDAP_BIND_PASSWORD
|
||||
policy_name: net-kingdom-bind-executor-svc-authelia-write
|
||||
policy_file: openbao/policies/net-kingdom-bind-executor-svc-authelia-write.hcl
|
||||
write:
|
||||
mode: next-version
|
||||
cas_required: true
|
||||
create: false
|
||||
metadata_read: false
|
||||
delete: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: net-kingdom-bind-executor-svc-authelia-write
|
||||
audience: openbao
|
||||
ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
policies:
|
||||
- net-kingdom-bind-executor-svc-authelia-write
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- nk-bind-executor
|
||||
service_account_namespaces:
|
||||
- sso
|
||||
bound_claims_confirmed: true
|
||||
access_frontdoor:
|
||||
type: platform-owner
|
||||
catalog_id: net-kingdom-bind-executor-svc-authelia-write
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
agent_fence:
|
||||
data_and_metadata_denied: true
|
||||
note: The svc-authelia data and metadata paths are already denied to coding agents
|
||||
since CCR-2026-0053; setup re-checks the deny before the role is created.
|
||||
amendments:
|
||||
- at: '2026-10-31'
|
||||
by: claude-code
|
||||
change: 'Policy file corrected: removed required_parameters = ["options"]. OpenBao
|
||||
2.5.4 applies it to reads as well, so the executor could not read the version
|
||||
it must name for CAS (net-kingdom NK-WP-0056-T12 synthetic pilot, docs/evidence/2026-10-31-nk56-t12-synthetic-pilot/).
|
||||
Writes without options.cas are still refused by cas_required on the key; metadata,
|
||||
delete, sibling and new paths stay denied (tested). Status reset to proposed:
|
||||
the approved policy file changed, re-approval needed.'
|
||||
lifecycle:
|
||||
role_window: 'Window-scoped (founder decision 2026-10-31, hub c29c6eda): the policy
|
||||
is applied once and grants nothing on its own; the Kubernetes-auth role is created
|
||||
by the attended platform-admin when an admission naming this lane is approved,
|
||||
bounded to that admission''s window, and deleted after the run''s receipt or at
|
||||
window end, whichever comes first. No write role exists between admitted runs.'
|
||||
deactivate: Delete the role, then the policy; the KV entry and the ESO reader lane
|
||||
(CCR-2026-0053) are unchanged. The bind falls back to founder-run rotation.
|
||||
rotate: Executor reads the current version N, signs in to LLDAP as svc-authelia
|
||||
with it and changes that account's own password (self-rotation, founder decision
|
||||
2026-10-31, hub 3d0c8f86; no lldap_password_manager account), writes version N+1
|
||||
with options.cas=N; ESO projects it, Authelia restarts, the synthetic journey
|
||||
proves sign-in. Revert changes the password back the same way and writes the prior
|
||||
value as version N+2 with options.cas=N+1.
|
||||
compromised: Delete the role first (stops new logins; existing tokens expire within
|
||||
15m, or revoke by accessor), then reset svc-authelia in LLDAP and rotate attended
|
||||
as in CCR-2026-0053.
|
||||
delivery:
|
||||
surface: native-workload-login
|
||||
namespace: sso
|
||||
service_account: nk-bind-executor
|
||||
reader: Executor runtime (net-kingdom source, rapp-* package per ADR-0015, NK-WP-0056-T03);
|
||||
logs in with its projected ServiceAccount token, audience openbao; no Secret is
|
||||
created by this request
|
||||
risk:
|
||||
classification: medium
|
||||
notes:
|
||||
- A write role on the svc-authelia bind is new; until now only the attended platform-admin
|
||||
wrote this path. It exists only during an admitted window. Within it, a compromised
|
||||
executor ServiceAccount can replace the bind value (Authelia sign-in outage on
|
||||
next restart) but cannot read any other path, create paths, delete versions or
|
||||
read metadata; outside it, the ServiceAccount cannot log in to this role at all.
|
||||
- cas_required on the key makes every write state the version it replaces; a racing
|
||||
or replayed write is refused by the engine, not by the executor.
|
||||
- The executor also reads the current value (needed for CAS and revert). svc-authelia
|
||||
is a read-only directory bind (lldap_strict_readonly).
|
||||
- The role is a separate role from the ESO reader (CCR-2026-0053) and from the journey
|
||||
read lane (NK-WP-0056-T10); policies are never combined in one role.
|
||||
- Node root, ESO administration, cluster admins able to mint tokens for sso/nk-bind-executor
|
||||
and platform-admin remain inside this credential boundary.
|
||||
verification:
|
||||
positive:
|
||||
- Exact policy and role read back; login as sso/nk-bind-executor (audience openbao)
|
||||
gets only this policy with a lease of at most 15m
|
||||
- Write of the next version with options.cas equal to the current version succeeds
|
||||
in a rehearsal on a synthetic sibling-free test path or under the T06 admission;
|
||||
the value is compared in memory only
|
||||
negative:
|
||||
- Write without options or with a stale cas is refused
|
||||
- Create of a new sibling path, metadata read, delete, destroy, undelete and list
|
||||
are refused
|
||||
- Wrong ServiceAccount, wrong namespace and wrong audience logins are denied
|
||||
- Coding-agent boundary denies data and metadata of the path
|
||||
activation_conditions:
|
||||
- Explicit platform-owner and net-kingdom-owner approval of this exact request.
|
||||
- ServiceAccount sso/nk-bind-executor exists from the reviewed rapp-* package (bound_claims_confirmed
|
||||
then set to true by the platform-owner).
|
||||
- Attended platform-admin (aal2) session sets cas_required on the key and applies
|
||||
the policy once from a reviewed commit.
|
||||
- Per admitted run, the attended platform-admin creates the role for the admission's
|
||||
window and deletes it after the receipt (or at window end); both steps are recorded
|
||||
with the admission id.
|
||||
- Positive and negative results recorded in value-free receipts.
|
||||
evidence:
|
||||
- at: '2026-10-31T18:48:55+00:00'
|
||||
actor: founder (attended), claude-code guiding
|
||||
kind: executor_lane_policy_setup
|
||||
result: passed
|
||||
details:
|
||||
- '2026-10-31: policy applied and cas_required set on the svc-authelia key (metadata
|
||||
write naming only cas_required; max_versions, delete_version_after and custom_metadata
|
||||
unchanged) by net-kingdom tools/service-binds/executor-lanes.py; the window-scoped
|
||||
role was not created and its login is denied (verified). Role creation remains
|
||||
per admitted window. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-t10-executor-lanes/
|
||||
(ef6284c).'
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
id: CCR-2026-0059
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: NetKingdom bounded executor reads the synthetic journey identity (syn-journey)
|
||||
status: verified
|
||||
created: '2026-10-31'
|
||||
updated: '2026-10-31'
|
||||
requester:
|
||||
agent: claude-code
|
||||
reason: 'NK-WP-0056-T10/T06: the bounded executor service:nk-bind-executor runs
|
||||
the synthetic MFA journey (KeyCape, Authelia, privacyIDEA) as the check after
|
||||
each consumer restart, with no person signing in. It needs the syn-journey password
|
||||
and TOTP seed held under CCR-2026-0057. Lane design: railiance-platform docs/credential-lane-designs/bounded-executor.md
|
||||
"Journey secrets (T10)". Drafted by net-kingdom; not approved.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- net-kingdom-owner
|
||||
comments:
|
||||
- at: '2026-10-31T18:06:02+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: approved
|
||||
comment: 'Approved by the founder 2026-10-31: standing journey reader for NK-WP-0056-T10,
|
||||
accepting the CCR-2026-0057 runtime_access widening.'
|
||||
- at: '2026-10-31T18:06:12+00:00'
|
||||
reviewer: net-kingdom-owner
|
||||
decision: approved
|
||||
comment: Approved by the founder 2026-10-31 for NK-WP-0056-T10/T06.
|
||||
- at: '2026-10-31T18:38:23+00:00'
|
||||
reviewer: platform-owner
|
||||
decision: binding_confirmed
|
||||
comment: 'Binding confirmed: ServiceAccount nk-bind-executor in namespace sso
|
||||
only (uid bd15569c-8a85-4625-b001-d9f799cb857b), audience openbao, from rapp-bounded-executor.'
|
||||
supersedes_boundary:
|
||||
ccr: CCR-2026-0057
|
||||
field: openbao.runtime_access
|
||||
was: false
|
||||
change: 'This request gives one workload reader to the path CCR-2026-0057 holds
|
||||
with runtime_access: false. CCR-2026-0057''s attended setup, teardown and rotation
|
||||
stay valid and unchanged; its rotate (teardown + setup) does not need this role.'
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-bind-executor
|
||||
rapp: rapp-bounded-executor
|
||||
environment: production
|
||||
purpose: Let only the executor ServiceAccount sso/nk-bind-executor read the syn-journey
|
||||
PASSWORD and TOTP_SEED to run the synthetic journey; no write, metadata, list,
|
||||
sibling path, or other identity
|
||||
admission:
|
||||
standard: net-kingdom canon/standards/bounded-execution-admission_v0.1.md (proposed)
|
||||
executor_identity: service:nk-bind-executor
|
||||
example: net-kingdom examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml
|
||||
note: The journey is a check step under an admission. The OpenBao role does not
|
||||
evaluate the admission; it bounds what the ServiceAccount can read to one synthetic,
|
||||
group-less identity.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/net-kingdom/synthetic-journey/syn-journey
|
||||
fields:
|
||||
- PASSWORD
|
||||
- TOTP_SEED
|
||||
policy_name: net-kingdom-bind-executor-syn-journey-read
|
||||
policy_file: openbao/policies/net-kingdom-bind-executor-syn-journey-read.hcl
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: net-kingdom-bind-executor-syn-journey-read
|
||||
audience: openbao
|
||||
ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
policies:
|
||||
- net-kingdom-bind-executor-syn-journey-read
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- nk-bind-executor
|
||||
service_account_namespaces:
|
||||
- sso
|
||||
bound_claims_confirmed: true
|
||||
access_frontdoor:
|
||||
type: platform-owner
|
||||
catalog_id: net-kingdom-bind-executor-syn-journey-read
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
agent_fence:
|
||||
data_and_metadata_denied: true
|
||||
note: The syn-journey data and metadata paths are denied to coding agents since
|
||||
CCR-2026-0057; setup re-checks the deny before the role is created.
|
||||
lifecycle:
|
||||
role_window: Standing (founder decision 2026-10-31, net-kingdom session). Unlike
|
||||
the CCR-2026-0058 write role, this role only reads a group-less synthetic identity,
|
||||
and a standing reader lets the journey run as a scheduled check (T10) without
|
||||
a platform-admin per run.
|
||||
deactivate: Delete the role, then the policy. The KV entry, the LLDAP user and the
|
||||
privacyIDEA token stay under CCR-2026-0057.
|
||||
rotate: As in CCR-2026-0057 (lane.py teardown and setup); the reader needs no change,
|
||||
because the path stays the same.
|
||||
compromised: Delete the privacyIDEA token SYNJOURNEY01 first (the account then cannot
|
||||
pass MFA), delete this role, then tear down and set up as in CCR-2026-0057. The
|
||||
identity has no groups, so a leak grants a sign-in that reaches no protected service.
|
||||
delivery:
|
||||
surface: native-workload-login
|
||||
namespace: sso
|
||||
service_account: nk-bind-executor
|
||||
reader: Executor runtime (net-kingdom bounded-executor/, deployed by rapp-bounded-executor);
|
||||
logs in with its projected ServiceAccount token, audience openbao, separately
|
||||
from the CCR-2026-0058 write role; values stay in memory and never reach a Secret,
|
||||
log or receipt
|
||||
manifest: 'rapp-bounded-executor: manifests/rbac.yaml, templates/job.yaml'
|
||||
risk:
|
||||
classification: low
|
||||
notes:
|
||||
- Reachable value is one synthetic identity with no groups. Authelia and KeyCape
|
||||
admit it, but no consuming service grants it anything (CCR-2026-0057 risk notes).
|
||||
- A reader holding both values can complete an MFA sign-in as syn-journey, which
|
||||
is the purpose. privacyIDEA refuses a reused TOTP time step, so a parallel use
|
||||
and the journey can race; the journey then fails closed.
|
||||
- The role is separate from the CCR-2026-0058 write role and from the ESO reader
|
||||
(CCR-2026-0053); policies are never combined in one role.
|
||||
- Node root, cluster admins able to mint tokens for sso/nk-bind-executor and platform-admin
|
||||
remain inside this credential boundary.
|
||||
verification:
|
||||
positive:
|
||||
- Exact policy and role read back; login as sso/nk-bind-executor (audience openbao)
|
||||
gets only this policy with a lease of at most 15m
|
||||
- The synthetic journey passes at aal2 with the values read through this role, with
|
||||
a value-free receipt
|
||||
negative:
|
||||
- Write, metadata read, list, delete and sibling paths (including directory-binds)
|
||||
are refused
|
||||
- Wrong ServiceAccount, wrong namespace and wrong audience logins are denied
|
||||
- Coding-agent boundary denies data and metadata of the path
|
||||
activation_conditions:
|
||||
- Explicit platform-owner and net-kingdom-owner approval of this exact request,
|
||||
which includes the change to CCR-2026-0057's runtime_access boundary.
|
||||
- ServiceAccount sso/nk-bind-executor exists from the reviewed rapp-bounded-executor
|
||||
package (bound_claims_confirmed then set to true by the platform-owner).
|
||||
- Attended platform-admin (aal2) session applies the policy and role from a reviewed
|
||||
commit.
|
||||
- Positive and negative results recorded in value-free receipts.
|
||||
evidence:
|
||||
- at: '2026-10-31T18:48:55+00:00'
|
||||
actor: founder (attended), claude-code guiding
|
||||
kind: executor_lane_setup_and_proof
|
||||
result: passed
|
||||
details:
|
||||
- '2026-10-31: policy and standing role applied by net-kingdom tools/service-binds/executor-lanes.py
|
||||
setup (lanes_prepared); verify (lanes_verified): sso/nk-bind-executor audience
|
||||
openbao gets exactly this policy, lease <=15m, reads only syn-journey data (fields
|
||||
checked in memory), metadata/directory-binds/token-create denied, default SA,
|
||||
wrong audience and write-role login denied, coding-agent fence effective. The
|
||||
positive journey run through this role is the open part of NK-WP-0056-T10. Evidence:
|
||||
net-kingdom docs/evidence/2026-10-31-nk56-t10-executor-lanes/ (ef6284c).'
|
||||
- at: '2026-10-31T19:50:05+00:00'
|
||||
actor: founder started the Job; no person in the journey
|
||||
kind: executor_journey_live
|
||||
result: passed
|
||||
details:
|
||||
- '2026-10-31: rapp-bounded-executor make journey, Job sso/bounded-executor-journey-20261031-194719
|
||||
(image sha256:71da4a51), logged in as sso/nk-bind-executor with this role only
|
||||
(openbao audience), read syn-journey and passed the synthetic journey at aal2
|
||||
(pwd+otp, signature verified), effects false. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-t10-journey-only/live-receipt.json
|
||||
(a020758).'
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
id: CCR-2026-0060
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: 'nk-bind-executor-approval: keycape-reader-policy and eso-role (oidc-service-client@0.2)'
|
||||
status: proposed
|
||||
created: '2026-10-31'
|
||||
updated: '2026-11-01'
|
||||
requester:
|
||||
agent: railiance-platform
|
||||
reason: 'NK-WP-0056-T14 first case of SecurityCaseType oidc-service-client 0.2 (net-kingdom
|
||||
9ce0d60, acknowledged 2026-11-01; hub decision f8044f81; memo nk-wp-0056-t14-oidc-service-client-r2).
|
||||
Drafted by the platform owner in its review (docs/credential-lane-designs/oidc-service-client-review.md,
|
||||
R6). KeyCape reads the client secret of nk-bind-executor-approval only through ESO, as
|
||||
CCR-2026-0041 for the browser type. Not approved.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
comments: []
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: keycape
|
||||
environment: production
|
||||
purpose: KeyCape reads the nk-bind-executor-approval client secret only through ESO. No metadata,
|
||||
list, parent or sibling access.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/keycape/nk-bind-executor-approval
|
||||
fields:
|
||||
- CLIENT_SECRET
|
||||
policy_name: workload-kv-read-keycape-nk-bind-executor-approval-eso
|
||||
policy_file: openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-eso.hcl
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: external-secrets-keycape-nk-bind-executor-approval
|
||||
audience: openbao
|
||||
ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
policies:
|
||||
- workload-kv-read-keycape-nk-bind-executor-approval-eso
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- external-secrets
|
||||
service_account_namespaces:
|
||||
- external-secrets
|
||||
bound_claims_confirmed: false
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: bounded-executor-keycape-nk-bind-executor-approval
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- Rendered from the acknowledged SecurityCaseType oidc-service-client 0.2 (names and bodies
|
||||
checked against its materialization 2026-11-01); the case is not yet accepted.
|
||||
- Exact read on one KV path; no metadata, list, parent or sibling access.
|
||||
- The value lets its holder obtain approval-engine tokens as service:nk-bind-executor
|
||||
(approval:create/read/consume, tenant:platform).
|
||||
- Only ExternalSecrets in namespace sso may use the store; sso namespace writers are
|
||||
within this boundary (as CCR-2026-0033/0041).
|
||||
verification:
|
||||
positive:
|
||||
- policy workload-kv-read-keycape-nk-bind-executor-approval-eso grants read on
|
||||
platform/data/workloads/keycape/nk-bind-executor-approval only (capabilities check)
|
||||
- ExternalSecret sso/keycape-nk-bind-executor-approval-client reports Ready=True (status
|
||||
only)
|
||||
negative:
|
||||
- a default-only identity has no read capability on the path
|
||||
- sibling GET, parent LIST and metadata read are denied
|
||||
- wrong ServiceAccount and wrong audience logins are denied
|
||||
activation_conditions:
|
||||
- oidc-service-client 0.2 acknowledged by the founder (done 2026-11-01, memo r2, hub decision
|
||||
f8044f81).
|
||||
- SecurityCase nk-bind-executor-approval accepted by owner and founder.
|
||||
- Custody entry minted at version 1 by keycape-client-secret-generate, by the broker, which admits
|
||||
oidc-service-client@0.2 since RPF-WP-0069-T08.
|
||||
- platform-owner approval of this exact request and bound_claims_confirmed set to true.
|
||||
lifecycle:
|
||||
deactivate: delete ExternalSecret sso/keycape-nk-bind-executor-approval-client, then role
|
||||
external-secrets-keycape-nk-bind-executor-approval, then the policy (case rollback)
|
||||
rotate: no rotation lane exists yet (RPF-WP-0055 residual); until then a rotation is an
|
||||
attended platform-admin write with CAS=current, an ESO refresh and a KeyCape restart
|
||||
compromised: owner incident process; delete the role first; no automatic read, reseed or
|
||||
cleanup
|
||||
security_case:
|
||||
case: nk-bind-executor-approval
|
||||
type: oidc-service-client@0.2
|
||||
type_status: acknowledged
|
||||
type_source: net-kingdom security-case-types/oidc-service-client/type.yaml (9ce0d60, sha256:cd238693...)
|
||||
type_acknowledgment: founder accepted informed-decision memo nk-wp-0056-t14-oidc-service-client-r2 on 2026-11-01 (hub decision f8044f81)
|
||||
acceptance_record: pending
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
id: CCR-2026-0061
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: 'nk-bind-executor-approval: consumer reader for sso/nk-bind-executor (oidc-service-client@0.2)'
|
||||
status: proposed
|
||||
created: '2026-10-31'
|
||||
updated: '2026-11-01'
|
||||
requester:
|
||||
agent: railiance-platform
|
||||
reason: 'NK-WP-0056-T14 first case of SecurityCaseType oidc-service-client 0.2 (net-kingdom
|
||||
9ce0d60, acknowledged 2026-11-01; hub decision f8044f81). The bounded executor service:nk-bind-executor
|
||||
reads its approval-engine client secret natively per call to file, read and consume its
|
||||
own approvals. Drafted by the platform owner in its review
|
||||
(docs/credential-lane-designs/oidc-service-client-review.md, R2-R6). Not approved.'
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- net-kingdom-owner
|
||||
comments: []
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-bind-executor
|
||||
rapp: rapp-bounded-executor
|
||||
environment: production
|
||||
purpose: Let only the executor ServiceAccount sso/nk-bind-executor read the
|
||||
nk-bind-executor-approval CLIENT_SECRET in memory for its client_credentials exchange;
|
||||
no write, metadata, list, sibling path, Secret copy or other identity
|
||||
admission:
|
||||
standard: net-kingdom canon/standards/bounded-execution-admission_v0.1.md (proposed)
|
||||
executor_identity: service:nk-bind-executor
|
||||
note: The role does not evaluate the admission. It bounds what the ServiceAccount can read
|
||||
to one client secret whose tokens carry approval:create/read/consume at approval-engine.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/keycape/nk-bind-executor-approval
|
||||
fields:
|
||||
- CLIENT_SECRET
|
||||
policy_name: workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor
|
||||
policy_file: openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor.hcl
|
||||
metadata_read: false
|
||||
token_self_lifecycle: true
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
# Type 0.2 template keycape-{client_id}-{consuming_service}-workload-kv-read (review R4):
|
||||
# the delegated applier admits it.
|
||||
role: keycape-nk-bind-executor-approval-bounded-executor-workload-kv-read
|
||||
audience: openbao
|
||||
ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
token_explicit_max_ttl: 15m
|
||||
token_no_default_policy: true
|
||||
policies:
|
||||
- workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- nk-bind-executor
|
||||
service_account_namespaces:
|
||||
- sso
|
||||
bound_claims_confirmed: false
|
||||
access_frontdoor:
|
||||
type: platform-owner
|
||||
catalog_id: bounded-executor-keycape-nk-bind-executor-approval
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
agent_fence:
|
||||
data_and_metadata_denied: true
|
||||
note: Coding agents are denied data and metadata of platform/workloads/keycape/nk-bind-executor-approval;
|
||||
setup checks the deny before the role is created.
|
||||
lifecycle:
|
||||
role_window: Standing, as CCR-2026-0059. The executor exchanges a fresh token per call and
|
||||
keeps neither the secret nor a token beyond the call.
|
||||
deactivate: Delete the role, then the policy. The custody entry and the ESO reader stay
|
||||
under CCR-2026-0060 until the case rollback.
|
||||
rotate: No rotation lane exists yet (RPF-WP-0055 residual). The reader needs no change on
|
||||
rotation because the path stays the same.
|
||||
compromised: Remove client nk-bind-executor-approval from KeyCape (register-client rollback,
|
||||
attended), delete this role, then rotate attended. Approvals already created by the
|
||||
client still need a human entry under human_control.
|
||||
delivery:
|
||||
surface: native-workload-login
|
||||
namespace: sso
|
||||
service_account: nk-bind-executor
|
||||
reader: Executor runtime (net-kingdom bounded-executor/, deployed by rapp-bounded-executor);
|
||||
logs in with its projected ServiceAccount token, audience openbao, separately from the
|
||||
CCR-2026-0058 and CCR-2026-0059 roles; the value stays in memory and never reaches a
|
||||
Secret, log or receipt
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- Reachable value is one confidential client secret; its tokens act as service:nk-bind-executor
|
||||
at approval-engine (create approvals naming itself, read and consume approvals of
|
||||
tenant:platform). Approvals still need a human entry under human_control.
|
||||
- The role is separate from the CCR-2026-0058 write role, the CCR-2026-0059 journey reader
|
||||
and the CCR-2026-0060 ESO reader; policies are never combined in one role.
|
||||
- No Secret copy in namespace sso beyond KeyCape's own ESO target; other workloads in sso
|
||||
without the ServiceAccount cannot log in.
|
||||
- Node root, cluster admins able to mint tokens for sso/nk-bind-executor and platform-admin
|
||||
remain inside this credential boundary.
|
||||
verification:
|
||||
positive:
|
||||
- Exact policy and role read back; login as sso/nk-bind-executor (audience openbao) gets
|
||||
only this policy with a lease of at most 15m
|
||||
- The executor reports the claims of one client_credentials exchange per scope (sub
|
||||
service:nk-bind-executor, aud approval-engine, tenant:platform, lifetime <= 15m), value-free
|
||||
negative:
|
||||
- Write, metadata read, list, delete and sibling paths (including keycape/secrets-engine-openbao
|
||||
and directory-binds) are refused
|
||||
- Wrong ServiceAccount, wrong namespace and wrong audience logins are denied
|
||||
- Coding-agent boundary denies data and metadata of the path
|
||||
activation_conditions:
|
||||
- oidc-service-client 0.2 acknowledged (done 2026-11-01) and case nk-bind-executor-approval accepted.
|
||||
- Custody entry minted (CCR-2026-0040 lane; broker admits
|
||||
oidc-service-client@0.2 since RPF-WP-0069-T08).
|
||||
- Explicit platform-owner and net-kingdom-owner approval of this exact request.
|
||||
- bound_claims_confirmed set to true by the platform-owner (ServiceAccount sso/nk-bind-executor
|
||||
already exists, uid bd15569c, CCR-2026-0058/0059).
|
||||
- Attended platform-admin (aal2) session or the delegated applier applies policy and role
|
||||
from a reviewed commit; positive and negative results recorded in value-free receipts.
|
||||
security_case:
|
||||
case: nk-bind-executor-approval
|
||||
type: oidc-service-client@0.2
|
||||
type_status: acknowledged
|
||||
type_source: net-kingdom security-case-types/oidc-service-client/type.yaml (9ce0d60, sha256:cd238693...)
|
||||
type_acknowledgment: founder accepted informed-decision memo nk-wp-0056-t14-oidc-service-client-r2 on 2026-11-01 (hub decision f8044f81)
|
||||
acceptance_record: pending
|
||||
216
credential-change-requests/CCR-2026-0062-mas-backup-primary.yaml
Normal file
216
credential-change-requests/CCR-2026-0062-mas-backup-primary.yaml
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
{
|
||||
"id": "CCR-2026-0062",
|
||||
"kind": "credential-change-request",
|
||||
"schema_version": 1,
|
||||
"request_type": "workload-kv-read",
|
||||
"title": "MAS evidence archive node-delivery reader",
|
||||
"status": "applied",
|
||||
"created": "2026-11-02",
|
||||
"updated": "2026-11-03",
|
||||
"requester": {
|
||||
"agent": "grok",
|
||||
"reason": "Founder asked to prepare the MAS prefix publisher under RPF-WP-0070. Preparation is not a live grant."
|
||||
},
|
||||
"review": {
|
||||
"required": true,
|
||||
"required_approvers": [
|
||||
"platform-owner",
|
||||
"mas-owner"
|
||||
],
|
||||
"comments": [
|
||||
{
|
||||
"at": "2026-11-02T01:35:00Z",
|
||||
"reviewer": "User (platform-owner)",
|
||||
"decision": "approved",
|
||||
"comment": "Founder approved this reader on 2026-11-02. One read policy, ServiceAccount backup-primary-delivery in namespace mas, audience openbao.",
|
||||
"source_ref": "founder-approval-2026-11-02"
|
||||
},
|
||||
{
|
||||
"at": "2026-11-02T01:35:00Z",
|
||||
"reviewer": "User (mas-owner)",
|
||||
"decision": "approved",
|
||||
"comment": "Founder approved MAS node delivery of this backup pair. The IAM application, pair custody, age escrow, and offsite publication stay separate.",
|
||||
"source_ref": "founder-approval-2026-11-02"
|
||||
}
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"domain": "communication",
|
||||
"tenant": "platform",
|
||||
"workload": "mas",
|
||||
"environment": "production",
|
||||
"purpose": "Read one MAS evidence publisher key pair into the admitted node delivery process. No other workload credential, sibling prefix, or age identity."
|
||||
},
|
||||
"openbao": {
|
||||
"mount": "platform",
|
||||
"kv_path": "platform/workloads/mas/backup-primary",
|
||||
"fields": [
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY"
|
||||
],
|
||||
"policy_name": "workload-kv-read-mas-backup-primary",
|
||||
"policy_file": "openbao/policies/workload-kv-read-mas-backup-primary.hcl",
|
||||
"metadata_read": false,
|
||||
"token_self_lifecycle": true,
|
||||
"auth": {
|
||||
"method": "kubernetes",
|
||||
"mount": "kubernetes",
|
||||
"role": "mas-backup-primary-workload-kv-read",
|
||||
"audience": "openbao",
|
||||
"ttl": "5m",
|
||||
"token_max_ttl": "5m",
|
||||
"token_explicit_max_ttl": "5m",
|
||||
"token_no_default_policy": true,
|
||||
"token_num_uses": 4,
|
||||
"token_type": "service",
|
||||
"policies": [
|
||||
"workload-kv-read-mas-backup-primary"
|
||||
],
|
||||
"bound_claims": {
|
||||
"service_account_names": [
|
||||
"backup-primary-delivery"
|
||||
],
|
||||
"service_account_namespaces": [
|
||||
"mas"
|
||||
]
|
||||
},
|
||||
"bound_claims_confirmed": true
|
||||
}
|
||||
},
|
||||
"access_frontdoor": {
|
||||
"type": "platform-owner",
|
||||
"catalog_id": "mas-backup-primary-delivery",
|
||||
"readiness": "applied-pending-verify",
|
||||
"resolvable": false
|
||||
},
|
||||
"delivery": {
|
||||
"surface": "node-private-file",
|
||||
"host": "railiance01",
|
||||
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
|
||||
"target": "/etc/mas-backup/primary.env",
|
||||
"owner": "root:root",
|
||||
"mode": "0600",
|
||||
"directory_mode": "0700",
|
||||
"procedure": "scripts/application_backup_credentials.py",
|
||||
"admission": {
|
||||
"node_executor_approved": true,
|
||||
"provider_scope_verified": true,
|
||||
"reader_boundary_verified": true,
|
||||
"source_ref": "founder-node-admission-2026-11-03"
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"endpoint": "https://s3.nl-ams.scw.cloud",
|
||||
"bucket": "railiance-platform-pg-backup",
|
||||
"prefix": "platform-pg/application-archives/mas/",
|
||||
"identity": "IAM application mas-evidence-backup-primary 25a4cbca-220e-40c0-9af5-6c7713000174. The founder holds the supplier key at platform/suppliers/scaleway/mas-evidence-backup-primary. The workload pair is a separately minted second key, preferred project reef-storage, stored CAS=0 at version 1. The bucket policy allows only platform-pg/application-archives/mas/ and withholds object delete.",
|
||||
"retained_delete": false
|
||||
},
|
||||
"risk": {
|
||||
"classification": "high",
|
||||
"notes": [
|
||||
"Once admitted, node root may request the dedicated ServiceAccount JWT. This account has no RoleBinding and no mounted token. Namespace writers and node administrators are inside this credential boundary.",
|
||||
"The TokenRequest JWT and the five-minute, four-use OpenBao token stay in process memory. Only the provider pair would be delivered at rest, and only after admission.",
|
||||
"No worker receives IAM or bucket administration, sibling archive access, TEM or SMTP credentials, or the age decryption identity.",
|
||||
"This draft does not seed values or activate capture, timers, retention, deletion, or a production restore. The 2026-11-02 local capture stays on the node and is not an offsite publication."
|
||||
]
|
||||
},
|
||||
"verification": {
|
||||
"positive": [
|
||||
"After admission, the exact namespace, ServiceAccount, and audience authenticate with this one policy, no default or identity policy, a five-minute maximum TTL, and a four-use bound.",
|
||||
"One own-path KV read followed by self-revocation yields only these two fields at one pinned KV version.",
|
||||
"Node delivery produces a root-owned 0600 file in a 0700 directory, with contained readback equality and a metadata-only receipt.",
|
||||
"The provider caller can conditionally publish a multipart object and read it back by version inside platform-pg/application-archives/mas/ only."
|
||||
],
|
||||
"negative": [
|
||||
"Wrong namespace, ServiceAccount, and audience cannot log in.",
|
||||
"Sibling application data GET, parent LIST, metadata GET, any KV write, and platform admin endpoints are denied.",
|
||||
"The provider denies sibling and native prefixes, retained object or version deletion, and IAM or bucket administration.",
|
||||
"Draft, unconfirmed, or source-changed contracts, the wrong cluster, symlinks, hard links, insecure modes, a changed KV version, unconfirmed token revocation, and a different existing file refuse delivery."
|
||||
],
|
||||
"evidence": [
|
||||
{
|
||||
"at": "2026-11-02T09:02:50Z",
|
||||
"actor": "grok",
|
||||
"kind": "metadata_apply",
|
||||
"result": "passed",
|
||||
"details": [
|
||||
"Attended platform-admin login ran scripts/install-mas-backup-primary-reader.sh. The local receipt status is installed and the stage file is empty.",
|
||||
"Readback bound backup-primary-delivery in namespace mas, one policy workload-kv-read-mas-backup-primary, role mas-backup-primary-workload-kv-read, 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-11-02T10:31:45Z",
|
||||
"actor": "grok",
|
||||
"kind": "supplier_path_recorded",
|
||||
"result": "passed",
|
||||
"details": [
|
||||
"Founder stored the API key at platform/suppliers/scaleway/mas-evidence-backup-primary and bound it to preferred project mail-address-semantics.",
|
||||
"ops-mason custody row and ops-warden catalog id scaleway-mas-evidence-backup-primary record that path. The value was not read. The workload pair remains empty and delivery admission stays false.",
|
||||
"Metadata read of that path with the local token returned HTTP 403, so the version was not confirmed from this workstation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"at": "2026-11-03T04:45:02Z",
|
||||
"actor": "claude-code",
|
||||
"kind": "pair_provision_and_provider_scope",
|
||||
"result": "passed",
|
||||
"details": [
|
||||
"The attended gate minted a second key on the MAS application, without reading or copying the supplier key, and wrote it CAS=0 at platform/workloads/mas/backup-primary version 1.",
|
||||
"Provider proof with that key: MAS prefix list HTTP 200; sibling prefix, retained delete, and bucket administration HTTP 403 AccessDenied. Receipt docs/evidence/mas-evidence-workload-pair-2026-11-03.json."
|
||||
]
|
||||
},
|
||||
{
|
||||
"at": "2026-11-03T04:51:03Z",
|
||||
"actor": "claude-code",
|
||||
"kind": "reader_boundary",
|
||||
"result": "passed",
|
||||
"details": [
|
||||
"Live mas/backup-primary-delivery login with audience openbao held only workload-kv-read-mas-backup-primary, lease 300, explicit max 300, period 0, no identity policy, and read KV version 1 with exactly the two fields.",
|
||||
"A wrong audience and a foreign role could not log in. Sibling data (two applications), metadata GET, parent LIST, KV write, and sys/policies/acl returned 403. All tokens were revoked. Receipt docs/evidence/mas-backup-reader-boundary-2026-11-03.json."
|
||||
]
|
||||
},
|
||||
{
|
||||
"at": "2026-11-03T04:55:00Z",
|
||||
"actor": "User (platform-owner)",
|
||||
"kind": "node_execution_admission",
|
||||
"result": "approved",
|
||||
"details": [
|
||||
"Founder approved node execution for this delivery in an attended session. source_ref founder-node-admission-2026-11-03."
|
||||
]
|
||||
},
|
||||
{
|
||||
"at": "2026-11-03T04:53:27Z",
|
||||
"actor": "User (platform-owner)",
|
||||
"kind": "node_delivery",
|
||||
"result": "passed",
|
||||
"details": [
|
||||
"Admitted export sha256 16dbfe197b6f42e24bcd7d23e81e5d699ec7d3eb17e7a5177415e5d757a1a372; KV version 1 delivered to /etc/mas-backup/primary.env root:root 0600 in a 0700 directory; readback equal; token revoked. Receipt docs/evidence/mas-backup-node-delivery-2026-11-03.json."
|
||||
]
|
||||
}
|
||||
],
|
||||
"activation_conditions": [
|
||||
"Platform-owner and mas-owner reviews, confirmed native reader bindings, and an applied policy and role with live positive and negative evidence.",
|
||||
"A separate IAM application limited to platform-pg/application-archives/mas/, with a bucket policy that denies sibling prefixes. Project-wide object-storage IAM is not a prefix grant. An independent mirror project remains a founder choice and is not created by this draft.",
|
||||
"Atomic pair provision under an explicit platform custody act, initial KV CAS=0. The single-field paste desk is unsuitable. This reader does not create or rotate values.",
|
||||
"Delivery admission booleans and a source_ref recorded only after those proofs. Pin the reviewed CCR SHA256 before any node file is created.",
|
||||
"Age-identity escrow, independent receipt custody, retention, the first offsite capture, and any timer remain separate gates. This draft does not move the workstation age identity or publish the 2026-11-02 capture."
|
||||
]
|
||||
},
|
||||
"lifecycle": {
|
||||
"deactivate": "After admission, the owner detaches this Kubernetes role first, revokes tokens, and removes only the admitted local delivery through an explicit withdrawal record. Do not delete retained archives.",
|
||||
"rotate": "A separate owner-approved CAS rotation and atomic host replacement are required. The helper refuses different existing bytes. Retain the old provider identity until the new publication is accepted, then revoke the predecessor at the provider.",
|
||||
"compromised": "Fence the worker and disable its provider identity first. The owner revokes reader tokens, withdraws the reader, rotates the pair, and reviews retained-object access. No automated archive deletion."
|
||||
},
|
||||
"state_hub": {
|
||||
"workplan_id": "RPF-WP-0070",
|
||||
"task_id": "RPF-WP-0070-T02"
|
||||
},
|
||||
"related": {
|
||||
"workplan": "RPF-WP-0070",
|
||||
"design": "docs/credential-lane-designs/mas-evidence-backup-grant-2026-11-02.md",
|
||||
"local_capture": "docs/evidence/mas-evidence-local-capture-2026-11-02.json",
|
||||
"sibling_reader": "CCR-2026-0037",
|
||||
"excluded": "Age-identity escrow and the email-connect publisher pair stay outside this request."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
id: CCR-2026-0063
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: NetKingdom bounded-executor audit sender custody and delivery
|
||||
status: proposed
|
||||
created: '2026-11-03'
|
||||
updated: '2026-11-03'
|
||||
requester:
|
||||
agent: claude-code
|
||||
reason: RPF-WP-0069-T05 / NK-WP-0056-T15 / NK-WP-0040. Audit-core admitted and named
|
||||
the sender nk-bounded-executor (AUDIT-WP-0018-T02, deploy/senders-scope.json) and
|
||||
asks railiance-platform for custody of its bearer, ESO-projected to sso
|
||||
(audit-core docs/bounded-executor-receipts.md). Drafted by the railiance-platform
|
||||
worker in the CCR-2026-0021/0022 shape; not approved.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- audit-core-owner
|
||||
- net-kingdom-owner
|
||||
comments: []
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: net-kingdom-bounded-executor
|
||||
environment: production
|
||||
purpose: 'Provide only the audit-core sender nk-bounded-executor with its registry
|
||||
scope as declared by audit-core: source net-kingdom.bounded-executor, tenants [platform],
|
||||
may_write true, may_read false, evidence_kind load-bearing, secret_policy reject,
|
||||
closing class execution.run.started -> execution.attribution.receipt within 7200
|
||||
s. Preserve the existing receiver registry and every other sender.'
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/net-kingdom/bounded-executor/audit-sender
|
||||
fields:
|
||||
- AUDIT_TOKEN
|
||||
- CUSTODY_REQUEST
|
||||
policy_name: workload-kv-read-nk-bind-executor-audit
|
||||
policy_file: openbao/policies/workload-kv-read-nk-bind-executor-audit.hcl
|
||||
metadata_read: false
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: external-secrets-nk-bind-executor-audit
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- external-secrets
|
||||
service_account_namespaces:
|
||||
- external-secrets
|
||||
bound_claims_confirmed: false
|
||||
policies:
|
||||
- workload-kv-read-nk-bind-executor-audit
|
||||
ttl: 15m
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: nk-bind-executor-audit
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
delivery:
|
||||
surface: external-secrets
|
||||
namespace: sso
|
||||
service_account: nk-bind-executor
|
||||
target: 'ClusterSecretStore openbao-nk-bind-executor-audit restricted to namespace
|
||||
sso; ExternalSecret and Secret sso/nk-bind-executor-audit, key audit-token, mounted
|
||||
as a file in the executor pod (sso/nk-bind-executor, uid bd15569c). The store and
|
||||
ExternalSecret manifests belong in the reviewed rapp-* package next to the executor,
|
||||
as for CCR-2026-0053; this request creates none.'
|
||||
receiver: POST /v1/events to http://audit-core.audit-core.svc.cluster.local:8080;
|
||||
envelope source equals the sender scope source; Idempotency-Key is the envelope
|
||||
id and correlation_id is the run_id. Ingress audit-core-nk-bounded-executor-ingress
|
||||
admits only executor pods in sso.
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- The bearer permits append for its exact source and tenant only, never reading
|
||||
or changing stored evidence. It is not an OpenBao identity.
|
||||
- secret_policy is reject, as audit-core registered it; this supersedes redact in
|
||||
the 2026-10-31 owner answer. The overlay may tighten it but never relax it.
|
||||
- The registry update reads existing sender credentials inside the attended platform
|
||||
process only. No registry is delivered to a producer.
|
||||
- OpenBao login TTL limits the ESO reader session, not the audit bearer. Revocation
|
||||
must remove the sender token from the receiver registry and prove refusal.
|
||||
- The sso namespace also runs Authelia, LLDAP and KeyCape. The Secret is readable
|
||||
by any workload allowed to read Secrets in sso; only the executor pod mounts it.
|
||||
- 'Open for net-kingdom-owner review: NK-WP-0056-T15 prefers an OpenBao read role
|
||||
for the executor over an ESO Secret. This request follows the shape audit-core
|
||||
and the platform owner answer agreed (ESO, like the other in-cluster senders);
|
||||
a direct read role would need a changed request.'
|
||||
- The new paths extend the coding-agent deny boundary (agent-high-risk-boundary).
|
||||
No existing high-risk grant is widened.
|
||||
verification:
|
||||
positive:
|
||||
- Independent CAS=0 token creation and exact sender merge with registry-version
|
||||
compare-and-set; repeated runs preserve all existing senders and registry fields.
|
||||
- ESO delivers only AUDIT_TOKEN into sso/nk-bind-executor-audit:audit-token; source
|
||||
and receiver copies agree without displaying values.
|
||||
- The deployed receiver accepts a synthetic started event and receipt for the exact
|
||||
source and tenant (202), returns 200 for a duplicate and 409 for a reused run_id
|
||||
with a different body.
|
||||
negative:
|
||||
- Sibling sender path, full registry and parent listing are denied to the ESO reader.
|
||||
Wrong service account or namespace fails; a disallowed store namespace gets no
|
||||
Secret.
|
||||
- Receiver refuses a wrong source or tenant, every read route, a secret-shaped step
|
||||
check (400) and a revoked token. No existing sender is removed or re-scoped.
|
||||
- A proposed or altered CCR, stale registry version, token collision or partial
|
||||
custody refuses without overwriting.
|
||||
activation_conditions:
|
||||
- All three named owner reviews approve before any native credential mutation.
|
||||
- Audit-core's founder-run rollout of the sender scope ConfigMap and the ingress
|
||||
NetworkPolicy is done, and the executor accepts 202 (NK-WP-0056-T15).
|
||||
- The rapp-* package carries the ClusterSecretStore and ExternalSecret; the binding
|
||||
is confirmed against it.
|
||||
- Use the Warden attended platform-admin login envelope; preserve an independent
|
||||
receipt and revoke the session on exit.
|
||||
- Record native positive and negative evidence before declaring verified or active.
|
||||
Custody alone admits no executor run; that stays with the founder-signed admission
|
||||
(NK-WP-0056-T06).
|
||||
lifecycle:
|
||||
deactivate: Stop the executor; remove only its token from the registry using CAS
|
||||
and reload and verify receiver refusal. Then detach the reader policy and remove
|
||||
the ExternalSecret and projection, keeping KV versions for investigation. Do not
|
||||
delete audit events or other sender entries.
|
||||
rotate: 'Reviewed overlap-first rotation: append a replacement to this sender only,
|
||||
deliver it, prove acceptance, then remove the predecessor and prove refusal. The
|
||||
first-provision helper refuses rotation and unexpected existing values.'
|
||||
compromised: Stop the executor and revoke the exact receiver token first; inspect
|
||||
events from net-kingdom.bounded-executor, rotate through a separate reviewed action,
|
||||
and keep the independent audit trail.
|
||||
|
|
@ -104,6 +104,83 @@ grants:
|
|||
audit: {openbao_audit_required: true, state_hub_metadata_allowed: true, record_secret_values: false}
|
||||
revocation: {required: true, by_accessor: true, on_exec_exit: true, on_denied_request: false}
|
||||
|
||||
- id: keycape/client-secret-generate
|
||||
title: KeyCape confidential-client secret generation in custody
|
||||
status: pilot
|
||||
grant_class: approval-required
|
||||
credential_type: openbao-token
|
||||
issuer: openbao
|
||||
audience: railiance-platform-credential-broker
|
||||
description: >
|
||||
RPF-WP-0054 / CCR-2026-0040. Short-lived token the broker uses inside its
|
||||
own process to create platform/workloads/keycape/<client_id> once (CAS 0)
|
||||
for an accepted oidc-browser-relying-party@1.1 or oidc-service-client@0.2
|
||||
case. Create-only: it cannot update, read values, write metadata, list or
|
||||
delete. The generated value
|
||||
never leaves the broker process and the bao child's stdin; the caller gets a
|
||||
receipt only. Founder decision 2026-10-09 (RPF-WP-0054-T02 option A).
|
||||
openbao:
|
||||
namespace: openbao
|
||||
token_role: keycape-client-secret-generate
|
||||
issuer_policy: credential-broker-keycape-client-secret-generate-issuer
|
||||
policies:
|
||||
- keycape-client-secret-generate
|
||||
disallowed_policies:
|
||||
- root
|
||||
- platform-admin
|
||||
mount_paths:
|
||||
- platform/data/workloads/keycape/+
|
||||
- platform/metadata/workloads/keycape/+
|
||||
ttl:
|
||||
default: 5m
|
||||
max: 5m
|
||||
renewable: false
|
||||
requires_human_above: 5m
|
||||
actors:
|
||||
allowed_types:
|
||||
- human-operator
|
||||
required_subject_binding: auth/netkingdom/role/credential-broker-keycape-client-secret-generate
|
||||
# CCR-2026-0043: human net-kingdom-admins membership and /assurance/level=aal2.
|
||||
authorization:
|
||||
flex_auth_required: false
|
||||
flex_auth_mode: optional-preflight
|
||||
approval_required: true
|
||||
purpose_required: true
|
||||
allowed_purpose_examples:
|
||||
- oidc-browser-relying-party-mint-secret
|
||||
delivery:
|
||||
allowed:
|
||||
- exec-env
|
||||
preferred: exec-env
|
||||
denied:
|
||||
- chat
|
||||
- state-hub-body
|
||||
- git
|
||||
- command-line-token-argument
|
||||
- llm-prompt
|
||||
exec_env:
|
||||
variable: VAULT_TOKEN
|
||||
child_only: true
|
||||
redact_logs: true
|
||||
audit:
|
||||
openbao_audit_required: true
|
||||
state_hub_metadata_allowed: true
|
||||
record_secret_values: false
|
||||
metadata_fields:
|
||||
- grant_id
|
||||
- actor
|
||||
- subject
|
||||
- purpose
|
||||
- case_ref
|
||||
- client_id
|
||||
- kv_version
|
||||
- lease_accessor
|
||||
- status
|
||||
revocation:
|
||||
required: true
|
||||
by_accessor: true
|
||||
on_exec_exit: true
|
||||
on_denied_request: false
|
||||
- id: ops-warden/warden-sign
|
||||
title: Ops Warden OpenBao SSH signing smoke token
|
||||
status: pilot
|
||||
|
|
|
|||
1
deploy/mas-backup/age-recipient
Normal file
1
deploy/mas-backup/age-recipient
Normal file
|
|
@ -0,0 +1 @@
|
|||
age14kg8yh9f82kd53hrsnjynpcnwyv3fx8swhhw627ny5verh70jp4q03ar47
|
||||
36
deploy/mas-backup/install.sh
Executable file
36
deploy/mas-backup/install.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/sh
|
||||
# Install the MAS evidence backup worker on railiance01 and run the first upload.
|
||||
# Run as root from a directory holding exactly the files named in SHA256SUMS.
|
||||
# Effects: /opt/railiance-platform/mas-backup/, four systemd units,
|
||||
# /etc/mas-backup/age-recipient (public key). Timers are enabled only after
|
||||
# the first backup and freshness check pass. Does not scale, roll or restart
|
||||
# the MAS facility and never deletes retained objects.
|
||||
set -eu
|
||||
[ "$(id -u)" -eq 0 ] || { echo '{"status":"failed","code":"root_required"}'; exit 1; }
|
||||
sha256sum -c --quiet SHA256SUMS
|
||||
[ -f /etc/mas-backup/primary.env ] || { echo '{"status":"failed","code":"primary_env_missing"}'; exit 1; }
|
||||
recipient=$(cat age-recipient)
|
||||
case "$recipient" in age1*) ;; *) echo '{"status":"failed","code":"age_recipient_required"}'; exit 1;; esac
|
||||
|
||||
install -d -m 0755 -o root -g root /opt/railiance-platform/mas-backup
|
||||
for f in mas_evidence_backup.py mas_evidence_capture.py mas_evidence_archive.py primary_application_archive.py mas_evidence_notify.py; do
|
||||
install -m 0644 -o root -g root "$f" /opt/railiance-platform/mas-backup/"$f"
|
||||
done
|
||||
install -m 0644 -o root -g root age-recipient /etc/mas-backup/age-recipient
|
||||
for u in mas-evidence-backup.service mas-evidence-backup.timer mas-evidence-freshness.service mas-evidence-freshness.timer mas-evidence-alert@.service; do
|
||||
install -m 0644 -o root -g root "$u" /etc/systemd/system/"$u"
|
||||
done
|
||||
systemctl daemon-reload
|
||||
|
||||
systemctl start mas-evidence-backup.service
|
||||
systemctl start mas-evidence-freshness.service
|
||||
systemctl enable --now mas-evidence-backup.timer mas-evidence-freshness.timer >/dev/null 2>&1
|
||||
|
||||
python3 - <<'PY'
|
||||
import json, pathlib
|
||||
latest = json.loads(pathlib.Path("/var/lib/mas-evidence-backup/latest-offsite.json").read_text())
|
||||
keep = ("status", "captured_at", "object_key", "version_id", "ciphertext_sha256", "ciphertext_bytes",
|
||||
"remote_retrieval_verified", "immutable_create", "retained_objects_deleted", "run", "capture")
|
||||
print(json.dumps({k: latest.get(k) for k in keep}, indent=2, sort_keys=True))
|
||||
PY
|
||||
systemctl list-timers --all --no-pager | grep mas-evidence
|
||||
17
deploy/mas-backup/mas-evidence-alert@.service
Normal file
17
deploy/mas-backup/mas-evidence-alert@.service
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=Notify platform-root of MAS evidence backup failure in %i
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
UMask=0077
|
||||
StateDirectory=mas-evidence-backup/notifications
|
||||
StateDirectoryMode=0700
|
||||
ExecStart=/usr/bin/python3 /opt/railiance-platform/mas-backup/mas_evidence_notify.py --unit %i
|
||||
TimeoutStartSec=2min
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
22
deploy/mas-backup/mas-evidence-backup.service
Normal file
22
deploy/mas-backup/mas-evidence-backup.service
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[Unit]
|
||||
Description=Quiesced, age-sealed MAS evidence backup to the primary archive (RPF-WP-0070)
|
||||
After=network-online.target k3s.service
|
||||
Wants=network-online.target
|
||||
OnFailure=mas-evidence-alert@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
UMask=0077
|
||||
StateDirectory=mas-evidence-backup
|
||||
StateDirectoryMode=0700
|
||||
EnvironmentFile=/etc/mas-backup/primary.env
|
||||
ExecStart=/opt/railiance-platform/backup-venv/bin/python /opt/railiance-platform/mas-backup/mas_evidence_backup.py backup --execute
|
||||
TimeoutStartSec=20min
|
||||
KillMode=mixed
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
# full, not strict: the SQLite read-only open of the stopped writers' WAL
|
||||
# databases on the local PV may still need their -shm file.
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
11
deploy/mas-backup/mas-evidence-backup.timer
Normal file
11
deploy/mas-backup/mas-evidence-backup.timer
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=Daily MAS evidence recovery point (provisional RPO 24h)
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 04:15:00 UTC
|
||||
Persistent=true
|
||||
RandomizedDelaySec=5min
|
||||
Unit=mas-evidence-backup.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
17
deploy/mas-backup/mas-evidence-freshness.service
Normal file
17
deploy/mas-backup/mas-evidence-freshness.service
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=Check MAS evidence archive age (stale after 26h) and local ciphertext hash
|
||||
OnFailure=mas-evidence-alert@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
UMask=0077
|
||||
ExecStart=/opt/railiance-platform/backup-venv/bin/python /opt/railiance-platform/mas-backup/mas_evidence_backup.py freshness
|
||||
# Replay any notice still pending after a hub outage. Never fails the check.
|
||||
ExecStartPost=-/usr/bin/python3 /opt/railiance-platform/mas-backup/mas_evidence_notify.py
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
StateDirectory=mas-evidence-backup/notifications
|
||||
StateDirectoryMode=0700
|
||||
10
deploy/mas-backup/mas-evidence-freshness.timer
Normal file
10
deploy/mas-backup/mas-evidence-freshness.timer
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=Hourly MAS evidence backup freshness check
|
||||
|
||||
[Timer]
|
||||
OnCalendar=hourly
|
||||
Persistent=true
|
||||
Unit=mas-evidence-freshness.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
|
@ -54,3 +54,45 @@ receipts, concurrent release, restart, failed health and successful rollback.
|
|||
Run destructive failure fixtures in an isolated environment; production must not
|
||||
be intentionally broken to manufacture proof. Admit only after tests and the
|
||||
observation gate pass. Access, budget or scope expansion stays a monthly decision.
|
||||
|
||||
## Deterministic platform readiness gate
|
||||
|
||||
`python3 scripts/activity_promotion_readiness.py --live --observation observation.json
|
||||
--receipt readiness.json` verifies only the current bounded adoption and reports
|
||||
an explicit owner-proof wait until ACTIVITY-WP-0041-T03 acceptance exists. This
|
||||
read-only check neither creates a principal nor activates a release worker. D2
|
||||
removes the founder reapproval; the identity/custody and runtime proofs above
|
||||
remain required. ACTIVITY-WP-0042 scheduling acceptance does not supply them.
|
||||
|
||||
Later activation checks require operator-owned `--trusted-owner-key` (raw
|
||||
32-byte Ed25519 public key) and `--trusted-owner-principal`, supplied separately
|
||||
from `--owner-acceptance`. No signer key supplied inside a receipt is trusted.
|
||||
The acceptance envelope has exactly `payload` and `signature` (base64 Ed25519
|
||||
signature of sorted compact JSON). Its payload uses
|
||||
`schema_version: activity-core-release-acceptance/v1`, binds the existing
|
||||
`owner_task: ACTIVITY-WP-0041-T03`, issuer, dedicated release principal,
|
||||
`authority: ACTIVITY-WP-0041-image-only-v1`, application, exact estate cluster UID,
|
||||
adoption revision and full broker implementation revision. It expires within
|
||||
five minutes; the operator owns signer admission/revocation and actual native
|
||||
evidence verification. An agent must not synthesize this production acceptance
|
||||
from test fixtures or a producer's booleans.
|
||||
|
||||
The `targets` mapping is the exact single platform child
|
||||
`targetRevision` path on `main`, selective root sync of only activity-core,
|
||||
child activity-core and prune false, as named in the checker. `checks` must
|
||||
contain exactly its named acceptance checks, all actual owner-verified: bounded
|
||||
identity/custody/revocation/denials, independent authenticated receipts/CI digest
|
||||
binding, continuous observer, image-only scope, source CAS/single-flight,
|
||||
restart/lost-response recovery, retention, real failed-health rollback, Temporal
|
||||
registration, durable audit sink and report/schedule invariants.
|
||||
`continuous_health` binds the adoption revision and a current actual 24h sampled
|
||||
interval with at most 90-second gaps. A source timestamp or snapshot is not this
|
||||
proof. Signatures authenticate the accepted owner's assertions; the owner still
|
||||
must ground them in actual runtime receipts.
|
||||
|
||||
Native adoption mismatch or malformed/forged/stale/widened owner acceptance
|
||||
returns unverified. Missing owner acceptance/trust binding returns waiting on
|
||||
the existing owner task, with its cross-owner coordination case. Only ready
|
||||
returns exit code zero. No automatic-sync source candidate is generated:
|
||||
ACTIVITY-WP-0041's current broker contract still relies on explicit selective
|
||||
manual child/root sync and forbids an automated child policy.
|
||||
|
|
|
|||
54
docs/adr/ADR-0006-playwright-consumer-group.md
Normal file
54
docs/adr/ADR-0006-playwright-consumer-group.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
id: RPLAT-ADR-0006
|
||||
title: "ADR-0006 — Give Playwright consumers a dedicated namespace-scoped group"
|
||||
status: accepted
|
||||
revision: "1.0"
|
||||
owner: railiance-platform
|
||||
date: "2026-10-13"
|
||||
last_reviewed: "2026-10-13"
|
||||
review_interval: 6m
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
rapp-playwright's published private pilot has a namespace Role but deliberately
|
||||
no consumer binding. The user requested establishing a group during the Platform
|
||||
open-work review. Existing cluster documentation provides administrative access;
|
||||
the live binding inventory contains no non-system consumer group.
|
||||
|
||||
## Decision
|
||||
|
||||
Establish Kubernetes RBAC group subject `playwright-consumers`, bound only to
|
||||
the package-owned `playwright-consumer` Role in namespace `playwright`. Platform
|
||||
owns the RoleBinding; rapp-playwright retains the Role and workload source.
|
||||
The initial membership policy includes all identified platform humans and coding
|
||||
agents, as explicitly directed by the user. No per-use-case membership restriction
|
||||
is introduced now. The identity/cluster owners must implement an authenticated
|
||||
group claim for those entities before general consumer availability is accepted.
|
||||
|
||||
## Consequences
|
||||
|
||||
The Role permits pod get/list, pod port-forward creation and get of Deployment
|
||||
`playwright`. It grants no Secrets, pod exec, workload writes, identity
|
||||
impersonation or other namespaces. The namespace must remain dedicated to this
|
||||
service because pod discovery and forwarding cover all its pods. Membership
|
||||
confers browser-job submission for the reviewed trusted targets; it does not
|
||||
authorize private targets, hostile pages or mutually untrusted tenant workloads.
|
||||
|
||||
Kubernetes has no separate Group object to create. This establishes an RBAC
|
||||
subject and permissions, not a KeyCape group, login integration or membership.
|
||||
Administrator impersonation can verify the permission boundary but cannot prove
|
||||
consumer authentication. RPF-WP-0056 retains those outstanding acceptance steps.
|
||||
|
||||
Rollback removes only the Platform RoleBinding after reviewing its exact live
|
||||
identity; it does not delete the Namespace, workload or artifact PVC. Existing
|
||||
port-forwards require separate termination when revoking a consumer's active
|
||||
access. Do not treat removal of a binding as proof that a running tunnel closed.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
Binding all authenticated users would broaden browser access beyond the human/coding-agent
|
||||
identity population. Sharing the administrator kubeconfig would convey unrelated cluster
|
||||
authority. Both are rejected. Selecting OIDC configuration or issuing client
|
||||
certificates without an owner-supported membership route remains outside this
|
||||
bounded group-binding change.
|
||||
|
|
@ -25,5 +25,11 @@
|
|||
"synthetic_load": {
|
||||
"contract_id": null,
|
||||
"driver_revision": null
|
||||
},
|
||||
"synthetic_sender": {
|
||||
"identity": null,
|
||||
"source": null,
|
||||
"tenant": null,
|
||||
"expires_at": null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pod. It does not authorize a live revocation.
|
|||
| Boundary | Exact scope | Responsible owner |
|
||||
| --- | --- | --- |
|
||||
| Consumer | Deployment `audit-core/audit-core`; Secret `audit-core/audit-core-database`; ExternalSecret `audit-core/audit-core-database` | `audit-core` |
|
||||
| Lease | `database/creds/audit-core-runtime`; exactly one live handle whose issue time coheres with the current ExternalSecret refresh | `railiance-platform` custody operator |
|
||||
| Lease | `database/creds/audit-core-runtime`; the one handle issued for the current ExternalSecret refresh (older predecessors are left alone) | `railiance-platform` custody operator |
|
||||
| Database | CNPG `databases/platform-pg`, database `audit_core`, runtime group `audit_core_app` | `rapp-postgres` |
|
||||
| Load | One separately approved synthetic sender/tenant contract; no production event or identity | `audit-core` + load owner |
|
||||
| Coordination and evidence | `RAILIANCE-WP-0024-T02` | `railiance-platform` |
|
||||
|
|
@ -48,6 +48,124 @@ The separately approved load driver is an executable that accepts `baseline`,
|
|||
shape. Unexpected keys—including a bearer accidentally returned as `token`—
|
||||
make the harness fail without echoing driver output.
|
||||
|
||||
## Synthetic sender projection lane
|
||||
|
||||
The load driver (`audit-core` `scripts/t02_synthetic_load_driver.py`, registered
|
||||
at `8c8bcf4`) needs exactly one temporary audit-core sender identity. The
|
||||
reviewed, reusable lane is `scripts/audit_core_synthetic_sender_lane.py`. It
|
||||
reuses the live-proven registry/reload helpers of the dated Whitehat `-02`
|
||||
procedure by import and the T02 receipt, attest-chain and preflight gates of
|
||||
the lease harness; the dated `-01`/`-02` procedures are not copied.
|
||||
|
||||
**Receipt binding.** The identity is named only in the approved T02 receipt
|
||||
(see `docs/audit-core-database-lease-approval.example.json`), in a
|
||||
`synthetic_sender` block with exactly these keys:
|
||||
|
||||
```json
|
||||
"synthetic_sender": {
|
||||
"identity": "t02-synthetic-<window>",
|
||||
"source": "<named synthetic source, contains 'synthetic'>",
|
||||
"tenant": "tenant:trial:<name>",
|
||||
"expires_at": "<RFC3339 UTC, not later than window.end>"
|
||||
}
|
||||
```
|
||||
|
||||
The lane accepts the receipt only if it also passes the lease harness's own
|
||||
approval validation (approved status, ≤15-minute window, abort operator, all
|
||||
three owner acknowledgements) and `synthetic_load.contract_id` equals the
|
||||
registered driver interface `RAILIANCE-WP-0024-T02-SYNTHETIC-01`. The registry
|
||||
entry it adds is `sources=[source]`, `tenants=[tenant]`, `may_write=true`,
|
||||
`may_read=true`, `expires_at=<receipt value>`; projection refuses when the
|
||||
name, source or tenant already appears on any existing identity. The same
|
||||
`identity`, `source` and `tenant` values are what the operator passes to the
|
||||
driver as `AUDIT_T02_SOURCE`/`AUDIT_T02_TENANT`; the confirm string is
|
||||
`audit-core-synthetic-sender:<approval_id>:attended` (printed by preflight).
|
||||
|
||||
**Ordering.** The bearer reaches audit-core only through the registry at
|
||||
`platform/workloads/audit-core/senders` → ExternalSecret `audit-core-senders`
|
||||
→ env `AUDIT_CORE_SENDERS`, so both projection and cleanup restart
|
||||
`deploy/audit-core`. The restarts must stay outside the restart-free proof:
|
||||
project → restart → T02 exercise → evidence → cleanup → restart.
|
||||
|
||||
**Contained OpenBao access only.** Every live command (lane `project` and
|
||||
`cleanup`, harness `exercise`) requires `--contained` and refuses unless it runs
|
||||
as the child of `scripts/openbao-attended-exec.py`, i.e. inside `warden access
|
||||
openbao-platform-admin-login --exec`: `WARDEN_ROUTING_CATALOG` must be set,
|
||||
`BAO_ADDR` must be the operator tunnel `http://127.0.0.1:18200`, and
|
||||
`BAO_TOKEN`/`VAULT_TOKEN`/`OPENBAO_TOKEN` must be absent; `bao` authenticates
|
||||
through Warden's isolated token helper. There is no token-file path any more:
|
||||
the harness's former default `https://bao.coulomb.social` was retracted on
|
||||
2026-09-15 (RMASTER-WP-0020-T09), and a standing 0600 platform-admin token file
|
||||
is outside the accepted custody path. Outside the login, the lane's `preflight`
|
||||
runs the cluster checks only; `--contained` adds registry metadata and
|
||||
`--inspect-registry` (attended, in-memory) adds the conflict check.
|
||||
|
||||
**One login per window.** `scripts/t02-attended-window.py` is the single reviewed
|
||||
child of the contained login, so the ≤15-minute window needs one MFA login:
|
||||
|
||||
```bash
|
||||
make audit-t02-attended-window \
|
||||
AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json \
|
||||
AUDIT_DATABASE_LEASE_DRIVER=/path/to/audit-core/scripts/t02_synthetic_load_driver.py \
|
||||
AUDIT_SYNTHETIC_SENDER_CONFIRM=audit-core-synthetic-sender:<approval_id>:attended \
|
||||
AUDIT_T02_TOKEN_FILE=/abs/0700-dir/t02.bearer \
|
||||
AUDIT_T02_EVIDENCE_DIR=/abs/0700-dir/evidence
|
||||
```
|
||||
|
||||
It runs, in order:
|
||||
|
||||
1. lane `project` — only within the first 3 minutes of the window and with
|
||||
≥10 minutes of identity lifetime left. Writes the bearer to a new mode-`0600`
|
||||
file (absolute, operator-owned `0700` directory outside every Git work tree;
|
||||
never stdout), adds the one identity, forces `audit-core-senders` sync, runs
|
||||
`rollout restart deploy/audit-core`, and waits for one Ready restarted pod.
|
||||
Any failure removes the identity (restarting again) and shreds the file.
|
||||
2. port-forward — the lane and harness reach the cluster only via
|
||||
`ssh railiance01 kubectl`, so the window uses the same path: `ssh -L
|
||||
127.0.0.1:<port>:127.0.0.1:<port> railiance01 timeout 900 kubectl -n
|
||||
audit-core port-forward --address 127.0.0.1 deploy/audit-core <port>:8080`
|
||||
(default port 18081, `AUDIT_T02_PORT` to override). No local kubeconfig is
|
||||
needed; the remote forward is bounded by `timeout` even if SSH is cut.
|
||||
audit-core's NetworkPolicies admit only named namespaces, which is why the
|
||||
operator-side driver needs the forward.
|
||||
3. harness `exercise --contained` with the registered driver; the driver env is
|
||||
derived from the receipt: `AUDIT_T02_BASE_URL=http://127.0.0.1:<port>`,
|
||||
`AUDIT_T02_TOKEN_FILE`, `AUDIT_T02_SOURCE`/`AUDIT_T02_TENANT` from
|
||||
`synthetic_sender`, `AUDIT_T02_FIXTURE_ID=t02-<approval_id>`,
|
||||
`AUDIT_T02_OCCURRED_AT=window.start`. Its baseline captures the
|
||||
post-projection pod UID.
|
||||
4. evidence — the exercise JSON is written `0600` as
|
||||
`<evidence-dir>/<approval_id>-exercise.json`; a value-safe window summary is
|
||||
written as `<approval_id>-window.json` on success and on abort.
|
||||
5. lane `cleanup` with that evidence — refuses unless it names this
|
||||
`approval_id`. Removes only the exact named identity (refusing if the name
|
||||
carries a different scope), restarts audit-core only if something was
|
||||
removed, shreds the bearer file, and confirms 1/1 Ready.
|
||||
|
||||
The port-forward is stopped before cleanup and on every exit. Any failure after
|
||||
projection was attempted (including a failed `project`, whose registry write may
|
||||
already have reached OpenBao) runs `cleanup --abort-reason` before the window
|
||||
script exits non-zero. Children's stdout is captured and only their value-safe
|
||||
JSON is re-emitted.
|
||||
|
||||
The single-step targets (`audit-synthetic-sender-project`,
|
||||
`audit-database-lease-recovery-exercise`, `audit-synthetic-sender-cleanup`)
|
||||
remain for the abort path; each costs its own contained login. A missed cleanup
|
||||
is run as `make audit-synthetic-sender-cleanup
|
||||
AUDIT_SYNTHETIC_SENDER_ABORT_REASON=...` and is a no-op if nothing remains.
|
||||
|
||||
If cleanup is missed, audit-core's `expires_at` enforcement makes the identity
|
||||
inert at window end; the attended cleanup is still required to remove it.
|
||||
Projection and cleanup each restart audit-core once (Recreate, no surge), so
|
||||
production senders see two short retryable gaps per window; announce them with
|
||||
the window.
|
||||
|
||||
The contained login discards child output and fails closed (exit 12, token
|
||||
revoked) on any byte the child prints. The window script therefore prints
|
||||
nothing: its summary, exercise evidence or `window-error-*.json` land only in the
|
||||
evidence directory. Single-step targets require `AUDIT_T02_RESULT_FILE` so the
|
||||
lane/harness write their JSON there instead of stdout.
|
||||
|
||||
## Attended sequence
|
||||
|
||||
Keep one terminal attached from baseline through recovery. A value-handling
|
||||
|
|
@ -63,10 +181,15 @@ bodies and clear all transient JSON from memory or mode-`0600` temporary files.
|
|||
3. Inside the attended process, list only lease handles below
|
||||
`sys/leases/lookup/database/creds/audit-core-runtime`. OpenBao's lease lookup
|
||||
returns issue/expiry/TTL metadata but not the leased username, so exact
|
||||
selection is deliberately stricter: there must be exactly one live handle
|
||||
under that exact runtime prefix, and its issue/expiry interval must contain
|
||||
the current database Secret refresh time. Abort for database-owner
|
||||
disambiguation if zero or multiple handles exist. Retain only a one-way
|
||||
selection is deliberately strict: at most 8 handles may exist under that
|
||||
exact runtime prefix; exactly one must have been issued within 5 seconds of
|
||||
the current database Secret refresh (the mounted credential), with at least
|
||||
five minutes of TTL left; every other handle must be an older predecessor.
|
||||
ESO refreshes every 15 minutes while the role TTL is 15 minutes (max 1 hour),
|
||||
so a predecessor can legitimately still be live; it is never revoked. Abort
|
||||
if no handle, several handles, or a newer unmatched handle match the refresh.
|
||||
(2026-10-14: the first attended window aborted here under the earlier
|
||||
"exactly one handle" rule with two live handles.) Retain only a one-way
|
||||
handle fingerprint in evidence and clear the lookup response immediately.
|
||||
4. Recheck that the Secret `resourceVersion`, pod UID, and selected handle have
|
||||
not changed. Revoke only that handle. Do not revoke the role prefix and do
|
||||
|
|
@ -82,17 +205,16 @@ bodies and clear all transient JSON from memory or mode-`0600` temporary files.
|
|||
7. Prove recovery: `/readyz` returns 200, the retrying synthetic request reaches
|
||||
accepted or duplicate, the pre-exercise event remains readable, pod UID is
|
||||
unchanged, and restart count is unchanged.
|
||||
8. Stop the load, remove its separately approved fixture/identity, and confirm
|
||||
the predecessor lease is revoked. The replacement remains managed by ESO
|
||||
8. Stop the load, confirm the predecessor lease is revoked, record the
|
||||
evidence, then remove the identity with the synthetic sender lane cleanup
|
||||
(which restarts audit-core after the evidence is captured). The replacement remains managed by ESO
|
||||
and expires through its normal database role TTL.
|
||||
|
||||
Only inside the approved window, with the receipt and reviewed driver:
|
||||
|
||||
```bash
|
||||
make audit-database-lease-recovery-exercise \
|
||||
AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json \
|
||||
AUDIT_DATABASE_LEASE_DRIVER=/path/to/approved-load-driver
|
||||
```
|
||||
Only inside the approved window, with the receipt and reviewed driver, run the
|
||||
whole window through `make audit-t02-attended-window` (see the synthetic sender
|
||||
section above); the exercise step alone is
|
||||
`make audit-database-lease-recovery-exercise`, which also runs inside the
|
||||
contained login.
|
||||
|
||||
The exact confirmation string is embedded in the target. The harness rechecks
|
||||
the Secret/pod/lease immediately before revocation, requires at least five
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue