carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1# Census: carlos system box publish-record
2
3Status: RULED as census-ruling-box-publish-record (platform-1e, R1-R4), on
4ea68e8a6. This note records the ruling and the two fix rounds after it:
53165a3bf applied the ruling, and the second round replaced exact evidence
6matching with enrollment's own verifier. platform-1e rules as the host
7claim's owner.
8
9Branch `box-publish-record`, cut from main at b1118925 (region HA 2b).
10
11## The ruling
12
13- **R1.** The verb refuses euid 0 before any store, IMDS or host read. Root
14 cannot reach `publishBoxRecord` through it. The overlay header records
15 "root unreachable: verb refuses euid 0 (R1)" rather than a unit row.
16- **R2.** The one supported way to run it:
17 `systemd-run --wait --pipe -p EnvironmentFile=/etc/carlos/host.env -p User=carlos /opt/carlos/carlos system box publish-record`.
18- **R3.** No region HA gate. Run after the loop has published, the verb is a
19 no-op: it keeps the loop's record and writes nothing.
20- **R4.** `boxRecordStore.Put` is `nonrecord`, on condition that
21 `boxRecordStore` is the only store the verb hands `publishBoxRecord`. The
22 store-guard test asserts zero S3 requests for a delete, a list, an
23 overwrite and any other key.
24
25## What changed
26
27`carlos system box publish-record` makes one `publishBoxRecord` attempt on a
28box, by hand, without `boxRecordLoop`'s `CARLOS_REGION_HA` gate. The dev claim
29trial needs records at `control/boxes/<box>.json` for leo-climbing
30(carlos-dev-ie-b) and sophie-knitting (carlos-dev-ie). Region HA stays off on
31dev.
32
33- `internal/host/boxpublish.go` (new): `RunSystemBoxPublishRecord`,
34 `boxPublishRun`, `heldEvidence`, `boxRecordStore` and the verb's lines.
35- `system_roles.go`: `runSystemBoxVerb` hands `box publish-record` to the
36 host verb before `roleArgs`.
37- `system.go`: `systemUsage` gains the synopsis and description lines.
38- `internal/host/boxrecord.go` is not edited. `publishBoxRecord`,
39 `boxRecordLoop` and its gate are byte for byte main's.
40
41The verb's checks, in order:
42
431. The effective uid is not 0 (R1).
442. The box is not a fleet box (`shouldSkipPublisherElection`).
453. `CARLOS_DEPLOYMENT_BUCKET` is set. A deployment directory is refused: its
46 check-then-rename cannot refuse an overwrite, and the verb never opens one.
474. The box's label is `metricsBoxLabel`, the loop's, and it is a valid box
48 reference. This is the first host read (`/etc/machine-id`, or IMDS for a
49 group box).
505. The store is opened with `openDeployStoreEnv` and must be a
51 `release.S3Store`.
526. The record under the key is read through `boxRecordStore`, then this
53 box's record is read from IMDS once.
547. If a record is there, enrollment's own verifier decides
55 (`heldEvidence`). It calls `boxid.Verifier.Enroll` with the account from
56 this box's current IMDS identity and `boxid.EmbeddedCerts`. The `Expect`
57 holds the box label, the current AWS region, and the current instance id
58 as `TofuInstanceID`. Enroll checks:
59 - a strict version 1 envelope naming this box, with no other fields and
60 nothing after it;
61 - AWS's RSA-SHA256 signature under the certificate for the document's
62 region;
63 - the account, the region and the instance.
64
65 There is no freshness check. An older record whose `pendingTime` or
66 `instanceType` has since changed, or whose `written_at` is old, is kept.
67 Its bytes are never touched, because the record cannot be replaced.
68 Anything Enroll refuses is refused, with the reason named, and nothing is
69 written. Keeping a record means only that valid evidence is in place. The
70 roster and operator checks are still enrollment's.
718. If nothing is there, `publishBoxRecord` runs through `boxRecordStore` with
72 the record already read. Its outcome is reported by what reached the
73 bucket. A failure before any create says nothing was written. A failure
74 after a create reached the bucket says the write may have happened. A
75 create answered 412 whose stored bytes are exactly this run's (a retried
76 create) is reported as published. A record that arrived after step 6 is
77 held to the same verifier.
78
79## Root-read census (TestPlan14RootReadCensus)
80
81Overlay `internal/provision/testdata/plan14-root-reads-box-publish-record.tsv`,
82loaded after `plan14-root-reads-region-ha-2b.tsv`.
83
84The scan finds no read site in any new or changed unit, so no root-read unit
85is added or changed. The only discrepancies were the two source envelopes.
86
87| Unit | Disposition | Reason |
88|---|---|---|
89| `@coverage .` | re-pinned to `d123dce7…5402` | `system.go` (usage lines) and `system_roles.go` (dispatch) changed. Neither adds a read. |
90| `@coverage internal/host` | re-pinned to `b1fb4b84…7ab3` | `boxpublish.go` is new. Its units have no read site of their own, and root is refused before any read (R1). |
91| `internal/boxid/certs.go:EmbeddedCerts` | unchanged, accepted-legacy (J4, root-input) | The verb now calls it for the certificates enrollment verifies with. Its reads are the binary's own embedded files, its digest is unchanged, and root never reaches it through the verb (R1). |
92| `internal/boxid` envelope | unchanged (`0c6243af…3794`) | `Verifier.Enroll` has no read site. internal/host already imported internal/boxid (`claimproduction.go` calls `EmbeddedCerts`), so the verb's import graph gains no package. Only `crypto/x509` is added, from the standard library. |
93| legacy seal | unchanged (`fc584e11…1b5d`) | The overlay adds no `@legacy` row. |
94
95## Record-write census (TestPlan9B8RecordWriteCensus)
96
97The inventory is re-pinned by `internal/provision/box_publish_record_census_test.go`
98(`plan14BoxPublishRecordWriteCensusInventory`, which the test uses). It
99appends one entry to `plan14ActivationToolsWriteCensusInventory`.
100
101| Unit | Kinds | Class | Reason |
102|---|---|---|---|
103| `internal/host/boxpublish.go:boxRecordStore.Put` | `def:Put,sel:Put` | `nonrecord` (R4) | It writes `control/boxes/<box>.json`, a box identity record, never an instance or sidecar record. It touches no record-key constructor or record path literal. It admits only a create-only Put on its one key. |
104
105Digest `866978d6c6786a351bbeaf6f9e5382df0b3fd08cbb47aed0c01738f7c5120d7a`.
106
107Before this branch, the one writer of `control/boxes/` was `boxid.Publish`,
108reached only from `publishBoxRecord` in `boxRecordLoop`. The verb is a second
109caller of the same chain, not a second writer: `boxid.Publish` (`nonrecord`,
110`sel:Put`) is unchanged. `boxRecordStore` wraps the store the chain writes
111through, and refuses before the bucket any Put on another key, any Put with an
112etag, and every `Delete`, `List` and `ListDirs`. `Get` is admitted on the one
113key only.
114
115## Tests behind the claims
116
117`internal/host/boxpublish_test.go` runs the real `S3Store` (through
118`openDeployStoreEnv`) against `leasetest`'s S3 server. The server sits behind
119a fake of the dev bucket's policy, which denies a PUT under `control/boxes/`
120without `If-None-Match: *` and any DELETE there.
121
122- Fresh box: the record is written at the right key with the right fields,
123 by one create-only PUT.
124- After the loop has published (R3): kept, no PUT, bytes unchanged.
125- An older record, correctly signed for the same account, region and
126 instance but with a different `pendingTime` and `instanceType`: kept as
127 it is, no PUT, bytes unchanged.
128- Anything enrollment's verifier refuses is refused, named, with no PUT and
129 bytes unchanged. The signing certificates are generated the way boxid's
130 own tests generate them. The cases:
131 - a different account;
132 - a different region (one that has a certificate);
133 - another instance;
134 - a bad signature (another key);
135 - a missing signature;
136 - a document altered after signing;
137 - a different box;
138 - version 2;
139 - an unknown field;
140 - trailing data;
141 - not JSON;
142 - a document that is not JSON.
143- A record arriving between the verb's read and `publishBoxRecord`'s is held
144 to the same verifier: a valid older one is kept, a forged one refused.
145- The embedded production certificates cover eu-west-1.
146- A different record created just before the PUT: the bucket's 412 stops it
147 and nothing is overwritten.
148- A create committed but answered 500: the SDK retries, gets 412, and the
149 verb reports published.
150- A create committed but answered 400: the verb says the write may have
151 happened, and a second run reports it in place.
152- Root (R1), a fleet box, and no S3 bucket (a directory set, or a non-S3
153 store) are each refused before the machine id, the store or IMDS is
154 touched, with zero bucket requests. The directory is never written.
155- A group box publishes under its group reference, the loop's label.
156- `boxRecordStore` (R4): zero S3 requests for a delete, a list, an
157 overwrite, another box's key and any other key.
158- `boxRecordLoop` still returns at once with region HA off, unparseable, or
159 on a fleet box, and sends nothing.
160
161`system_roles_test.go` runs `carlos system box publish-record` from
162`runSystem` in a child process, and checks the refusal and the exit status.
163