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]>
67 lines
2.4 KiB
Go
67 lines
2.4 KiB
Go
package kms
|
|
|
|
// custody.go — how a serving side reaches node identity custody.
|
|
//
|
|
// The custody store holds node wallets (see ./custody). This file is the only
|
|
// way to obtain one from a running KMS, and it hands out the store rather than
|
|
// the database it lives in: a caller that needs to enrol a node has no business
|
|
// reading the secret keyspace, and it never sees the master key at all.
|
|
//
|
|
// Authorization is deliberately not here. This repository answers who signed a
|
|
// token and derives no permission from one; the org-scoped plane that decides
|
|
// who may enrol, sign, rebind or revoke lives in cloud (apps/kms), which passes
|
|
// the org and subject it derived from the verified bearer into the calls below.
|
|
// custody/mount_test.go is the reference for that mount.
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
|
|
"github.com/hanzoai/kms/custody"
|
|
)
|
|
|
|
// Reasons the record master key is unusable. Both are refusals: no caller has a
|
|
// plaintext fallback, so a surface that needs the key does not come up without
|
|
// it.
|
|
var (
|
|
errNoMasterKey = errors.New("KMS_MASTER_KEY_B64 is not set")
|
|
errBadMasterKey = errors.New("KMS_MASTER_KEY_B64 must decode to 32 raw bytes")
|
|
)
|
|
|
|
// recordMasterKey resolves the 32-byte record master. It is what the ZAP
|
|
// transport and node custody seal individual records under, and it is distinct
|
|
// from the volume key (KMS_ENCRYPTION_KEY_B64) on purpose: a leaked volume key
|
|
// alone must not yield a sealed record.
|
|
func recordMasterKey() ([]byte, error) {
|
|
b64 := envOr("KMS_MASTER_KEY_B64", "")
|
|
if b64 == "" {
|
|
return nil, errNoMasterKey
|
|
}
|
|
key, err := base64.StdEncoding.DecodeString(b64)
|
|
if err != nil || len(key) != 32 {
|
|
return nil, errBadMasterKey
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// Custody returns the node identity store backed by this KMS.
|
|
//
|
|
// It fails 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 degrade. The same store is returned on every call, so the lock that
|
|
// serializes enrolment and rotation is one lock.
|
|
func (e *Embedded) Custody() (*custody.Store, error) {
|
|
if e == nil || e.db == nil {
|
|
return nil, errors.New("kms: no store")
|
|
}
|
|
e.custodyOnce.Do(func() {
|
|
key, err := recordMasterKey()
|
|
if err != nil {
|
|
e.custodyErr = err
|
|
return
|
|
}
|
|
e.custodyStore, e.custodyErr = custody.New(e.db, key)
|
|
})
|
|
return e.custodyStore, e.custodyErr
|
|
}
|