View Source mix cloak.migrate.ecto (cloak_ecto v1.3.0)
Migrates a schema table to a new encryption cipher.
Rationale
Cloak vaults will automatically decrypt fields which were encrypted by a retired key, and reencrypt them with the new key when they change.
However, this usually is not enough for key rotation. Usually, you want to proactively reencrypt all your fields with the new key, so that the old key can be decommissioned.
This task allows you to do just that.
Strategy
This task will migrate a table following this strategy:
Page through the table using a cursor, selecting primary key values. By default, the cursor is based on the schema's primary key. To use custom fields in the cursor, see
Cloak.CustomCursor
.Here's an example SQL query generated by
mix cloak.migrate.ecto
:SELECT primary_key FROM table_name WHERE primary_key > cursor ORDER BY primary_key ASC LIMIT 100
For each primary key value, in a database transaction,
- Fetch the row with that key value, lock with "FOR UPDATE"
- Reencrypt all Cloak fields with the new cipher
- Write the row, unlocking it
Update queries are issued in parallel to maximize speed. Each row is fetched and written back as quickly as possible to reduce the amount of time the row is locked.
Warnings
mix cloak.migrate.ecto
works well with primary keys of the following types. If your key is not of these types, seeCloak.CustomCursor
.- Integers
- PostgreSQL UUID
- MongoDB ObjectID
Because
mix cloak.migrate.ecto
issues queries in parallel, it can consume all your database connections. For this reason, you may wish to use a separateRepo
with a limited:pool
just for Cloak migrations. This will allow you to prevent any performance impact by throttling Cloak to use only a limited number of database connections.
Configuration
Ensure that you have configured your vault to use the new cipher by default!
# If using mix configuration...
config :my_app, MyApp.Vault,
ciphers: [
default: {Cloak.Ciphers.AES.GCM, tag: "NEW", key: <<...>>},
retired: {Cloak.Ciphers.AES.CTR, tag: "OLD", key: <<...>>>}
]
# If configuring in the `init/1` callback:
defmodule MyApp.Vault do
use Cloak.Vault, otp_app: :my_app
@impl Cloak.Vault
def init(config) do
config =
Keyword.put(config, :ciphers, [
default: {Cloak.Ciphers.AES.GCM, tag: "NEW", key: <<...>>},
retired: {Cloak.Ciphers.AES.CTR, tag: "OLD", key: <<...>>>}
])
{:ok, config}
end
end
If you want to migrate multiple schemas at once, you may find it convenient
to specify the schemas in your config/config.exs
:
config :my_app,
cloak_repo: [MyApp.Repo],
cloak_schemas: [MyApp.Schema1, MyApp.Schema2]
Usage
To run against only a specific repo and schema, use the -r
and -s
flags:
mix cloak.migrate.ecto -r MyApp.Repo -s MyApp.Schema
If you've configured multiple schemas at once, as shown above, you can simply run:
mix cloak.migrate.ecto