home

Blog


Storing and managing secrets cheaply and safely


Every application has its secrets (database passwords, API keys, tokens, etc.) and they need to live somewhere that isn't a plaintext file committed to Git (please, don't). For those familiar with AWS, the first thought that comes to mind is to go with Secrets Manager to manage them, and it works great, except for the price per secret per month. Later down the road, it quietly turns into a line item nobody remembers approving once you multiply it across services, environments and regions. The good news is: for most secrets, there's a cheaper place to put them that's just as safe.

Storing secrets with SSM Parameter Store

Parameter Store is part of AWS Systems Manager and it holds configuration values for your applications. For anything sensitive, you use the SecureString type, which encrypts the value at rest using AWS KMS; for everything else you may either use String or StringList. The best part is that the standard tier is free and you can get a decent amount of storage and other benefits without paying a cent.

Writing a secret is a one-liner and if you don't point it at a specific key, it uses the AWS managed key aws/ssm that Systems Manager creates for you, which costs nothing to have:

aws ssm put-parameter \
--name "/myapp/prod/my-secrets" \
--value "super-secret-value" \
--type SecureString

Reading it back requires --with-decryption, otherwise you'll just get the ciphertext staring back at you:

aws ssm get-parameter \
--name "/myapp/prod/my-secrets" \
--with-decryption \
--query Parameter.Value \
--output text

A nice bonus is that the path-like naming isn't just for looks. You can scope IAM permissions to a prefix like /myapp/prod/*, so a service only ever sees the secrets it's supposed to and nothing from other paths.

Comparing with AWS Secrets Manager

Both services encrypt with KMS, so from a security standpoint they're basically the same. What you're actually paying for with Secrets Manager is a set of features Parameter Store doesn't have:

  • Lambda rotations included in the price;
  • Native integration with RDS, Redshift and DocumentDB, so rotating database credentials is mostly wired up already (managed rotation);
  • Cross-region replication and easier cross-account sharing.

Breaking it down, if you have credentials that auto-rotate through Secrets Manager, leave them there. Rebuilding rotation yourself to save a few cents might not be a good use of your time. However, a large chunk of secrets might never rotate at all - static API keys, third-party tokens, webhook signing secrets and so on. Those are often wasting money with Secrets Manager.

For those, Parameter Store solves the problem for free. And if you ever bump into the size limit or need parameter policies, advanced parameters are still way cheaper than Secrets Manager, in case someone tries to guilt you about it. However, just like every other AWS' product, Secrets Manager and Parameter Store both have quotas and limits, so check and compare before committing.

Watch out for the KMS decryption bill

Now, there's always a caveat (another one). Parameter Store doesn't charge you to store a SecureString, but the decryption and other requests, aren't free. Despite having a free tier, every single get-parameter --with-decryption triggers a KMS Decrypt call, and if you take a look at the pricing, it's nothing, right?!

Now, picture a Lambda that pulls fifteen separate secrets on every request, receiving traffic all day, plus a couple of CI pipelines fetching the same values on every build. Suddenly you're making KMS requests like you're trying to break a record. Also, if those parameters are on the advanced tier, you're also paying just to store them separately.

So, if applicable, group related secrets into a single SecureString parameter as a JSON string. One parameter, one decrypt call per fetch, one thing to manage.

aws ssm put-parameter \
--name "/myapp/prod/my-secrets" \
--type SecureString \
--value '{"db_password":"...","api_key":"...","jwt_secret":"..."}'

Then you fetch it once and let the application pull out whatever it needs:

RAW=$(aws ssm get-parameter \
--name "/myapp/prod/my-secrets" \
--with-decryption \
--query Parameter.Value \
--output text)

echo "$RAW" | jq -r '.db_password'

Also, since I don't want you creating new problems: both Parameter Store tiers have a storage cap, so don't try to cram every secret you own into one blob - group them by what's actually fetched together, usually per service or per environment. Adding to that, since the whole value is one parameter, changing a single field means rewriting the entire JSON, so anything that needs independent rotation might be better off on its own. It's a trade-off, not a silver bullet.

Wrap-up

Reach for SSM Parameter Store as your cheap-and-safe default and group the values into a JSON (beware of the storage limit) so KMS doesn't nickel-and-dime you on every request. Keep Secrets Manager for the handful of secrets where rotation and native integrations actually earn their price. Same security, a fraction of the bill.

Powered by Simple Blog API