cloud/cek is deleted and github.com/hanzoai/cek v0.2.0 is the whole of
encryption at rest. A database's key is DERIVED from the deployment's master
and the namespace that owns it — HKDF(master, "hanzo/cek/v1/" + ns + "/" +
subsystem) — so it is not generated, not wrapped, not stored and not rotated
in place.
What that deletes, and why each one was a hazard rather than a feature:
- the per-file DEK and its .dek sidecar. Key material beside a file is
key material that can go missing, and it did: the sidecar had to be
framed into every durable snapshot so a successor could open what it
restored, and a successor that received the database without it had an
unreadable store.
- rewrap, and the self-heal retry in OrgDB that called it. A derivation
that must be migrated is a derivation that can be half-migrated; that is
what took the git plane and the mirror engine down in production, and
with them every deploy.
- cek.Global / cek.Org / cek.Principal. There was cloud's name for an
entity (namespace) and cek's name for the same entity (Principal), with
nsPrincipal translating between them. Now the namespace IS what the key
is derived from, so a file and its key cannot name different things.
- cek.Exists and its sidecar probe. A store is a file; asking the
filesystem is os.Stat, at the one call site that asks.
- replication.go, 84 lines of unwired design commentary. Replication is
hanzoai/replicate over hanzoai/vfs.
Callers pass a DIRECTORY and a SUBSYSTEM NAME, never a path — cek renders
the path from the namespace itself. Three hand-rolled org→slug encoders go
with that: finance's orgPattern, treasury's tenantSlug and team's seg were
each a second answer to "which file holds this tenant's data", and the
treasury one needed a reserved slug to keep a tenant out of the house fund.
The system namespace is a different KIND, so no tenant string can render to
it however it is spelled.
New, and small:
basedb.Open is the ONE opener: cek plus the directory the file lives in.
cek does not create it, and on the pure-Go codec the database is written
back at CLOSE — so a missing parent does not fail the open, it loses the
data at the end. Stated once, beside the open, instead of in ~50 stores.
internal/devmaster keys a test binary. cek reads no environment, so a
process with no KMS mints its own master; one blank import per test
package says so, replacing seventeen near-identical TestMains that set
CLOUD_KMS_MASTER_KEY_REF for a reader that no longer exists.
Two consequences worth naming. A store that is OPEN has no file yet on the
pure-Go codec, so OrgStore.Has is the union of the open set and the disk,
and Each and Stored both go through it. And apps/iam never closed its
*sql.DB at all (orm's AdaptSQLDB borrows the handle; its Close is a
documented no-op), which on that codec means the identity store was never
written back — it now has a Shutdown, wired like every other subsystem's.
Databases written under the old wrapping will not open under this
derivation. That is expected: there is no migration, no fallback and no
version probe, because a second derivation tried on failure is exactly what
made the old binding unenforceable.
Also fixes six test-only KMS fakes that never gained DeleteSecret and two
missing imports in apps/kms — pre-existing at origin/main, and the reason
eight packages could not be test-verified at all.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
74 lines
4.0 KiB
Go
74 lines
4.0 KiB
Go
package cloud
|
|
|
|
import (
|
|
// namespace is the ONE thing that names the database an entity's data lives
|
|
// in, and the ONE thing that turns that name into a location. It is a value
|
|
// with constructors, not a string with a validator, because the string form
|
|
// IS the path: a name a validator lets through late has already been written
|
|
// to disk and shipped to the object store by then.
|
|
"github.com/hanzoai/namespace"
|
|
)
|
|
|
|
// orgns.go is the ONE place cloud turns a validated principal into the NAME of
|
|
// the database that principal's records live in.
|
|
//
|
|
// The naming itself is not cloud's to own. A namespace, the injective slug it
|
|
// is built from, and the key and path it renders to are one primitive, and it
|
|
// lives in hanzoai/namespace so that every service reaches the same answer —
|
|
// these strings are directory names on live volumes and keys in live buckets,
|
|
// and a second implementation of them does not fail, it opens an empty database
|
|
// beside a real one. What stays here is the DOOR: which cloud values are
|
|
// allowed to become a name.
|
|
//
|
|
// THE ISOLATION ARGUMENT, in one paragraph. An ENTITY's namespace is reachable
|
|
// only through OrgNamespace. Its input is folded through namespace.Sanitize —
|
|
// which refuses any org carrying a whitespace, control or format rune, and
|
|
// disambiguates every other fold with a hash of the raw owner, so it is
|
|
// injective — and then through the segment rule, which admits only
|
|
// [a-z0-9][a-z0-9_-]* and folds case. So the only way to name an entity's
|
|
// database is to hold an org string, and the only org strings in this codebase
|
|
// come from principal.Org (a validated IAM claim) or from a server-side
|
|
// resolution an in-process caller states as its contract. A namespace built from
|
|
// a query parameter, a body field, a header or a caller-supplied id would have
|
|
// to pass through OrgNamespace too — which is the point of having one door.
|
|
//
|
|
// namespace.System is outside the argument rather than an exception to it: it
|
|
// takes no input, so nothing can be folded into it, and it names the deployment
|
|
// rather than an entity. A platform store says so where it opens.
|
|
|
|
// OrgNamespace names the database an org's records live in — or, when project
|
|
// is non-empty, the database that org's records for one project live in.
|
|
//
|
|
// org MUST be the VALIDATED principal value (principal.Org, and for the project
|
|
// scope principal.Project), never a raw request body or header. It IS
|
|
// namespace.OrgProject: the org is folded through the ONE injective slugger, so
|
|
// two distinct orgs can never share a namespace, and the project rides in the
|
|
// GROUP slot, which is what a group is for.
|
|
//
|
|
// It stays spelled here, as cloud's name for that door rather than a second
|
|
// implementation of it, because "which values may name a database" is a
|
|
// question about cloud's principals — and TestOnlyOrgnsBuildsANamespace answers
|
|
// it by proving this file is the only one that asks.
|
|
func OrgNamespace(org, project string) (namespace.Namespace, error) {
|
|
return namespace.OrgProject(org, project)
|
|
}
|
|
|
|
// MustOrgNamespace is OrgNamespace for an org fixed in the source — a test, a
|
|
// seed, a constant in a migration. It panics, which is correct for a value that
|
|
// is wrong before the program runs and wrong for anything from a request.
|
|
func MustOrgNamespace(org, project string) namespace.Namespace {
|
|
return namespace.MustOrgProject(org, project)
|
|
}
|
|
|
|
// nsOnDisk reads a namespace back out of the directory name OrgNamespace wrote.
|
|
//
|
|
// It is the inverse of the door above, not a second one: the segment it is
|
|
// given was produced by namespace.Sanitize when the store was created, so
|
|
// folding it through Sanitize AGAIN would be wrong — a slug that already
|
|
// carries a disambiguation suffix looks exactly like a raw owner that needs
|
|
// one, and would be re-suffixed into the name of a different, empty database.
|
|
//
|
|
// It exists so the one construction in this package that does not start at a
|
|
// principal is visible and greppable rather than an inline call.
|
|
func nsOnDisk(slug string) (namespace.Namespace, error) { return namespace.Org(slug) }
|