How to migrate from AWS Secrets Manager
Secrets Manager is built for services that read their own configuration at runtime with an IAM role. It is much less comfortable for the part of the job that happens on a laptop, in a pull request, or in a conversation with a new hire — which is the part Sink is for.
What moves and what does not
Sink reads the current value of each secret under a name prefix and stores it as a versioned environment variable. What stays behind in AWS is everything that depends on being inside AWS: rotation Lambdas, IAM-based access at runtime, resource policies, replica regions. Sink is not a drop-in replacement for those, and it does not pretend to be — it is where the values are versioned, diffed and shared with people.
Make a read-only credential first
The import needs two actions and nothing else. Create a dedicated IAM user or an STS session for the migration, attach exactly this, and delete it when you are done:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["secretsmanager:ListSecrets", "secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:myapp/prod/*"
}]
}
Scope the Resource to the prefix you are importing. An access key with
broader rights than the secrets it unlocks is the thing worth avoiding here; if you
are using temporary credentials, pass the session token too.
Run the import
sink login
sink init # writes .sink.json — ids only, safe to commit
export SINK_AWS_SECRETS_MANAGER_ACCESS_KEY_ID=AKIA...
export SINK_AWS_SECRETS_MANAGER_SECRET_ACCESS_KEY=...
export SINK_AWS_SECRETS_MANAGER_REGION=eu-west-1
sink import aws_secrets_manager production \
--scope prefix=myapp/prod/ \
--dry-run
Leave the environment variables out and the CLI prompts for each field instead. The
prefix does two jobs: it filters the ListSecrets call, so nothing
outside it is ever read, and it is stripped when naming the variable.
Names change, and you see how
myapp/prod/db-password is not a legal environment variable name, so the
import rewrites it. The whole remaining path is used, not just the last segment —
that is what stops prod/password and staging/password from
quietly becoming the same key:
myapp/prod/db-password → DB_PASSWORD (prefix myapp/prod/)
myapp/prod/db-password → MYAPP_PROD_DB_PASSWORD (no prefix)
A secret whose value is a flat JSON object — the shape an RDS-managed credential
has — is split into one variable per field, each qualified by the secret's own name
so two secrets cannot both claim PASSWORD:
myapp/prod/db {"username": "app", "password": "…", "port": 5432}
→ DB_USERNAME, DB_PASSWORD, DB_PORT
Pass --no-expand-json to keep the JSON whole instead. Either way every
rename is printed before anything is written, and two names that collide are
reported and skipped rather than silently overwriting each other. Binary secrets are
skipped: they are not environment variables.
Saving the credential
A one-off migration should just use the environment variables above and be done. If you are going to keep importing — a staging environment refreshed each sprint, say — a workspace admin can store the credential instead:
sink connections add aws_secrets_manager "AWS production" \
--scope prefix=myapp/prod/
sink import --connection "AWS production" production
The credential is checked against AWS before it is saved, encrypted at rest with the
same envelope scheme as your secrets, and never returned by any endpoint — it is
decrypted server-side for one import and nowhere else. Revoke it from
Settings › Connections, or with sink connections rm. Prefer a
narrow, read-only IAM policy for anything you save.
Afterwards
- Re-run with
--dry-runto see whether AWS has drifted from Sink. - Rotate in AWS first, then re-import, then let everyone re-pull.
- An import of more than 500 secrets is refused — narrow the prefix and do it in batches.
- Delete the migration IAM user once you are finished with it.
Sink keeps every environment under AES-256-GCM envelope encryption, with roles, versions and an audit trail — and puts it back in your .env with one command. The free tier does not ask for a card.