A node holds its link open, and applies what the mesh signs
The loop the whole thing exists for: told, apply, report. `run` holds one outbound connection open and consumes the node's own queue. Every declaration is verified against the control plane's signing key before a byte of it is read as an instruction -- not once at connect, every time. The transport being pinned is a different question from the instruction being genuine, and pinning only the first would make the second transitive: a compromised broker could forge declarations, and this host applies whatever the link delivers. Malformed and forged are reported differently, because ADR 0004 requires a host to tell "this is not from the mesh I joined" from "this is broken". One means somebody is trying and the other means something needs fixing. A node now keeps what it needs to come back on its own: the broker's address and fingerprint, the signing key it believes, and its own broker password -- which the mesh issues at enrolment to replace the token's secret, so the one-time thing stays one-time and the credential it holds for years is not the one that was pasted into a terminal. Verified in the lab end to end. The node enrolled, held its link, received a signed declaration and applied it -- the file is on the machine with the right contents, and the host's own record lists both resources. That run also found issue 010, which is recorded in novox/hq: the declaration removed every container on the machine, including the control plane that sent it. Correct reconciliation, shared store, and the first thing that happens.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package link
|
||||
|
||||
// The wire formats shared with the control plane, which defines them separately because this
|
||||
// binary requires nothing present and does not import it. A test on each side asserts the field
|
||||
// names, so a rename breaks both at once rather than on a real machine months later.
|
||||
|
||||
// Routing keys a node may publish. Its broker account is scoped to this exchange and its own
|
||||
// queue, so it can say these things and nothing else.
|
||||
const (
|
||||
KeyReport = "report"
|
||||
)
|
||||
|
||||
// Signed is a declaration and the signature over it.
|
||||
//
|
||||
// novox/hq ADR 0004: the transport is verified once at connect, and **each declaration is
|
||||
// verified by its signature, every time**. The two are different questions — a node connects to
|
||||
// the broker and takes instruction from the control plane behind it, and pinning only the first
|
||||
// would make the second transitive.
|
||||
//
|
||||
// The signature is over Declaration exactly as it arrived, bytes unchanged. Re-encoding before
|
||||
// verifying would mean checking a signature over something other than what was sent, and any
|
||||
// difference in key order or spacing would break it — so the raw message is what is signed and
|
||||
// what is checked.
|
||||
type Signed struct {
|
||||
Declaration []byte `json:"declaration"`
|
||||
Signature []byte `json:"signature"`
|
||||
}
|
||||
|
||||
// Report is what a node says after applying, and it is a statement rather than a write.
|
||||
//
|
||||
// A node states; the context that owns the data writes (novox/hq ADR 0006). The difference is the
|
||||
// security boundary: something that can write cannot be prevented from writing anything, and
|
||||
// something that can only state has its blast radius bounded by what this struct can say.
|
||||
type Report struct {
|
||||
Node string `json:"node"`
|
||||
|
||||
// Applied is what this machine now owns, by resource id.
|
||||
Applied []string `json:"applied,omitempty"`
|
||||
|
||||
// Failed says what could not be applied, and why, in words for a person.
|
||||
Failed map[string]string `json:"failed,omitempty"`
|
||||
|
||||
// Refused is set when the declaration was rejected whole rather than applied in part.
|
||||
Refused string `json:"refused,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user