Pixelex.Secrets (Pixelex v0.2.0)

Copy Markdown View Source

Encryption at rest for the credentials a tenant pastes into the settings screen. Off unless you give it a key.

config :pixelex, secret_key: System.get_env("PIXELEX_SECRET_KEY")

Generate one with:

:crypto.strong_rand_bytes(32) |> Base.encode64()

Why this exists now and did not before

While credentials only arrived from config :pixelex, sites:, they lived wherever the host already kept secrets and pixelex never owned them. Pixelex.Dashboard.Settings changes that: it invites a tenant to paste a long-lived ad-platform access token into a column pixelex writes. Owning the write path means owning what the column looks like in a pg_dump.

AES-256-GCM, and the prefix

Ciphertext is stored as pxenc1:<base64(iv <> tag <> ciphertext)>. The prefix is what makes turning encryption on a no-op migration: a plaintext value has no prefix, decrypt/1 returns it unchanged, and it becomes ciphertext the next time it is saved. There is no flag day.

The prefix is also the AAD, so a value cannot be moved between schemes.

A wrong key returns nil, not garbage

If the key is missing or rotated away, decrypt/1 yields nil rather than the ciphertext. configured?/1 then reads false and the destination goes quiet, which is the same behaviour as "not set up" — the platform's normal state. Handing the ciphertext to Meta instead would mean an authenticated integration that 401s forever while reporting itself as configured.

Summary

Functions

Decrypt a value. Plaintext passes through; an undecryptable value is nil.

True when a key is configured and values will actually be encrypted.

Encrypt a value. Without a key, returns it unchanged.

Is this value stored encrypted?

Functions

decrypt(value)

@spec decrypt(String.t() | nil) :: String.t() | nil

Decrypt a value. Plaintext passes through; an undecryptable value is nil.

enabled?()

@spec enabled?() :: boolean()

True when a key is configured and values will actually be encrypted.

encrypt(already)

@spec encrypt(String.t() | nil) :: String.t() | nil

Encrypt a value. Without a key, returns it unchanged.

encrypted?(arg1)

@spec encrypted?(term()) :: boolean()

Is this value stored encrypted?