diff --git a/go/catalogue/seats.go b/go/catalogue/seats.go index 79b48f0..0180575 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -233,13 +233,35 @@ var defaultSeats = append([]Seat{ "controller's, never anew for one the vault holds a value or an adopted hash for. Never the value.", Input: schema(map[string]string{"user": "the bus user, e.g. ace.mesh-build-go"}, []string{"user"})}, {Name: "sign", Optional: true, Description: "Sign with the vault's key or authority (novox/hq ADR 0550 §2): " + - "kind node-certificate signs the certificate of the calling machine's internal name for the serving key " + - "its record states, built from bus state alone and asked by that machine's node-engine only; kind " + - "enrolment signs an enrolment reply under its own context, for a caller the adoption record names. " + - "Answers the certificate or the signature and the signer's fingerprint, and says signed; never a key.", + "kind node-certificate signs the certificate of a machine's internal name, asked by that machine's " + + "node-engine for the serving key its record states, built from bus state alone, or by mesh-enrolment for " + + "a node it enrols, for the serving key the node presented at redeem (ADR 0587 §3); kind enrolment signs a " + + "redeem answer under its own context, for mesh-enrolment alone. Answers the certificate or the signature " + + "and the signer's fingerprint, and says signed; never a key.", Input: schema(map[string]string{"kind": "node-certificate or enrolment", - "node": "the machine, for a node certificate: the caller's own", - "reply": "the enrolment reply's bytes in base64, for an enrolment"}, []string{"kind"})}, + "node": "the machine, for a node certificate: the caller's own, or the node mesh-enrolment enrols", + "serving_key": "from mesh-enrolment: the serving key's public half the enrolling node presented", + "reply": "the redeem answer's bytes in base64, for an enrolment"}, []string{"kind"})}, + // Enrolment (novox/hq ADR 0561 §5, ADR 0587 §3, §5; issue 774): mesh-enrolment is the only caller the vault's + // manifest names for these two. + {Name: "enrolment-token", Optional: true, Description: "Make a one-time token for a node mesh-enrolment " + + "issues on the operator's warrant: its secret kept with its copy sealed to the operator key and its hash " + + "written as the bus user enrol.. Answers only what the joining machine must trust (the bus's " + + "address, the bus certificate's fingerprint, the vault's public signing key), never the token; with " + + "sealed, the token sealed to the operator key, for tokens to relay; with drop, the token and its hash " + + "dropped, at the enrolment's end. Asked by mesh-enrolment alone, only from the lease write.", + Input: schema(map[string]string{"node": "the node the token is for", + "expires": "when the token expires (RFC 3339), as the enrolment's key says", + "sealed": "\"true\": answer the token sealed to the operator key", + "drop": "\"true\": drop the node's token and its hash"}, []string{"node"}, "sealed", "drop")}, + {Name: "node-account", Optional: true, Description: "Mint a joining node's bus account node., sealed " + + "to the sealing key it presented, and write its hash; asked again for the same node and keys, answers the " + + "account it already minted; with retire, the account it minted for a revoked enrolment retired. Writes the " + + "node's node-logins key naming node.. Asked by mesh-enrolment alone. Never the value in the clear.", + Input: schema(map[string]string{"node": "the joining node", + "sealing_key": "the sealing key's public half the node presented, which the account is sealed to", + "keys_digest": "the digest of every public key the node presented (delivery.KeysDigest)", + "retire": "\"true\": retire the account minted for this node"}, []string{"node"}, "retire")}, }}, // The owner of every config value (novox/hq ADR 0348 §9, design 57 K3): its holder alone writes the buckets // mesh-config_values, _resolved, _predefined and _proposals, and serves the one verb `config` (§1, §10). diff --git a/go/catalogue/vault_seat_test.go b/go/catalogue/vault_seat_test.go index 77630ee..7fd3265 100644 --- a/go/catalogue/vault_seat_test.go +++ b/go/catalogue/vault_seat_test.go @@ -65,6 +65,10 @@ func TestTheVaultsSeatPromisesItsIntakeVerbs(t *testing.T) { "secret-ask": {"node", "module", "name", "desk", "asked_by", "outcome", "call", "sealed"}, "issued": {"provision", "provider", "module", "call", "sealed", "holders"}, "rotate": {"node", "module", "name", "provider", "provision", "by"}, + // 774/S37/AC1: the enrolment verbs mesh-enrolment asks (ADR 0587 §3, §5). + "enrolment-token": {"node", "expires", "sealed", "drop"}, + "node-account": {"node", "sealing_key", "keys_digest", "retire"}, + "sign": {"kind", "node", "serving_key", "reply"}, } for name, args := range want { var verb *Verb @@ -105,7 +109,7 @@ func TestTheVaultsSeatPromisesItsIntakeVerbs(t *testing.T) { } serving := Manifest{Module: "mesh-vault", Provides: []Offer{{Name: "secret", Scope: ScopeMesh}}, Claims: []Claim{{Name: "mesh-vault", Scope: ScopeMesh, - Serves: []string{"get", "accept", "secret-ask", "issued", "rotate"}}}} + Serves: []string{"get", "accept", "secret-ask", "issued", "rotate", "sign", "enrolment-token", "node-account"}}}} if err := CanHold(serving, seat); err != nil { t.Fatalf("the vault serving its verbs was refused: %v", err) } diff --git a/go/delivery/enrolment.go b/go/delivery/enrolment.go index de8afad..0a07c7a 100644 --- a/go/delivery/enrolment.go +++ b/go/delivery/enrolment.go @@ -238,3 +238,31 @@ func (a RedeemAnswer) SignedBytes() []byte { raw, _ := json.Marshal(a) return raw } + +// **The vault's enrolment verbs** (ADR 0561 §5, ADR 0587 §3, §5), asked by mesh-enrolment alone. +const ( + EnrolmentTokenVerb = "enrolment-token" + NodeAccountVerb = "node-account" +) + +// EnrolmentTokenAnswer is what enrolment-token answers: what the joining machine must trust (the bus's address, its +// certificate's fingerprint, the vault's public signing key in base64), never the token; Sealed holds the token sealed +// to the operator key when asked with sealed, relayed by tokens as it is; Dropped says a drop left neither the token +// nor its hash. +type EnrolmentTokenAnswer struct { + Node string `json:"node"` + Broker string `json:"broker,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + Signer string `json:"signer,omitempty"` + Sealed string `json:"sealed,omitempty"` + Dropped bool `json:"dropped,omitempty"` +} + +// NodeAccountAnswer is what node-account answers: the login minted, the account sealed to the sealing key the node +// presented (base64), never in the clear; Retired says a retire left no account. +type NodeAccountAnswer struct { + Node string `json:"node"` + Login string `json:"login,omitempty"` + Account string `json:"account,omitempty"` + Retired bool `json:"retired,omitempty"` +} diff --git a/go/delivery/signers.go b/go/delivery/signers.go index 811ea13..d98f430 100644 --- a/go/delivery/signers.go +++ b/go/delivery/signers.go @@ -48,8 +48,8 @@ const ( // KindNodeCertificate is a node's certificate for its internal name, asked by that node's node-engine alone. KindNodeCertificate = "node-certificate" - // KindEnrolment is an enrolment reply, signed under its own context; no caller is named until adoption's record - // (ADR 0561 §5). + // KindEnrolment is a redeem answer (RedeemAnswer), signed under its own context for mesh-enrolment alone (ADR 0587 + // §3, §6). KindEnrolment = "enrolment" // KindAuthority is the vault's own authority certificate, which the vault signs for itself. KindAuthority = "authority"