AWS Secrets Manager
AWS Secrets Manager provides centralized secret management with IAM access control, audit logs, and automatic rotation.
Quick Start
# 1. Configure provider in fnox.toml
cat >> fnox.toml << 'EOF'
[providers]
aws = { type = "aws-sm", region = "us-east-1", prefix = "myapp/" }
EOF
# 2. Create secret in AWS
aws secretsmanager create-secret \
--name "myapp/database-url" \
--secret-string "postgresql://prod.example.com/db"
# 3. Reference in fnox.toml
cat >> fnox.toml << 'EOF'
[secrets]
DATABASE_URL = { provider = "aws", value = "database-url" } # With prefix, fetches "myapp/database-url"
EOF
# 4. Fetch secret
fnox get DATABASE_URLPrerequisites
- AWS account
- AWS credentials configured (CLI, environment variables, or IAM role)
- IAM permissions (see below)
IAM Permissions
Read-Only Access (Minimum)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListSecrets",
"Effect": "Allow",
"Action": "secretsmanager:ListSecrets",
"Resource": "*"
},
{
"Sid": "ReadSecrets",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:myapp/*"
}
]
}ListSecrets Permission
The secretsmanager:ListSecrets action must use "Resource": "*" and cannot be scoped to specific ARNs.
Full Access (For Testing)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListSecretsPermission",
"Effect": "Allow",
"Action": "secretsmanager:ListSecrets",
"Resource": "*"
},
{
"Sid": "SecretsManagerPermissions",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:PutSecretValue",
"secretsmanager:CreateSecret",
"secretsmanager:UpdateSecret",
"secretsmanager:DeleteSecret"
],
"Resource": ["arn:aws:secretsmanager:REGION:ACCOUNT:secret:myapp/*"]
}
]
}Configuration
Configure AWS Credentials
Choose one:
Option 1: Environment Variables
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_REGION="us-east-1"Option 2: AWS CLI Profile
aws configure
# Or use named profile
export AWS_PROFILE=myappOption 3: IAM Role (Automatic on AWS)
If running on EC2, ECS, Lambda, or other AWS services:
# No configuration needed!
# Credentials are automatic via instance metadataConfigure fnox Provider
[providers]
aws = { type = "aws-sm", region = "us-east-1", prefix = "myapp/" } # prefix is optionalCreating Secrets
Via AWS CLI
# Create a secret
aws secretsmanager create-secret \
--name "myapp/database-url" \
--secret-string "postgresql://prod.db.example.com/mydb"
# Create with description
aws secretsmanager create-secret \
--name "myapp/api-key" \
--description "Production API key for external service" \
--secret-string "sk_live_abc123xyz789"
# Create JSON secret
aws secretsmanager create-secret \
--name "myapp/db-creds" \
--secret-string '{"username":"admin","password":"secret123"}'Via AWS Console
- Go to AWS Secrets Manager Console
- Click "Store a new secret"
- Choose "Other type of secret"
- Enter key/value pairs or plaintext
- Name it with your prefix (e.g.,
myapp/database-url) - Configure rotation (optional)
- Store
Referencing Secrets
Add references to fnox.toml:
[secrets]
DATABASE_URL = { provider = "aws", value = "database-url" } # → Fetches "myapp/database-url"
API_KEY = { provider = "aws", value = "api-key" } # → Fetches "myapp/api-key"
# Without prefix in provider, use full name like: value = "myapp/api-key"Usage
Get a Secret
fnox get DATABASE_URLRun Commands
# Fetches all secrets from AWS
fnox exec -- ./start-server.shUse Different Profiles
# Different profile for different environments
fnox exec --profile production -- ./deploy.shPrefix Behavior
The prefix is prepended to the value:
[providers]
aws = { prefix = "myapp/" }
[secrets]
DATABASE_URL = { provider = "aws", value = "database-url" } # → Fetches "myapp/database-url"
API_KEY = { provider = "aws", value = "api-key" } # → Fetches "myapp/api-key"Without prefix:
[providers]
aws = { } # No prefix
[secrets]
DATABASE_URL = { provider = "aws", value = "myapp/database-url" } # → Fetches "myapp/database-url"Multi-Environment Example
# Development: age encryption
[providers]
age = { type = "age", recipients = ["age1..."] }
[secrets]
DATABASE_URL = { provider = "age", value = "encrypted-dev-db..." }
# Staging: AWS Secrets Manager (us-east-1)
[profiles.staging.providers]
aws = { type = "aws-sm", region = "us-east-1", prefix = "myapp-staging/" }
[profiles.staging.secrets]
DATABASE_URL = { provider = "aws", value = "database-url" } # → myapp-staging/database-url
# Production: AWS Secrets Manager (us-west-2)
[profiles.production.providers]
aws = { type = "aws-sm", region = "us-west-2", prefix = "myapp-prod/" }
[profiles.production.secrets]
DATABASE_URL = { provider = "aws", value = "database-url" } # → myapp-prod/database-url# Development (local)
fnox get DATABASE_URL
# Staging
fnox get DATABASE_URL --profile staging
# Production
fnox get DATABASE_URL --profile productionJSON Secrets
AWS Secrets Manager supports JSON secrets:
# Create JSON secret
aws secretsmanager create-secret \
--name "myapp/db-credentials" \
--secret-string '{"host":"db.example.com","port":"5432","username":"admin","password":"secret"}'By default, fnox returns the entire JSON string. Use json_path to extract specific fields:
[providers]
aws = { type = "aws-sm", region = "us-east-1", prefix = "myapp/" }
[secrets]
DB_CREDENTIALS = { provider = "aws", value = "db-credentials" }
DB_PASS = { provider = "aws", value = "db-credentials", json_path = "password" }fnox get DB_CREDENTIALS
# Output: {"host":"db.example.com","port":"5432","username":"admin","password":"secret"}
fnox get DB_PASS
# Output: secretThis also supports nested JSON paths using dot notation.
Literal dots need to be escaped (\.). In TOML, either literal strings have to be used ('\.') or the backslash itself has to be escaped ("\\."):
# Create nested JSON secret
aws secretsmanager create-secret \
--name "myapp/config" \
--secret-string '{"database":{"host":"db.example.com","cache.key":"foo"}}'[providers]
aws = { type = "aws-sm", region = "us-east-1", prefix = "myapp/" }
[secrets]
DB_HOST = { provider = "aws", value = "config", json_path = "database.host" }
DB_CACHE_KEY = { provider = "aws", value = "config", json_path = 'database.cache\.key' }Secret Rotation
AWS Secrets Manager supports automatic rotation:
# Enable rotation via AWS CLI
aws secretsmanager rotate-secret \
--secret-id "myapp/database-url" \
--rotation-lambda-arn "arn:aws:lambda:..."fnox always fetches the current version, so rotation is transparent.
Costs
AWS Secrets Manager pricing (as of 2024):
- $0.40 per secret per month
- $0.05 per 10,000 API calls
Example:
- 10 secrets × $0.40 = $4.00/month
- 1,000 deployments × 10 secrets × $0.05/10k = $0.50/month
- Total: ~$4.50/month
Cost Optimization
Use age encryption for development/staging secrets to reduce AWS Secrets Manager costs. Reserve AWS SM for production-only secrets.
CI/CD Example
GitHub Actions
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy with secrets
run: |
fnox exec --profile production -- ./deploy.shPros
- ✅ Centralized secret management
- ✅ IAM access control
- ✅ CloudTrail audit logs
- ✅ Automatic rotation support
- ✅ Secrets never in git
- ✅ Easy key rotation (no re-encryption needed)
- ✅ Versioning included
Cons
- ❌ Requires AWS account and network access
- ❌ Costs money ($0.40/secret/month + API calls)
- ❌ More complex setup than encryption
- ❌ Slower (network latency)
- ❌ AWS vendor lock-in
Comparison: AWS Secrets Manager vs AWS KMS
| Feature | AWS Secrets Manager | AWS KMS |
|---|---|---|
| Storage | Remote (AWS) | Local (encrypted in fnox.toml) |
| Secrets in git | No (references only) | Yes (encrypted ciphertext) |
| Pricing | $0.40/secret/month | $1/key/month (all secrets use one key) |
| Rotation | Automatic | Manual |
| Offline | No | No (needs AWS to encrypt/decrypt) |
| Access Control | IAM policies | IAM policies |
Use AWS SM when: You want centralized storage, rotation, and don't want secrets in git.
Use AWS KMS when: You want secrets in git (version control) but with AWS-managed keys.
Troubleshooting
"AccessDeniedException"
Check IAM permissions:
# Test access
aws secretsmanager list-secrets
aws secretsmanager get-secret-value --secret-id "myapp/database-url""ResourceNotFoundException"
Secret doesn't exist. Check:
# List all secrets
aws secretsmanager list-secrets
# Check if prefix is correct in fnox.toml
cat fnox.toml | grep prefix"Invalid Region"
Verify region matches:
# Check fnox.toml region
cat fnox.toml | grep region
# Check AWS credentials region
echo $AWS_REGIONNext Steps
- AWS KMS - Alternative with secrets in git
- Real-World Example - Complete AWS setup
- Profiles - Multi-environment configuration