feat: experimenting with ThreadPoolExecutor

This commit is contained in:
xpk
2025-11-17 21:45:20 +08:00
parent db7d1ee205
commit e75abcf971
+95 -48
View File
@@ -18,6 +18,7 @@ from datetime import date
from mdutils.mdutils import MdUtils from mdutils.mdutils import MdUtils
import os import os
import json import json
import concurrent.futures
def printTitle(level: int, title: str): def printTitle(level: int, title: str):
@@ -40,7 +41,7 @@ def getAgeFromDate(inputDate):
def printResult(content: list, header: str): def printResult(content: list, header: str):
if len(content) <= 0: if len(content) <= 0:
mdFile.new_paragraph("👏 No issue found.") mdFile.new_paragraph(" No issue found.")
return return
header = "Item," + header header = "Item," + header
table = header.split(",") table = header.split(",")
@@ -53,19 +54,30 @@ def printResult(content: list, header: str):
return return
print("Script started. It may take 7+ minutes to run. Report will be saved to AwsReviewReport.md. Please be patient...") def botoSession(region:str) -> boto3.Session:
session = boto3.Session(region_name=region)
return session
# initialize md file output
mdFile = MdUtils(file_name='AwsReviewReport.md', title='Aws Review ' + str(date.today())) mdFile = MdUtils(file_name='AwsReviewReport.md', title='Aws Review ' + str(date.today()))
sts = boto3.client("sts")
aid = sts.get_caller_identity().get("Account")
client = boto3.client('ec2', region_name="us-east-1")
regions = getAllRegions(client)
printTitle(3, f"Primary region: {os.environ['AWS_DEFAULT_REGION']}\n")
mdFile.write("-" * 5) mdFile.write("-" * 5)
outTable = [] outTable = []
print("Script started. It may take 7+ minutes to run. Report will be saved to AwsReviewReport.md. Please be patient...")
printTitle(3, f"Primary region: {os.environ['AWS_DEFAULT_REGION']}\n")
# create sessions
globalSession = botoSession("us-east-1")
localSession = botoSession(os.environ['AWS_DEFAULT_REGION'])
# create clients
sts = globalSession.client("sts")
ec2Client = localSession.client("ec2")
aid = sts.get_caller_identity().get("Account")
regions = getAllRegions(ec2Client)
"""Check instances stopped for long time""" """Check instances stopped for long time"""
printTitle(1, "Ec2 service review") printTitle(1, "Ec2 service review")
printTitle(2, "[Cost Optimization] Instances stopped for over 14 days") printTitle(2, "[Cost Optimization] Instances stopped for over 14 days")
@@ -73,7 +85,8 @@ printTitle(3, "Consider backing up and terminate instances "
"or use AutoScalingGroup to spin up and down instances as needed.") "or use AutoScalingGroup to spin up and down instances as needed.")
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
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):
@@ -88,7 +101,8 @@ printTitle(3, "Consider requiring IDMSv2. For more information, "
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
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):
@@ -106,7 +120,8 @@ printTitle(2,"[Sustainability] Use of early generation instance type")
printTitle(3, "Consider using current generation instances") printTitle(3, "Consider using current generation instances")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
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):
@@ -125,7 +140,8 @@ printTitle(2, "[Cost Optimization] Unattached EBS volumes")
printTitle(3, "Consider backing up the volumes and delete them") printTitle(3, "Consider backing up the volumes and delete them")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
response = client.describe_volumes( response = client.describe_volumes(
Filters=[ Filters=[
{ {
@@ -143,7 +159,8 @@ printTitle(2, "[Cost Optimization] EBS snapshots more than 365 days old")
printTitle(3,"Consider removing snapshots if no longer needed") printTitle(3,"Consider removing snapshots if no longer needed")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
response = client.describe_snapshots( response = client.describe_snapshots(
OwnerIds=[aid] OwnerIds=[aid]
) )
@@ -163,7 +180,8 @@ printTitle(3, "Consider replacing volume with encrypted ones. "
"and snapshots afterwards.") "and snapshots afterwards.")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
response = client.describe_volumes( response = client.describe_volumes(
Filters=[ Filters=[
{ {
@@ -186,7 +204,8 @@ printTitle(3, "Consider deleting unused EIP")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
response = client.describe_addresses() response = client.describe_addresses()
for i in response.get("Addresses"): for i in response.get("Addresses"):
if i.get("AssociationId") is None: if i.get("AssociationId") is None:
@@ -200,7 +219,8 @@ printTitle(3, "Consider setting more restrictive rules allowing access from spec
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
response = client.describe_security_group_rules() response = client.describe_security_group_rules()
for sgr in jmespath.search("SecurityGroupRules[?IsEgress==`false`]", response): for sgr in jmespath.search("SecurityGroupRules[?IsEgress==`false`]", response):
if (not sgr.get("IsEgress") if (not sgr.get("IsEgress")
@@ -220,7 +240,8 @@ printTitle(3, "Consider encrypting RDS instances. For more detail, see "
"https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/encrypt-an-existing-amazon-rds-for-postgresql-db-instance.html") "https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/encrypt-an-existing-amazon-rds-for-postgresql-db-instance.html")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('rds', region_name=r) newSession = botoSession(region=r)
client = newSession.client("rds")
response = client.describe_db_instances() response = client.describe_db_instances()
for i in response.get("DBInstances"): for i in response.get("DBInstances"):
if i.get("StorageEncrypted") == "False": if i.get("StorageEncrypted") == "False":
@@ -236,7 +257,8 @@ printTitle(2, "[Reliability] RDS instance running in single availability zone")
printTitle(3, "Consider enabling multi-az for production use.") printTitle(3, "Consider enabling multi-az for production use.")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('rds', region_name=r) newSession = botoSession(region=r)
client = newSession.client("rds")
response = client.describe_db_instances() response = client.describe_db_instances()
for i in response.get("DBInstances"): for i in response.get("DBInstances"):
if not i.get("MultiAZ"): if not i.get("MultiAZ"):
@@ -247,13 +269,15 @@ for r in regions:
outTable.append([r, aid, i.get("DBClusterIdentifier"), i.get("Engine")]) outTable.append([r, aid, i.get("DBClusterIdentifier"), i.get("Engine")])
printResult(outTable, "Region, AccountID, DBIdentifier, Engine") printResult(outTable, "Region, AccountID, DBIdentifier, Engine")
"""Check outdated lambda runtime"""
printTitle(1, "Lambda service review") printTitle(1, "Lambda service review")
printTitle(2, "[Security] Outdated Lambda runtime") printTitle(2, "[Security] Outdated Lambda runtime")
printTitle(3, "Consider changing to currently supported Lambda runtime versions, " printTitle(3, "Consider changing to currently supported Lambda runtime versions, "
"listed on https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html") "listed on https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('lambda', region_name=r) newSession = botoSession(region=r)
client = newSession.client("lambda")
response = client.list_functions() response = client.list_functions()
for i in response.get("Functions"): for i in response.get("Functions"):
if i.get("Runtime") is not None: if i.get("Runtime") is not None:
@@ -267,7 +291,7 @@ printTitle(2, "[Security] Iam user access key not rotated for 180 days")
printTitle(3, "Consider rotating access key") printTitle(3, "Consider rotating access key")
outTable = [] outTable = []
client = boto3.client('iam', region_name="us-east-1") client = globalSession.client("iam")
listUsers = client.list_users() listUsers = client.list_users()
users = jmespath.search("Users[*].UserName", listUsers) users = jmespath.search("Users[*].UserName", listUsers)
for u in users: for u in users:
@@ -284,7 +308,7 @@ printTitle(3, "Consider granting minimum privileges "
"https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html") "https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html")
outTable = [] outTable = []
client = boto3.client('iam', region_name="us-east-1") client = globalSession.client("iam")
entityResp = client.list_entities_for_policy( entityResp = client.list_entities_for_policy(
PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess' PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'
) )
@@ -303,7 +327,7 @@ printTitle(3, "Typically these roles should not have admin or iam permissions.")
outTable = [] outTable = []
# Get a list of roles # Get a list of roles
client = boto3.client('lambda') client = localSession.client("lambda")
roles = set() roles = set()
paginator = client.get_paginator('list_functions') paginator = client.get_paginator('list_functions')
for page in paginator.paginate(): for page in paginator.paginate():
@@ -322,7 +346,7 @@ for page in paginator.paginate():
roles.add(role['RoleName']) roles.add(role['RoleName'])
# Need to remove non-existent roles # Need to remove non-existent roles
iam_client = boto3.client('iam') iam_client = globalSession.client("iam")
confirmed_roles = set() confirmed_roles = set()
for role in roles: for role in roles:
try: try:
@@ -351,7 +375,7 @@ high_risk_actions = {
} }
# Check inline policies for each role # Check inline policies for each role
client = boto3.client('iam', region_name="us-east-1") client = globalSession.client("iam")
for role in roles: for role in roles:
inline_policy_names = client.list_role_policies(RoleName=role)['PolicyNames'] inline_policy_names = client.list_role_policies(RoleName=role)['PolicyNames']
for policy_name in inline_policy_names: for policy_name in inline_policy_names:
@@ -402,7 +426,8 @@ printTitle(3, "Consider setting retention")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('logs', region_name=r) newSession = botoSession(region=r)
client = newSession.client("logs")
response = client.describe_log_groups() response = client.describe_log_groups()
for i in response.get("logGroups"): for i in response.get("logGroups"):
if i.get("retentionInDays") is None: if i.get("retentionInDays") is None:
@@ -415,7 +440,8 @@ printTitle(3, "Consider encrypting LogGroups")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('logs', region_name=r) newSession = botoSession(region=r)
client = newSession.client("logs")
response = client.describe_log_groups() response = client.describe_log_groups()
for i in response.get("logGroups"): for i in response.get("logGroups"):
if i.get("kmsKeyId") is None: if i.get("kmsKeyId") is None:
@@ -428,7 +454,8 @@ printTitle(2, "[Reliability] Ec2/Rds instances found but AWSBackup plan missing"
printTitle(3, "Consider setting up AWSBackup plans to backup AWS resources.") printTitle(3, "Consider setting up AWSBackup plans to backup AWS resources.")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('backup', region_name=r) newSession = botoSession(region=r)
client = newSession.client("backup")
response = client.list_backup_plans() response = client.list_backup_plans()
if len(response.get("BackupPlansList")) <= 0: if len(response.get("BackupPlansList")) <= 0:
ec2client = boto3.client("ec2", region_name=r) ec2client = boto3.client("ec2", region_name=r)
@@ -448,7 +475,7 @@ printTitle(2, "[Security] S3 bucket policy missing")
printTitle(3, "Consider creating bucket policy and restrict access to bucket") printTitle(3, "Consider creating bucket policy and restrict access to bucket")
outTable = [] outTable = []
client = boto3.client('s3', region_name="us-east-1") client = globalSession.client("s3")
response = client.list_buckets() response = client.list_buckets()
for i in jmespath.search("Buckets[*].Name", response): for i in jmespath.search("Buckets[*].Name", response):
try: try:
@@ -462,7 +489,7 @@ printTitle(2, "[Security] S3 public access block")
printTitle(3, "Blocking public access prevents accidental data leak due to misconfigurations in bucket policy or acl") printTitle(3, "Blocking public access prevents accidental data leak due to misconfigurations in bucket policy or acl")
# get account id # get account id
sts = boto3.client("sts") sts = globalSession.client("sts")
account_id = sts.get_caller_identity()["Account"] account_id = sts.get_caller_identity()["Account"]
s3control = boto3.client("s3control") s3control = boto3.client("s3control")
try: try:
@@ -482,7 +509,8 @@ printTitle(3, "Consider Graviton instances such as t4g/r7g to optimize your infr
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('elasticache', region_name=r) newSession = botoSession(region=r)
client = newSession.client("elasticache")
response = client.describe_cache_clusters() response = client.describe_cache_clusters()
for i in response.get("CacheClusters"): for i in response.get("CacheClusters"):
if re.search("[0-9]g.", i.get("CacheNodeType")) is None: if re.search("[0-9]g.", i.get("CacheNodeType")) is None:
@@ -496,7 +524,8 @@ printTitle(3, "Consider removing empty target groups")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('elbv2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("elbv2")
response = client.describe_target_groups() response = client.describe_target_groups()
for i in response.get("TargetGroups"): for i in response.get("TargetGroups"):
tgResp = client.describe_target_health(TargetGroupArn=i.get("TargetGroupArn")) tgResp = client.describe_target_health(TargetGroupArn=i.get("TargetGroupArn"))
@@ -513,7 +542,8 @@ printTitle(3, "Consider enabling auto key rotation. When a key is rotated, previ
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('kms', region_name=r) newSession = botoSession(region=r)
client = newSession.client("kms")
response = client.list_keys() response = client.list_keys()
for i in jmespath.search("Keys[*].KeyId", response): for i in jmespath.search("Keys[*].KeyId", response):
try: try:
@@ -538,7 +568,8 @@ printTitle(3, "Consider restricting access to private API with a "
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('apigateway', region_name=r) newSession = botoSession(region=r)
client = newSession.client("apigateway")
response = client.get_rest_apis() response = client.get_rest_apis()
for i in response.get("items"): for i in response.get("items"):
if "PRIVATE" in i.get("endpointConfiguration").get("types") and len(i.get("policy")) <= 0: if "PRIVATE" in i.get("endpointConfiguration").get("types") and len(i.get("policy")) <= 0:
@@ -552,7 +583,7 @@ printTitle(3, "Consider enabling encryption for cloudtrail")
outTable = [] outTable = []
#for r in regions: #for r in regions:
client = boto3.client('cloudtrail') client = localSession.client('cloudtrail')
response = client.describe_trails() response = client.describe_trails()
for i in response.get("trailList"): for i in response.get("trailList"):
if i.get("KmsKeyId") is None: if i.get("KmsKeyId") is None:
@@ -566,7 +597,8 @@ outTable = []
multiRegionTrailCount = 0 multiRegionTrailCount = 0
for r in regions: for r in regions:
client = boto3.client('cloudtrail', region_name=r) newSession = botoSession(region=r)
client = newSession.client("cloudtrail")
response = client.describe_trails() response = client.describe_trails()
for i in response.get("trailList"): for i in response.get("trailList"):
if i.get("IsMultiRegionTrail"): if i.get("IsMultiRegionTrail"):
@@ -583,7 +615,8 @@ printTitle(2, "[Security] VPC flow log not enabled")
printTitle(3, "Consider enabling VPC flowlog for audit purpose or delete the default VPCs if not in use") printTitle(3, "Consider enabling VPC flowlog for audit purpose or delete the default VPCs if not in use")
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
response = client.describe_vpcs() response = client.describe_vpcs()
vpc_ids = [vpc['VpcId'] for vpc in response['Vpcs']] vpc_ids = [vpc['VpcId'] for vpc in response['Vpcs']]
for vpc_id in vpc_ids: for vpc_id in vpc_ids:
@@ -607,7 +640,8 @@ printTitle(3, "Consider deleting the default VPCs if not in use")
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
response = client.describe_vpcs( response = client.describe_vpcs(
Filters=[{'Name': 'isDefault', 'Values': ['true']}] Filters=[{'Name': 'isDefault', 'Values': ['true']}]
) )
@@ -617,16 +651,26 @@ for r in regions:
printResult(outTable, "Region, AccountID, DefaultVpcId") printResult(outTable, "Region, AccountID, DefaultVpcId")
"""Check VPN endpoints""" """Check VPC endpoints"""
def check_vpc_endpoint(region: str):
fSession = botoSession(region=region)
fClient = fSession.client("ec2")
fResponse = fClient.describe_vpc_endpoints()
if len(fResponse['VpcEndpoints']) <= 0:
return [region, aid, "Vpc endpoint not found"]
else:
return None
printTitle(2, "[Security] Use of VPC endpoints") printTitle(2, "[Security] Use of VPC endpoints")
printTitle(3, "Consider deploying VPC endpoints and connect to AWS api endpoints privately") printTitle(3, "Consider deploying VPC endpoints and connect to AWS api endpoints privately")
outTable = [] outTable = []
for r in regions: with concurrent.futures.ThreadPoolExecutor() as executor:
client = boto3.client('ec2', region_name=r) # Map returns results in the same order as regions
response = client.describe_vpc_endpoints() results = executor.map(check_vpc_endpoint, regions)
if len(response['VpcEndpoints']) <= 0: for result in results:
outTable.append([r, aid, "Vpc endpoint not found"]) if result:
outTable.append(result)
printResult(outTable, "Region, AccountID, VpcEndpointCount") printResult(outTable, "Region, AccountID, VpcEndpointCount")
@@ -638,7 +682,8 @@ printTitle(3, "Consider having 2 tunnels for each site VPN connection. "
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('ec2', region_name=r) newSession = botoSession(region=r)
client = newSession.client("ec2")
response = client.describe_vpn_connections() response = client.describe_vpn_connections()
for i in response.get("VpnConnections"): for i in response.get("VpnConnections"):
if len(jmespath.search("Options.TunnelOptions[*].OutsideIpAddress", i)) < 2: if len(jmespath.search("Options.TunnelOptions[*].OutsideIpAddress", i)) < 2:
@@ -651,7 +696,7 @@ printTitle(2, "[Security] Cloudfront origins allow public access")
printTitle(3, "Your ALB is exposed to public, which bypass edge and WAF protection. Consider restricting access from Cloudfront only") printTitle(3, "Your ALB is exposed to public, which bypass edge and WAF protection. Consider restricting access from Cloudfront only")
outTable = [] outTable = []
client = boto3.client('elbv2') client = localSession.client("elbv2")
response = client.describe_load_balancers() response = client.describe_load_balancers()
alb_sgs = {} alb_sgs = {}
for alb in response['LoadBalancers']: for alb in response['LoadBalancers']:
@@ -686,7 +731,8 @@ printTitle(3, "Consider using AmazonLinux2023. "
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('eks', region_name=r) newSession = botoSession(region=r)
client = newSession.client("eks")
response = client.list_clusters() response = client.list_clusters()
for cluster in response.get("clusters"): for cluster in response.get("clusters"):
ngsResp = client.list_nodegroups(clusterName=cluster) ngsResp = client.list_nodegroups(clusterName=cluster)
@@ -708,7 +754,8 @@ printTitle(3, "Consider using upgrading Eks cluster. "
outTable = [] outTable = []
for r in regions: for r in regions:
client = boto3.client('eks', region_name=r) newSession = botoSession(region=r)
client = newSession.client("eks")
response = client.list_clusters() response = client.list_clusters()
for cluster in response.get("clusters"): for cluster in response.get("clusters"):
clusterResp = client.describe_cluster(name=cluster) clusterResp = client.describe_cluster(name=cluster)