PhoenixKit.Integrations.KeyStore.Chain (phoenix_kit v2.14.1)

Copy Markdown View Source

Keeps the encryption secret in more than one place at once.

This is what makes a remote store an addition rather than a foundation. Configure the local file first and something remote second, and the secret is written to both while every read is served by the local one:

config :phoenix_kit,
  integrations_key_store:
    {PhoenixKit.Integrations.KeyStore.Chain,
     stores: [
       PhoenixKit.Integrations.KeyStore.File,
       {PhoenixKit.Integrations.KeyStore.S3, bucket: "my-ops-bucket"}
     ]}

A PhoenixKit install that wants nothing remote configures nothing and loses nothing: the file store on its own remains the default and the whole feature is opt-in. Requiring an AWS account before someone can use integrations at all would be the wrong shape, and this is the shape that avoids it.

Reading

The first store that has the secret answers. A later store answering when an earlier one did not is the recovery case — the local file was lost and the remote copy saved it — so it is reported at :warning rather than passing silently: the operator should know the primary is empty before the remote one is also gone.

Writing

Every store is written, and the read-back verification in PhoenixKit.Integrations.KeyStore.write_verified/1 then checks the chain, which by definition checks the first store. A failure in any store is reported: a backup that quietly stopped being written is not a backup, and finding out during a recovery is finding out too late.

What this deliberately does not do

It does not fall back to a later store on write failure, and it does not hide a partial success behind an :ok. Both would produce the single situation this whole area exists to prevent — believing the secret is safe somewhere it is not.

Summary

Functions

The configured members, normalised to {module, opts}.

Functions

stores(opts)

@spec stores(keyword()) :: [{module(), keyword()}]

The configured members, normalised to {module, opts}.

An empty chain is a configuration mistake, not a working "store nothing" state, and the callbacks above report it as such rather than pretending to have saved something.