UPD: Added more services to the review

This commit is contained in:
xpk 2024-08-01 16:51:10 +08:00
parent 9ab4873613
commit a593c13ac1
Signed by: xpk
GPG Key ID: CD4FF6793F09AB86

View File

@ -1,5 +1,15 @@
#!/usr/bin/python3
"""
Review AWS environment based on 6 WAR pillars, namely:
1. Operational Excellence
2. Security
3. Reliability
4. Performance Efficiency
5. Cost Optimization
6. Sustainability
"""
import boto3
import botocore
import jmespath
import re
from pprint import pprint
@ -7,20 +17,21 @@ from datetime import date
def printTitle(title):
print("=" * 20)
print("_" * 40)
print(title)
print("=" * 20)
print("_" * 40)
return
def printSubTitle(title):
print(title)
print("\n" + title + "\n")
return
def getAllRegions(myclient):
return jmespath.search("Regions[*].RegionName", myclient.describe_regions(AllRegions=False))
def getAgeFromDate(inputDate):
today = date.today()
delta = today - inputDate.date()
@ -29,12 +40,15 @@ def getAgeFromDate(inputDate):
sts = boto3.client("sts")
aid = sts.get_caller_identity().get("Account")
client = boto3.client('ec2', region_name="us-east-1")
regions = getAllRegions(client)
print("AWS Environment Review - " + str(date.today()) + "\n\n")
printTitle("Ec2 service review")
printSubTitle("[Cost saving] Instances stopped for over 14 days - Consider backing up instances and terminate them")
client = boto3.client('ec2')
regions = getAllRegions(client)
printSubTitle("[Cost Optimization] Instances stopped for over 14 days - Consider backing up and terminate instances")
print("Region", "AccountID", "InstanceId", "DaysStopped", sep=", ")
for r in regions:
client = boto3.client('ec2', region_name=r)
response = client.describe_instances()
@ -42,11 +56,9 @@ for r in regions:
for i in jmespath.search("Reservations[*].Instances[*]", response):
if i[0].get("State").get("Name") == "stopped":
print(r, aid, i[0].get("InstanceId"), getAgeFromDate(i[0].get("UsageOperationUpdateTime")), sep=", ")
print("--END OF SECTION--")
printSubTitle("[Performance] Previous instance generation - Consider using current instance generation")
client = boto3.client('ec2')
regions = getAllRegions(client)
printSubTitle("[Performance Efficiency] Previous instance generation - Consider using current instance generation")
print("Region", "AccountID", "InstanceId", "InstanceType", sep=", ")
for r in regions:
client = boto3.client('ec2', region_name=r)
@ -55,9 +67,10 @@ for r in regions:
for i in jmespath.search("Reservations[*].Instances[*]", response):
if re.search("^(t1|t2|m3|m1|m2|m4|c1|c2|c3|c4|r3|r4|i2)", i[0].get("InstanceType")) is not None:
print(r, aid, i[0].get("InstanceId"), i[0].get("InstanceType"), sep=", ")
print("--END OF SECTION--")
printSubTitle("[Cost saving] Unattached EBS volumes - Consider taking snapshot and delete volumes")
printSubTitle("[Cost Optimization] Unattached EBS volumes - Consider taking snapshot and delete volumes")
print("Region", "AccountID", "VolumeId", "Size", "VolumeType", sep=", ")
for r in regions:
client = boto3.client('ec2', region_name=r)
@ -71,6 +84,21 @@ for r in regions:
)
for i in response.get("Volumes"):
print(r, aid, i.get("VolumeId"), i.get("Size"), i.get("VolumeType"), sep=", ")
print("--END OF SECTION--")
printSubTitle("[Cost Optimization] EBS snapshots more than 365 days old - Consider removing snapshots if no longer needed")
print("Region", "AccountID", "SnapshotId", "Description", "SnapshotAge", sep=", ")
for r in regions:
client = boto3.client('ec2', region_name=r)
response = client.describe_snapshots(
OwnerIds=[aid]
)
for i in response.get("Snapshots"):
if getAgeFromDate(i.get("StartTime")) > 365 and i.get("Description") != "This snapshot is created by the AWS Backup service.":
print(r, aid, i.get("SnapshotId"), i.get("Description")[:70], getAgeFromDate(i.get("StartTime")), sep=", ")
print("--END OF SECTION--")
printSubTitle("[Security] Unencrypted EBS volumes - Consider replacing volume with encrypted ones")
print("Region", "AccountID", "VolumeId", "Size", "VolumeType", sep=", ")
@ -90,4 +118,184 @@ for r in regions:
)
for i in response.get("Volumes"):
print(r, aid, i.get("VolumeId"), i.get("Size"), i.get("VolumeType"), sep=", ")
print("--END OF SECTION--")
printSubTitle("[Cost Optimization] Unused Elastic IP - Consider deleting unused EIP")
print("Region", "AccountID", "PublicIp", sep=", ")
for r in regions:
client = boto3.client('ec2', region_name=r)
response = client.describe_addresses()
for i in response.get("Addresses"):
if i.get("AssociationId") is None:
print(r, aid, i.get("PublicIp"), sep=", ")
print("--END OF SECTION--")
printTitle("Rds service review")
printSubTitle("[Security] Unencrypted RDS instances - Consider encrypting RDS instances")
print("Region", "AccountID", "DBIdentifier", "Engine", sep=", ")
for r in regions:
client = boto3.client('rds', region_name=r)
response = client.describe_db_instances()
for i in response.get("DBInstances"):
if i.get("StorageEncrypted") == "False":
print(r, aid, i.get("DBInstanceIdentifier"), i.get("Engine"), sep=", ")
response = client.describe_db_clusters()
for i in response.get("DBClusters"):
if i.get("StorageEncrypted") == "False":
print(r, aid, i.get("DBClusterIdentifier"), i.get("Engine"), sep=", ")
print("--END OF SECTION--")
printSubTitle("[Reliability] Single AZ RDS instance - Consider enabling multi-az for production")
print("Region", "AccountID", "DBIdentifier", "Engine", sep=", ")
for r in regions:
client = boto3.client('rds', region_name=r)
response = client.describe_db_instances()
for i in response.get("DBInstances"):
if not i.get("MultiAZ"):
print(r, aid, i.get("DBInstanceIdentifier"), i.get("Engine"), sep=", ")
response = client.describe_db_clusters()
for i in response.get("DBClusters"):
if not i.get("MultiAZ"):
print(r, aid, i.get("DBClusterIdentifier"), i.get("Engine"), sep=", ")
print("--END OF SECTION--")
printTitle("Lambda service review")
printSubTitle("[Security] Outdated Lambda runtime - Consider changing to currently supported Lambda runtime")
print("Region", "AccountID", "FunctionName", "Runtime", sep=", ")
for r in regions:
client = boto3.client('lambda', region_name=r)
response = client.list_functions()
for i in response.get("Functions"):
if i.get("Runtime") is not None:
if re.search("python2|python3.[678]|java8|nodejs[468]|nodejs1[024]|dotnet6", i.get("Runtime")) is not None:
print(r, aid, i.get("FunctionName"), i.get("Runtime"), sep=", ")
print("--END OF SECTION--")
printTitle("Iam service review")
printSubTitle("[Security] Iam user access key age - Consider rotating access key")
print("AccountID", "UserName", "AccessKeyId", "AccessKeyAge", sep=", ")
client = boto3.client('iam', region_name="us-east-1")
listUsers = client.list_users()
users = jmespath.search("Users[*].UserName", listUsers)
for u in users:
response = client.list_access_keys(UserName=u)
for i in response.get("AccessKeyMetadata"):
if getAgeFromDate(i.get("CreateDate")) > 180:
print(aid, u, i.get("AccessKeyId"), getAgeFromDate(i.get("CreateDate")), sep=", ")
print("--END OF SECTION--")
printTitle("Cloudwatch service review")
printSubTitle("[Cost Optimization] Cloudwatch LogGroups without retention period - Consider setting retention")
print("Region", "AccountID", "LogGroup", "SizeMiB", sep=", ")
for r in regions:
client = boto3.client('logs', region_name=r)
response = client.describe_log_groups()
for i in response.get("logGroups"):
if i.get("retentionInDays") is None:
print(r, aid, i.get("logGroupName"), int(round(i.get("storedBytes")/1024/1024,0)), sep=", ")
print("--END OF SECTION--")
printSubTitle("[Security] Cloudwatch LogGroups unencrypted - Consider encrypting loggroup")
print("Region", "AccountID", "LogGroup", sep=", ")
for r in regions:
client = boto3.client('logs', region_name=r)
response = client.describe_log_groups()
for i in response.get("logGroups"):
if i.get("kmsKeyId") is None:
print(r, aid, i.get("logGroupName"), sep=", ")
print("--END OF SECTION--")
printTitle("Backup service review")
printSubTitle("[Reliability] Ec2/Rds instances found but AWSBackup plan missing - Consider setting up AWSBackup plans to backup AWS resources")
print("Region", "AccountID", "BackupPlan", "Ec2RdsInstances", sep=", ")
for r in regions:
client = boto3.client('backup', region_name=r)
response = client.list_backup_plans()
if len(response.get("BackupPlansList")) <= 0:
ec2client = boto3.client("ec2", region_name=r)
ec2resp = ec2client.describe_instances()
ec2instances = jmespath.search("Reservations[*].Instances[*]", ec2resp)
rdsclient = boto3.client("rds", region_name=r)
rdsresp = rdsclient.describe_db_instances()
rdsinstances = rdsresp.get("DBInstances")
instanceCount = len(ec2instances) + len(rdsinstances)
if instanceCount >= 1:
print(r, aid, "AWSBackup plan missing", instanceCount, sep=", ")
print("--END OF SECTION--")
printTitle("S3 service review")
printSubTitle("[Security] S3 bucket policy missing - Consider creating bucket policy and restrict access to bucket")
print("AccountID", "BucketName", sep=", ")
client = boto3.client('s3', region_name="us-east-1")
response = client.list_buckets()
for i in jmespath.search("Buckets[*].Name", response):
try:
policyResp = client.get_bucket_policy(Bucket=i)
except:
print(aid, i, sep=", ")
print("--END OF SECTION--")
printTitle("ElastiCache review")
printSubTitle("[Cost Optimization] ElastiCache instances on x64 platform - Consider Graviton instances such as t4g/r7g for cost saving")
print("Region", "AccountID", "CacheClusterId", "CacheNodeType", sep=", ")
for r in regions:
client = boto3.client('elasticache', region_name=r)
response = client.describe_cache_clusters()
for i in response.get("CacheClusters"):
if re.search("[0-9]g.", i.get("CacheNodeType")) is None:
print(r, aid, i.get("CacheClusterId"), i.get("CacheNodeType"), sep=", ")
print("--END OF SECTION--")
printTitle("LoadBalancer review")
printSubTitle("[Cost Optimization] LB Target group without targets - Consider removing target groups")
print("Region", "AccountID", "TargetGroupArn", sep=", ")
for r in regions:
client = boto3.client('elbv2', region_name=r)
response = client.describe_target_groups()
for i in jmespath.search("TargetGroups[*].TargetGroupArn", response):
tgResp = client.describe_target_health(TargetGroupArn=i)
if len(jmespath.search("TargetHealthDescriptions[*].Target", tgResp)) == 0:
print(r, aid, i, sep=", ")
print("--END OF SECTION--")
printTitle("KMS review")
printSubTitle("[Security] Customer Managed Keys do not have auto rotation enabled - Consider enabling auto key rotation")
print("Region", "AccountID", "KeyId", sep=", ")
for r in regions:
client = boto3.client('kms', region_name=r)
response = client.list_keys()
for i in jmespath.search("Keys[*].KeyId", response):
try:
keyResp = client.describe_key(KeyId=i)
if (keyResp.get("KeyMetadata").get("Enabled") == "True"
and keyResp.get("KeyMetadata").get("KeyManager") == "CUSTOMER"):
krResp = client.get_key_rotation_status(KeyId=i)
if krResp.get("KeyRotationEnabled") != "False":
print(r, aid, i, sep=", ")
except:
pass
print("--END OF SECTION--")
# TODO
"""
- SG allowing public access
- config enabled for all regions
"""