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 #!/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 boto3
import botocore
import jmespath import jmespath
import re import re
from pprint import pprint from pprint import pprint
@ -7,20 +17,21 @@ from datetime import date
def printTitle(title): def printTitle(title):
print("=" * 20) print("_" * 40)
print(title) print(title)
print("=" * 20) print("_" * 40)
return return
def printSubTitle(title): def printSubTitle(title):
print(title) print("\n" + title + "\n")
return return
def getAllRegions(myclient): def getAllRegions(myclient):
return jmespath.search("Regions[*].RegionName", myclient.describe_regions(AllRegions=False)) return jmespath.search("Regions[*].RegionName", myclient.describe_regions(AllRegions=False))
def getAgeFromDate(inputDate): def getAgeFromDate(inputDate):
today = date.today() today = date.today()
delta = today - inputDate.date() delta = today - inputDate.date()
@ -29,12 +40,15 @@ def getAgeFromDate(inputDate):
sts = boto3.client("sts") sts = boto3.client("sts")
aid = sts.get_caller_identity().get("Account") 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") printTitle("Ec2 service review")
printSubTitle("[Cost saving] Instances stopped for over 14 days - Consider backing up instances and terminate them") printSubTitle("[Cost Optimization] Instances stopped for over 14 days - Consider backing up and terminate instances")
client = boto3.client('ec2')
regions = getAllRegions(client)
print("Region", "AccountID", "InstanceId", "DaysStopped", sep=", ") print("Region", "AccountID", "InstanceId", "DaysStopped", sep=", ")
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) client = boto3.client('ec2', region_name=r)
response = client.describe_instances() response = client.describe_instances()
@ -42,11 +56,9 @@ for r in regions:
for i in jmespath.search("Reservations[*].Instances[*]", response): for i in jmespath.search("Reservations[*].Instances[*]", response):
if i[0].get("State").get("Name") == "stopped": if i[0].get("State").get("Name") == "stopped":
print(r, aid, i[0].get("InstanceId"), getAgeFromDate(i[0].get("UsageOperationUpdateTime")), sep=", ") print(r, aid, i[0].get("InstanceId"), getAgeFromDate(i[0].get("UsageOperationUpdateTime")), sep=", ")
print("--END OF SECTION--")
printSubTitle("[Performance Efficiency] Previous instance generation - Consider using current instance generation")
printSubTitle("[Performance] Previous instance generation - Consider using current instance generation")
client = boto3.client('ec2')
regions = getAllRegions(client)
print("Region", "AccountID", "InstanceId", "InstanceType", sep=", ") print("Region", "AccountID", "InstanceId", "InstanceType", sep=", ")
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) client = boto3.client('ec2', region_name=r)
@ -55,9 +67,10 @@ for r in regions:
for i in jmespath.search("Reservations[*].Instances[*]", response): 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: 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(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=", ") print("Region", "AccountID", "VolumeId", "Size", "VolumeType", sep=", ")
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) client = boto3.client('ec2', region_name=r)
@ -71,6 +84,21 @@ for r in regions:
) )
for i in response.get("Volumes"): for i in response.get("Volumes"):
print(r, aid, i.get("VolumeId"), i.get("Size"), i.get("VolumeType"), sep=", ") 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") printSubTitle("[Security] Unencrypted EBS volumes - Consider replacing volume with encrypted ones")
print("Region", "AccountID", "VolumeId", "Size", "VolumeType", sep=", ") print("Region", "AccountID", "VolumeId", "Size", "VolumeType", sep=", ")
@ -90,4 +118,184 @@ for r in regions:
) )
for i in response.get("Volumes"): for i in response.get("Volumes"):
print(r, aid, i.get("VolumeId"), i.get("Size"), i.get("VolumeType"), sep=", ") 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
"""