feat(tags): add resource tags to E services (#2007)

Co-authored-by: Pepe Fagoaga <pepe@verica.io>
This commit is contained in:
Sergio Garcia
2023-03-02 13:55:26 +01:00
committed by GitHub
parent cd8770a3e3
commit 76bb418ea9
78 changed files with 321 additions and 230 deletions

View File

@@ -10,6 +10,7 @@ class ec2_ami_public(Check):
report.region = image.region
report.resource_id = image.id
report.resource_arn = image.arn
report.resource_tags = image.tags
report.status = "PASS"
report.status_extended = f"EC2 AMI {image.id} is not public."
if image.public:

View File

@@ -9,6 +9,7 @@ class ec2_ebs_public_snapshot(Check):
report = Check_Report_AWS(self.metadata())
report.region = snapshot.region
report.resource_arn = snapshot.arn
report.resource_tags = snapshot.tags
if not snapshot.public:
report.status = "PASS"
report.status_extended = f"EBS Snapshot {snapshot.id} is not Public."

View File

@@ -9,6 +9,7 @@ class ec2_ebs_snapshots_encrypted(Check):
report = Check_Report_AWS(self.metadata())
report.region = snapshot.region
report.resource_arn = snapshot.arn
report.resource_tags = snapshot.tags
if snapshot.encrypted:
report.status = "PASS"
report.status_extended = f"EBS Snapshot {snapshot.id} is encrypted."

View File

@@ -10,6 +10,7 @@ class ec2_ebs_volume_encryption(Check):
report.region = volume.region
report.resource_id = volume.id
report.resource_arn = volume.arn
report.resource_tags = volume.tags
if volume.encrypted:
report.status = "PASS"
report.status_extended = f"EBS Snapshot {volume.id} is encrypted."

View File

@@ -16,6 +16,7 @@ class ec2_elastic_ip_shodan(Check):
report = Check_Report_AWS(self.metadata())
report.region = eip.region
report.resource_arn = eip.arn
report.resource_tags = eip.tags
if eip.public_ip:
try:
shodan_info = api.host(eip.public_ip)

View File

@@ -11,6 +11,7 @@ class ec2_elastic_ip_unassgined(Check):
if eip.public_ip:
report.resource_id = eip.public_ip
report.resource_arn = eip.arn
report.resource_tags = eip.tags
report.status = "FAIL"
report.status_extended = f"Elastic IP {eip.public_ip} is not associated with an instance or network interface."
if eip.association_id:

View File

@@ -10,6 +10,7 @@ class ec2_instance_imdsv2_enabled(Check):
report.region = instance.region
report.resource_id = instance.id
report.resource_arn = instance.arn
report.resource_tags = instance.tags
report.status = "FAIL"
report.status_extended = (
f"EC2 Instance {instance.id} has IMDSv2 disabled or not required."

View File

@@ -10,6 +10,7 @@ class ec2_instance_internet_facing_with_instance_profile(Check):
report.region = instance.region
report.resource_id = instance.id
report.resource_arn = instance.arn
report.resource_tags = instance.tags
report.status = "PASS"
report.status_extended = f"EC2 Instance {instance.id} is not internet facing with an instance profile."
if instance.public_ip and instance.instance_profile:

View File

@@ -10,6 +10,7 @@ class ec2_instance_managed_by_ssm(Check):
report = Check_Report_AWS(self.metadata())
report.region = instance.region
report.resource_arn = instance.arn
report.resource_tags = instance.tags
if not ssm_client.managed_instances.get(instance.id):
report.status = "FAIL"
report.status_extended = (

View File

@@ -14,6 +14,7 @@ class ec2_instance_older_than_specific_days(Check):
report.region = instance.region
report.resource_id = instance.id
report.resource_arn = instance.arn
report.resource_tags = instance.tags
report.status = "PASS"
report.status_extended = f"EC2 Instance {instance.id} is not running."
if instance.state == "running":

View File

@@ -10,6 +10,7 @@ class ec2_instance_profile_attached(Check):
report.region = instance.region
report.resource_id = instance.id
report.resource_arn = instance.arn
report.resource_tags = instance.tags
report.status = "FAIL"
report.status_extended = f"EC2 Instance {instance.id} not associated with an Instance Profile Role."
if instance.instance_profile:

View File

@@ -9,6 +9,7 @@ class ec2_instance_public_ip(Check):
report = Check_Report_AWS(self.metadata())
report.region = instance.region
report.resource_arn = instance.arn
report.resource_tags = instance.tags
if instance.public_ip:
report.status = "FAIL"
report.status_extended = f"EC2 Instance {instance.id} has a Public IP: {instance.public_ip} ({instance.public_dns})."

View File

@@ -18,7 +18,7 @@ class ec2_instance_secrets_user_data(Check):
report.region = instance.region
report.resource_id = instance.id
report.resource_arn = instance.arn
report.resource_tags = instance.tags
if instance.user_data:
temp_user_data_file = tempfile.NamedTemporaryFile(delete=False)
user_data = b64decode(instance.user_data)

View File

@@ -13,6 +13,7 @@ class ec2_networkacl_allow_ingress_any_port(Check):
report.region = network_acl.region
report.resource_id = network_acl.id
report.resource_arn = network_acl.arn
report.resource_tags = network_acl.tags
# If some entry allows it, that ACL is not securely configured
if not check_network_acl(network_acl.entries, tcp_protocol, check_port):
report.status = "PASS"

View File

@@ -12,6 +12,7 @@ class ec2_networkacl_allow_ingress_tcp_port_22(Check):
report = Check_Report_AWS(self.metadata())
report.region = network_acl.region
report.resource_arn = network_acl.arn
report.resource_tags = network_acl.tags
# If some entry allows it, that ACL is not securely configured
if not check_network_acl(network_acl.entries, tcp_protocol, check_port):
report.status = "PASS"

View File

@@ -12,6 +12,7 @@ class ec2_networkacl_allow_ingress_tcp_port_3389(Check):
report = Check_Report_AWS(self.metadata())
report.region = network_acl.region
report.resource_arn = network_acl.arn
report.resource_tags = network_acl.tags
# If some entry allows it, that ACL is not securely configured
if not check_network_acl(network_acl.entries, tcp_protocol, check_port):
report.status = "PASS"

View File

@@ -13,6 +13,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_any_port(Check):
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not all ports open to the Internet."
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
# Loop through every security group's ingress rule and check it
for ingress_rule in security_group.ingress_rules:
if check_security_group(ingress_rule, "-1", any_address=True):

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018(
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not MongoDB ports 27017 and 27018 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -14,6 +14,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21(Check)
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not FTP ports 20 and 21 open to the Internet."
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
# Loop through every security group's ingress rule and check it
for ingress_rule in security_group.ingress_rules:
if check_security_group(

View File

@@ -14,6 +14,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22(Check):
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not SSH port 22 open to the Internet."
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
# Loop through every security group's ingress rule and check it
for ingress_rule in security_group.ingress_rules:
if check_security_group(

View File

@@ -14,6 +14,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389(Check):
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Microsoft RDP port 3389 open to the Internet."
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
# Loop through every security group's ingress rule and check it
for ingress_rule in security_group.ingress_rules:
if check_security_group(

View File

@@ -14,6 +14,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_cassandra_7199_9
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Casandra ports 7199, 8888 and 9160 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -14,6 +14,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_elasticsearch_ki
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Elasticsearch/Kibana ports 9200, 9300 and 5601 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_kafka_9092(Check
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Kafka port 9092 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_memcached_11211(
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Memcached port 11211 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_mysql_3306(Check
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not MySQL port 3306 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_oracle_1521_2483
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Oracle ports 1521 and 2483 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_postgres_5432(Ch
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Postgres port 5432 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_redis_6379(Check
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Redis port 6379 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -14,6 +14,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_sql_server_1433_
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Microsoft SQL Server ports 1433 and 1434 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_telnet_23(Check)
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has not Telnet port 23 open to the Internet."
# Loop through every security group's ingress rule and check it

View File

@@ -13,6 +13,7 @@ class ec2_securitygroup_allow_wide_open_public_ipv4(Check):
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has no potential wide-open non-RFC1918 address."
# Loop through every security group's ingress rule and check it

View File

@@ -11,6 +11,7 @@ class ec2_securitygroup_default_restrict_traffic(Check):
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
# Find default security group
if security_group.name == "default":
report.status = "PASS"

View File

@@ -10,6 +10,7 @@ class ec2_securitygroup_from_launch_wizard(Check):
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) was not created using the EC2 Launch Wizard."
if "launch-wizard" in security_group.name:

View File

@@ -11,6 +11,7 @@ class ec2_securitygroup_in_use_without_ingress_filtering(Check):
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has ingress filtering."
for ingress_rule in security_group.ingress_rules:

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_not_used(Check):
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) it is being used."
if len(security_group.network_interfaces) == 0:

View File

@@ -12,6 +12,7 @@ class ec2_securitygroup_with_many_ingress_egress_rules(Check):
report.region = security_group.region
report.resource_id = security_group.id
report.resource_arn = security_group.arn
report.resource_tags = security_group.tags
report.status = "PASS"
report.status_extended = f"Security group {security_group.name} ({security_group.id}) has {len(security_group.ingress_rules)} inbound rules and {len(security_group.egress_rules)} outbound rules"
if (

View File

@@ -1,7 +1,9 @@
import threading
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from botocore.client import ClientError
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
@@ -86,20 +88,21 @@ class EC2:
self.instances.append(
Instance(
instance["InstanceId"],
arn,
instance["State"]["Name"],
regional_client.region,
instance["InstanceType"],
instance["ImageId"],
instance["LaunchTime"],
instance["PrivateDnsName"],
private_ip,
public_dns,
public_ip,
http_tokens,
http_endpoint,
instance_profile,
id=instance["InstanceId"],
arn=arn,
state=instance["State"]["Name"],
region=regional_client.region,
type=instance["InstanceType"],
image_id=instance["ImageId"],
launch_time=instance["LaunchTime"],
private_dns=instance["PrivateDnsName"],
private_ip=private_ip,
public_dns=public_dns,
public_ip=public_ip,
http_tokens=http_tokens,
http_endpoint=http_endpoint,
instance_profile=instance_profile,
tags=instance.get("Tags"),
)
)
except Exception as error:
@@ -121,12 +124,13 @@ class EC2:
):
self.security_groups.append(
SecurityGroup(
sg["GroupName"],
arn,
regional_client.region,
sg["GroupId"],
sg["IpPermissions"],
sg["IpPermissionsEgress"],
name=sg["GroupName"],
arn=arn,
region=regional_client.region,
id=sg["GroupId"],
ingress_rules=sg["IpPermissions"],
egress_rules=sg["IpPermissionsEgress"],
tags=sg.get("Tags"),
)
)
except Exception as error:
@@ -148,10 +152,11 @@ class EC2:
):
self.network_acls.append(
NetworkACL(
nacl["NetworkAclId"],
arn,
regional_client.region,
nacl["Entries"],
id=nacl["NetworkAclId"],
arn=arn,
region=regional_client.region,
entries=nacl["Entries"],
tags=nacl.get("Tags"),
)
)
except Exception as error:
@@ -176,10 +181,11 @@ class EC2:
encrypted = True
self.snapshots.append(
Snapshot(
snapshot["SnapshotId"],
arn,
regional_client.region,
encrypted,
id=snapshot["SnapshotId"],
arn=arn,
region=regional_client.region,
encrypted=encrypted,
tags=snapshot.get("Tags"),
)
)
except Exception as error:
@@ -264,11 +270,12 @@ class EC2:
public = True
self.images.append(
Image(
image["ImageId"],
arn,
image["Name"],
public,
regional_client.region,
id=image["ImageId"],
arn=arn,
name=image["Name"],
public=public,
region=regional_client.region,
tags=image.get("Tags"),
)
)
except Exception as error:
@@ -290,10 +297,11 @@ class EC2:
):
self.volumes.append(
Volume(
volume["VolumeId"],
arn,
regional_client.region,
volume["Encrypted"],
id=volume["VolumeId"],
arn=arn,
region=regional_client.region,
encrypted=volume["Encrypted"],
tags=volume.get("Tags"),
)
)
except Exception as error:
@@ -320,11 +328,12 @@ class EC2:
):
self.elastic_ips.append(
ElasticIP(
public_ip,
association_id,
allocation_id,
elastic_ip_arn,
regional_client.region,
public_ip=public_ip,
association_id=association_id,
allocation_id=allocation_id,
arn=elastic_ip_arn,
region=regional_client.region,
tags=address.get("Tags"),
)
)
except Exception as error:
@@ -337,10 +346,10 @@ class EC2:
try:
self.ebs_encryption_by_default.append(
EbsEncryptionByDefault(
regional_client.get_ebs_encryption_by_default()[
status=regional_client.get_ebs_encryption_by_default()[
"EbsEncryptionByDefault"
],
regional_client.region,
region=regional_client.region,
)
)
except Exception as error:
@@ -349,159 +358,79 @@ class EC2:
)
@dataclass
class Instance:
class Instance(BaseModel):
id: str
arn: str
state: str
region: str
type: str
image_id: str
launch_time: str
launch_time: datetime
private_dns: str
private_ip: str
public_dns: str
public_ip: str
user_data: str
http_tokens: str
http_endpoint: str
instance_profile: str
def __init__(
self,
id,
arn,
state,
region,
type,
image_id,
launch_time,
private_dns,
private_ip,
public_dns,
public_ip,
http_tokens,
http_endpoint,
instance_profile,
):
self.id = id
self.arn = arn
self.state = state
self.region = region
self.type = type
self.image_id = image_id
self.launch_time = launch_time
self.private_dns = private_dns
self.private_ip = private_ip
self.public_dns = public_dns
self.public_ip = public_ip
self.http_tokens = http_tokens
self.http_endpoint = http_endpoint
self.user_data = None
self.instance_profile = instance_profile
private_ip: Optional[str]
public_dns: Optional[str]
public_ip: Optional[str]
user_data: Optional[str]
http_tokens: Optional[str]
http_endpoint: Optional[str]
instance_profile: Optional[dict]
tags: Optional[list] = []
@dataclass
class Snapshot:
class Snapshot(BaseModel):
id: str
arn: str
region: str
encrypted: bool
public: bool
def __init__(self, id, arn, region, encrypted):
self.id = id
self.arn = arn
self.region = region
self.encrypted = encrypted
self.public = False
public: bool = False
tags: Optional[list] = []
@dataclass
class Volume:
class Volume(BaseModel):
id: str
arn: str
region: str
encrypted: bool
def __init__(self, id, arn, region, encrypted):
self.id = id
self.arn = arn
self.region = region
self.encrypted = encrypted
tags: Optional[list] = []
@dataclass
class SecurityGroup:
class SecurityGroup(BaseModel):
name: str
arn: str
region: str
id: str
network_interfaces: list[str]
network_interfaces: list[str] = []
ingress_rules: list[dict]
egress_rules: list[dict]
def __init__(self, name, arn, region, id, ingress_rules, egress_rules):
self.name = name
self.arn = arn
self.region = region
self.id = id
self.ingress_rules = ingress_rules
self.egress_rules = egress_rules
self.network_interfaces = []
tags: Optional[list] = []
@dataclass
class NetworkACL:
class NetworkACL(BaseModel):
id: str
arn: str
region: str
entries: list[dict]
def __init__(self, id, arn, region, entries):
self.id = id
self.arn = arn
self.region = region
self.entries = entries
tags: Optional[list] = []
@dataclass
class ElasticIP:
public_ip: str
association_id: str
class ElasticIP(BaseModel):
public_ip: Optional[str]
association_id: Optional[str]
arn: str
allocation_id: str
allocation_id: Optional[str]
region: str
def __init__(self, public_ip, association_id, allocation_id, arn, region):
self.public_ip = public_ip
self.association_id = association_id
self.allocation_id = allocation_id
self.arn = arn
self.region = region
tags: Optional[list] = []
@dataclass
class Image:
class Image(BaseModel):
id: str
arn: str
name: str
public: bool
region: str
def __init__(self, id, arn, name, public, region):
self.id = id
self.arn = arn
self.name = name
self.public = public
self.region = region
tags: Optional[list] = []
@dataclass
class EbsEncryptionByDefault:
class EbsEncryptionByDefault(BaseModel):
status: bool
region: str
def __init__(self, status, region):
self.status = status
self.region = region

View File

@@ -10,6 +10,7 @@ class ecr_repositories_lifecycle_policy_enabled(Check):
report.region = repository.region
report.resource_id = repository.name
report.resource_arn = repository.arn
report.resource_tags = repository.tags
report.status = "FAIL"
report.status_extended = (
f"Repository {repository.name} has no lifecycle policy"

View File

@@ -10,6 +10,7 @@ class ecr_repositories_not_publicly_accessible(Check):
report.region = repository.region
report.resource_id = repository.name
report.resource_arn = repository.arn
report.resource_tags = repository.tags
report.status = "PASS"
report.status_extended = f"Repository {repository.name} is not open"
if repository.policy:

View File

@@ -10,6 +10,7 @@ class ecr_repositories_scan_images_on_push_enabled(Check):
report.region = repository.region
report.resource_id = repository.name
report.resource_arn = repository.arn
report.resource_tags = repository.tags
report.status = "PASS"
report.status_extended = (
f"ECR repository {repository.name} has scan on push enabled"

View File

@@ -11,6 +11,7 @@ class ecr_repositories_scan_vulnerabilities_in_latest_image(Check):
report.region = repository.region
report.resource_id = repository.name
report.resource_arn = repository.arn
report.resource_tags = repository.tags
report.status = "PASS"
report.status_extended = f"ECR repository {repository.name} has imageTag {image.latest_tag} scanned without findings"
if not image.scan_findings_status:

View File

@@ -1,6 +1,8 @@
import threading
from dataclasses import dataclass
from json import loads
from typing import Optional
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
@@ -19,6 +21,7 @@ class ECR:
self.__describe_repository_policies__()
self.__get_image_details__()
self.__get_repository_lifecycle_policy__()
self.__list_tags_for_resource__()
def __get_session__(self):
return self.session
@@ -104,7 +107,6 @@ class ECR:
for page in describe_images_paginator.paginate(
repositoryName=repository.name
):
for image in page["imageDetails"]:
severity_counts = None
last_scan_status = None
@@ -147,61 +149,40 @@ class ECR:
f"-- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __list_tags_for_resource__(self):
logger.info("ECR - List Tags...")
try:
for repository in self.repositories:
regional_client = self.regional_clients[repository.region]
response = regional_client.list_tags_for_resource(
resourceArn=repository.arn
)["tags"]
repository.tags = response
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
@dataclass
class FindingSeverityCounts:
class FindingSeverityCounts(BaseModel):
critical: int
high: int
medium: int
def __init__(
self,
critical,
high,
medium,
):
self.critical = critical
self.high = high
self.medium = medium
@dataclass
class ImageDetails:
class ImageDetails(BaseModel):
latest_tag: str
latest_digest: str
scan_findings_status: str
scan_findings_severity_count: FindingSeverityCounts
def __init__(
self,
latest_tag,
latest_digest,
scan_findings_status,
scan_findings_severity_count,
):
self.latest_tag = latest_tag
self.latest_digest = latest_digest
self.scan_findings_status = scan_findings_status
self.scan_findings_severity_count = scan_findings_severity_count
scan_findings_status: Optional[str]
scan_findings_severity_count: Optional[FindingSeverityCounts]
@dataclass
class Repository:
class Repository(BaseModel):
name: str
arn: str
region: str
scan_on_push: bool
policy: dict
images_details: list[ImageDetails]
lyfecicle_policy: str
def __init__(
self, name, arn, region, scan_on_push, policy, images_details, lyfecicle_policy
):
self.name = name
self.arn = arn
self.region = region
self.scan_on_push = scan_on_push
self.policy = policy
self.images_details = images_details
self.lyfecicle_policy = lyfecicle_policy
policy: Optional[dict]
images_details: Optional[list[ImageDetails]]
lyfecicle_policy: Optional[str]
tags: Optional[list] = []

View File

@@ -1,5 +1,6 @@
import threading
from re import sub
from typing import Optional
from pydantic import BaseModel
@@ -34,7 +35,6 @@ class ECS:
def __list_task_definitions__(self, regional_client):
logger.info("ECS - Listing Task Definitions...")
try:
list_ecs_paginator = regional_client.get_paginator("list_task_definitions")
for page in list_ecs_paginator.paginate():
for task_definition in page["taskDefinitionArns"]:
@@ -61,9 +61,15 @@ class ECS:
try:
for task_definition in self.task_definitions:
client = self.regional_clients[task_definition.region]
container_definitions = client.describe_task_definition(
taskDefinition=task_definition.arn
)["taskDefinition"]["containerDefinitions"]
response = client.describe_task_definition(
taskDefinition=task_definition.arn,
include=[
"TAGS",
],
)
container_definitions = response["taskDefinition"][
"containerDefinitions"
]
for container in container_definitions:
if "environment" in container:
for env_var in container["environment"]:
@@ -72,6 +78,7 @@ class ECS:
name=env_var["name"], value=env_var["value"]
)
)
task_definition.tags = response.get("tags")
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -89,3 +96,4 @@ class TaskDefinition(BaseModel):
revision: str
region: str
environment_variables: list[ContainerEnvVariable]
tags: Optional[list] = []

View File

@@ -17,6 +17,7 @@ class ecs_task_definitions_no_environment_secrets(Check):
report.region = task_definition.region
report.resource_id = f"{task_definition.name}:{task_definition.revision}"
report.resource_arn = task_definition.arn
report.resource_tags = task_definition.tags
report.status = "PASS"
report.status_extended = f"No secrets found in variables of ECS task definition {task_definition.name} with revision {task_definition.revision}"
if task_definition.environment_variables:

View File

@@ -9,6 +9,7 @@ class efs_encryption_at_rest_enabled(Check):
report = Check_Report_AWS(self.metadata())
report.region = fs.region
report.resource_id = fs.id
report.resource_tags = fs.tags
report.resource_arn = ""
report.status = "FAIL"
report.status_extended = (

View File

@@ -10,6 +10,7 @@ class efs_have_backup_enabled(Check):
report.region = fs.region
report.resource_id = fs.id
report.resource_arn = ""
report.resource_tags = fs.tags
report.status = "PASS"
report.status_extended = f"EFS {fs.id} has backup enabled"
if fs.backup_policy == "DISABLED" or fs.backup_policy == "DISABLING":

View File

@@ -10,6 +10,7 @@ class efs_not_publicly_accessible(Check):
report.region = fs.region
report.resource_id = fs.id
report.resource_arn = ""
report.resource_tags = fs.tags
report.status = "PASS"
report.status_extended = (
f"EFS {fs.id} has policy which does not allow access to everyone"

View File

@@ -1,8 +1,9 @@
import json
import threading
from dataclasses import dataclass
from typing import Optional
from botocore.client import ClientError
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
@@ -50,6 +51,7 @@ class EFS:
policy=None,
backup_policy=None,
encrypted=efs["Encrypted"],
tags=efs.get("Tags"),
)
)
except Exception as error:
@@ -85,24 +87,10 @@ class EFS:
)
@dataclass
class FileSystem:
class FileSystem(BaseModel):
id: str
region: str
policy: dict
backup_policy: str
policy: Optional[dict]
backup_policy: Optional[str]
encrypted: bool
def __init__(
self,
id,
region,
policy,
backup_policy,
encrypted,
):
self.id = id
self.region = region
self.policy = policy
self.backup_policy = backup_policy
self.encrypted = encrypted
tags: Optional[list] = []

View File

@@ -10,6 +10,7 @@ class eks_cluster_kms_cmk_encryption_in_secrets_enabled(Check):
report.region = cluster.region
report.resource_id = cluster.name
report.resource_arn = cluster.arn
report.resource_tags = cluster.tags
report.status = "FAIL"
report.status_extended = (
f"EKS cluster {cluster.name} has not encryption for Kubernetes secrets."

View File

@@ -10,6 +10,7 @@ class eks_control_plane_endpoint_access_restricted(Check):
report.region = cluster.region
report.resource_id = cluster.name
report.resource_arn = cluster.arn
report.resource_tags = cluster.tags
report.status = "PASS"
report.status_extended = (
f"Cluster endpoint access is private for EKS cluster {cluster.name}"

View File

@@ -10,6 +10,7 @@ class eks_control_plane_logging_all_types_enabled(Check):
report.region = cluster.region
report.resource_id = cluster.name
report.resource_arn = cluster.arn
report.resource_tags = cluster.tags
report.status = "FAIL"
report.status_extended = (
f"Control plane logging is not enabled for EKS cluster {cluster.name}"

View File

@@ -10,6 +10,7 @@ class eks_endpoints_not_publicly_accessible(Check):
report.region = cluster.region
report.resource_id = cluster.name
report.resource_arn = cluster.arn
report.resource_tags = cluster.tags
report.status = "PASS"
report.status_extended = (
f"Cluster endpoint access is private for EKS cluster {cluster.name}"

View File

@@ -1,4 +1,5 @@
import threading
from typing import Optional
from pydantic import BaseModel
@@ -90,6 +91,7 @@ class EKS:
]["publicAccessCidrs"]
if "encryptionConfig" in describe_cluster["cluster"]:
cluster.encryptionConfig = True
cluster.tags = [describe_cluster["cluster"].get("tags")]
except Exception as error:
logger.error(
@@ -111,3 +113,4 @@ class EKSCluster(BaseModel):
endpoint_private_access: bool = None
public_access_cidrs: list[str] = None
encryptionConfig: bool = None
tags: Optional[list] = []

View File

@@ -12,6 +12,7 @@ class elb_insecure_ssl_ciphers(Check):
report = Check_Report_AWS(self.metadata())
report.region = lb.region
report.resource_id = lb.name
report.resource_tags = lb.tags
report.status = "PASS"
report.status_extended = (
f"ELB {lb.name} has not insecure SSL protocols or ciphers."

View File

@@ -9,6 +9,7 @@ class elb_internet_facing(Check):
report = Check_Report_AWS(self.metadata())
report.region = lb.region
report.resource_id = lb.name
report.resource_tags = lb.tags
report.status = "PASS"
report.status_extended = f"ELB {lb.name} is not internet facing."
if lb.scheme == "internet-facing":

View File

@@ -9,6 +9,7 @@ class elb_logging_enabled(Check):
report = Check_Report_AWS(self.metadata())
report.region = lb.region
report.resource_id = lb.name
report.resource_tags = lb.tags
report.status = "FAIL"
report.status_extended = f"ELB {lb.name} has not configured access logs."
if lb.access_logs:

View File

@@ -20,6 +20,7 @@ class ELB:
self.loadbalancers = []
self.__threading_call__(self.__describe_load_balancers__)
self.__threading_call__(self.__describe_load_balancer_attributes__)
self.__describe_tags__()
def __get_session__(self):
return self.session
@@ -85,6 +86,20 @@ class ELB:
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __describe_tags__(self):
logger.info("ELB - List Tags...")
try:
for lb in self.loadbalancers:
regional_client = self.regional_clients[lb.region]
response = regional_client.describe_tags(LoadBalancerNames=[lb.name])[
"TagDescriptions"
][0]
lb.tags = response.get("Tags")
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class Listener(BaseModel):
protocol: str
@@ -99,3 +114,4 @@ class LoadBalancer(BaseModel):
scheme: str
access_logs: Optional[bool]
listeners: list[Listener]
tags: Optional[list] = []

View File

@@ -10,6 +10,7 @@ class elb_ssl_listeners(Check):
report = Check_Report_AWS(self.metadata())
report.region = lb.region
report.resource_id = lb.name
report.resource_tags = lb.tags
report.status = "PASS"
report.status_extended = f"ELB {lb.name} has HTTPS listeners only."
for listener in lb.listeners:

View File

@@ -10,6 +10,7 @@ class elbv2_deletion_protection(Check):
report.region = lb.region
report.resource_id = lb.name
report.resource_arn = lb.arn
report.resource_tags = lb.tags
report.status = "FAIL"
report.status_extended = f"ELBv2 {lb.name} has not deletion protection."
if lb.deletion_protection == "true":

View File

@@ -11,6 +11,7 @@ class elbv2_desync_mitigation_mode(Check):
report.region = lb.region
report.resource_id = lb.name
report.resource_arn = lb.arn
report.resource_tags = lb.tags
report.status = "PASS"
report.status_extended = f"ELBv2 ALB {lb.name} is configured with correct desync mitigation mode."
if lb.desync_mitigation_mode == "monitor":

View File

@@ -22,6 +22,7 @@ class elbv2_insecure_ssl_ciphers(Check):
report.region = lb.region
report.resource_id = lb.name
report.resource_arn = lb.arn
report.resource_tags = lb.tags
report.status = "PASS"
report.status_extended = (
f"ELBv2 {lb.name} has not insecure SSL protocols or ciphers."

View File

@@ -10,6 +10,7 @@ class elbv2_internet_facing(Check):
report.region = lb.region
report.resource_id = lb.name
report.resource_arn = lb.arn
report.resource_tags = lb.tags
report.status = "PASS"
report.status_extended = f"ELBv2 ALB {lb.name} is not internet facing."
if lb.scheme == "internet-facing":

View File

@@ -10,6 +10,7 @@ class elbv2_listeners_underneath(Check):
report.region = lb.region
report.resource_id = lb.name
report.resource_arn = lb.arn
report.resource_tags = lb.tags
report.status = "PASS"
report.status_extended = f"ELBv2 {lb.name} has listeners underneath."
if len(lb.listeners) == 0:

View File

@@ -10,6 +10,7 @@ class elbv2_logging_enabled(Check):
report.region = lb.region
report.resource_id = lb.name
report.resource_arn = lb.arn
report.resource_tags = lb.tags
report.status = "FAIL"
report.status_extended = (
f"ELBv2 ALB {lb.name} has not configured access logs."

View File

@@ -11,6 +11,7 @@ class elbv2_request_smugling(Check):
report.region = lb.region
report.resource_id = lb.name
report.resource_arn = lb.arn
report.resource_tags = lb.tags
report.status = "FAIL"
report.status_extended = (
f"ELBv2 ALB {lb.name} is not dropping invalid header fields."

View File

@@ -22,6 +22,7 @@ class ELBv2:
self.__threading_call__(self.__describe_listeners__)
self.__threading_call__(self.__describe_load_balancer_attributes__)
self.__threading_call__(self.__describe_rules__)
self.__describe_tags__()
def __get_session__(self):
return self.session
@@ -148,6 +149,20 @@ class ELBv2:
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __describe_tags__(self):
logger.info("ELBv2 - List Tags...")
try:
for lb in self.loadbalancersv2:
regional_client = self.regional_clients[lb.region]
response = regional_client.describe_tags(ResourceArns=[lb.arn])[
"TagDescriptions"
][0]
lb.tags = response.get("Tags")
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class ListenerRule(BaseModel):
arn: str
@@ -176,3 +191,4 @@ class LoadBalancerv2(BaseModel):
drop_invalid_header_fields: Optional[str]
listeners: list[Listenerv2]
scheme: Optional[str]
tags: Optional[list] = []

View File

@@ -11,6 +11,7 @@ class elbv2_ssl_listeners(Check):
report.region = lb.region
report.resource_id = lb.name
report.resource_arn = lb.arn
report.resource_tags = lb.tags
report.status = "PASS"
report.status_extended = (
f"ELBv2 ALB {lb.name} has HTTPS listeners only."

View File

@@ -13,6 +13,7 @@ class elbv2_waf_acl_attached(Check):
report.region = lb.region
report.resource_id = lb.name
report.resource_arn = lb.arn
report.resource_tags = lb.tags
report.status = "FAIL"
report.status_extended = (
f"ELBv2 ALB {lb.name} is not protected by WAF Web ACL."

View File

@@ -15,7 +15,7 @@ class emr_cluster_master_nodes_no_public_ip(Check):
report.region = cluster.region
report.resource_id = cluster.id
report.resource_arn = cluster.arn
report.resource_tags = cluster.tags
if cluster.public:
report.status = "FAIL"
report.status_extended = f"EMR Cluster {cluster.id} has a Public IP"

View File

@@ -19,7 +19,7 @@ class emr_cluster_publicly_accesible(Check):
report.region = cluster.region
report.resource_id = cluster.id
report.resource_arn = cluster.arn
report.resource_tags = cluster.tags
report.status = "PASS"
report.status_extended = (
f"EMR Cluster {cluster.id} is not publicly accessible"
@@ -28,7 +28,6 @@ class emr_cluster_publicly_accesible(Check):
# their Security Groups for the Master,
# the Slaves and the additional ones
if cluster.public:
# Check Public Master Security Groups
master_node_sg_groups = deepcopy(
cluster.master.additional_security_groups_id

View File

@@ -125,6 +125,7 @@ class EMR:
and ".amazonaws.com" in master_public_dns_name
):
self.clusters[cluster.id].public = True
cluster.tags = cluster_info["Cluster"].get("Tags")
except Exception as error:
logger.error(
@@ -185,3 +186,4 @@ class Cluster(BaseModel):
slave: Node = Node()
master_public_dns_name: str = ""
public: bool = False
tags: Optional[list] = []

View File

@@ -127,6 +127,14 @@ class Test_EC2_Service:
sg_id = ec2_client.create_security_group(
Description="test-description",
GroupName="test-security-group",
TagSpecifications=[
{
"ResourceType": "security-group",
"Tags": [
{"Key": "test", "Value": "test"},
],
},
],
)["GroupId"]
# EC2 client for this test class
audit_info = self.set_mocked_audit_info()
@@ -153,6 +161,9 @@ class Test_EC2_Service:
"UserIdGroupPairs": [],
}
]
assert security_group.tags == [
{"Key": "test", "Value": "test"},
]
# Test EC2 Describe Nacls
@mock_ec2
@@ -164,6 +175,14 @@ class Test_EC2_Service:
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
nacl_id = ec2_resource.create_network_acl(
VpcId=vpc_id,
TagSpecifications=[
{
"ResourceType": "network-acl",
"Tags": [
{"Key": "test", "Value": "test"},
],
},
],
).id
# EC2 client for this test class
audit_info = self.set_mocked_audit_info()
@@ -178,6 +197,9 @@ class Test_EC2_Service:
== f"arn:{audit_info.audited_partition}:ec2:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:network-acl/{acl.id}"
)
assert acl.entries == []
assert acl.tags == [
{"Key": "test", "Value": "test"},
]
# Test EC2 Describe Snapshots
@mock_ec2
@@ -193,6 +215,14 @@ class Test_EC2_Service:
).id
snapshot_id = ec2_client.create_snapshot(
VolumeId=volume_id,
TagSpecifications=[
{
"ResourceType": "snapshot",
"Tags": [
{"Key": "test", "Value": "test"},
],
},
],
)["SnapshotId"]
# EC2 client for this test class
audit_info = self.set_mocked_audit_info()
@@ -207,6 +237,9 @@ class Test_EC2_Service:
== f"arn:{audit_info.audited_partition}:ec2:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:snapshot/{snapshot.id}"
)
assert snapshot.region == AWS_REGION
assert snapshot.tags == [
{"Key": "test", "Value": "test"},
]
assert not snapshot.encrypted
assert not snapshot.public
@@ -286,7 +319,16 @@ class Test_EC2_Service:
# Generate EC2 Client
ec2_client = client("ec2", region_name=AWS_REGION)
allocation_id = ec2_client.allocate_address(
Domain="vpc", Address="127.38.43.222"
Domain="vpc",
Address="127.38.43.222",
TagSpecifications=[
{
"ResourceType": "elastic-ip",
"Tags": [
{"Key": "test", "Value": "test"},
],
},
],
)["AllocationId"]
# EC2 client for this test class
audit_info = self.set_mocked_audit_info()
@@ -296,6 +338,9 @@ class Test_EC2_Service:
ec2.elastic_ips[0].arn
== f"arn:aws:ec2:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:eip-allocation/{allocation_id}"
)
assert ec2.elastic_ips[0].tags == [
{"Key": "test", "Value": "test"},
]
# Test EC2 Describe Network Interfaces
@mock_ec2
@@ -310,7 +355,6 @@ class Test_EC2_Service:
GroupName="test-securitygroup", Description="n/a"
)
eni_id = subnet.create_network_interface(Groups=[sg.id]).id
print(eni_id)
ec2_client.modify_network_interface_attribute(
NetworkInterfaceId=eni_id, Groups=[sg.id]
)
@@ -383,6 +427,13 @@ class Test_EC2_Service:
)
assert not ec2.images[0].public
assert ec2.images[0].region == AWS_REGION
assert ec2.images[0].tags == [
{
"Key": "Base_AMI_Name",
"Value": "Deep Learning Base AMI (Amazon Linux 2) Version 31.0",
},
{"Key": "OS_Version", "Value": "AWS Linux 2"},
]
# Test EC2 Describe Volumes
@mock_ec2
@@ -394,7 +445,14 @@ class Test_EC2_Service:
AvailabilityZone=AWS_REGION,
Encrypted=False,
Size=40,
TagSpecifications=[],
TagSpecifications=[
{
"ResourceType": "volume",
"Tags": [
{"Key": "test", "Value": "test"},
],
},
],
)["VolumeId"]
# EC2 client for this test class
@@ -410,3 +468,6 @@ class Test_EC2_Service:
)
assert ec2.volumes[0].region == AWS_REGION
assert not ec2.volumes[0].encrypted
assert ec2.volumes[0].tags == [
{"Key": "test", "Value": "test"},
]

View File

@@ -118,6 +118,9 @@ class Test_ECR_Service:
ecr_client.create_repository(
repositoryName=repo_name,
imageScanningConfiguration={"scanOnPush": True},
tags=[
{"Key": "test", "Value": "test"},
],
)
audit_info = self.set_mocked_audit_info()
ecr = ECR(audit_info)
@@ -125,6 +128,9 @@ class Test_ECR_Service:
assert ecr.repositories[0].name == repo_name
assert ecr.repositories[0].arn == repo_arn
assert ecr.repositories[0].scan_on_push
assert ecr.repositories[0].tags == [
{"Key": "test", "Value": "test"},
]
# Test describe ECR repository policies
@mock_ecr

View File

@@ -111,6 +111,9 @@ class Test_ECS_Service:
],
}
],
tags=[
{"key": "test", "value": "test"},
],
)
task_definition = ecs_client.register_task_definition(**definition)
@@ -121,6 +124,9 @@ class Test_ECS_Service:
assert (
ecs.task_definitions[0].name == task_definition["taskDefinition"]["family"]
)
assert ecs.task_definitions[0].tags == [
{"key": "test", "value": "test"},
]
assert (
ecs.task_definitions[0].arn
== task_definition["taskDefinition"]["taskDefinitionArn"]

View File

@@ -91,12 +91,19 @@ class Test_EFS:
def test__describe_file_systems__(self):
efs_client = client("efs", AWS_REGION)
efs = efs_client.create_file_system(
CreationToken=creation_token, Encrypted=True
CreationToken=creation_token,
Encrypted=True,
Tags=[
{"Key": "test", "Value": "test"},
],
)
filesystem = EFS(self.set_mocked_audit_info())
assert len(filesystem.filesystems) == 1
assert filesystem.filesystems[0].id == efs["FileSystemId"]
assert filesystem.filesystems[0].encrypted == efs["Encrypted"]
assert filesystem.filesystems[0].tags == [
{"Key": "test", "Value": "test"},
]
@mock_efs
# Test EFS describe file systems

View File

@@ -92,12 +92,14 @@ class Test_EKS_Service:
],
},
roleArn=f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:role/eks-service-role-AWSServiceRoleForAmazonEKS-J7ONKE3BQ4PI",
tags={"test": "test"},
)
audit_info = self.set_mocked_audit_info()
eks = EKS(audit_info)
assert len(eks.clusters) == 1
assert eks.clusters[0].name == cluster_name
assert eks.clusters[0].region == AWS_REGION
assert eks.clusters[0].tags == [{"test": "test"}]
# Test EKS describe clusters
@mock_ec2

View File

@@ -108,6 +108,9 @@ class Test_EMR_Service:
Name=cluster_name,
ServiceRole="EMR_DefaultRole",
VisibleToAllUsers=True,
Tags=[
{"Key": "test", "Value": "test"},
],
)
cluster_id = emr_client.run_job_flow(**run_job_flow_args)["JobFlowId"]
# EMR Class
@@ -127,6 +130,9 @@ class Test_EMR_Service:
== "ec2-184-0-0-1.us-west-1.compute.amazonaws.com"
)
assert emr.clusters[cluster_id].public
assert emr.clusters[cluster_id].tags == [
{"Key": "test", "Value": "test"},
]
@mock_emr
def test__get_block_public_access_configuration__(self):