119 lines
6.6 KiB
Go
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"))
|
|
}},
|
|
}
|
|
}
|