mix encryptor.ecto.gen.key_store_migration (Encryptor.Ecto v0.4.0)

Copy Markdown View Source

Generates the Ecto migration that creates Encryptor.Ecto.KeyStore's table.

mix encryptor.ecto.gen.key_store_migration [--table NAME] [--migrations-path PATH]

The table holds one row per tenant per key version: the six fields Encryptor.Envelope.WrappedKey fixes, which is the whole of the vault's storage contract (encryptor ADR-0003 decision 9), plus the two this package's ADR-0005 adds - wrapping_shape and key_id. The vault owns the six; this package owns the table, the two, the migration and the repo.

This task creates tables and never alters one

It is the whole-table generator, and it refuses to run where a migration for the table already exists. An adopter whose table was created under 0.3.0 - before wrapping_shape and key_id existed - wants mix encryptor.ecto.gen.key_store_shape_migration instead, which writes the additive ALTER those two columns need.

This task issues no DDL

It writes one file. Nothing here opens a database connection, and nothing in this package issues CREATE TABLE at runtime or from a task (ADR-0002 decision 9) - Encryptor.Ecto.KeyStore reads the table and lets the adapter's "relation does not exist" raise where creating it would have been convenient. The generated file is yours the moment it lands: review it in a diff, commit it, and run it with your own mix ecto.migrate, on your own deploy schedule and with your own rollback.

The module name is generated from this project's application name and will need renaming if your repo lives in another namespace.

There is no --prefix, on purpose

A host whose wrapped-key table belongs in a non-default Postgres schema runs the generated file with mix ecto.migrate --prefix my_schema, which is Ecto's own way to place a migration and applies to the table and both indexes together, and passes the same name as prefix: in its Encryptor.Ecto.KeyStore provider options. A flag here would write that schema name into a file the host commits, which freezes a deployment-time placement decision into source that outlives it - the same argument Encryptor.Ecto.Migrator makes for keeping :prefix a run option rather than a fact about the plan.

Flags

Flag
--table NAMEencryptor_wrapped_keysThe table to create. A host that renames it passes the same name as table: in its Encryptor.Ecto.KeyStore provider options
--migrations-path PATHpriv/repo/migrationsWhere to write the file

Exit codes

0The file was written; its path is printed
2Usage error, or a migration for this table already exists in that directory - the generator never overwrites one and never writes a second

The shape it writes

One row per {tenant_ref, version}, with two unique indexes.

{tenant_ref, version} closes the race encryptor ADR-0003 leaves to this package: Encryptor.Envelope.provision/3 called twice concurrently for one tenant can produce two rows claiming the same version, and a candidate list with two entries for one version is a key nobody can vouch for.

{namespace, name} is the name contract made mechanical. RawAes.unwrap_key/3 accepts an encrypted data key only when the header's provider id and key name equal the keyring's, so reusing a name for different material makes previously written messages undecryptable, silently, at some later date. The database is the only thing that can catch it at write time.

wrapped is :binary and nothing else: it is a complete Encryptor message produced by the root vault, so its size is the engine's business and not a length this migration should guess at.