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.
46 lines
2.0 KiB
Go
46 lines
2.0 KiB
Go
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"`
|
|
}
|