Security Guide - AWS Credentials and Storageď
This document describes secure ways to manage AWS credentials for PutPlaceâs S3 storage backend.
Table of Contentsď
Overviewď
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)
Credential Methods (Ranked by Security)ď
âââââ 1. IAM Roles (BEST - Production Recommended)ď
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 needed
Advantages:
â 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
ââââ 2. AWS Credentials File with Named Profilesď
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 profile
Advantages:
â 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
ââââ 3. Environment Variables (via Secure Secret Management)ď
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: AWS Secrets Manager
Store secret in AWS Secrets Manager:
aws secretsmanager create-secret \
--name putplace/aws-credentials \
--secret-string '{"access_key":"AKIAI...","secret_key":"wJalr..."}'
Retrieve in startup script:
#!/bin/bash
# startup.sh
SECRET=$(aws secretsmanager get-secret-value \
--secret-id putplace/aws-credentials \
--query SecretString --output text)
export AWS_ACCESS_KEY_ID=$(echo $SECRET | jq -r .access_key)
export AWS_SECRET_ACCESS_KEY=$(echo $SECRET | jq -r .secret_key)
# Start application
uvicorn putplace.main:app
Option B: 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:app
Option C: 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
âââ 4. Local Configuration File (.env)ď
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" >> .gitignore
Advantages:
â 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
ââ 5. Environment Variables (Direct)ď
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:app
Or 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:app
Advantages:
â 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
â 6. Hardcoded Credentials (NEVER USE IN PRODUCTION)ď
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/bPxRfiCYEXAMPLEKEY
Disadvantages:
â Credentials in version control (if committed)
â Visible to anyone with access
â Hard to rotate
â Security nightmare if leaked
Configuration Examplesď
Development (Local)ď
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-dev
Production (AWS EC2/ECS)ď
Use 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 automatically
Production (On-Premises Server)ď
Use 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 profile
Docker Containerď
Use 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:latest
Or 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
Best Practicesď
â DO:ď
Use IAM roles whenever running on AWS infrastructure
Use named profiles from
~/.aws/credentialsfor on-premisesRotate 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:ď
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 actionsDonâ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)
IAM Policy Examplesď
Minimal S3 Policy (Least Privilege)ď
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/*"
}
}
}
]
}
IAM Role for EC2 Instanceď
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=PutPlaceEC2Profile
Credential Rotationď
Automated Rotation with AWS Secrets Managerď
Store credentials in Secrets Manager
Enable automatic rotation (every 30/60/90 days)
Update application to fetch from Secrets Manager on startup
Example rotation Lambda function (Python):
import boto3
import json
def lambda_handler(event, context):
iam = boto3.client('iam')
secrets = boto3.client('secretsmanager')
# Get current secret
secret_arn = event['SecretId']
token = event['ClientRequestToken']
# Create new access key
username = 'putplace-user'
new_key = iam.create_access_key(UserName=username)
# Store new credentials
new_secret = {
'access_key': new_key['AccessKey']['AccessKeyId'],
'secret_key': new_key['AccessKey']['SecretAccessKey']
}
secrets.put_secret_value(
SecretId=secret_arn,
SecretString=json.dumps(new_secret),
VersionStages=['AWSPENDING'],
ClientRequestToken=token
)
# Delete old access key (after verification)
# ... implementation details ...
Troubleshootingď
Check Which Credentials Are Being Usedď
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
Common Issuesď
â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
Security Checklistď
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