AWS Parameter Store vs. Secrets Manager: How to Store API Keys for Free
You need to securely store an API key or database password for your Lambda function, but AWS Secrets Manager's per-secret pricing adds up fast — especially across dozens of microservices or non-production environments. AWS Systems Manager (SSM) Parameter Store offers a free tier for standard parameters that covers the majority of non-rotating secret use cases, with no monthly per-secret charge.
TL;DR
| Dimension | SSM Parameter Store (Standard) | AWS Secrets Manager |
|---|---|---|
| Cost | Free (Standard tier) | ~$0.40/secret/month + API call fees (verify current pricing) |
| Automatic Rotation | ❌ Not built-in | ✅ Native rotation via Lambda |
| Encryption | ✅ KMS (SecureString type) | ✅ KMS (always encrypted) |
| Max Value Size | 4 KB (Standard) / 8 KB (Advanced) | 64 KB |
| Versioning | ✅ Limited history | ✅ Full version staging |
| Cross-account Access | Limited | ✅ Native resource policy support |
| Best For | Static config, API keys, feature flags | Rotating DB credentials, OAuth tokens |
How Parameter Store Works: The Architecture
Parameter Store is a capability of AWS Systems Manager. It stores values in a hierarchical namespace (like a filesystem path), integrates natively with IAM for access control, and optionally encrypts values using AWS KMS. Your application retrieves values at runtime via the SSM API — no secrets ever live in environment variables or source code.
- Developer/CI Pipeline writes the secret once using the AWS CLI or Console — the value is encrypted with a KMS key and stored in Parameter Store.
- Lambda / ECS Task assumes an IAM Role that grants
ssm:GetParameteron the specific parameter path. - At runtime, the application calls the SSM API (
GetParameterwithWithDecryption: true). - SSM calls KMS to decrypt the value transparently, returning the plaintext only to the authorized caller.
- The application uses the secret in-memory — it is never written to disk or logs.
Parameter Types: Which One to Use
| Type | Encrypted? | Use Case |
|---|---|---|
String | No | Non-sensitive config: region names, feature flags, URLs |
StringList | No | Comma-separated non-sensitive values |
SecureString | Yes (KMS) | API keys, passwords, tokens — anything sensitive |
Rule of thumb: If you would be embarrassed to see it in a CloudTrail log, use SecureString.
Naming Convention: Use Hierarchical Paths
Parameter Store supports path-based naming. This is not just cosmetic — it enables wildcard IAM policies and bulk retrieval with GetParametersByPath.
/myapp/production/database/password
/myapp/production/stripe/api_key
/myapp/staging/stripe/api_key
/shared/datadog/api_key
This structure lets you grant a production service access to /myapp/production/* without exposing staging secrets — a clean least-privilege boundary.
Step 1: Store a Secret (AWS CLI)
Use the aws ssm put-parameter command. The --key-id flag is optional; omitting it uses the AWS-managed default KMS key for SSM (aws/ssm), which is free to use.
# Store an encrypted API key using the default SSM KMS key
aws ssm put-parameter \
--name "/myapp/production/stripe/api_key" \
--value "sk_live_XXXXXXXXXXXX" \
--type "SecureString" \
--description "Stripe live API key for production" \
--region us-east-1
# To use a customer-managed KMS key instead:
aws ssm put-parameter \
--name "/myapp/production/stripe/api_key" \
--value "sk_live_XXXXXXXXXXXX" \
--type "SecureString" \
--key-id "arn:aws:kms:us-east-1:123456789012:key/your-key-id" \
--region us-east-1
Step 2: IAM Policy — Least Privilege Access
Grant your Lambda function or ECS task role only the permissions it needs. The policy below allows reading any parameter under /myapp/production/ and decrypting with the associated KMS key.
🔽 Click to expand — IAM Policy JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSSMRead",
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath"
],
"Resource": "arn:aws:ssm:us-east-1:123456789012:parameter/myapp/production/*"
},
{
"Sid": "AllowKMSDecrypt",
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:us-east-1:123456789012:key/your-key-id"
}
]
}
Note: If you use the default aws/ssm managed key, you do not need the explicit kms:Decrypt statement — IAM permissions on the parameter are sufficient. For customer-managed KMS keys, the explicit kms:Decrypt grant is required.
Step 3: Retrieve the Secret at Runtime
Below are examples in Python (boto3) and Node.js. The critical pattern is fetch-once-and-cache — retrieve the value on cold start, cache it in memory, and reuse it across invocations. This avoids SSM API call latency on every request and reduces API call costs.
🔽 Click to expand — Python (boto3) Lambda Example
import boto3
import os
# Module-level cache — persists across warm Lambda invocations
_ssm_client = boto3.client('ssm', region_name='us-east-1')
_param_cache = {}
def get_parameter(name: str) -> str:
"""Fetch from cache or SSM. Decrypts SecureString automatically."""
if name not in _param_cache:
response = _ssm_client.get_parameter(
Name=name,
WithDecryption=True
)
_param_cache[name] = response['Parameter']['Value']
return _param_cache[name]
def lambda_handler(event, context):
api_key = get_parameter('/myapp/production/stripe/api_key')
# Use api_key — never log it
return {'statusCode': 200, 'body': 'OK'}
🔽 Click to expand — Node.js (AWS SDK v3) Lambda Example
import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';
const ssmClient = new SSMClient({ region: 'us-east-1' });
const paramCache = {};
async function getParameter(name) {
if (!paramCache[name]) {
const command = new GetParameterCommand({
Name: name,
WithDecryption: true,
});
const response = await ssmClient.send(command);
paramCache[name] = response.Parameter.Value;
}
return paramCache[name];
}
export const handler = async (event) => {
const apiKey = await getParameter('/myapp/production/stripe/api_key');
// Use apiKey — never log it
return { statusCode: 200, body: 'OK' };
};
Bulk Retrieval: GetParametersByPath
If your application needs multiple parameters at startup, use GetParametersByPath to fetch an entire namespace in one API call instead of making N individual calls.
aws ssm get-parameters-by-path \
--path "/myapp/production/" \
--with-decryption \
--recursive \
--region us-east-1
Standard vs. Advanced Parameters: Know the Boundary
| Feature | Standard (Free) | Advanced (Paid) |
|---|---|---|
| Max value size | 4 KB | 8 KB |
| Parameter policies (TTL/expiry) | ❌ | ✅ |
| Higher throughput | Standard | Higher (check docs for current limits) |
| Cost | Free | Per-parameter/month charge applies |
For most API key storage use cases, Standard tier is sufficient. Advanced parameters are useful when you need parameter expiration policies — a lightweight alternative to full rotation.
When to Upgrade to Secrets Manager
Use this decision tree to choose the right service:
- If your secret requires automatic rotation (e.g., RDS passwords rotated every 30 days), Secrets Manager is the right tool — its native Lambda rotation integration is purpose-built for this.
- If you need cross-account secret sharing with resource-based policies, Secrets Manager handles this more cleanly.
- If your value exceeds 4 KB and you don't want Advanced Parameters, Secrets Manager supports up to 64 KB.
- For everything else — static API keys, third-party tokens, feature flags — Parameter Store Standard is the right choice.
Analogy: Parameter Store is a secure filing cabinet with a combination lock — reliable, free, and perfect for documents you update manually. Secrets Manager is a bank vault with an automated document-shredding and replacement service built in. If your documents don't need automatic replacement, you're paying for a feature you'll never use.
Security Best Practices Checklist
- ✅ Always use
SecureStringfor sensitive values — neverString - ✅ Scope IAM policies to specific parameter paths, not
* - ✅ Enable CloudTrail to audit all
ssm:GetParametercalls - ✅ Use hierarchical naming (
/app/env/service/key) from day one - ✅ Cache parameters in Lambda memory — avoid per-invocation API calls
- ✅ Never log decrypted parameter values
- ✅ Rotate manually by updating the parameter value and redeploying — or use Advanced Parameter policies for expiry notifications
Glossary
| Term | Definition |
|---|---|
| SecureString | A Parameter Store type that encrypts the value at rest using AWS KMS before storage. |
| KMS (Key Management Service) | AWS service that creates and manages cryptographic keys used to encrypt/decrypt data. |
| Parameter Hierarchy | A slash-delimited naming convention (e.g., /app/env/key) enabling path-based IAM scoping and bulk retrieval. |
| WithDecryption | A boolean flag in the SSM GetParameter API that instructs SSM to call KMS and return the plaintext value. |
| Cold Start Cache | The pattern of fetching external config once during Lambda initialization and storing it in module-level variables for reuse across warm invocations. |
Next Steps
- 📖 Official SSM Parameter Store Documentation
- 📖 Advanced Parameters — Policies and Pricing
- 📖 AWS Secrets Manager — When to Use It
Comments
Post a Comment