UPD: Added more services to the review
This commit is contained in:
parent
9ab4873613
commit
a593c13ac1
@ -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
|
||||
"""
|
Loading…
Reference in New Issue
Block a user