mirror of
https://github.com/hanzoai/kms.git
synced 2026-10-17 11:18:16 +00:00
Docker Build & Push (Reusable) / create-manifest (push) Blocked by required conditions
Docker Build & Push (Reusable) / tag-single-arch (push) Blocked by required conditions
Docker Build & Push (Reusable) / deploy (push) Blocked by required conditions
Docker Build & Push (Reusable) / sbom (push) Blocked by required conditions
Docker Build & Push (Reusable) / build-amd64 (push) Waiting to run
Docker Build & Push (Reusable) / build-arm64 (push) Skipped
Build kms / build (push) Waiting to run
Release / Build binaries (linux/amd64) (push) Waiting to run
Release / GitHub Release (push) Blocked by required conditions
Release / Multi-arch Docker (ghcr.io/hanzoai/kms) (push) Blocked by required conditions
Release / Notify universe (auto-bump pin) (push) Blocked by required conditions
Release / Build binaries (darwin/amd64) (push) Failing after 0s
Release / Build binaries (darwin/arm64) (push) Failing after 0s
Release / Build binaries (linux/arm64) (push) Failing after 0s
The custody store had no way out of a running KMS: Embedded.db is unexported and there was no accessor, so cloud could not construct one at all. (*Embedded).Custody() closes that. It hands out the STORE rather than the database it lives in and never the master key: a caller that needs to enrol a node has no business reading the secret keyspace, and the key stays inside this module. It refuses when KMS_MASTER_KEY_B64 is absent or malformed, because nothing can be sealed without it and a custody surface that cannot seal must refuse rather than come up holding node wallets under something weaker. One store per Embedded, memoized on the struct rather than in a package variable. The store's lock is what serializes enrolment and rotation, so a second store over the same database would let two rotations interleave and leave a subject with two active identities. Co-authored-by: Hanzo Dev <[email protected]>
81 lines
2.4 KiB
Go
81 lines
2.4 KiB
Go
package kms
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
badger "github.com/luxfi/zapdb"
|
|
)
|
|
|
|
// embedded builds the minimum Embedded the custody accessor needs, without
|
|
// booting a listener: Custody reads the database and the record master key and
|
|
// nothing else.
|
|
func embedded(t *testing.T) *Embedded {
|
|
t.Helper()
|
|
db, err := badger.Open(badger.DefaultOptions(filepath.Join(t.TempDir(), "kms")).WithLogger(nil))
|
|
if err != nil {
|
|
t.Fatalf("open zapdb: %v", err)
|
|
}
|
|
t.Cleanup(func() { db.Close() })
|
|
return &Embedded{db: db}
|
|
}
|
|
|
|
// The serving side gets the store, never the database it lives in and never the
|
|
// master key. One store per Embedded, because that store's lock is what
|
|
// serializes enrolment and rotation.
|
|
func TestCustodyHandsOutOneStore(t *testing.T) {
|
|
t.Setenv("KMS_MASTER_KEY_B64", base64.StdEncoding.EncodeToString(make([]byte, 32)))
|
|
e := embedded(t)
|
|
|
|
first, err := e.Custody()
|
|
if err != nil {
|
|
t.Fatalf("Custody: %v", err)
|
|
}
|
|
second, err := e.Custody()
|
|
if err != nil {
|
|
t.Fatalf("second Custody: %v", err)
|
|
}
|
|
if first != second {
|
|
t.Fatal("Custody built a second store; two locks over one database can interleave a rotation")
|
|
}
|
|
}
|
|
|
|
// No key, no custody. A store that cannot seal must refuse rather than come up
|
|
// and hold node wallets under something weaker.
|
|
func TestCustodyRefusesWithoutTheRecordMasterKey(t *testing.T) {
|
|
for name, key := range map[string]struct {
|
|
value string
|
|
want error
|
|
}{
|
|
"absent": {"", errNoMasterKey},
|
|
"not base64": {"not-base64-at-all!!", errBadMasterKey},
|
|
"too short": {base64.StdEncoding.EncodeToString(make([]byte, 16)), errBadMasterKey},
|
|
"too long": {base64.StdEncoding.EncodeToString(make([]byte, 64)), errBadMasterKey},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Setenv("KMS_MASTER_KEY_B64", key.value)
|
|
e := embedded(t)
|
|
store, err := e.Custody()
|
|
if !errors.Is(err, key.want) {
|
|
t.Fatalf("Custody with a %s key: got %v, want %v", name, err, key.want)
|
|
}
|
|
if store != nil {
|
|
t.Fatal("Custody returned a store it could not seal with")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCustodyOnNothingIsRefused(t *testing.T) {
|
|
t.Setenv("KMS_MASTER_KEY_B64", base64.StdEncoding.EncodeToString(make([]byte, 32)))
|
|
var nilEmbedded *Embedded
|
|
if _, err := nilEmbedded.Custody(); err == nil {
|
|
t.Fatal("Custody on a nil Embedded returned a store")
|
|
}
|
|
if _, err := (&Embedded{}).Custody(); err == nil {
|
|
t.Fatal("Custody with no database returned a store")
|
|
}
|
|
}
|