Files
mesh-delivery/modules/mesh-enrolment/cmd/mesh-enrolment/tools.go
T

119 lines
6.6 KiB
Go

package main
import (
"encoding/json"
"fmt"
"strings"
"time"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// Tool names: the seat's verbs, as the manifest claims them (ADR 0587 §1, §8).
const (
IssueTool = Seat + ".issue"
AgainTool = Seat + ".again"
RedeemTool = Seat + ".redeem"
ConfirmTool = Seat + ".confirm"
RevokeTool = Seat + ".revoke"
TokensTool = Seat + ".tokens"
)
func obj(props map[string]any, required ...string) map[string]any {
m := map[string]any{"type": "object", "properties": props}
if len(required) > 0 {
m["required"] = required
}
return m
}
func str(d string) map[string]any { return map[string]any{"type": "string", "description": d} }
func arg(a map[string]any, k string) string {
s, _ := a[k].(string)
return strings.TrimSpace(s)
}
func flag(a map[string]any, k string) bool {
return a[k] == true || strings.EqualFold(arg(a, k), "true")
}
// lastsOf reads an expiry as a duration (meshnodeenrollerclient.TokenLife); none is the default.
func lastsOf(s string) (time.Duration, error) { return meshnodeenrollerclient.TokenLife(s) }
// Tools are what the runtime serves.
func Tools(h *Holder) []stdio.Tool {
return []stdio.Tool{
{Name: IssueTool,
Description: "Open an enrolment: let a new machine join the mesh (novox/hq ADR 0587 §4). Takes the node's name, a why and an " +
"expiry (one hour unless given, at most 24 hours). Refused at once, naming every finding, for a name that is no node name " +
"or that a node record, an assignments key or an open enrolment holds. Otherwise it chooses the lowest free private-network address, reserves it, " +
"writes the enrolment at stage asked and asks the operator on a channel that proves who answered; only their yes adds the " +
"node to assignments with the machine's tunnel key (overlay_key, as `nox-mesh-host key` prints it there), so the hub peers " +
"it, and then makes the token, which carries the first tunnel (the vault makes it, this seat never holds it). The caller is the one the runtime proves.",
Input: obj(map[string]any{"node": str("the new machine's name"), "why": str("why this machine joins"),
"expires": str("how long the token lasts, e.g. 1h (default) up to 24h"),
"overlay_key": str("the new machine's tunnel public key, as `nox-mesh-host key` prints it there")}, "node", "why", "overlay_key"),
Run: func(a map[string]any) (any, error) {
lasts, err := lastsOf(arg(a, "expires"))
if err != nil {
return nil, err
}
return h.Issue(stdio.CallerOf(a), arg(a, "node"), arg(a, "why"), arg(a, "overlay_key"), lasts)
}},
{Name: RedeemTool,
Description: "The joining machine's one call (novox/hq ADR 0587 §5, §6), answered only for a caller the runtime proves to be " +
"enrol.<node>: its node-engine presents the public halves of the keys it made where it runs and a proof signed with its " +
"identity key, and the tunnel key it was issued for. The token is spent (once, with those keys) once assignments holds the " +
"node (placed on the operator's yes), the vault gives the node's account and signs its certificate, and the answer is given once the bus node loaded node.<node>, " +
"signed by the vault. Called again with the same keys it finishes what is not done and answers the same.",
Input: obj(map[string]any{"node": str("the machine's name"), "public_key": str("its identity key, base64"),
"overlay_key": str("its private-network key"), "sealing_key": str("its sealing key, base64"),
"serving_key": str("its serving key"), "proof": str("the proof, base64")}, "node", "public_key", "proof"),
Run: func(a map[string]any) (any, error) {
var args meshnodeenrollerclient.Redeem
raw, err := json.Marshal(a)
if err != nil {
return nil, err
}
if err := json.Unmarshal(raw, &args); err != nil {
return nil, fmt.Errorf("the redeem call does not read: %w", err)
}
return h.Redeem(stdio.CallerOf(a), args)
}},
{Name: ConfirmTool,
Description: "The new machine's confirmation (novox/hq ADR 0587 §6), answered only for the node dialled as its own login " +
"node.<node> after it redeemed: the runtime's proof of that caller is the confirmation. Says node-enrolled and closes " +
"the enrolment; the vault is then asked to drop the token.",
Input: obj(map[string]any{"node": str("the machine's own name (optional)")}),
Run: func(a map[string]any) (any, error) { return h.Confirm(stdio.CallerOf(a), arg(a, "node")) }},
{Name: RevokeTool,
Description: "End an enrolment at any stage (novox/hq ADR 0587 §5): at asked it withdraws the ask; at live or redeeming it deletes " +
"the key and its address key, and at redeeming the vault retires an account it already minted; a node the yes placed " +
"and that never confirmed is taken off assignments again. It only takes access away, so it needs no Approve; it is recorded as an act done by hand. Says " +
"enrolment-revoked; the vault then drops the token. A machine stuck at redeeming is ended by this. A revoke that stopped is " +
"finished by calling it again.",
Input: obj(map[string]any{"node": str("the machine"), "why": str("why, kept with the act")}, "node", "why"),
Run: func(a map[string]any) (any, error) {
return h.Revoke(stdio.CallerOf(a), arg(a, "node"), arg(a, "why"))
}},
{Name: AgainTool,
Description: "Ask the operator again about an enrolment still at stage asked whose ask the router no longer holds open " +
"(novox/hq ADR 0587 §8): a new ask for the same node, address and expiry. Nothing more: a redeem stopped midway is finished " +
"by the machine calling redeem again with the same keys.",
Input: obj(map[string]any{"node": str("the machine")}, "node"),
Run: func(a map[string]any) (any, error) { return h.Again(arg(a, "node")) }},
{Name: TokensTool,
Description: "What the seat holds (novox/hq ADR 0587 §8): every open enrolment, or one node's, with its stage (asked, live or " +
"redeeming), address, expiry, ask, revision and age; the bucket's fill. With a node at stage live, the token sealed to the operator key, asked of the vault at " +
"this moment and relayed as the vault sealed it: the one route by which the operator gets a token, which this seat never " +
"reads and keeps nowhere. With history=true the ended enrolments its events say. A machine stuck at redeeming shows here " +
"with its age; revoke ends it. Read-only.",
Input: obj(map[string]any{"node": str("one node"), "history": str("\"true\": the ended enrolments")}),
Run: func(a map[string]any) (any, error) {
return h.Tokens(arg(a, "node"), flag(a, "history"))
}},
}
}