Optional off-host copy of the encryption secret, in an S3-compatible bucket.
AWS S3, Cloudflare R2, Backblaze B2, Tigris, MinIO — anything the existing
ex_aws_s3 dependency talks to. Optional in the strong sense: PhoenixKit
works with no bucket, no AWS account and no network. Use it as a second member
of PhoenixKit.Integrations.KeyStore.Chain, behind the local file, so the
bucket is a spare copy rather than the thing everything depends on:
config :phoenix_kit,
integrations_key_store:
{PhoenixKit.Integrations.KeyStore.Chain,
stores: [
PhoenixKit.Integrations.KeyStore.File,
{PhoenixKit.Integrations.KeyStore.S3, bucket: "my-ops-bucket"}
]}Why the credentials do NOT come from an integration connection
The obvious idea is to configure this through the integrations system, the same way any other provider is configured. It cannot carry the read path, and the reason is structural rather than a matter of effort.
An integration connection's secret_key is in
PhoenixKit.Integrations.Encryption.sensitive_fields/0, so it is stored
encrypted with the integration key. Reading the secret out of a bucket
whose credentials are themselves encrypted under that secret requires the
secret you are trying to fetch. Writes work — during a rotation the old key is
live, so the credentials decrypt — which is the dangerous part: it would
produce a backup that is written successfully and can never be restored from.
A backup you cannot restore from is worse than none, because you stop looking
for a real one.
So credentials come from ExAws's own resolution: environment variables, an
instance role, or an explicit :ex_aws_config. On AWS with an instance role
there is no stored secret at all, which removes the circularity completely.
What the integrations system can usefully hold is the part that is not secret — which bucket, which region, which object — and that is a later, separate step; nothing here needs it.
What is stored
One object, the secret as its body, written with AES256 server-side
encryption. Bucket-side access control is the operator's to set: this module
refuses to pretend it can make a public bucket safe.