Skip to content

AWS Secrets Manager

AWS Secrets Manager provides centralized secret management with IAM access control, audit logs, and automatic rotation.

Quick Start

bash
# 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_URL

Prerequisites

  • AWS account
  • AWS credentials configured (CLI, environment variables, or IAM role)
  • IAM permissions (see below)

IAM Permissions

Read-Only Access (Minimum)

json
{
  "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)

json
{
  "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

bash
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_REGION="us-east-1"

Option 2: AWS CLI Profile

bash
aws configure

# Or use named profile
export AWS_PROFILE=myapp

Option 3: IAM Role (Automatic on AWS)

If running on EC2, ECS, Lambda, or other AWS services:

bash
# No configuration needed!
# Credentials are automatic via instance metadata

Configure fnox Provider

toml
[providers.aws]
type = "aws-sm"
region = "us-east-1"
prefix = "myapp/"  # Optional: prepended to all secret names

Creating Secrets

Via AWS CLI

bash
# 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

  1. Go to AWS Secrets Manager Console
  2. Click "Store a new secret"
  3. Choose "Other type of secret"
  4. Enter key/value pairs or plaintext
  5. Name it with your prefix (e.g., myapp/database-url)
  6. Configure rotation (optional)
  7. Store

Referencing Secrets

Add references to fnox.toml:

toml
[secrets.DATABASE_URL]
provider = "aws"
value = "database-url"  # → Fetches "myapp/database-url"

[secrets.API_KEY]
provider = "aws"
value = "api-key"  # → Fetches "myapp/api-key"

# Without prefix in provider, use full name:
# value = "myapp/api-key"

Usage

Get a Secret

bash
fnox get DATABASE_URL

Run Commands

bash
# Fetches all secrets from AWS
fnox exec -- ./start-server.sh

Use Different Profiles

bash
# Different profile for different environments
fnox exec --profile production -- ./deploy.sh

Prefix Behavior

The prefix is prepended to the value:

toml
[providers.aws]
prefix = "myapp/"

[secrets.DATABASE_URL]
provider = "aws"
value = "database-url"  # → Fetches "myapp/database-url"

[secrets.API_KEY]
provider = "aws"
value = "api-key"  # → Fetches "myapp/api-key"

Without prefix:

toml
[providers.aws]
# No prefix

[secrets.DATABASE_URL]
provider = "aws"
value = "myapp/database-url"  # → Fetches "myapp/database-url"

Multi-Environment Example

toml
# 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
bash
# Development (local)
fnox get DATABASE_URL

# Staging
fnox get DATABASE_URL --profile staging

# Production
fnox get DATABASE_URL --profile production

JSON Secrets

AWS Secrets Manager supports JSON secrets:

bash
# Create JSON secret
aws secretsmanager create-secret \
  --name "myapp/db-credentials" \
  --secret-string '{"host":"db.example.com","port":"5432","username":"admin","password":"secret"}'

fnox returns the entire JSON string:

bash
fnox get DB_CREDENTIALS
# Output: {"host":"db.example.com","port":"5432","username":"admin","password":"secret"}

To extract a specific field, use jq:

bash
fnox get DB_CREDENTIALS | jq -r '.password'
# Output: secret

Secret Rotation

AWS Secrets Manager supports automatic rotation:

bash
# 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

yaml
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.sh

Pros

  • ✅ 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

FeatureAWS Secrets ManagerAWS KMS
StorageRemote (AWS)Local (encrypted in fnox.toml)
Secrets in gitNo (references only)Yes (encrypted ciphertext)
Pricing$0.40/secret/month$1/key/month (all secrets use one key)
RotationAutomaticManual
OfflineNoNo (needs AWS to encrypt/decrypt)
Access ControlIAM policiesIAM 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:

bash
# Test access
aws secretsmanager list-secrets
aws secretsmanager get-secret-value --secret-id "myapp/database-url"

"ResourceNotFoundException"

Secret doesn't exist. Check:

bash
# 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:

bash
# Check fnox.toml region
cat fnox.toml | grep region

# Check AWS credentials region
echo $AWS_REGION

Next Steps

Released under the MIT License.