480 lines
20 KiB
Python
480 lines
20 KiB
Python
|
#!/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
|
||
|
from datetime import date
|
||
|
from mdutils.mdutils import MdUtils
|
||
|
|
||
|
|
||
|
def printTitle(level: int, title: str):
|
||
|
if level <= 2:
|
||
|
mdFile.new_header(level=level, title=title)
|
||
|
else:
|
||
|
mdFile.new_paragraph(title)
|
||
|
return
|
||
|
|
||
|
|
||
|
def getAllRegions(myclient):
|
||
|
return jmespath.search("Regions[*].RegionName", myclient.describe_regions(AllRegions=False))
|
||
|
|
||
|
|
||
|
def getAgeFromDate(inputDate):
|
||
|
today = date.today()
|
||
|
delta = today - inputDate.date()
|
||
|
return delta.days
|
||
|
|
||
|
|
||
|
def printResult(content: list, header: str):
|
||
|
if len(content) <= 0:
|
||
|
mdFile.new_paragraph("👏 No issue found.")
|
||
|
return
|
||
|
header = "Item," + header
|
||
|
table = header.split(",")
|
||
|
tableCol = len(table)
|
||
|
for count, row in enumerate(content):
|
||
|
row.insert(0, count+1)
|
||
|
table.extend(row)
|
||
|
mdFile.new_line()
|
||
|
mdFile.new_table(columns=tableCol, rows=len(content)+1, text=table, text_align='left')
|
||
|
return
|
||
|
|
||
|
|
||
|
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)
|
||
|
|
||
|
mdFile.write("-" * 5)
|
||
|
|
||
|
printTitle(1, "Ec2 service review")
|
||
|
printTitle(2, "[Cost Optimization] Instances stopped for over 14 days")
|
||
|
printTitle(3, "Consider backing up and terminate instances "
|
||
|
"or use AutoScalingGroup to spin up and down instances as needed.")
|
||
|
outTable = []
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('ec2', region_name=r)
|
||
|
response = client.describe_instances()
|
||
|
if len(response.get("Reservations")) > 0:
|
||
|
for i in jmespath.search("Reservations[*].Instances[*]", response):
|
||
|
if i[0].get("State").get("Name") == "stopped":
|
||
|
outTable.append([r, aid, i[0].get("InstanceId"), getAgeFromDate(i[0].get("UsageOperationUpdateTime"))])
|
||
|
printResult(outTable, "Region, AccountID, InstanceId, DaysStopped")
|
||
|
|
||
|
printTitle(2, "[Security] Insecure IDMSv1 allowed")
|
||
|
printTitle(3, "Consider requiring IDMSv2. For more information, "
|
||
|
"see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html")
|
||
|
outTable = []
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('ec2', region_name=r)
|
||
|
response = client.describe_instances()
|
||
|
if len(response.get("Reservations")) > 0:
|
||
|
for i in jmespath.search("Reservations[*].Instances[*]", response):
|
||
|
if i[0].get("MetadataOptions").get("HttpTokens") == "optional":
|
||
|
outTable.append([r, aid, i[0].get("InstanceId"), i[0].get("MetadataOptions").get("HttpTokens")])
|
||
|
printResult(outTable, "Region, AccountID, InstanceId, IDMSv2")
|
||
|
|
||
|
printTitle(2,"[Sustainability] Use of early generation instance type")
|
||
|
printTitle(3, "Consider using current generation instances")
|
||
|
outTable = []
|
||
|
for r in regions:
|
||
|
client = boto3.client('ec2', region_name=r)
|
||
|
response = client.describe_instances()
|
||
|
if len(response.get("Reservations")) > 0:
|
||
|
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:
|
||
|
outTable.append([r, aid, i[0].get("InstanceId"), i[0].get("InstanceType")])
|
||
|
printResult(outTable, "Region, AccountID, InstanceId, InstanceType")
|
||
|
|
||
|
printTitle(2, "[Cost Optimization] Unattached EBS volumes")
|
||
|
printTitle(3, "Consider backing up the volumes and delete them")
|
||
|
outTable = []
|
||
|
for r in regions:
|
||
|
client = boto3.client('ec2', region_name=r)
|
||
|
response = client.describe_volumes(
|
||
|
Filters=[
|
||
|
{
|
||
|
'Name': 'status',
|
||
|
'Values': ['available']
|
||
|
}
|
||
|
]
|
||
|
)
|
||
|
for i in response.get("Volumes"):
|
||
|
outTable.append([r, aid, i.get("VolumeId"), i.get("Size"), i.get("VolumeType")])
|
||
|
printResult(outTable, "Region, AccountID, VolumeId, Size, VolumeType")
|
||
|
|
||
|
printTitle(2, "[Cost Optimization] EBS snapshots more than 365 days old")
|
||
|
printTitle(3,"Consider removing snapshots if no longer needed")
|
||
|
outTable = []
|
||
|
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.":
|
||
|
outTable.append(
|
||
|
[r, aid, i.get("SnapshotId"), i.get("Description")[:70], getAgeFromDate(i.get("StartTime"))])
|
||
|
printResult(outTable, "Region, AccountID, SnapshotId, Description, SnapshotAge")
|
||
|
|
||
|
printTitle(2, "[Security] Unencrypted EBS volumes")
|
||
|
printTitle(3, "Consider replacing volume with encrypted ones. "
|
||
|
"One can do so by stopping the Ec2 instance, creating snapshot for the unencrypted volume, "
|
||
|
"copy the snapshot to a new encrypted snapshot, create a volume from the encrypted snapshot,"
|
||
|
"detach the original volume and attach the encrypted volume. Remember to clean up the volumes"
|
||
|
"and snapshots afterwards.")
|
||
|
outTable = []
|
||
|
for r in regions:
|
||
|
client = boto3.client('ec2', region_name=r)
|
||
|
response = client.describe_volumes(
|
||
|
Filters=[
|
||
|
{
|
||
|
'Name': 'encrypted',
|
||
|
'Values': ['false']
|
||
|
},
|
||
|
{
|
||
|
'Name': 'status',
|
||
|
'Values': ['in-use']
|
||
|
}
|
||
|
]
|
||
|
)
|
||
|
for i in response.get("Volumes"):
|
||
|
outTable.append([r, aid, i.get("VolumeId"), i.get("Size"), i.get("VolumeType")])
|
||
|
printResult(outTable, "Region, AccountID, VolumeId, Size, VolumeType")
|
||
|
|
||
|
printTitle(2, "[Cost Optimization] Unused Elastic IP")
|
||
|
printTitle(3, "Consider deleting unused EIP")
|
||
|
outTable = []
|
||
|
|
||
|
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:
|
||
|
outTable.append([r, aid, i.get("PublicIp")])
|
||
|
printResult(outTable, "Region, AccountID, PublicIp")
|
||
|
|
||
|
printTitle(1, "Security group review")
|
||
|
printTitle(2, "[Security] Security group rules allowing ingress from 0.0.0.0/0")
|
||
|
printTitle(3, "Consider setting more restrictive rules allowing access from specific sources.")
|
||
|
outTable = []
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('ec2', region_name=r)
|
||
|
response = client.describe_security_group_rules()
|
||
|
for sgr in jmespath.search("SecurityGroupRules[?IsEgress==`false`]", response):
|
||
|
if (not sgr.get("IsEgress")
|
||
|
and sgr.get("CidrIpv4") == "0.0.0.0/0"
|
||
|
and sgr.get("FromPort") != 443
|
||
|
and sgr.get("ToPort") != 443
|
||
|
and sgr.get("FromPort") != 80
|
||
|
and sgr.get("ToPort") != 80):
|
||
|
outTable.append(
|
||
|
[r, aid, sgr.get("GroupId"), sgr.get("SecurityGroupRuleId"), sgr.get("FromPort"), sgr.get("ToPort")])
|
||
|
printResult(outTable, "Region, AccountID, SecurityGroup, Rule, FromPort, ToPort")
|
||
|
|
||
|
printTitle(1, "Rds service review")
|
||
|
printTitle(2, "[Security] Unencrypted RDS instances")
|
||
|
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")
|
||
|
outTable = []
|
||
|
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":
|
||
|
outTable.append([r, aid, i.get("DBInstanceIdentifier"), i.get("Engine")])
|
||
|
response = client.describe_db_clusters()
|
||
|
for i in response.get("DBClusters"):
|
||
|
if i.get("StorageEncrypted") == "False":
|
||
|
outTable.append([r, aid, i.get("DBClusterIdentifier"), i.get("Engine")])
|
||
|
printResult(outTable, "Region, AccountID, DBIdentifier, Engine")
|
||
|
|
||
|
printTitle(2, "[Reliability] RDS instance running in single availability zone")
|
||
|
printTitle(3, "Consider enabling multi-az for production use.")
|
||
|
outTable = []
|
||
|
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"):
|
||
|
outTable.append([r, aid, i.get("DBInstanceIdentifier"), i.get("Engine")])
|
||
|
response = client.describe_db_clusters()
|
||
|
for i in response.get("DBClusters"):
|
||
|
if not i.get("MultiAZ"):
|
||
|
outTable.append([r, aid, i.get("DBClusterIdentifier"), i.get("Engine")])
|
||
|
printResult(outTable, "Region, AccountID, DBIdentifier, Engine")
|
||
|
|
||
|
printTitle(1, "Lambda service review")
|
||
|
printTitle(2, "[Security] Outdated Lambda runtime")
|
||
|
printTitle(3, "Consider changing to currently supported Lambda runtime versions, "
|
||
|
"listed on https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html")
|
||
|
outTable = []
|
||
|
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:
|
||
|
outTable.append([r, aid, i.get("FunctionName"), i.get("Runtime")])
|
||
|
printResult(outTable, "Region, AccountID, FunctionName, Runtime")
|
||
|
|
||
|
printTitle(1, "Iam service review")
|
||
|
printTitle(2, "[Security] Iam user access key not rotated for 180 days")
|
||
|
printTitle(3, "Consider rotating access key")
|
||
|
outTable = []
|
||
|
|
||
|
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:
|
||
|
outTable.append([aid, u, i.get("AccessKeyId"), getAgeFromDate(i.get("CreateDate"))])
|
||
|
printResult(outTable, "AccountID, UserName, AccessKeyId, AccessKeyAge")
|
||
|
|
||
|
printTitle(2, "[Security] Iam AdministratorAccess policy attached")
|
||
|
printTitle(3, "Consider granting minimum privileges "
|
||
|
"to users/groups/roles. AWS managed policies for job functions are recommended. See "
|
||
|
"https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html")
|
||
|
outTable = []
|
||
|
|
||
|
client = boto3.client('iam', region_name="us-east-1")
|
||
|
entityResp = client.list_entities_for_policy(
|
||
|
PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'
|
||
|
)
|
||
|
for group in jmespath.search("PolicyGroups[*].GroupName", entityResp):
|
||
|
outTable.append([aid, "Group", group])
|
||
|
for user in jmespath.search("PolicyUsers[*].UserName", entityResp):
|
||
|
outTable.append([aid, "User", user])
|
||
|
for role in jmespath.search("PolicyRoles[*].RoleName", entityResp):
|
||
|
outTable.append([aid, "Role", role])
|
||
|
printResult(outTable, "AccountID, Type, Name")
|
||
|
|
||
|
printTitle(1, "Cloudwatch service review")
|
||
|
printTitle(2, "[Cost Optimization] Cloudwatch LogGroups without retention period")
|
||
|
printTitle(3, "Consider setting retention")
|
||
|
outTable = []
|
||
|
|
||
|
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:
|
||
|
outTable.append([r, aid, i.get("logGroupName"), int(round(i.get("storedBytes") / 1024 / 1024, 0))])
|
||
|
printResult(outTable, "Region, AccountID, LogGroup, SizeMiB")
|
||
|
|
||
|
printTitle(2, "[Security] Cloudwatch LogGroups unencrypted")
|
||
|
printTitle(3, "Consider encrypting LogGroups")
|
||
|
outTable = []
|
||
|
|
||
|
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:
|
||
|
outTable.append([r, aid, i.get("logGroupName")])
|
||
|
printResult(outTable, "Region, AccountID, LogGroup")
|
||
|
|
||
|
printTitle(1, "Backup service review")
|
||
|
printTitle(2, "[Reliability] Ec2/Rds instances found but AWSBackup plan missing")
|
||
|
printTitle(3, "Consider setting up AWSBackup plans to backup AWS resources.")
|
||
|
outTable = []
|
||
|
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:
|
||
|
outTable.append([r, aid, "AWSBackup plan missing", instanceCount])
|
||
|
printResult(outTable, "Region, AccountID, BackupPlan, Ec2RdsInstances")
|
||
|
|
||
|
printTitle(1, "S3 service review")
|
||
|
printTitle(2, "[Security] S3 bucket policy missing")
|
||
|
printTitle(3, "Consider creating bucket policy and restrict access to bucket")
|
||
|
outTable = []
|
||
|
|
||
|
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:
|
||
|
outTable.append([aid, i])
|
||
|
printResult(outTable, "AccountID, BucketName")
|
||
|
|
||
|
printTitle(1, "ElastiCache review")
|
||
|
printTitle(2, "[Sustainability] ElastiCache instances on x64 platform")
|
||
|
printTitle(3, "Consider Graviton instances such as t4g/r7g to optimize your infrastructure investment.")
|
||
|
outTable = []
|
||
|
|
||
|
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:
|
||
|
outTable.append([r, aid, i.get("CacheClusterId"), i.get("CacheNodeType")])
|
||
|
printResult(outTable, "Region, AccountID, CacheClusterId, CacheNodeType")
|
||
|
|
||
|
printTitle(1, "LoadBalancer service review")
|
||
|
printTitle(2, "[Cost Optimization] LB Target group without targets")
|
||
|
printTitle(3, "Consider removing empty target groups")
|
||
|
outTable = []
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('elbv2', region_name=r)
|
||
|
response = client.describe_target_groups()
|
||
|
for i in response.get("TargetGroups"):
|
||
|
tgResp = client.describe_target_health(TargetGroupArn=i.get("TargetGroupArn"))
|
||
|
if len(jmespath.search("TargetHealthDescriptions[*].Target", tgResp)) == 0:
|
||
|
outTable.append([r, aid, i.get("TargetGroupName")])
|
||
|
printResult(outTable, "Region, AccountID, TargetGroup")
|
||
|
|
||
|
printTitle(1, "KMS service review")
|
||
|
printTitle(2, "[Security] Customer Managed Keys do not have auto rotation enabled")
|
||
|
printTitle(3, "Consider enabling auto key rotation. When a key is rotated, previous ones "
|
||
|
"are still kept within AWS to allow data retrival.")
|
||
|
outTable = []
|
||
|
|
||
|
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":
|
||
|
outTable.append([r, aid, i])
|
||
|
except:
|
||
|
pass
|
||
|
printResult(outTable, "Region, AccountID, KeyId")
|
||
|
|
||
|
printTitle(1, "ApiGateway service review")
|
||
|
printTitle(2, "[Security] ApiGateway resource policy missing")
|
||
|
printTitle(3, "Consider restricting access to private API with a "
|
||
|
"policy. Private Api should be accessed through Vpc endpoint and a policy ensures the Api cannot "
|
||
|
"be accessed otherwise. For more detail, see "
|
||
|
"https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-resource-policies-examples.html")
|
||
|
outTable = []
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('apigateway', region_name=r)
|
||
|
response = client.get_rest_apis()
|
||
|
for i in response.get("items"):
|
||
|
if "PRIVATE" in i.get("endpointConfiguration").get("types") and len(i.get("policy")) <= 0:
|
||
|
outTable.append([r, aid, i.get("name")])
|
||
|
printResult(outTable, "Region, AccountID, PrivateApiName")
|
||
|
|
||
|
printTitle(1, "Cloudtrail service review")
|
||
|
printTitle(2, "[Security] Cloudtrail not encrypted")
|
||
|
printTitle(3, "Consider enabling encryption for cloudtrail")
|
||
|
outTable = []
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('cloudtrail', region_name=r)
|
||
|
response = client.describe_trails()
|
||
|
for i in response.get("trailList"):
|
||
|
if i.get("KmsKeyId") is None:
|
||
|
outTable.append([r, aid, i.get("Name")])
|
||
|
printResult(outTable, "Region, AccountID, Trail")
|
||
|
|
||
|
printTitle(2, "[Security] Multi-Region cloudtrail not enabled")
|
||
|
printTitle(3, "Consider enabling Multi-Region for at least 1 cloudtrail")
|
||
|
outTable = []
|
||
|
multiRegionTrailCount = 0
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('cloudtrail', region_name=r)
|
||
|
response = client.describe_trails()
|
||
|
for i in response.get("trailList"):
|
||
|
if i.get("IsMultiRegionTrail"):
|
||
|
multiRegionTrailCount += 1
|
||
|
|
||
|
if multiRegionTrailCount <= 0:
|
||
|
outTable.append([r, aid, "Missing multi region trail"])
|
||
|
printResult(outTable, "Region, AccountID, Status")
|
||
|
|
||
|
printTitle(1, "Vpc service review")
|
||
|
printTitle(2, "[Reliability] Insufficient VPN tunnels")
|
||
|
printTitle(3, "Consider having 2 tunnels for each site VPN connection. "
|
||
|
"AWS performs VPN tunnel endpoint maintenance rather frequently. Having 2 tunnel reduces the risk "
|
||
|
"of service interruption.")
|
||
|
outTable = []
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('ec2', region_name=r)
|
||
|
response = client.describe_vpn_connections()
|
||
|
for i in response.get("VpnConnections"):
|
||
|
if len(jmespath.search("Options.TunnelOptions[*].OutsideIpAddress", i)) < 2:
|
||
|
outTable.append([r, aid, i.get("VpnConnectionId"),
|
||
|
len(jmespath.search("Options.TunnelOptions[*].OutsideIpAddress", i))])
|
||
|
printResult(outTable, "Region, AccountID, VpnConnection, TunnelCount")
|
||
|
|
||
|
printTitle(1, "Eks service review")
|
||
|
printTitle(2, "[Sustainability] Eks node running on AmazonLinux2 (AL2)")
|
||
|
printTitle(3, "Consider using AmazonLinux2023. "
|
||
|
"AL2's end of life date is 2025-06-30. AmazonLinux2023 runs on newer kernel and libraries, "
|
||
|
"which offers better performance and security.")
|
||
|
outTable = []
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('eks', region_name=r)
|
||
|
response = client.list_clusters()
|
||
|
for cluster in response.get("clusters"):
|
||
|
ngsResp = client.list_nodegroups(clusterName=cluster)
|
||
|
for ng in ngsResp.get("nodegroups"):
|
||
|
ngResp = client.describe_nodegroup(
|
||
|
clusterName=cluster,
|
||
|
nodegroupName=ng
|
||
|
)
|
||
|
if re.search("^AL2_", ngResp.get("nodegroup").get("amiType")):
|
||
|
outTable.append([r, aid, cluster, ng, ngResp.get("nodegroup").get("amiType")])
|
||
|
printResult(outTable, "Region, AccountID, Cluster, NodeGroup, AmiType")
|
||
|
|
||
|
printTitle(2, "[Sustainability] Eks control plane version outdated")
|
||
|
printTitle(3, "Consider using upgrading Eks cluster. "
|
||
|
"Reference https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html for a list "
|
||
|
"of current versions. Reference https://docs.aws.amazon.com/eks/latest/userguide/update-cluster.html "
|
||
|
"for upgrade instructions.")
|
||
|
outTable = []
|
||
|
|
||
|
for r in regions:
|
||
|
client = boto3.client('eks', region_name=r)
|
||
|
response = client.list_clusters()
|
||
|
for cluster in response.get("clusters"):
|
||
|
clusterResp = client.describe_cluster(name=cluster)
|
||
|
if float(jmespath.search("cluster.version", clusterResp)) < 1.28:
|
||
|
outTable.append([r, aid, cluster, clusterResp.get("cluster").get("version")])
|
||
|
printResult(outTable, "Region, AccountID, Cluster, Version")
|
||
|
|
||
|
mdFile.create_md_file()
|
||
|
print("Report written to AwsReviewReport.md")
|
||
|
|
||
|
# TODO
|
||
|
"""
|
||
|
- config enabled for all regions
|
||
|
"""
|