Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Undocumented support for migrations (workaround) #47

Open
KristerV opened this issue Feb 13, 2023 · 1 comment
Open

Undocumented support for migrations (workaround) #47

KristerV opened this issue Feb 13, 2023 · 1 comment
Labels
docs Change the documentation feature New feature proposal A proposal for a change

Comments

@KristerV
Copy link

KristerV commented Feb 13, 2023

Problem

It's common to need to do migrations in the database and convert data between encrypted fields with the following conditions:

  1. elixir is not installed (iex/mix not available)
  2. migration is fully automated with rollback support
  3. migration shouldn't start the whole runtime

Unfortunately this process is not documented and trying it myself resulted in the same error as danielberkompas/cloak#125 .

TLDR encrypt_existing_data.md solution is suboptimal.

Migration steps

I want to change a DateTime field to Date.

  1. load records
  2. decrypt field
  3. use DateTime.to_date()
  4. encrypt
  5. update

Workaround

I did manage to read through a bunch of cloak's code and come up with a solution.

  1. use MyApp.Vault.start_link() to start the Vault in Release (solves ETS error when running Vault without mix cloak#125).
  2. use load() to decrypt your data.
  3. use dump() to encrypt data.

Here's my full(ish) code, because I hate it when I find a solution online and a crucial piece is left out.

defmodule MyApp.Repo.Migrations.AlterTransactionDatetime do
  use Ecto.Migration
  import Ecto.Query, warn: false
  alias MyApp.Repo
  alias MyApp.Encryption.Types

  def up do
    dates =
      from(t in "table", select: {t.id, t.datetime})
      |> Repo.all()
      |> Enum.map(fn {id, datetime_encrypted} ->
        {:ok, datetime} = Types.DateTime.load(datetime_encrypted)
        {:ok, new_val} = datetime |> DateTime.to_date() |> Types.Date.dump()
        {id, new_val}
      end)

    alter table(:table) do
      remove :datetime
      add :date, :binary
    end

    flush()

    for {id, date} <- dates do
      from(t in "table", where: t.id == ^id, update: [set: [date: ^date]])
      |> Repo.update_all([])
    end
  end
end

defmodule MyApp.Release do
  @moduledoc """
  Used for executing DB release tasks when run in production without Mix
  installed.
  """
  @app :my_app

  def migrate do
    MyApp.Vault.start_link()
    load_app()

    for repo <- repos() do
      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
    end
  end

  [...]
end

Suggestions

  1. There should be an actual migration guide, like the above (or better since i don't know if this is how the package is supposed to be used). and encrypt_existing_data.md imho should be deprecated in it's current form.
  2. Document crucial functions like start_link, load and dump. Nothing fancy, for starters that they exist at all.
  3. Ideally the package would check if Vault is running (or if the :ets key exists) so ETS error when running Vault without mix cloak#125 would have a nicer error.
@danielberkompas danielberkompas added feature New feature proposal A proposal for a change docs Change the documentation labels Apr 6, 2024
@danielberkompas
Copy link
Owner

cloak version 1.1.4 (just released) will raise a helpful error if the vault hasn't been started, which will solve part of this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Change the documentation feature New feature proposal A proposal for a change
Projects
None yet
Development

No branches or pull requests

2 participants