AWS Security Best Practices: Comprehensive Guide for 2026
Learn to secure AWS infrastructure, IAM, S3, and services with best practices, automation, and real-world security implementations.
AWS security best practices protect cloud infrastructure from 80% of common attacks and reduce security incidents by 70%. According to the 2024 AWS Security Report, organizations following security best practices experience 75% fewer breaches and 60% faster incident response. Misconfigured AWS resources expose sensitive data and create attack surfaces. This guide shows you how to secure AWS infrastructure with IAM best practices, S3 security, network security, monitoring, and automation.
Table of Contents
- Understanding AWS Security
- Setting Up the Project
- IAM Security Best Practices
- S3 Security Configuration
- Network Security
- Monitoring and Logging
- Real-World Project: GitHub Credential Scanner
- Advanced Security Patterns
- Real-World Case Study
- Troubleshooting Guide
- FAQ
- Conclusion
Key Takeaways
- AWS security best practices prevent 80% of common attacks
- Reduce security incidents by 70% with proper configuration
- IAM misconfigurations cause 80% of breaches
- S3 bucket security is critical for data protection
- Monitoring and logging enable threat detection
- Automation prevents misconfigurations
TL;DR
AWS security requires proper IAM configuration, S3 security, network controls, and monitoring. Follow best practices for access control, data protection, network security, and threat detection. Build automated tools to detect and prevent security misconfigurations.
Understanding AWS Security
Common AWS Security Risks
1. IAM Misconfigurations:
- Overly permissive policies
- Hardcoded credentials
- Missing MFA
- Unused access keys
2. S3 Security Issues:
- Public buckets
- Missing encryption
- Weak access controls
- Exposed sensitive data
3. Network Security:
- Open security groups
- Misconfigured VPCs
- Exposed services
- Missing network segmentation
4. Monitoring Gaps:
- Missing CloudTrail logs
- No CloudWatch alarms
- Undetected anomalies
- Delayed incident response
AWS Security Best Practices
1. Identity and Access Management:
- Least privilege access
- MFA for all users
- Regular access reviews
- IAM roles over users
2. Data Protection:
- Encryption at rest and in transit
- Secure S3 buckets
- Secrets management
- Data classification
3. Network Security:
- VPC isolation
- Security groups
- Network ACLs
- Private subnets
4. Monitoring and Logging:
- CloudTrail enabled
- CloudWatch alarms
- GuardDuty for threats
- Config for compliance
Prerequisites
- macOS or Linux with Python 3.12+ (
python3 --version) - AWS account (free tier sufficient)
- AWS CLI installed (
aws --version) - 2 GB free disk space
- Basic understanding of AWS services
- Only test on AWS accounts you own or have permission
Safety and Legal
- Only test on AWS accounts you own or have explicit authorization
- Use test/development accounts, not production
- Follow AWS Acceptable Use Policy
- Implement proper access controls
- Real-world defaults: Use production-grade security, monitoring, and backup
Step 1) Set up the project
Create an isolated environment:
Click to view commands
mkdir -p aws-security/{src,config,scripts}
cd aws-security
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
Validation: python3 --version shows Python 3.12+.
Step 2) Install AWS dependencies
Click to view commands
pip install boto3==1.34.0 botocore==1.34.0 requests==2.31.0 python-dotenv==1.0.0
Validation: python3 -c "import boto3; print('OK')" prints OK.
Step 3) Configure AWS credentials
Click to view commands
# Configure AWS CLI (if not already done)
aws configure
# Or set environment variables
export AWS_ACCESS_KEY_ID=your_access_key
export AWS_SECRET_ACCESS_KEY=your_secret_key
export AWS_DEFAULT_REGION=us-east-1
Validation: aws sts get-caller-identity returns your AWS account info.
Step 4) Implement IAM security checks
Click to view code
# src/iam_security.py
"""IAM security checks and best practices."""
import boto3
from botocore.exceptions import ClientError
from typing import List, Dict
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class IAMSecurityChecker:
"""Checks IAM security configurations."""
def __init__(self):
"""Initialize IAM client."""
self.iam = boto3.client('iam')
def check_mfa_enabled(self) -> Dict:
"""Check if MFA is enabled for users."""
try:
users = self.iam.list_users()
results = {
"users_without_mfa": [],
"users_with_mfa": [],
"total_users": len(users['Users'])
}
for user in users['Users']:
username = user['UserName']
mfa_devices = self.iam.list_mfa_devices(UserName=username)
if not mfa_devices['MFADevices']:
results["users_without_mfa"].append(username)
else:
results["users_with_mfa"].append(username)
return results
except ClientError as e:
logger.error(f"Error checking MFA: {e}")
return {"error": str(e)}
def check_overly_permissive_policies(self) -> List[Dict]:
"""Check for overly permissive IAM policies."""
issues = []
try:
policies = self.iam.list_policies(Scope='Local')
for policy in policies['Policies']:
policy_version = self.iam.get_policy_version(
PolicyArn=policy['Arn'],
VersionId=policy['DefaultVersionId']
)
document = policy_version['PolicyVersion']['Document']
# Check for wildcard actions
for statement in document.get('Statement', []):
actions = statement.get('Action', [])
if isinstance(actions, str):
actions = [actions]
if '*' in actions or 'Action' in statement and statement['Action'] == '*':
issues.append({
"policy": policy['PolicyName'],
"arn": policy['Arn'],
"issue": "Wildcard action found",
"statement": statement
})
return issues
except ClientError as e:
logger.error(f"Error checking policies: {e}")
return []
def check_unused_access_keys(self) -> List[Dict]:
"""Check for unused access keys."""
unused_keys = []
try:
users = self.iam.list_users()
for user in users['Users']:
username = user['UserName']
access_keys = self.iam.list_access_keys(UserName=username)
for key_metadata in access_keys['AccessKeyMetadata']:
key_id = key_metadata['AccessKeyId']
last_used = self.iam.get_access_key_last_used(AccessKeyId=key_id)
if 'LastUsedDate' not in last_used['AccessKeyLastUsed']:
unused_keys.append({
"username": username,
"access_key_id": key_id,
"status": "Never used",
"created": key_metadata['CreateDate'].isoformat()
})
return unused_keys
except ClientError as e:
logger.error(f"Error checking access keys: {e}")
return []
Step 5) Implement S3 security checks
Click to view code
# src/s3_security.py
"""S3 security checks and best practices."""
import boto3
from botocore.exceptions import ClientError
from typing import List, Dict
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class S3SecurityChecker:
"""Checks S3 security configurations."""
def __init__(self):
"""Initialize S3 client."""
self.s3 = boto3.client('s3')
def check_public_buckets(self) -> List[Dict]:
"""Check for publicly accessible buckets."""
public_buckets = []
try:
buckets = self.s3.list_buckets()
for bucket in buckets['Buckets']:
bucket_name = bucket['Name']
try:
# Check bucket policy
policy = self.s3.get_bucket_policy(Bucket=bucket_name)
# Check for public access
if 'Public' in policy.get('Policy', ''):
public_buckets.append({
"bucket": bucket_name,
"issue": "Public bucket policy",
"policy": policy['Policy']
})
except ClientError as e:
if e.response['Error']['Code'] != 'NoSuchBucketPolicy':
logger.warning(f"Error checking bucket {bucket_name}: {e}")
# Check public access block
try:
public_access = self.s3.get_public_access_block(Bucket=bucket_name)
if not all([
public_access['PublicAccessBlockConfiguration'].get('BlockPublicAcls', False),
public_access['PublicAccessBlockConfiguration'].get('BlockPublicPolicy', False)
]):
public_buckets.append({
"bucket": bucket_name,
"issue": "Public access not blocked"
})
except ClientError as e:
if e.response['Error']['Code'] == 'NoSuchPublicAccessBlockConfiguration':
public_buckets.append({
"bucket": bucket_name,
"issue": "No public access block configured"
})
return public_buckets
except ClientError as e:
logger.error(f"Error checking buckets: {e}")
return []
def check_encryption(self) -> List[Dict]:
"""Check for encryption configuration."""
unencrypted_buckets = []
try:
buckets = self.s3.list_buckets()
for bucket in buckets['Buckets']:
bucket_name = bucket['Name']
try:
encryption = self.s3.get_bucket_encryption(Bucket=bucket_name)
# Encryption is configured
except ClientError as e:
if e.response['Error']['Code'] == 'ServerSideEncryptionConfigurationNotFoundError':
unencrypted_buckets.append({
"bucket": bucket_name,
"issue": "No encryption configured"
})
return unencrypted_buckets
except ClientError as e:
logger.error(f"Error checking encryption: {e}")
return []
Real-World Project: GitHub Credential Scanner
Build a tool that detects exposed AWS credentials on GitHub to prevent credential leaks.
Click to view project code
# src/github_credential_scanner.py
"""Scan GitHub repositories for exposed AWS credentials."""
import requests
import re
import json
from typing import List, Dict, Optional
import logging
from datetime import datetime
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class GitHubCredentialScanner:
"""Scans GitHub for exposed AWS credentials."""
def __init__(self, github_token: Optional[str] = None):
"""
Initialize scanner.
Args:
github_token: GitHub personal access token (optional, increases rate limit)
"""
self.github_token = github_token
self.session = requests.Session()
if github_token:
self.session.headers.update({
'Authorization': f'token {github_token}',
'Accept': 'application/vnd.github.v3+json'
})
# AWS credential patterns
self.aws_key_pattern = re.compile(
r'AKIA[0-9A-Z]{16}', # AWS Access Key ID
re.IGNORECASE
)
self.aws_secret_pattern = re.compile(
r'["\']?(AWS|aws)[_\-]?SECRET[_\-]?ACCESS[_\-]?KEY["\']?\s*[:=]\s*["\']?([A-Za-z0-9/+=]{40})["\']?',
re.IGNORECASE
)
def search_repositories(self, query: str, max_results: int = 100) -> List[Dict]:
"""
Search GitHub repositories.
Args:
query: Search query
max_results: Maximum results to return
Returns:
List of repository information
"""
repos = []
page = 1
per_page = 100
try:
while len(repos) < max_results:
url = f"https://api.github.com/search/repositories"
params = {
"q": query,
"page": page,
"per_page": min(per_page, max_results - len(repos))
}
response = self.session.get(url, params=params)
response.raise_for_status()
data = response.json()
if not data.get('items'):
break
repos.extend(data['items'])
if len(data['items']) < per_page:
break
page += 1
return repos[:max_results]
except requests.RequestException as e:
logger.error(f"Error searching repositories: {e}")
return []
def scan_repository(self, owner: str, repo: str) -> List[Dict]:
"""
Scan a repository for AWS credentials.
Args:
owner: Repository owner
repo: Repository name
Returns:
List of potential credential leaks
"""
findings = []
try:
# Search code in repository
url = f"https://api.github.com/search/code"
params = {
"q": f"AKIA repo:{owner}/{repo}",
"per_page": 100
}
response = self.session.get(url, params=params)
response.raise_for_status()
results = response.json()
for item in results.get('items', []):
file_path = item['path']
file_url = item['html_url']
# Get file content
content_response = self.session.get(item['url'])
content_response.raise_for_status()
content_data = content_response.json()
content = content_data.get('content', '')
# Decode base64 content
import base64
try:
decoded_content = base64.b64decode(content).decode('utf-8', errors='ignore')
except:
decoded_content = content
# Check for AWS keys
key_matches = self.aws_key_pattern.findall(decoded_content)
secret_matches = self.aws_secret_pattern.findall(decoded_content)
if key_matches or secret_matches:
findings.append({
"repository": f"{owner}/{repo}",
"file_path": file_path,
"file_url": file_url,
"aws_keys_found": len(key_matches),
"aws_secrets_found": len(secret_matches),
"timestamp": datetime.utcnow().isoformat()
})
return findings
except requests.RequestException as e:
logger.error(f"Error scanning repository {owner}/{repo}: {e}")
return []
def validate_aws_key(self, access_key_id: str) -> Dict:
"""
Validate if an AWS access key is active.
Args:
access_key_id: AWS access key ID
Returns:
Validation result
"""
# Note: This is a simplified check. In production, use AWS API properly.
# Never use real credentials in validation without proper authorization.
return {
"access_key_id": access_key_id,
"format_valid": len(access_key_id) == 20 and access_key_id.startswith('AKIA'),
"warning": "Do not use real credentials for validation without authorization"
}
def generate_report(self, findings: List[Dict]) -> str:
"""
Generate a report of findings.
Args:
findings: List of credential leaks found
Returns:
Report as string
"""
report = f"""
# AWS Credential Leak Report
Generated: {datetime.utcnow().isoformat()}
## Summary
Total repositories scanned: {len(set(f['repository'] for f in findings))}
Total files with potential leaks: {len(findings)}
## Findings
"""
for finding in findings:
report += f"""
### {finding['repository']}
- File: {finding['file_path']}
- URL: {finding['file_url']}
- AWS Keys Found: {finding['aws_keys_found']}
- AWS Secrets Found: {finding['aws_secrets_found']}
- Timestamp: {finding['timestamp']}
"""
return report
# Usage example
if __name__ == "__main__":
scanner = GitHubCredentialScanner()
# Search for repositories
repos = scanner.search_repositories("language:python", max_results=10)
# Scan repositories
all_findings = []
for repo in repos[:5]: # Limit to first 5 for demo
owner = repo['owner']['login']
repo_name = repo['name']
findings = scanner.scan_repository(owner, repo_name)
all_findings.extend(findings)
# Generate report
report = scanner.generate_report(all_findings)
print(report)
Advanced Security Patterns
1. Automated Security Scanning
Schedule regular security scans:
import schedule
import time
def run_security_scan():
iam_checker = IAMSecurityChecker()
s3_checker = S3SecurityChecker()
# Run checks
mfa_results = iam_checker.check_mfa_enabled()
public_buckets = s3_checker.check_public_buckets()
# Send alerts if issues found
if mfa_results.get('users_without_mfa'):
send_alert("Users without MFA detected")
if public_buckets:
send_alert(f"Public buckets found: {len(public_buckets)}")
# Schedule daily scans
schedule.every().day.at("02:00").do(run_security_scan)
2. CloudWatch Alarms
Set up monitoring:
import boto3
cloudwatch = boto3.client('cloudwatch')
def create_security_alarm():
cloudwatch.put_metric_alarm(
AlarmName='UnauthorizedAPICalls',
ComparisonOperator='GreaterThanThreshold',
EvaluationPeriods=1,
MetricName='UnauthorizedAPICalls',
Namespace='AWS/CloudTrail',
Period=300,
Statistic='Sum',
Threshold=1.0,
AlarmActions=['arn:aws:sns:us-east-1:123456789012:alerts']
)
Advanced Scenarios
Scenario 1: Basic AWS Security
Objective: Implement basic AWS security best practices. Steps: Enable security services, configure IAM, enable logging. Expected: Basic AWS security operational.
Scenario 2: Intermediate Advanced Security
Objective: Implement advanced AWS security. Steps: All best practices + monitoring + compliance + automation. Expected: Advanced AWS security operational.
Scenario 3: Advanced Comprehensive AWS Security
Objective: Complete AWS security program. Steps: All practices + monitoring + testing + optimization + governance. Expected: Comprehensive AWS security program.
Theory and “Why” AWS Security Best Practices Work
Why IAM Best Practices are Critical
- Controls access to AWS resources
- Prevents unauthorized access
- Principle of least privilege
- Essential security foundation
Why Monitoring and Logging Matter
- Provides visibility
- Enables incident detection
- Supports compliance
- Audit trail
Comprehensive Troubleshooting
Issue: IAM Permissions Too Permissive
Diagnosis: Review IAM policies, check permissions, analyze access. Solutions: Apply least privilege, update policies, restrict access.
Issue: Security Alerts Not Triggering
Diagnosis: Check CloudWatch alarms, verify configurations, test alerts. Solutions: Fix alarm configurations, verify setups, test alerts.
Issue: Compliance Gaps
Diagnosis: Review compliance requirements, assess current state, identify gaps. Solutions: Implement missing controls, update configurations, fill gaps.
Cleanup
# Clean up AWS resources
# Remove test configurations
# Clean up IAM policies if needed
Real-World Case Study: AWS Security Success
Challenge: A company had 200+ S3 buckets, 50 IAM users, and frequent security incidents from misconfigurations.
Solution: Implemented comprehensive security:
- Automated IAM security checks
- S3 bucket security scanning
- CloudWatch monitoring
- GitHub credential scanning
Results:
- 80% reduction in security incidents
- 100% MFA adoption
- Zero public S3 buckets
- 60% faster incident response
- $300K annual savings in security operations
Troubleshooting Guide
Issue: AWS credentials not working
Solutions:
- Verify credentials:
aws sts get-caller-identity - Check IAM permissions: Ensure required permissions
- Verify region: Set correct AWS region
- Check credentials format: Valid access key format
Issue: Rate limiting on GitHub API
Solutions:
- Use GitHub token: Increases rate limit
- Add delays: Rate limit requests
- Use pagination: Process in batches
- Cache results: Avoid duplicate requests
AWS Security Architecture Diagram
Recommended Diagram: AWS Security Layers
AWS Resources
↓
┌────┴────┬──────────┬──────────┐
↓ ↓ ↓ ↓
IAM Network Encryption Monitoring
(Policies) (VPC) (KMS) (CloudWatch)
↓ ↓ ↓ ↓
└────┬────┴──────────┴──────────┘
↓
Secure AWS
Environment
Security Layers:
- IAM for access control
- Network security (VPC)
- Encryption (KMS)
- Monitoring (CloudWatch)
Limitations and Trade-offs
AWS Security Limitations
Complexity:
- AWS security is complex
- Many services and configurations
- Easy to misconfigure
- Requires expertise
- Ongoing maintenance needed
Shared Responsibility:
- Security is shared with AWS
- Customer responsible for configuration
- Requires understanding responsibilities
- AWS provides infrastructure
- Customer secures workloads
Cost:
- Security services add cost
- Monitoring and logging expensive
- May exceed budget
- Requires optimization
- Cost management important
AWS Security Trade-offs
Security vs. Cost:
- More security = better protection but expensive
- Less security = cheaper but vulnerable
- Balance based on requirements
- Prioritize critical resources
- Cost optimization strategies
Native vs. Third-Party:
- Native tools = integrated but vendor lock-in
- Third-party = flexible but complex integration
- Balance based on needs
- Native for simplicity
- Third-party for advanced features
Automation vs. Control:
- More automation = faster but less control
- More control = safer but slow
- Balance based on risk
- Automate routine
- Control for critical
When AWS Security May Be Challenging
Multi-Account:
- Multiple accounts complicate security
- Requires organization management
- Consistent policies needed
- Centralized management helps
- AWS Organizations important
Legacy Applications:
- Legacy apps may not fit security models
- Require special configurations
- May need modernization
- Gradual migration approach
- Hybrid solutions may be needed
Compliance Requirements:
- Compliance complex in AWS
- Requires understanding regulations
- AWS certifications help
- Customer still responsible
- Audit and monitoring critical
FAQ
Q: How do I secure S3 buckets?
A: Best practices:
- Enable public access block
- Use bucket policies
- Enable encryption
- Enable versioning
- Use lifecycle policies
- Enable access logging
Q: What IAM permissions are needed?
A: Minimum permissions:
iam:ListUsersiam:ListPoliciesiam:GetPolicyVersions3:ListBucketss3:GetBucketPolicys3:GetBucketEncryption
Q: How often should I scan for credentials?
A: Recommended:
- Daily: Automated scans
- Weekly: Comprehensive reviews
- Real-time: CI/CD integration
- On-demand: Before deployments
Code Review Checklist for AWS Security
IAM Configuration
- Least privilege principles applied
- MFA enabled for all users
- No hardcoded credentials in code
- IAM roles used instead of access keys where possible
- Regular access reviews conducted
S3 Security
- Public access blocked on all buckets
- Bucket policies properly configured
- Encryption enabled (at rest and in transit)
- Versioning enabled for critical buckets
- Access logging enabled
Network Security
- VPCs properly configured
- Security groups follow least privilege
- Unnecessary ports closed
- Network ACLs configured appropriately
- Private subnets used for sensitive resources
Monitoring and Logging
- CloudTrail enabled and logging to S3
- CloudWatch alarms configured
- GuardDuty enabled
- Config rules defined
- Log retention policies set
Secrets Management
- Secrets stored in Secrets Manager or Parameter Store
- No secrets in code repositories
- Credential scanning in CI/CD
- Rotation policies configured
- Access to secrets audited
Compliance
- Compliance requirements identified
- Compliance monitoring configured
- Audit trails maintained
- Data classification implemented
- Backup and recovery tested
Conclusion
AWS security requires comprehensive approach covering IAM, S3, networking, and monitoring. By following best practices and implementing automated security tools, you can protect AWS infrastructure from common attacks.
Action Steps
- Review IAM: Check users, policies, MFA
- Secure S3: Enable encryption, block public access
- Configure networking: Use VPCs, security groups
- Enable monitoring: CloudTrail, CloudWatch, GuardDuty
- Automate scanning: Regular security checks
- Scan for leaks: GitHub credential scanning
- Review regularly: Monthly security audits
Related Topics
Educational Use Only: This content is for educational purposes. Only test on AWS accounts you own or have explicit authorization. Follow AWS Acceptable Use Policy and implement proper security controls.