Vault: secret_own reads the module-own secrets it keeps, as fingerprints, so a generation can be told without the value
mesh/delivery closed: a newer head of the same pull request
mesh/check pass e572937d97fa on 567736f7795a | unit 2143 run, 2198 kept, 5 skipped | integration 2165 run, 2217 kept, 9 skipped | over bound: unit, in…
mesh/delivery closed: a newer head of the same pull request
mesh/check pass e572937d97fa on 567736f7795a | unit 2143 run, 2198 kept, 5 skipped | integration 2165 run, 2217 kept, 9 skipped | over bound: unit, in…
Issue: 1945
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -340,6 +341,19 @@ func definitions(env *Env) []toolDef {
|
||||
}
|
||||
return env.Vault.Store.Handed(limitOf(a))
|
||||
}},
|
||||
{Name: "secret_own",
|
||||
Description: "The secrets the vault keeps for modules themselves (`own/<node>/<module>/<name>`, e.g. a module's " +
|
||||
"bus account `broker`): for each its name, generation, the fingerprint of the value it holds now " +
|
||||
"(sha256:<hex>), when it was made and last changed, by whom, where the value came from, and whether it is a " +
|
||||
"covered bus account. Narrow by node, module or name prefix. At most 1000 rows; says when cut. Never a value.",
|
||||
Input: map[string]any{"node": str("only this machine's secrets"), "module": str("only this module's secrets"),
|
||||
"name": str("only secrets whose name (the last part) starts with this, e.g. broker")},
|
||||
run: func(_ string, a map[string]any) (any, error) {
|
||||
if env.Vault == nil {
|
||||
return nil, errors.New("the vault is not ready")
|
||||
}
|
||||
return ownAnswer(env.Vault, arg(a, "node"), arg(a, "module"), arg(a, "name"))
|
||||
}},
|
||||
{Name: "secret_vault_export",
|
||||
Description: "Every secret the vault itself holds, as its copy sealed to the operator key, in the shape of " +
|
||||
"`secret export`: saved to a file, `nox secret recover vault mesh-vault <name> --key <operator key file> " +
|
||||
@@ -661,3 +675,48 @@ func tools(env *Env) []stdio.Tool {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ownRowsMax bounds what secret_own answers.
|
||||
const ownRowsMax = 1000
|
||||
|
||||
// ownAnswer lists the own secrets the store holds, narrowed by node, module and name prefix: fingerprints only.
|
||||
func ownAnswer(v *Vault, node, module, prefix string) (map[string]any, error) {
|
||||
records, err := v.Store.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows := []map[string]any{}
|
||||
matched := 0
|
||||
for _, r := range records {
|
||||
parts := strings.Split(r.Name, "/")
|
||||
if len(parts) != 4 || parts[0] != "own" {
|
||||
continue
|
||||
}
|
||||
if (node != "" && parts[1] != node) || (module != "" && parts[2] != module) || !strings.HasPrefix(parts[3], prefix) {
|
||||
continue
|
||||
}
|
||||
matched++
|
||||
if len(rows) >= ownRowsMax {
|
||||
continue
|
||||
}
|
||||
fp := ""
|
||||
var note any
|
||||
if value, err := seal.Open(r.AtRest, v.Own.PrivateKey); err != nil {
|
||||
note = "the vault could not open its copy: " + err.Error()
|
||||
} else {
|
||||
fp = Fingerprint(value)
|
||||
}
|
||||
row := map[string]any{"name": r.Name, "node": parts[1], "module": parts[2], "secret": parts[3],
|
||||
"generation": r.Generation, "fingerprint": fp, "made": r.MadeAt, "changed": r.ChangedAt,
|
||||
"changedBy": r.ChangedBy, "origin": r.Origin, "busAccount": Covered(r.Name)}
|
||||
if r.ChangedVia != "" {
|
||||
row["changedVia"] = r.ChangedVia
|
||||
}
|
||||
if note != nil {
|
||||
row["error"] = note
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return map[string]any{"secrets": rows, "count": len(rows), "matched": matched, "cut": matched > len(rows),
|
||||
"note": "the store records no hash of a bus account's password; fingerprint is sha256 of the value the vault holds"}, nil
|
||||
}
|
||||
|
||||
@@ -150,3 +150,54 @@ func contains(list []string, s string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestSecretOwnListsFingerprintsOfOwnSecretsOnly(t *testing.T) {
|
||||
v := newVault(t, AllowList{})
|
||||
for name, value := range map[string]string{"own/ace/mesh-build-go/broker": "pw-one", "own/ace/mesh-build-go/token": "tok-secret",
|
||||
"own/g14/dunst/broker": "pw-two", "pair/postgres/gitea": "pair-value"} {
|
||||
if _, err := v.Put(name, value, "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := v.Put("own/ace/mesh-build-go/broker", "pw-one-b", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
own := toolNamed(t, definitions(&Env{Vault: v.Vault}), "secret_own")
|
||||
for _, c := range []struct {
|
||||
args map[string]any
|
||||
names []string
|
||||
}{
|
||||
{map[string]any{}, []string{"own/ace/mesh-build-go/broker", "own/ace/mesh-build-go/token", "own/g14/dunst/broker"}},
|
||||
{map[string]any{"node": "ace"}, []string{"own/ace/mesh-build-go/broker", "own/ace/mesh-build-go/token"}},
|
||||
{map[string]any{"module": "dunst"}, []string{"own/g14/dunst/broker"}},
|
||||
{map[string]any{"name": "bro"}, []string{"own/ace/mesh-build-go/broker", "own/g14/dunst/broker"}},
|
||||
{map[string]any{"node": "ace", "name": "broker"}, []string{"own/ace/mesh-build-go/broker"}},
|
||||
{map[string]any{"node": "nowhere"}, nil},
|
||||
} {
|
||||
got, err := own.Run(c.args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, _ := json.Marshal(got)
|
||||
for _, value := range []string{"pw-one", "pw-two", "tok-secret", "pair-value"} {
|
||||
if strings.Contains(string(raw), value) {
|
||||
t.Errorf("%v returned a value: %s", c.args, raw)
|
||||
}
|
||||
}
|
||||
rows := got.(map[string]any)["secrets"].([]map[string]any)
|
||||
if len(rows) != len(c.names) || got.(map[string]any)["cut"] != false {
|
||||
t.Fatalf("%v said %s", c.args, raw)
|
||||
}
|
||||
for i, n := range c.names {
|
||||
if rows[i]["name"] != n {
|
||||
t.Errorf("%v row %d is %v, want %s", c.args, i, rows[i]["name"], n)
|
||||
}
|
||||
}
|
||||
if len(rows) > 0 && rows[0]["name"] == "own/ace/mesh-build-go/broker" {
|
||||
if rows[0]["generation"] != 2 || rows[0]["fingerprint"] != Fingerprint("pw-one-b") || rows[0]["busAccount"] != true ||
|
||||
rows[0]["changedBy"] != "the test" {
|
||||
t.Errorf("%v: broker row said %v", c.args, rows[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
"secret_holders",
|
||||
"secret_verify",
|
||||
"secret_handed",
|
||||
"secret_own",
|
||||
"secret_asks",
|
||||
"secret_vault_export",
|
||||
"provisioner_retirement",
|
||||
|
||||
Reference in New Issue
Block a user