mesh/merge-gate head 5a7ec3775b98 merged into trunk 53fd32618916 beside mesh-controller#346@c711e622 — pass: builds mesh-host → ace, g14, novox, shanks; no…
mesh/repo-check head 5a7ec3775b98 merged into trunk 53fd32618916 beside mesh-controller#346@c711e622 — pass: [release feat/57-config-and-provisioning, judg…
mesh/delivery stopped: its walk plan-1792114347281764422 was stopped by a person (mesh-controller): mesh-host#114 merged into the release branch feat/57-…
mesh/delivery-group group 57-config-and-provisioning stopped: a member was stopped
The node-engine renders the feature's templates, collections and moved providers' files, on the SDK the feature's modules build on (feature 57, config and provisioning).
132 lines
5.0 KiB
Go
132 lines
5.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/subtle"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/nacl/box"
|
|
|
|
"github.com/novox/mesh-host/internal/identity"
|
|
"github.com/novox/mesh-host/internal/link"
|
|
)
|
|
|
|
// The move of a value sealed to this machine into the vault (design 57's V3, issue 676; internal/link
|
|
// movesecret.go). The controller cannot open what it holds; this machine can open what was sealed to it, and so
|
|
// hands it to the vault, sealed to a key the vault made for the call. The value is in this process for that one
|
|
// call and written nowhere.
|
|
|
|
// vaultAsk asks one of the vault's verbs in this engine's own name and answers its result.
|
|
type vaultAsk func(verb string, args any) (json.RawMessage, error)
|
|
|
|
// moveSecretFrom is the engine's move: this machine's sealing key, read for each ask, and the vault asked over
|
|
// the link open now.
|
|
func moveSecretFrom(statePath string, queue *link.Queue) link.MoveSecretFunc {
|
|
return func(m link.MoveSecret) (link.MovedSecret, error) {
|
|
key, err := identity.LoadSealingKey(identity.SealingKeyPath(statePath))
|
|
if err != nil {
|
|
return link.MovedSecret{}, err
|
|
}
|
|
ask := func(verb string, args any) (json.RawMessage, error) {
|
|
return queue.AskVault(context.Background(), verb, args)
|
|
}
|
|
moved, err := moveIntoTheVault(ask, key, m)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "mesh-host: a value was not moved into the vault: %v\n", err)
|
|
}
|
|
return moved, err
|
|
}
|
|
}
|
|
|
|
// moveIntoTheVault opens the row's value with this machine's sealing key, hands it to the vault's move-in in its two
|
|
// steps, and answers what the vault kept once its read-back — sealed to a key made here for this one call — opens
|
|
// to the same value.
|
|
func moveIntoTheVault(ask vaultAsk, key identity.SealingKey, m link.MoveSecret) (link.MovedSecret, error) {
|
|
if m.SealedTo != "" && m.SealedTo != key.Public {
|
|
return link.MovedSecret{}, errors.New("the controller's store says this value was sealed to a sealing key this " +
|
|
"machine no longer has, so it cannot be opened here")
|
|
}
|
|
value, err := key.Unseal(m.Sealed)
|
|
if err != nil {
|
|
return link.MovedSecret{}, fmt.Errorf("the value does not open with this machine's sealing key: %w", err)
|
|
}
|
|
defer wipe(value)
|
|
|
|
// This machine's sealing key, stated in its own name: the key the vault seals the core's credentials to for the
|
|
// controller to carry here (novox/hq ADR 0349 §16, issue 763), never a key the controller names.
|
|
first := map[string]any{"origin": m.Origin, "generation": m.Generation, "sealing_key": key.Public}
|
|
if !m.MadeAt.IsZero() {
|
|
first["made_at"] = m.MadeAt.UTC().Format(time.RFC3339Nano)
|
|
}
|
|
if m.Own() {
|
|
first["node"], first["module"], first["name"] = m.Node, m.Module, m.Name
|
|
} else {
|
|
first["provider"], first["provision"] = m.Provider, m.Provision
|
|
first["consumer"], first["consumer_module"] = m.Consumer, m.ConsumerModule
|
|
if m.Local != "" {
|
|
first["local"] = m.Local
|
|
}
|
|
}
|
|
raw, err := ask("move-in", first)
|
|
if err != nil {
|
|
return link.MovedSecret{}, fmt.Errorf("the vault did not open the move: %w", err)
|
|
}
|
|
var opened struct {
|
|
Call string `json:"call"`
|
|
SealTo string `json:"seal_to"`
|
|
Secret string `json:"secret"`
|
|
}
|
|
if err := json.Unmarshal(raw, &opened); err != nil || opened.Call == "" || opened.SealTo == "" {
|
|
return link.MovedSecret{}, errors.New("the vault answered no call and no key to seal to")
|
|
}
|
|
sealed, err := identity.Seal(opened.SealTo, value)
|
|
if err != nil {
|
|
return link.MovedSecret{}, fmt.Errorf("the vault's key for the call is not a sealing key: %w", err)
|
|
}
|
|
backPub, backPriv, err := box.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
return link.MovedSecret{}, err
|
|
}
|
|
defer wipe(backPriv[:])
|
|
raw, err = ask("move-in", map[string]any{"call": opened.Call, "sealed": sealed,
|
|
"read_back_to": base64.StdEncoding.EncodeToString(backPub[:])})
|
|
if err != nil {
|
|
return link.MovedSecret{}, fmt.Errorf("the vault did not take %s: %w", opened.Secret, err)
|
|
}
|
|
var kept struct {
|
|
Name string `json:"name"`
|
|
Generation int `json:"generation"`
|
|
Unchanged bool `json:"unchanged"`
|
|
ReadBack string `json:"read_back"`
|
|
}
|
|
if err := json.Unmarshal(raw, &kept); err != nil || kept.Name == "" || kept.ReadBack == "" {
|
|
return link.MovedSecret{}, fmt.Errorf("the vault answered %s with no read-back", opened.Secret)
|
|
}
|
|
blob, err := base64.StdEncoding.DecodeString(kept.ReadBack)
|
|
if err != nil {
|
|
return link.MovedSecret{}, fmt.Errorf("the vault's read-back of %s is not sealed", kept.Name)
|
|
}
|
|
back, ok := box.OpenAnonymous(nil, blob, backPub, backPriv)
|
|
if !ok {
|
|
return link.MovedSecret{}, fmt.Errorf("the vault's read-back of %s does not open with the key made for it", kept.Name)
|
|
}
|
|
defer wipe(back)
|
|
if subtle.ConstantTimeCompare(back, value) != 1 {
|
|
return link.MovedSecret{}, fmt.Errorf("the vault keeps %s, but it read back another value than the one handed "+
|
|
"in: the move of it did not hold", kept.Name)
|
|
}
|
|
return link.MovedSecret{Secret: kept.Name, Generation: kept.Generation, Unchanged: kept.Unchanged}, nil
|
|
}
|
|
|
|
func wipe(b []byte) {
|
|
for i := range b {
|
|
b[i] = 0
|
|
}
|
|
}
|