UPD: Added more services to the review
This commit is contained in:
parent
9ab4873613
commit
a593c13ac1
@ -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,36 +40,38 @@ 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')
|
print("Region", "AccountID", "InstanceId", "DaysStopped", sep=", ")
|
||||||
regions = getAllRegions(client)
|
|
||||||
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()
|
||||||
if len(response.get("Reservations")) > 0:
|
if len(response.get("Reservations")) > 0:
|
||||||
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")
|
print("Region", "AccountID", "InstanceId", "InstanceType", sep=", ")
|
||||||
client = boto3.client('ec2')
|
|
||||||
regions = getAllRegions(client)
|
|
||||||
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)
|
||||||
response = client.describe_instances()
|
response = client.describe_instances()
|
||||||
if len(response.get("Reservations")) > 0:
|
if len(response.get("Reservations")) > 0:
|
||||||
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)
|
||||||
response = client.describe_volumes(
|
response = client.describe_volumes(
|
||||||
@ -70,10 +83,25 @@ 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=", ")
|
||||||
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_volumes(
|
response = client.describe_volumes(
|
||||||
@ -89,5 +117,185 @@ 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
|
||||||
|
"""
|
Loading…
Reference in New Issue
Block a user