towerops/lib/towerops/vault.ex
Graham McIntire 7c1aea50ec fix: M19, L16 — protobuf validation on 6 decode paths, vault ETS docs
- M19: Add validate_mikrotik_device, validate_mikrotik_command, validate_check,
  validate_snmp_query, validate_sensor, and validate_interface functions with
  string length and list size validation, wired into all call sites
- L16: Document that Cloak Vault ETS table name collision risk is mitigated by
  ETS tables being node-local (only a concern on same-BEAM-node, not a supported
  production configuration)
2026-05-12 15:46:13 -05:00

64 lines
1.9 KiB
Elixir

defmodule Towerops.Vault do
@moduledoc """
Encryption vault for sensitive data using Cloak.
Encrypts data using AES-256-GCM encryption. The encryption key is loaded
from the CLOAK_KEY environment variable (base64-encoded).
## Usage
Fields that use this vault are automatically encrypted/decrypted by Ecto.
See `Towerops.Encrypted.Binary` for the custom Ecto type.
## Key Generation
Generate a new encryption key:
openssl rand -base64 32
Store this key securely in 1Password and set it as the CLOAK_KEY
environment variable in production.
"""
# The Cloak-backed ETS config table name is derived from the module name
# at compile time by Cloak's `use` macro. ETS tables are node-local, so
# two nodes in a cluster never collide. The only risk is running two
# instances of this module on the same BEAM node outside a cluster (e.g.
# one-off `eval`), which is not a supported production configuration.
use Cloak.Vault, otp_app: :towerops
@impl GenServer
def init(config) do
# In dev/test, use the key from config files so a developer who happens
# to have CLOAK_KEY set in their shell (e.g., from a prod-deploy env)
# doesn't accidentally encrypt local data with the production key.
# In production (runtime.exs), the env var override is required.
config =
if Application.get_env(:towerops, :env, :prod) == :prod and System.get_env("CLOAK_KEY") do
Keyword.put(config, :ciphers,
default: {
Cloak.Ciphers.AES.GCM,
tag: "AES.GCM.V1", key: decode_env!("CLOAK_KEY")
}
)
else
config
end
{:ok, config}
end
defp decode_env!(var) do
var
|> System.get_env()
|> case do
nil ->
raise """
Environment variable #{var} is missing.
Generate a key with: openssl rand -base64 32
"""
value ->
Base.decode64!(value)
end
end
end