def audit_ec2(findings, region): json_blob = query_aws(region.account, "ec2-describe-instances", region) route_table_json = query_aws(region.account, "ec2-describe-route-tables", region) for reservation in json_blob.get("Reservations", []): for instance in reservation.get("Instances", []): if instance.get("State", {}).get("Name", "") == "terminated": # Ignore EC2's that are off continue # Check for old instances if instance.get("LaunchTime", "") != "": MAX_RESOURCE_AGE_DAYS = 365 collection_date = get_collection_date(region.account) launch_time = instance["LaunchTime"].split(".")[0] age_in_days = days_between(launch_time, collection_date) if age_in_days > MAX_RESOURCE_AGE_DAYS: findings.add( Finding( region, "EC2_OLD", instance["InstanceId"], resource_details={ "Age in days": age_in_days, "Name": get_name(instance, "InstanceId"), "Tags": instance.get("Tags", {}), }, )) # Check for EC2 Classic if "vpc" not in instance.get("VpcId", ""): findings.add( Finding(region, "EC2_CLASSIC", instance["InstanceId"])) if not instance.get("SourceDestCheck", True): route_to_instance = None for table in route_table_json["RouteTables"]: if table["VpcId"] == instance.get("VpcId", ""): for route in table["Routes"]: if route.get("InstanceId", "") == instance["InstanceId"]: route_to_instance = route break if route_to_instance is not None: break findings.add( Finding( region, "EC2_SOURCE_DEST_CHECK_OFF", instance["InstanceId"], resource_details={ "routes": route_to_instance, "Name": get_name(instance, "InstanceId"), "Tags": instance.get("Tags", {}), }, ))
def audit_users(findings, region): MAX_DAYS_SINCE_LAST_USAGE = 90 # TODO: Convert all of this into a table json_blob = query_aws(region.account, "iam-get-credential-report", region) csv_lines = json_blob["Content"].split("\n") collection_date = json_blob["GeneratedTime"] # Skip header csv_lines.pop(0) # Header: # user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed, # password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated, # access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service, # access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date, # access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated, # cert_2_active,cert_2_last_rotated for line in csv_lines: parts = line.split(",") user = { "user": parts[0], "arn": parts[1], "user_creation_time": parts[2], "password_enabled": parts[3], "password_last_used": parts[4], "password_last_changed": parts[5], "password_next_rotation": parts[6], "mfa_active": parts[7], "access_key_1_active": parts[8], "access_key_1_last_rotated": parts[9], "access_key_1_last_used_date": parts[10], "access_key_1_last_used_region": parts[11], "access_key_1_last_used_service": parts[12], "access_key_2_active": parts[13], "access_key_2_last_rotated": parts[14], "access_key_2_last_used_date": parts[15], "access_key_2_last_used_region": parts[16], "access_key_2_last_used_service": parts[17], "cert_1_active": parts[18], "cert_1_last_rotated": parts[19], "cert_2_active": parts[20], "cert_2_last_rotated": parts[21], } user_age = days_between(collection_date, user["user_creation_time"]) if user["password_enabled"] == "true": if user["mfa_active"] == "false": findings.add( Finding( region, "USER_WITH_PASSWORD_LOGIN_BUT_NO_MFA", user["user"], resource_details={ "Number of days since user was created": user_age }, )) if user["password_last_used"] == "no_information": findings.add( Finding( region, "USER_HAS_NEVER_LOGGED_IN", user["user"], resource_details={ "Number of days since user was created": user_age }, )) else: password_last_used_days = days_between( collection_date, user["password_last_used"]) if password_last_used_days > MAX_DAYS_SINCE_LAST_USAGE: findings.add( Finding( region, "USER_HAS_NOT_LOGGED_IN_FOR_OVER_MAX_DAYS", user["user"], resource_details={ "Number of days since user was created": user_age, "Number of days since last login": password_last_used_days, }, )) if (user["access_key_1_active"] == "true" and user["access_key_2_active"] == "true"): age_of_key1 = days_between(collection_date, user["access_key_1_last_rotated"]) age_of_key2 = days_between(collection_date, user["access_key_2_last_rotated"]) findings.add( Finding( region, "USER_HAS_TWO_ACCESS_KEYS", user["user"], resource_details={ "Number of days since key1 was rotated": age_of_key1, "Number of days since key2 was rotated": age_of_key2, }, )) if user["access_key_1_active"] == "true": age_of_key = days_between(collection_date, user["access_key_1_last_rotated"]) if user["access_key_1_last_used_date"] == "N/A": findings.add( Finding( region, "USER_HAS_UNUSED_ACCESS_KEY", user["user"], resource_details={ "Unused key": 1, "Number of days since key was rotated": age_of_key, }, )) else: days_since_key_use = days_between( collection_date, user["access_key_1_last_used_date"]) if days_since_key_use > MAX_DAYS_SINCE_LAST_USAGE: findings.add( Finding( region, "USER_HAS_NOT_USED_ACCESS_KEY_FOR_MAX_DAYS", user["user"], resource_details={ "Days since key 1 used:": days_since_key_use, "Number of days since key was rotated": age_of_key, }, )) if user["access_key_2_active"] == "true": age_of_key = days_between(collection_date, user["access_key_2_last_rotated"]) if user["access_key_2_last_used_date"] == "N/A": findings.add( Finding( region, "USER_HAS_UNUSED_ACCESS_KEY", user["user"], resource_details={ "Unused key": 2, "Number of days since key was rotated": age_of_key, }, )) else: days_since_key_use = days_between( collection_date, user["access_key_2_last_used_date"]) if days_since_key_use > MAX_DAYS_SINCE_LAST_USAGE: findings.add( Finding( region, "USER_HAS_NOT_USED_ACCESS_KEY_FOR_MAX_DAYS", user["user"], resource_details={ "Days since key 2 used:": days_since_key_use, "Number of days since key was rotated": age_of_key, }, ))