carlos / platform Public
Census: carlos system box publish-record
Status: RULED as census-ruling-box-publish-record (platform-1e, R1-R4), on ea68e8a6. This note records the ruling and the two fix rounds after it: 3165a3bf applied the ruling, and the second round replaced exact evidence matching with enrollment's own verifier. platform-1e rules as the host claim's owner.
Branch box-publish-record, cut from main at b1118925 (region HA 2b).
The ruling
- R1. The verb refuses euid 0 before any store, IMDS or host read. Root cannot reach
publishBoxRecordthrough it. The overlay header records "root unreachable: verb refuses euid 0 (R1)" rather than a unit row. - R2. The one supported way to run it:
systemd-run --wait --pipe -p EnvironmentFile=/etc/carlos/host.env -p User=carlos /opt/carlos/carlos system box publish-record. - R3. No region HA gate. Run after the loop has published, the verb is a no-op: it keeps the loop's record and writes nothing.
- R4.
boxRecordStore.Putisnonrecord, on condition thatboxRecordStoreis the only store the verb handspublishBoxRecord. The store-guard test asserts zero S3 requests for a delete, a list, an overwrite and any other key.
What changed
carlos system box publish-record makes one publishBoxRecord attempt on a box, by hand, without boxRecordLoop's CARLOS_REGION_HA gate. The dev claim trial needs records at control/boxes/<box>.json for leo-climbing (carlos-dev-ie-b) and sophie-knitting (carlos-dev-ie). Region HA stays off on dev.
internal/host/boxpublish.go(new):RunSystemBoxPublishRecord,boxPublishRun,heldEvidence,boxRecordStoreand the verb's lines.system_roles.go:runSystemBoxVerbhandsbox publish-recordto the host verb beforeroleArgs.system.go:systemUsagegains the synopsis and description lines.internal/host/boxrecord.gois not edited.publishBoxRecord,boxRecordLoopand its gate are byte for byte main's.
The verb's checks, in order:
- The effective uid is not 0 (R1).
- The box is not a fleet box (
shouldSkipPublisherElection). CARLOS_DEPLOYMENT_BUCKETis set. A deployment directory is refused: its check-then-rename cannot refuse an overwrite, and the verb never opens one.- The box's label is
metricsBoxLabel, the loop's, and it is a valid box reference. This is the first host read (/etc/machine-id, or IMDS for a group box). - The store is opened with
openDeployStoreEnvand must be arelease.S3Store. - The record under the key is read through
boxRecordStore, then this box's record is read from IMDS once. - If a record is there, enrollment's own verifier decides (
heldEvidence). It callsboxid.Verifier.Enrollwith the account from this box's current IMDS identity andboxid.EmbeddedCerts. TheExpectholds the box label, the current AWS region, and the current instance id asTofuInstanceID. Enroll checks: - a strict version 1 envelope naming this box, with no other fields and nothing after it; - AWS's RSA-SHA256 signature under the certificate for the document's region; - the account, the region and the instance.
There is no freshness check. An older record whose pendingTime or instanceType has since changed, or whose written_at is old, is kept. Its bytes are never touched, because the record cannot be replaced. Anything Enroll refuses is refused, with the reason named, and nothing is written. Keeping a record means only that valid evidence is in place. The roster and operator checks are still enrollment's.
- If nothing is there,
publishBoxRecordruns throughboxRecordStorewith the record already read. Its outcome is reported by what reached the bucket. A failure before any create says nothing was written. A failure after a create reached the bucket says the write may have happened. A create answered 412 whose stored bytes are exactly this run's (a retried create) is reported as published. A record that arrived after step 6 is held to the same verifier.
Root-read census (TestPlan14RootReadCensus)
Overlay internal/provision/testdata/plan14-root-reads-box-publish-record.tsv, loaded after plan14-root-reads-region-ha-2b.tsv.
The scan finds no read site in any new or changed unit, so no root-read unit is added or changed. The only discrepancies were the two source envelopes.
| Unit | Disposition | Reason | |---|---|---| | @coverage . | re-pinned to d123dce7…5402 | system.go (usage lines) and system_roles.go (dispatch) changed. Neither adds a read. | | @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). | | 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). | | 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. | | legacy seal | unchanged (fc584e11…1b5d) | The overlay adds no @legacy row. |
Record-write census (TestPlan9B8RecordWriteCensus)
The inventory is re-pinned by internal/provision/box_publish_record_census_test.go (plan14BoxPublishRecordWriteCensusInventory, which the test uses). It appends one entry to plan14ActivationToolsWriteCensusInventory.
| Unit | Kinds | Class | Reason | |---|---|---|---| | 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. |
Digest 866978d6c6786a351bbeaf6f9e5382df0b3fd08cbb47aed0c01738f7c5120d7a.
Before this branch, the one writer of control/boxes/ was boxid.Publish, reached only from publishBoxRecord in boxRecordLoop. The verb is a second caller of the same chain, not a second writer: boxid.Publish (nonrecord, sel:Put) is unchanged. boxRecordStore wraps the store the chain writes through, and refuses before the bucket any Put on another key, any Put with an etag, and every Delete, List and ListDirs. Get is admitted on the one key only.
Tests behind the claims
internal/host/boxpublish_test.go runs the real S3Store (through openDeployStoreEnv) against leasetest's S3 server. The server sits behind a fake of the dev bucket's policy, which denies a PUT under control/boxes/ without If-None-Match: * and any DELETE there.
- Fresh box: the record is written at the right key with the right fields, by one create-only PUT.
- After the loop has published (R3): kept, no PUT, bytes unchanged.
- An older record, correctly signed for the same account, region and instance but with a different
pendingTimeandinstanceType: kept as it is, no PUT, bytes unchanged. - Anything enrollment's verifier refuses is refused, named, with no PUT and bytes unchanged. The signing certificates are generated the way boxid's own tests generate them. The cases: - a different account; - a different region (one that has a certificate); - another instance; - a bad signature (another key); - a missing signature; - a document altered after signing; - a different box; - version 2; - an unknown field; - trailing data; - not JSON; - a document that is not JSON.
- A record arriving between the verb's read and
publishBoxRecord's is held to the same verifier: a valid older one is kept, a forged one refused. - The embedded production certificates cover eu-west-1.
- A different record created just before the PUT: the bucket's 412 stops it and nothing is overwritten.
- A create committed but answered 500: the SDK retries, gets 412, and the verb reports published.
- A create committed but answered 400: the verb says the write may have happened, and a second run reports it in place.
- Root (R1), a fleet box, and no S3 bucket (a directory set, or a non-S3 store) are each refused before the machine id, the store or IMDS is touched, with zero bucket requests. The directory is never written.
- A group box publishes under its group reference, the loop's label.
boxRecordStore(R4): zero S3 requests for a delete, a list, an overwrite, another box's key and any other key.boxRecordLoopstill returns at once with region HA off, unparseable, or on a fleet box, and sends nothing.
system_roles_test.go runs carlos system box publish-record from runSystem in a child process, and checks the refusal and the exit status.