This document describes secure ways to manage AWS credentials for PutPlace's S3 storage backend.
- Overview
- Credential Methods (Ranked by Security)
- Configuration Examples
- Best Practices
- IAM Policy Examples
PutPlace supports multiple methods for AWS credential management. The application uses aioboto3 (async AWS SDK), which follows the standard AWS credential chain in this order:
- Explicit credentials passed to the application (not recommended)
- Environment variables
- AWS credentials file (
~/.aws/credentials) - IAM roles (for EC2/ECS/Lambda/etc.)
- Container credentials (for ECS tasks)
- Instance metadata service (for EC2)
Use when: Running on AWS infrastructure (EC2, ECS, Lambda, EKS, etc.)
How it works: AWS automatically provides temporary credentials to your application through the instance metadata service. No credential files or environment variables needed!
Setup:
- Create IAM Role with S3 permissions (see IAM Policy Examples)
- Attach role to your EC2 instance, ECS task, or Lambda function
- Configure PutPlace - no credentials needed!
Configuration (.env):
STORAGE_BACKEND=s3
S3_BUCKET_NAME=my-putplace-bucket
S3_REGION_NAME=us-east-1
# That's it! No AWS credentials neededAdvantages:
- ✅ Most secure - no long-lived credentials
- ✅ Automatic credential rotation
- ✅ No credential files to manage
- ✅ Built-in AWS audit trail (CloudTrail)
- ✅ Fine-grained permissions per service
Disadvantages:
- ❌ Only works on AWS infrastructure
- ❌ Requires AWS configuration outside the app
Use when: Running on-premises or locally, multiple AWS accounts
How it works: Store credentials in ~/.aws/credentials with named profiles. Each profile has separate access keys.
Setup:
- Create AWS credentials file at
~/.aws/credentials:
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
[putplace-prod]
aws_access_key_id = AKIAI44QH8DHBEXAMPLE
aws_secret_access_key = je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY
[putplace-dev]
aws_access_key_id = AKIAI44QH8DHBEXAMPLE
aws_secret_access_key = another-secret-key-here- Set file permissions (IMPORTANT):
chmod 600 ~/.aws/credentials- Configure PutPlace (.env):
STORAGE_BACKEND=s3
S3_BUCKET_NAME=my-putplace-bucket
S3_REGION_NAME=us-east-1
AWS_PROFILE=putplace-prod # Use specific profileAdvantages:
- ✅ Secure file permissions (600)
- ✅ Multiple profiles for different environments
- ✅ Standard AWS practice
- ✅ Shared with other AWS tools (aws-cli, terraform, etc.)
- ✅ Credentials stored locally, not in code
Disadvantages:
- ❌ Long-lived credentials (must rotate manually)
- ❌ Credentials in plaintext (though file-protected)
- ❌ Must secure the server's filesystem
Use when: Using container orchestration (Docker, Kubernetes) or secret management systems
How it works: Store credentials in a secret management system, inject as environment variables at runtime.
Option A: HashiCorp Vault
- Store secret in Vault:
vault kv put secret/putplace \
aws_access_key_id=AKIAI... \
aws_secret_access_key=wJalr...- Retrieve in startup script:
#!/bin/bash
export AWS_ACCESS_KEY_ID=$(vault kv get -field=aws_access_key_id secret/putplace)
export AWS_SECRET_ACCESS_KEY=$(vault kv get -field=aws_secret_access_key secret/putplace)
uvicorn putplace.main:appOption B: Kubernetes Secrets
- Create Kubernetes secret:
kubectl create secret generic putplace-aws \
--from-literal=aws-access-key-id=AKIAI... \
--from-literal=aws-secret-access-key=wJalr...- Mount in pod (deployment.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: putplace
spec:
template:
spec:
containers:
- name: putplace
image: putplace:latest
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: putplace-aws
key: aws-access-key-id
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: putplace-aws
key: aws-secret-access-key
- name: STORAGE_BACKEND
value: "s3"
- name: S3_BUCKET_NAME
value: "my-putplace-bucket"Advantages:
- ✅ Centralized secret management
- ✅ Audit logging
- ✅ Secret rotation capabilities
- ✅ Access control policies
- ✅ Encrypted at rest
Disadvantages:
- ❌ Additional infrastructure required
- ❌ More complex setup
- ❌ Credentials still in environment at runtime
Use when: Development, small deployments, single server
How it works: Store configuration in a .env file with strict file permissions.
Setup:
- Create .env file in the application directory:
# .env
STORAGE_BACKEND=s3
S3_BUCKET_NAME=my-putplace-bucket
S3_REGION_NAME=us-east-1
# Option 1: Use AWS profile (better)
AWS_PROFILE=putplace-prod
# Option 2: Explicit credentials (less secure)
# AWS_ACCESS_KEY_ID=AKIAI44QH8DHBEXAMPLE
# AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY- Set strict file permissions:
chmod 600 .env
chown putplace:putplace .env # Application user only- Add to .gitignore:
echo ".env" >> .gitignoreAdvantages:
- ✅ Simple to set up
- ✅ Easy to change configuration
- ✅ Works anywhere
Disadvantages:
- ❌ Credentials in plaintext file
- ❌ Easy to accidentally commit to git
- ❌ Must secure file permissions
- ❌ Hard to rotate credentials
Use when: Quick testing, CI/CD pipelines
How it works: Set environment variables directly in shell or systemd service.
Setup:
export STORAGE_BACKEND=s3
export S3_BUCKET_NAME=my-putplace-bucket
export S3_REGION_NAME=us-east-1
export AWS_ACCESS_KEY_ID=AKIAI44QH8DHBEXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
uvicorn putplace.main:appOr in systemd service:
# /etc/systemd/system/putplace.service
[Service]
Environment="STORAGE_BACKEND=s3"
Environment="S3_BUCKET_NAME=my-putplace-bucket"
Environment="AWS_ACCESS_KEY_ID=AKIAI..."
Environment="AWS_SECRET_ACCESS_KEY=wJalr..."
ExecStart=/usr/local/bin/uvicorn putplace.main:appAdvantages:
- ✅ Simple
- ✅ No files to manage
Disadvantages:
- ❌ Visible in process list (
ps aux | grep AWS) - ❌ Stored in shell history
- ❌ Easy to leak in logs
- ❌ Hard to rotate
Use when: Never in production! Only for local development/testing.
Setup (.env):
STORAGE_BACKEND=s3
S3_BUCKET_NAME=my-putplace-bucket
AWS_ACCESS_KEY_ID=AKIAI44QH8DHBEXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYDisadvantages:
- ❌ Credentials in version control (if committed)
- ❌ Visible to anyone with access
- ❌ Hard to rotate
- ❌ Security nightmare if leaked
Use AWS credentials file with profile:
# ~/.aws/credentials
[putplace-dev]
aws_access_key_id = AKIAI...
aws_secret_access_key = wJalr...
# .env
STORAGE_BACKEND=s3
S3_BUCKET_NAME=putplace-dev-bucket
AWS_PROFILE=putplace-devUse IAM roles (no credentials needed):
# .env
STORAGE_BACKEND=s3
S3_BUCKET_NAME=putplace-prod-bucket
S3_REGION_NAME=us-east-1
# No AWS credentials - uses IAM role automaticallyUse AWS credentials file with restricted permissions:
# /etc/putplace/.aws/credentials (owned by putplace user, mode 600)
[default]
aws_access_key_id = AKIAI...
aws_secret_access_key = wJalr...
# /etc/putplace/.env
STORAGE_BACKEND=s3
S3_BUCKET_NAME=putplace-prod-bucket
AWS_PROFILE=default # Or omit to use default profileUse secrets mounted as environment variables:
docker run -d \
-e STORAGE_BACKEND=s3 \
-e S3_BUCKET_NAME=putplace-bucket \
-e AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_key_id) \
-e AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_secret) \
putplace:latestOr mount credentials file:
docker run -d \
-v ~/.aws:/root/.aws:ro \
-e STORAGE_BACKEND=s3 \
-e S3_BUCKET_NAME=putplace-bucket \
-e AWS_PROFILE=putplace-prod \
putplace:latest- Use IAM roles whenever running on AWS infrastructure
- Use named profiles from
~/.aws/credentialsfor on-premises - Rotate credentials regularly (every 90 days)
- Use least-privilege IAM policies (see examples below)
- Set restrictive file permissions (600 for credential files)
- Never commit credentials to version control
- Use separate credentials for dev/staging/production
- Enable CloudTrail for audit logging
- Use MFA for IAM users creating access keys
- Monitor for leaked credentials (AWS Access Analyzer, git-secrets)
- Don't hardcode credentials in source code
- Don't use root AWS account credentials
- Don't share credentials between applications
- Don't log credentials (check application logs!)
- Don't grant
s3:*permissions - use specific actions - Don't commit .env files to git
- Don't use long-lived credentials when IAM roles are available
- Don't store credentials in public repos (even accidentally)
Grant only the permissions PutPlace needs:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PutPlaceS3Access",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:HeadObject"
],
"Resource": "arn:aws:s3:::my-putplace-bucket/files/*"
},
{
"Sid": "PutPlaceS3BucketAccess",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::my-putplace-bucket",
"Condition": {
"StringLike": {
"s3:prefix": "files/*"
}
}
}
]
}- Create IAM policy (use JSON above)
- Create IAM role for EC2:
aws iam create-role \
--role-name PutPlaceEC2Role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'- Attach policy to role:
aws iam attach-role-policy \
--role-name PutPlaceEC2Role \
--policy-arn arn:aws:iam::ACCOUNT_ID:policy/PutPlaceS3Policy- Create instance profile:
aws iam create-instance-profile --instance-profile-name PutPlaceEC2Profile
aws iam add-role-to-instance-profile \
--instance-profile-name PutPlaceEC2Profile \
--role-name PutPlaceEC2Role- Attach to EC2 instance:
aws ec2 associate-iam-instance-profile \
--instance-id i-1234567890abcdef0 \
--iam-instance-profile Name=PutPlaceEC2ProfileRotate IAM access keys periodically (every 30/60/90 days). The recommended approach is to issue a new access key, deploy it to the host running PutPlace (via your existing secret distribution mechanism — Vault, Kubernetes Secrets, your CI/CD secret store, etc.), verify the application still works, then deactivate and delete the old access key.
For workloads running on AWS, prefer IAM roles over long-lived access keys — roles provide automatic short-lived credential rotation without any application changes.
import boto3
# Check current credentials
session = boto3.Session()
credentials = session.get_credentials()
print(f"Access Key: {credentials.access_key[:8]}...")
print(f"Method: {credentials.method}") # Shows how credentials were obtained"Unable to locate credentials"
- Check AWS_ACCESS_KEY_ID environment variable
- Check ~/.aws/credentials file exists and has correct permissions
- Check AWS_PROFILE is set correctly
- For EC2: Verify IAM role is attached to instance
"Access Denied"
- Check IAM policy allows required S3 actions
- Verify bucket name is correct
- Check bucket policy doesn't deny access
- Verify region is correct
"Credentials expired"
- IAM role credentials expire automatically (renewed by AWS)
- Access keys don't expire (must be rotated manually)
- Temporary credentials (STS) expire after specified duration
Before deploying to production:
- Using IAM roles (if on AWS) or AWS credentials file (if on-premises)
- NOT using hardcoded credentials in .env or code
- IAM policy follows least-privilege principle
- Credentials file has 600 permissions
- .env file is in .gitignore
- CloudTrail is enabled for audit logging
- Credentials are rotated regularly (or using short-lived tokens)
- Separate credentials for dev/staging/production
- MFA enabled for IAM users
- No credentials in application logs