How to Monitor Cloak.Ecto with Vigilmon
Cloak.Ecto provides Ecto types that transparently encrypt and decrypt database fields using the Cloak cryptography library. You annotate your schema fields with types like Cloak.Ecto.Binary or Cloak.Ecto.SHA256, and Cloak handles AES-GCM encryption on write and decryption on read — no changes to your query or changeset logic.
The monitoring challenge: encryption failures are catastrophic and silent. If a key rotation goes wrong or the vault configuration drifts, every database read for an encrypted field starts returning decryption errors. Depending on your error handling, this might surface as application errors, blank fields, or complete crashes — and none of these generate a useful uptime alert by default.
This tutorial sets up monitoring for a Cloak.Ecto-powered application:
- A health endpoint that validates the encryption/decryption round-trip
- HTTP uptime monitoring with Vigilmon
- Heartbeat monitoring tied to key rotation events
- Alerts on cryptographic failures
Why Monitor Cloak.Ecto?
| Signal | What it catches | |---|---| | Encryption round-trip health | Key misconfiguration, vault unavailability | | Key rotation status | Failed key rotations that leave stale ciphertext | | Decryption error rate | Ciphertext corruption, version mismatch | | Application availability | OTP crashes caused by vault startup failures | | Database connectivity | Ecto repo unavailability affecting encrypted reads |
Cloak.Ecto failures are not recoverable at runtime — if the key is wrong, the data is unreadable. You want a monitor that fires the moment this state is entered.
Step 1: Configure Cloak.Ecto
Set up your vault and annotate your schema:
# lib/my_app/vault.ex
defmodule MyApp.Vault do
use Cloak.Vault, otp_app: :my_app
end
# config/config.exs
config :my_app, MyApp.Vault,
ciphers: [
default: {Cloak.Ciphers.AES.GCM, tag: "AES.GCM.V1", key: Base.decode64!("your_base64_key")}
]
Annotate your Ecto schema:
# lib/my_app/accounts/user.ex
defmodule MyApp.Accounts.User do
use Ecto.Schema
schema "users" do
field :name, :string
field :email, MyApp.Encrypted.Binary
field :email_hash, MyApp.Hashed.Binary
field :ssn, MyApp.Encrypted.Binary
timestamps()
end
end
Define your encrypted types:
# lib/my_app/encrypted/binary.ex
defmodule MyApp.Encrypted.Binary do
use Cloak.Ecto.Binary, vault: MyApp.Vault
end
# lib/my_app/hashed/binary.ex
defmodule MyApp.Hashed.Binary do
use Cloak.Ecto.SHA256
end
Step 2: Add an Encryption Health Check Endpoint
Create a health endpoint that performs a live encrypt/decrypt round-trip against your vault configuration:
# lib/my_app_web/controllers/health_controller.ex
defmodule MyAppWeb.HealthController do
use MyAppWeb, :controller
@test_plaintext "vigilmon-health-check-2026"
def index(conn, _params) do
checks = %{
vault: check_vault(),
database: check_database(),
encryption_roundtrip: check_encryption()
}
all_ok = Enum.all?(checks, fn {_k, v} -> v == :ok end)
status = if all_ok, do: "ok", else: "error"
http_status = if all_ok, do: 200, else: 503
conn
|> put_status(http_status)
|> json(%{
status: status,
checks: Map.new(checks, fn {k, v} -> {k, Atom.to_string(v)} end),
timestamp: DateTime.utc_now() |> DateTime.to_iso8601()
})
end
defp check_vault do
case MyApp.Vault.encrypt(@test_plaintext) do
{:ok, _ciphertext} -> :ok
{:error, _} -> :error
end
rescue
_ -> :error
end
defp check_encryption do
with {:ok, ciphertext} <- MyApp.Vault.encrypt(@test_plaintext),
{:ok, plaintext} <- MyApp.Vault.decrypt(ciphertext),
true <- plaintext == @test_plaintext do
:ok
else
_ -> :error
end
rescue
_ -> :error
end
defp check_database do
case MyApp.Repo.query("SELECT 1") do
{:ok, _} -> :ok
{:error, _} -> :error
end
rescue
_ -> :error
end
end
Add the route:
# lib/my_app_web/router.ex
scope "/", MyAppWeb do
pipe_through :api
get "/health", HealthController, :index
end
Test it:
curl http://localhost:4000/health
# => {"status":"ok","checks":{"vault":"ok","database":"ok","encryption_roundtrip":"ok"},...}
The endpoint returns 503 if the vault key is wrong, the vault process is down, or the encrypt/decrypt round-trip fails. This is the most important monitor you can add for a Cloak.Ecto application.
Step 3: Add Vigilmon HTTP Monitoring
- Log in to vigilmon.online and click Add Monitor.
- Set Type to
HTTP / HTTPS. - Enter your health endpoint:
https://yourapp.com/health. - Set Check interval to
1 minute. - Set Expected HTTP status to
200. - Under Advanced, add Response body contains:
"encryption_roundtrip":"ok". - Click Save.
This monitor is your first line of defense against key configuration problems. Set the alert threshold to 1 consecutive failure — encryption failures require immediate response.
Step 4: Monitor Key Rotation with a Heartbeat
Key rotation is the most error-prone operation in a Cloak.Ecto deployment. Add a post-rotation validation step that pings a Vigilmon heartbeat only if rotation succeeded:
# lib/mix/tasks/cloak_rotate.ex
defmodule Mix.Tasks.CloakRotate do
use Mix.Task
@shortdoc "Rotate Cloak.Ecto encrypted fields and validate"
def run(_args) do
Mix.Task.run("app.start")
IO.puts("Starting key rotation for User.email...")
count = rotate_user_emails()
IO.puts("Rotated #{count} records")
IO.puts("Validating encryption round-trip post-rotation...")
case validate_post_rotation() do
:ok ->
IO.puts("Validation passed — pinging Vigilmon")
ping_heartbeat()
{:error, reason} ->
IO.puts("VALIDATION FAILED: #{reason}")
System.halt(1)
end
end
defp rotate_user_emails do
MyApp.Repo.all(MyApp.Accounts.User)
|> Enum.reduce(0, fn user, count ->
case MyApp.Accounts.update_user(user, %{email: user.email}) do
{:ok, _} -> count + 1
{:error, _} -> count
end
end)
end
defp validate_post_rotation do
test_plaintext = "rotate-check-#{:os.system_time(:second)}"
with {:ok, ct} <- MyApp.Vault.encrypt(test_plaintext),
{:ok, pt} <- MyApp.Vault.decrypt(ct),
true <- pt == test_plaintext do
:ok
else
_ -> {:error, "encrypt/decrypt round-trip failed after rotation"}
end
end
defp ping_heartbeat do
url = System.get_env("VIGILMON_ROTATION_HEARTBEAT_URL")
if url do
:httpc.request(:get, {String.to_charlist(url), []}, [], [])
end
end
end
Run this as part of your key rotation workflow:
VIGILMON_ROTATION_HEARTBEAT_URL=https://vigilmon.online/api/heartbeat/YOUR_ID \
mix cloak_rotate
To create the rotation heartbeat in Vigilmon:
- Click Add Monitor → Cron / Heartbeat.
- Set expected interval to match your rotation schedule (e.g.,
30 days). - Set grace period to allow for maintenance windows.
- Copy the heartbeat URL to
VIGILMON_ROTATION_HEARTBEAT_URL.
If a rotation is missed or fails, Vigilmon alerts you — preventing the scenario where an old key expires without a valid replacement in place.
Step 5: Add Decryption Error Tracking
Track decryption errors in your application logic to catch individual field corruption:
# lib/my_app/encrypted/binary.ex
defmodule MyApp.Encrypted.Binary do
use Cloak.Ecto.Binary, vault: MyApp.Vault
def cast(value) do
result = super(value)
case result do
{:error, _} ->
:telemetry.execute([:my_app, :cloak, :decrypt_error], %{count: 1}, %{type: :cast})
result
_ -> result
end
end
end
Attach a telemetry handler to count errors:
:telemetry.attach(
"cloak-decrypt-error",
[:my_app, :cloak, :decrypt_error],
fn _event, %{count: count}, _meta, _config ->
MyApp.DecryptMetrics.increment(count)
end,
nil
)
Expose this counter in your health endpoint for deeper inspection.
Step 6: Set Up Alerts
Configure alert channels with appropriate severity:
- Go to Alert Channels → Add Channel.
- Set up a PagerDuty channel for encryption failures (high severity — data may be unreadable).
- Set up a Slack channel for rotation heartbeat misses (scheduled reminders).
- For the HTTP monitor, set threshold to 1 consecutive failure — encryption failures need immediate response.
- For the rotation heartbeat, set Missing heartbeat to alert after the grace period expires.
Also set up SSL certificate monitoring — certificate expiry can disrupt TLS-wrapped key delivery services:
- Click Add Monitor → SSL Certificate.
- Enter your domain.
- Alert when the certificate expires in < 30 days (longer lead time for crypto infrastructure).
Key Metrics to Watch
| Metric | Vigilmon feature | What to alert on |
|---|---|---|
| Vault availability | HTTP monitor body check | "vault":"error" |
| Encryption round-trip | HTTP monitor body check | "encryption_roundtrip":"error" |
| Database connectivity | HTTP monitor body check | "database":"error" |
| Key rotation freshness | Heartbeat monitor | Any missed rotation heartbeat |
| Application availability | HTTP monitor on /health | Any 5xx or timeout |
| SSL certificate | Cert expiry monitor | Expires in < 30 days |
Conclusion
Cloak.Ecto makes field-level encryption transparent to your application code — but that transparency cuts both ways. When encryption breaks, the failure is as invisible as the feature itself. An encrypt/decrypt health check that runs every minute and a rotation heartbeat that fires after every key change give you two layers of protection: catch configuration failures immediately, and catch missed rotations before they become emergencies.
Get started free at vigilmon.online.