Peering transport: control plane, data plane and single-port routing (#102) #122
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/peering-transport"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes the transport half of #102 and settles #118. Builds on #101's registry.
This branch is inert at runtime. Nothing in
cmd/cairndconstructs any ofit yet, so merging changes no behaviour on a running instance. See "Not in this
PR" below for why the wiring stops here.
A schema defect, found before any code was written against it
IMPLEMENTED-PEERING.md§5 requires the receiver to mint the per-transfer agerecipient and to decrypt the stream before re-encrypting to the recipient's own
key. The proto from part 1 put
age_recipientinPrepareRequest— travellingsender → receiver, where the sender cannot possibly know it yet. As written the
receiver could never have decrypted, and #104 would have been unbuildable.
Moved to
PrepareResponsefield 4, withreserved 8;onPrepareRequestso theretired number is never reused. Recorded as §0.1 item 4 alongside the
HMAC → Ed25519 correction, and §2.1/§5 brought into line.
This is the second time the decision record contradicted itself and the second
time writing the test found it.
What landed
PUT /peer/v1/blob/{transfer_id}data planeProperties the tests pin down, chosen because each is quiet when broken:
Hellois not an oracle. An unknown key id gets a challenge of identicallength and TTL to a known one.
Authenticatefails identically for a bad proof and an unknown key — thetest compares code and message, and the implementation returns one shared
package-level error rather than building messages per call, so they cannot
drift apart later.
authenticated, allow-listed peer can enumerate recipients; an oversized
request should not get that probe for free.
valid token cannot be re-pointed at another transfer by guessing an id.
authorization, so an over-long stream is detected rather than silently
trimmed to exactly the declared length — which would have staged a corrupt
file that still looked the right size.
Muxrequires HTTP/2, not just the content type: gRPC does not exist overHTTP/1.1, and without the version check a crafted 1.1 request would reach a
server that cannot speak it.
Decisions taken here
following the storage
postQuantumtoggle. The recipient is minted pertransfer and discarded, so there is no data at rest to migrate and no rollback
path to preserve; and the only senders are Cairn instances speaking this
protocol, where hybrid support (v0.3.29) predates peering entirely. Wire
protocol = 1now means hybrid — a classical fallback would take its ownprotocol number rather than a silent per-transfer downgrade. (§5, row E.)
CompleteTransfer.Verifying it means decrypting the stream, which delivery already does;
checking it here would decrypt twice. It is part of the
Deliverercontract.CompleteTransferrefuses loudly with noDelivererwired, rather thanacknowledging a file that went nowhere.
#118 settled
The README claimed "Four Go dependencies total" while ARCHITECTURE §1.4 said
five — stale before gRPC even arrived. README now states five for the core and
names peering's two additions as the one place the discipline was deliberately
broken (opt-in, off unless configured, control messages only). §1.4 records why
option C was chosen, what bounds the blast radius, and — since the point is
honesty — that a self-hoster enabling peering does audit a larger surface.
Status: v0.3.x→v0.4.xwhile in there.Not in this PR
Wiring into
cmd/cairnd, because it is blocked on a decision rather than onwork: there is no user-enumeration API in
internal/auth.auth.Localkeeps an unexported
usersmap, and inoidcorldapmode the instance holdsno list of users at all — a user does not exist locally until first login. So
§4.3's "verify the recipient exists here" currently has nothing to call in two
of the three auth modes.
Recommendation, to be settled before the wiring lands: define existence as
"the recipient has a home directory on the storage backend". It works
uniformly across all three auth modes, needs no new auth surface, and fails
safe — a user who has never logged in has no home, so the transfer is refused,
which is correct because delivery would have had nowhere to put the file.
Also outstanding:
peering.*config fields (staging dir, base URL,maxFileBytes) and aRecipientsimplementation. Both are natural companionsto #103's send pipeline.
Green at every commit:
gofmt,go vet,go test ./internal/peering/...,go build ./....