When introducing Cloak to an existing project, you must migrate unencrypted fields to new encrypted fields. This process involves creating a vault, defining local Ecto types, adding duplicate encrypted fields to your schema, migrating the data, and finally replacing the original fields with the encrypted ones.
Step 1: Setup Vault and Types
- Ensure a
Cloak.Vault is configured as per the installation guide. - Create local Ecto types for each field type you wish to encrypt (e.g.,
MyApp.Encrypted.Binary for strings or MyApp.Encrypted.Map for maps).
Step 2: Add encrypted_* Fields
Add new binary columns to your database table to hold the encrypted data. For example, to encrypt :name and :metadata:
alter table(:users) do
add :encrypted_name, :binary
add :encrypted_metadata, :binary
end
Update your Ecto schema to include these new fields using your custom encrypted types. During the migration phase, use a temporary helper function in your changeset to keep the new fields in sync with the old ones:
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :encrypted_name, MyApp.Encrypted.Binary
field :metadata, :map
field :encrypted_metadata, MyApp.Encrypted.Map
end
def changeset(struct, attrs \ %{}) do
struct
|> cast(attrs, [:name, :metadata])
|> put_encrypted_fields()
end
# Temporary function to ensure changes are copied to new fields
defp put_encrypted_fields(changeset) do
changeset
|> put_change(:encrypted_name, get_field(changeset, :name))
|> put_change(:encrypted_metadata, get_field(changeset, :metadata))
end
end
Step 3: Copy Data
Run a script in a Mix task or IEx to migrate existing records to the new fields:
MypApp.Accounts.User
|> MyApp.Repo.all()
|> Enum.map(fn user ->
user
|> Ecto.Changeset.change(%{
encrypted_name: user.name,
encrypted_metadata: user.metadata
})
|> MyApp.Repo.update!()
end)
Step 4: Remove Original Fields
Once data is verified, create a migration to remove the unencrypted columns and rename the encrypted columns back to the original names:
alter table(:users) do
remove :name
remove :metadata
end
rename table(:users), :encrypted_name, to: :name
rename table(:users), :encrypted_metadata, to: :metadata
Finally, clean up your schema by removing the temporary put_encrypted_fields/1 function and updating the field types to the encrypted versions:
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, MyApp.Encrypted.Binary
field :metadata, MyApp.Encrypted.Map
end
def changeset(struct, attrs \ %{}) do
struct
|> cast(attrs, [:name, :metadata])
end
end
# Full migration workflow is provided in the content summary above.