From da834c0935724a7d3819f8d324fe300f7f3fbcad Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Thu, 2 Mar 2023 13:14:53 +0100 Subject: [PATCH] feat(tags): add resource tags to C-D services (#2003) Co-authored-by: Pepe Fagoaga --- .../cloudformation_outputs_find_secrets.py | 1 + .../cloudformation/cloudformation_service.py | 30 ++--- ...n_stacks_termination_protection_enabled.py | 1 + ...ibutions_field_level_encryption_enabled.py | 1 + ..._distributions_geo_restrictions_enabled.py | 1 + .../cloudfront_distributions_https_enabled.py | 1 + ...loudfront_distributions_logging_enabled.py | 1 + ...ibutions_using_deprecated_ssl_protocols.py | 1 + .../cloudfront_distributions_using_waf.py | 1 + .../services/cloudfront/cloudfront_service.py | 51 ++++---- .../cloudtrail_cloudwatch_logging_enabled.py | 1 + .../cloudtrail_kms_encryption_enabled.py | 1 + .../cloudtrail_log_file_validation_enabled.py | 1 + ...l_logs_s3_bucket_access_logging_enabled.py | 1 + ...gs_s3_bucket_is_not_publicly_accessible.py | 1 + .../cloudtrail_multi_region_enabled.py | 1 + .../cloudtrail_s3_dataevents_read_enabled.py | 2 + .../cloudtrail_s3_dataevents_write_enabled.py | 2 + .../services/cloudtrail/cloudtrail_service.py | 19 +++ ...dwatch_log_group_kms_encryption_enabled.py | 1 + ..._retention_policy_specific_days_enabled.py | 1 + .../services/cloudwatch/cloudwatch_service.py | 118 ++++++++---------- ...ges_external_public_publishing_disabled.py | 2 + .../codeartifact/codeartifact_service.py | 16 +++ .../services/codebuild/codebuild_service.py | 6 - .../aws/services/config/config_service.py | 47 +++---- ...ervice_directory_log_forwarding_enabled.py | 1 + ...service_directory_monitor_notifications.py | 1 + ...ectoryservice_directory_snapshots_limit.py | 1 + ...toryservice_ldap_certificate_expiration.py | 1 + ...service_radius_server_security_protocol.py | 1 + .../directoryservice_service.py | 18 ++- ...oryservice_supported_mfa_radius_enabled.py | 1 + ..._accelerator_cluster_encryption_enabled.py | 1 + .../aws/services/dynamodb/dynamodb_service.py | 83 ++++++------ ...amodb_tables_kms_cmk_encryption_enabled.py | 1 + .../dynamodb_tables_pitr_enabled.py | 1 + .../cloudformation_service_test.py | 4 + ...cks_termination_protection_enabled_test.py | 4 +- .../cloudfront/cloudfront_service_test.py | 12 ++ .../cloudtrail/cloudtrail_service_test.py | 18 ++- .../codeartifact/codeartifact_service_test.py | 10 ++ ...onfig_recorder_all_regions_enabled_test.py | 9 +- .../directoryservice_service_test.py | 10 +- .../dynamodb/dynamodb_service_test.py | 18 +++ 45 files changed, 306 insertions(+), 198 deletions(-) diff --git a/prowler/providers/aws/services/cloudformation/cloudformation_outputs_find_secrets/cloudformation_outputs_find_secrets.py b/prowler/providers/aws/services/cloudformation/cloudformation_outputs_find_secrets/cloudformation_outputs_find_secrets.py index 3e272cb3..edbab25c 100644 --- a/prowler/providers/aws/services/cloudformation/cloudformation_outputs_find_secrets/cloudformation_outputs_find_secrets.py +++ b/prowler/providers/aws/services/cloudformation/cloudformation_outputs_find_secrets/cloudformation_outputs_find_secrets.py @@ -21,6 +21,7 @@ class cloudformation_outputs_find_secrets(Check): report.region = stack.region report.resource_id = stack.name report.resource_arn = stack.arn + report.resource_tags = stack.tags report.status = "PASS" report.status_extended = f"No secrets found in Stack {stack.name} Outputs." if stack.outputs: diff --git a/prowler/providers/aws/services/cloudformation/cloudformation_service.py b/prowler/providers/aws/services/cloudformation/cloudformation_service.py index 345d7672..90fa737a 100644 --- a/prowler/providers/aws/services/cloudformation/cloudformation_service.py +++ b/prowler/providers/aws/services/cloudformation/cloudformation_service.py @@ -1,5 +1,7 @@ import threading -from dataclasses import dataclass +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 @@ -50,6 +52,7 @@ class CloudFormation: Stack( arn=stack["StackId"], name=stack["StackName"], + tags=stack.get("Tags"), outputs=outputs, region=regional_client.region, ) @@ -82,8 +85,7 @@ class CloudFormation: ) -@dataclass -class Stack: +class Stack(BaseModel): """Stack holds a CloudFormation Stack""" arn: str @@ -92,25 +94,11 @@ class Stack: """Stacks[].StackName""" outputs: list[str] """Stacks[].Outputs""" - enable_termination_protection: bool + enable_termination_protection: bool = False """Stacks[].EnableTerminationProtection""" - root_nested_stack: str + root_nested_stack: str = "" """Stacks[].RootId""" - is_nested_stack: str + is_nested_stack: bool = False """True if the Stack is a Nested Stack""" + tags: Optional[list] = [] region: str - - def __init__( - self, - arn, - name, - outputs, - region, - ): - self.arn = arn - self.name = name - self.outputs = outputs - self.enable_termination_protection = False - self.is_nested_stack = False - self.root_nested_stack = "" - self.region = region diff --git a/prowler/providers/aws/services/cloudformation/cloudformation_stacks_termination_protection_enabled/cloudformation_stacks_termination_protection_enabled.py b/prowler/providers/aws/services/cloudformation/cloudformation_stacks_termination_protection_enabled/cloudformation_stacks_termination_protection_enabled.py index c68392ee..1bfb83a5 100644 --- a/prowler/providers/aws/services/cloudformation/cloudformation_stacks_termination_protection_enabled/cloudformation_stacks_termination_protection_enabled.py +++ b/prowler/providers/aws/services/cloudformation/cloudformation_stacks_termination_protection_enabled/cloudformation_stacks_termination_protection_enabled.py @@ -16,6 +16,7 @@ class cloudformation_stacks_termination_protection_enabled(Check): report.region = stack.region report.resource_id = stack.name report.resource_arn = stack.arn + report.resource_tags = stack.tags if stack.enable_termination_protection: report.status = "PASS" diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_field_level_encryption_enabled/cloudfront_distributions_field_level_encryption_enabled.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_field_level_encryption_enabled/cloudfront_distributions_field_level_encryption_enabled.py index d2a566c1..a1529b6e 100644 --- a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_field_level_encryption_enabled/cloudfront_distributions_field_level_encryption_enabled.py +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_field_level_encryption_enabled/cloudfront_distributions_field_level_encryption_enabled.py @@ -12,6 +12,7 @@ class cloudfront_distributions_field_level_encryption_enabled(Check): report.region = distribution.region report.resource_arn = distribution.arn report.resource_id = distribution.id + report.resource_tags = distribution.tags if ( distribution.default_cache_config and distribution.default_cache_config.field_level_encryption_id diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_geo_restrictions_enabled/cloudfront_distributions_geo_restrictions_enabled.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_geo_restrictions_enabled/cloudfront_distributions_geo_restrictions_enabled.py index 1b0d9542..5e8bf9d6 100644 --- a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_geo_restrictions_enabled/cloudfront_distributions_geo_restrictions_enabled.py +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_geo_restrictions_enabled/cloudfront_distributions_geo_restrictions_enabled.py @@ -15,6 +15,7 @@ class cloudfront_distributions_geo_restrictions_enabled(Check): report.region = distribution.region report.resource_arn = distribution.arn report.resource_id = distribution.id + report.resource_tags = distribution.tags if distribution.geo_restriction_type == GeoRestrictionType.none: report.status = "FAIL" report.status_extended = f"CloudFront Distribution {distribution.id} has Geo restrictions disabled" diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_https_enabled/cloudfront_distributions_https_enabled.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_https_enabled/cloudfront_distributions_https_enabled.py index 64599365..16737b83 100644 --- a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_https_enabled/cloudfront_distributions_https_enabled.py +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_https_enabled/cloudfront_distributions_https_enabled.py @@ -15,6 +15,7 @@ class cloudfront_distributions_https_enabled(Check): report.region = distribution.region report.resource_arn = distribution.arn report.resource_id = distribution.id + report.resource_tags = distribution.tags if ( distribution.default_cache_config and distribution.default_cache_config.viewer_protocol_policy diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_logging_enabled/cloudfront_distributions_logging_enabled.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_logging_enabled/cloudfront_distributions_logging_enabled.py index 458f6335..34d476cf 100644 --- a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_logging_enabled/cloudfront_distributions_logging_enabled.py +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_logging_enabled/cloudfront_distributions_logging_enabled.py @@ -12,6 +12,7 @@ class cloudfront_distributions_logging_enabled(Check): report.region = distribution.region report.resource_arn = distribution.arn report.resource_id = distribution.id + report.resource_tags = distribution.tags if distribution.logging_enabled or ( distribution.default_cache_config and distribution.default_cache_config.realtime_log_config_arn diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_using_deprecated_ssl_protocols/cloudfront_distributions_using_deprecated_ssl_protocols.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_using_deprecated_ssl_protocols/cloudfront_distributions_using_deprecated_ssl_protocols.py index 8b8e4662..f22f0d41 100644 --- a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_using_deprecated_ssl_protocols/cloudfront_distributions_using_deprecated_ssl_protocols.py +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_using_deprecated_ssl_protocols/cloudfront_distributions_using_deprecated_ssl_protocols.py @@ -15,6 +15,7 @@ class cloudfront_distributions_using_deprecated_ssl_protocols(Check): report.region = distribution.region report.resource_arn = distribution.arn report.resource_id = distribution.id + report.resource_tags = distribution.tags report.status = "PASS" report.status_extended = f"CloudFront Distribution {distribution.id} is not using a deprecated SSL protocol" diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_using_waf/cloudfront_distributions_using_waf.py b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_using_waf/cloudfront_distributions_using_waf.py index 3357c808..13629e86 100644 --- a/prowler/providers/aws/services/cloudfront/cloudfront_distributions_using_waf/cloudfront_distributions_using_waf.py +++ b/prowler/providers/aws/services/cloudfront/cloudfront_distributions_using_waf/cloudfront_distributions_using_waf.py @@ -12,6 +12,7 @@ class cloudfront_distributions_using_waf(Check): report.region = distribution.region report.resource_arn = distribution.arn report.resource_id = distribution.id + report.resource_tags = distribution.tags if distribution.web_acl_id: report.status = "PASS" report.status_extended = f"CloudFront Distribution {distribution.id} is using AWS WAF web ACL {distribution.web_acl_id}" diff --git a/prowler/providers/aws/services/cloudfront/cloudfront_service.py b/prowler/providers/aws/services/cloudfront/cloudfront_service.py index 2eaf9d49..fd8f2fda 100644 --- a/prowler/providers/aws/services/cloudfront/cloudfront_service.py +++ b/prowler/providers/aws/services/cloudfront/cloudfront_service.py @@ -1,5 +1,8 @@ from dataclasses import dataclass from enum import Enum +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 @@ -24,6 +27,9 @@ class CloudFront: self.__get_distribution_config__( self.client, self.distributions, self.region ) + self.__list_tags_for_resource__( + self.client, self.distributions, self.region + ) def __get_session__(self): return self.session @@ -97,6 +103,19 @@ class CloudFront: f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags_for_resource__(self, client, distributions, region): + logger.info("CloudFront - List Tags...") + try: + for distribution in distributions.values(): + response = client.list_tags_for_resource(Resource=distribution.arn)[ + "Tags" + ] + distribution.tags = response.get("Items") + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class OriginsSSLProtocols(Enum): SSLv3 = "SSLv3" @@ -128,35 +147,15 @@ class DefaultCacheConfigBehaviour: field_level_encryption_id: str -@dataclass -class Distribution: +class Distribution(BaseModel): """Distribution holds a CloudFront Distribution with the required information to run the rela""" arn: str id: str region: str - logging_enabled: bool - default_cache_config: DefaultCacheConfigBehaviour - geo_restriction_type: GeoRestrictionType + logging_enabled: bool = False + default_cache_config: Optional[DefaultCacheConfigBehaviour] + geo_restriction_type: Optional[GeoRestrictionType] origins: list - web_acl_id: str - - def __init__( - self, - arn, - id, - region, - origins, - logging_enabled=False, - default_cache_config=None, - geo_restriction_type=None, - web_acl_id="", - ): - self.arn = arn - self.id = id - self.region = region - self.logging_enabled = logging_enabled - self.default_cache_config = default_cache_config - self.geo_restriction_type = geo_restriction_type - self.origins = origins - self.web_acl_id = web_acl_id + web_acl_id: str = "" + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.py index f62beeb6..7d2e984e 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.py @@ -17,6 +17,7 @@ class cloudtrail_cloudwatch_logging_enabled(Check): report.region = trail.region report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags report.status = "PASS" if trail.is_multiregion: report.status_extended = ( diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.py index 95eb1fd3..b2c7e3ed 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.py @@ -13,6 +13,7 @@ class cloudtrail_kms_encryption_enabled(Check): report.region = trail.region report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags report.status = "FAIL" if trail.is_multiregion: report.status_extended = ( diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.py index c913b661..ed8838a7 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.py @@ -13,6 +13,7 @@ class cloudtrail_log_file_validation_enabled(Check): report.region = trail.region report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags report.status = "FAIL" if trail.is_multiregion: report.status_extended = ( diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.py index 7bd1bf62..cffc5272 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.py @@ -16,6 +16,7 @@ class cloudtrail_logs_s3_bucket_access_logging_enabled(Check): report.region = trail.region report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags report.status = "FAIL" if trail.is_multiregion: report.status_extended = f"Multiregion Trail {trail.name} S3 bucket access logging is not enabled for bucket {trail_bucket}" diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py index 0c9ce4fd..1dfff638 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py @@ -16,6 +16,7 @@ class cloudtrail_logs_s3_bucket_is_not_publicly_accessible(Check): report.region = trail.region report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags report.status = "PASS" if trail.is_multiregion: report.status_extended = f"S3 Bucket {trail_bucket} from multiregion trail {trail.name} is not publicly accessible" diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.py index b708f03a..db013467 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.py @@ -16,6 +16,7 @@ class cloudtrail_multi_region_enabled(Check): report.status = "PASS" report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags if trail.is_multiregion: report.status_extended = ( f"Trail {trail.name} is multiregion and it is logging" diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.py index 025d0fb0..e8be4b11 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.py @@ -32,6 +32,7 @@ class cloudtrail_s3_dataevents_read_enabled(Check): report.region = trail.region report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags report.status = "PASS" report.status_extended = f"Trail {trail.name} has a classic data event selector to record all S3 object-level API operations." # advanced event selectors @@ -44,6 +45,7 @@ class cloudtrail_s3_dataevents_read_enabled(Check): report.region = trail.region report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags report.status = "PASS" report.status_extended = f"Trail {trail.name} has an advanced data event selector to record all S3 object-level API operations." diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.py index dbf714bf..470aec9f 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.py @@ -32,6 +32,7 @@ class cloudtrail_s3_dataevents_write_enabled(Check): report.region = trail.region report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags report.status = "PASS" report.status_extended = f"Trail {trail.name} has a classic data event selector to record all S3 object-level API operations." # advanced event selectors @@ -44,6 +45,7 @@ class cloudtrail_s3_dataevents_write_enabled(Check): report.region = trail.region report.resource_id = trail.name report.resource_arn = trail.arn + report.resource_tags = trail.tags report.status = "PASS" report.status_extended = f"Trail {trail.name} has an advanced data event selector to record all S3 object-level API operations." findings.append(report) diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_service.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_service.py index 3139916d..01443176 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_service.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_service.py @@ -1,5 +1,6 @@ import threading from datetime import datetime +from typing import Optional from pydantic import BaseModel @@ -22,6 +23,7 @@ class Cloudtrail: self.__threading_call__(self.__get_trails__) self.__get_trail_status__() self.__get_event_selectors__() + self.__list_tags_for_resource__() def __get_session__(self): return self.session @@ -132,6 +134,22 @@ class Cloudtrail: f"{client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags_for_resource__(self): + logger.info("CloudTrail - List Tags...") + try: + for trail in self.trails: + # Check if trails are in this region + if trail.region == trail.home_region: + regional_client = self.regional_clients[trail.region] + response = regional_client.list_tags(ResourceIdList=[trail.arn])[ + "ResourceTagList" + ][0] + trail.tags = response.get("TagsList") + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class Event_Selector(BaseModel): is_advanced: bool @@ -151,3 +169,4 @@ class Trail(BaseModel): kms_key: str = None log_group_arn: str = None data_events: list[Event_Selector] = [] + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_kms_encryption_enabled/cloudwatch_log_group_kms_encryption_enabled.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_kms_encryption_enabled/cloudwatch_log_group_kms_encryption_enabled.py index 6695ee5f..e30ddfca 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_kms_encryption_enabled/cloudwatch_log_group_kms_encryption_enabled.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_kms_encryption_enabled/cloudwatch_log_group_kms_encryption_enabled.py @@ -10,6 +10,7 @@ class cloudwatch_log_group_kms_encryption_enabled(Check): report.region = log_group.region report.resource_id = log_group.name report.resource_arn = log_group.arn + report.resource_tags = log_group.tags if log_group.kms_id: report.status = "PASS" report.status_extended = f"Log Group {log_group.name} does have AWS KMS key {log_group.kms_id} associated." diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py index 8b28b8a3..a3051213 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py @@ -12,6 +12,7 @@ class cloudwatch_log_group_retention_policy_specific_days_enabled(Check): report.region = log_group.region report.resource_id = log_group.name report.resource_arn = log_group.arn + report.resource_tags = log_group.tags if log_group.retention_days < specific_retention_days: report.status = "FAIL" report.status_extended = f"Log Group {log_group.name} has less than {specific_retention_days} days retention period ({log_group.retention_days} days)." diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py index ba7aeefd..a597aea0 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py @@ -1,7 +1,8 @@ import threading -from dataclasses import dataclass 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 from prowler.providers.aws.aws_provider import generate_regional_clients @@ -22,6 +23,7 @@ class CloudWatch: self.regional_clients = generate_regional_clients(self.service, audit_info) self.metric_alarms = [] self.__threading_call__(self.__describe_alarms__) + self.__list_tags_for_resource__() def __get_session__(self): return self.session @@ -52,11 +54,11 @@ class CloudWatch: namespace = alarm["Namespace"] self.metric_alarms.append( MetricAlarm( - alarm["AlarmArn"], - alarm["AlarmName"], - metric_name, - namespace, - regional_client.region, + arn=alarm["AlarmArn"], + name=alarm["AlarmName"], + metric=metric_name, + name_space=namespace, + region=regional_client.region, ) ) except Exception as error: @@ -64,6 +66,20 @@ class CloudWatch: f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags_for_resource__(self): + logger.info("CloudWatch - List Tags...") + try: + for metric_alarm in self.metric_alarms: + regional_client = self.regional_clients[metric_alarm.region] + response = regional_client.list_tags_for_resource( + ResourceARN=metric_alarm.arn + )["Tags"] + metric_alarm.tags = response + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + ################## CloudWatch Logs class Logs: @@ -77,6 +93,7 @@ class Logs: self.log_groups = [] self.__threading_call__(self.__describe_metric_filters__) self.__threading_call__(self.__describe_log_groups__) + self.__list_tags_for_resource__() def __get_session__(self): return self.session @@ -103,11 +120,11 @@ class Logs: ): self.metric_filters.append( MetricFilter( - filter["filterName"], - filter["metricTransformations"][0]["metricName"], - filter["filterPattern"], - filter["logGroupName"], - regional_client.region, + name=filter["filterName"], + metric=filter["metricTransformations"][0]["metricName"], + pattern=filter["filterPattern"], + log_group=filter["logGroupName"], + region=regional_client.region, ) ) except Exception as error: @@ -134,11 +151,11 @@ class Logs: retention_days = log_group["retentionInDays"] self.log_groups.append( LogGroup( - log_group["arn"], - log_group["logGroupName"], - retention_days, - kms, - regional_client.region, + arn=log_group["arn"], + name=log_group["logGroupName"], + retention_days=retention_days, + kms_id=kms, + region=regional_client.region, ) ) except Exception as error: @@ -146,71 +163,42 @@ class Logs: f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags_for_resource__(self): + logger.info("CloudWatch Logs - List Tags...") + try: + for log_group in self.log_groups: + regional_client = self.regional_clients[log_group.region] + response = regional_client.list_tags_for_resource( + resourceArn=log_group.arn.replace(":*", "") # Remove the tailing :* + )["tags"] + log_group.tags = [response] + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) -@dataclass -class MetricAlarm: + +class MetricAlarm(BaseModel): arn: str name: str metric: Optional[str] name_space: Optional[str] region: str - - def __init__( - self, - arn, - name, - metric, - name_space, - region, - ): - self.arn = arn - self.name = name - self.metric = metric - self.name_space = name_space - self.region = region + tags: Optional[list] = [] -@dataclass -class MetricFilter: +class MetricFilter(BaseModel): name: str metric: str pattern: str log_group: str region: str - def __init__( - self, - name, - metric, - pattern, - log_group, - region, - ): - self.name = name - self.metric = metric - self.pattern = pattern - self.log_group = log_group - self.region = region - -@dataclass -class LogGroup: +class LogGroup(BaseModel): arn: str name: str retention_days: int - kms_id: str + kms_id: Optional[str] region: str - - def __init__( - self, - arn, - name, - retention_days, - kms_id, - region, - ): - self.arn = arn - self.name = name - self.retention_days = retention_days - self.kms_id = kms_id - self.region = region + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/codeartifact/codeartifact_packages_external_public_publishing_disabled/codeartifact_packages_external_public_publishing_disabled.py b/prowler/providers/aws/services/codeartifact/codeartifact_packages_external_public_publishing_disabled/codeartifact_packages_external_public_publishing_disabled.py index 9576ae01..509b5289 100644 --- a/prowler/providers/aws/services/codeartifact/codeartifact_packages_external_public_publishing_disabled/codeartifact_packages_external_public_publishing_disabled.py +++ b/prowler/providers/aws/services/codeartifact/codeartifact_packages_external_public_publishing_disabled/codeartifact_packages_external_public_publishing_disabled.py @@ -16,6 +16,8 @@ class codeartifact_packages_external_public_publishing_disabled(Check): report = Check_Report_AWS(self.metadata()) report.region = repository.region report.resource_id = package.name + report.resource_arn = repository.arn + report.resource_tags = repository.tags if package.latest_version.origin.origin_type in ( OriginInformationValues.INTERNAL, diff --git a/prowler/providers/aws/services/codeartifact/codeartifact_service.py b/prowler/providers/aws/services/codeartifact/codeartifact_service.py index 22997b1d..b0a99464 100644 --- a/prowler/providers/aws/services/codeartifact/codeartifact_service.py +++ b/prowler/providers/aws/services/codeartifact/codeartifact_service.py @@ -21,6 +21,7 @@ class CodeArtifact: self.repositories = {} self.__threading_call__(self.__list_repositories__) self.__threading_call__(self.__list_packages__) + self.__list_tags_for_resource__() def __get_session__(self): return self.session @@ -148,6 +149,20 @@ class CodeArtifact: f" {error}" ) + def __list_tags_for_resource__(self): + logger.info("CodeArtifact - List Tags...") + try: + for repository in self.repositories.values(): + 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}" + ) + class RestrictionValues(Enum): """Possible values for the package origin restriction""" @@ -227,3 +242,4 @@ class Repository(BaseModel): domain_owner: str packages: list[Package] = [] region: str + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/codebuild/codebuild_service.py b/prowler/providers/aws/services/codebuild/codebuild_service.py index cb881fae..e885fe29 100644 --- a/prowler/providers/aws/services/codebuild/codebuild_service.py +++ b/prowler/providers/aws/services/codebuild/codebuild_service.py @@ -89,9 +89,3 @@ class CodebuildProject: region: str last_invoked_time: Optional[datetime.datetime] buildspec: Optional[str] - - def __init__(self, name, region, last_invoked_time, buildspec): - self.name = name - self.region = region - self.last_invoked_time = last_invoked_time - self.buildspec = buildspec diff --git a/prowler/providers/aws/services/config/config_service.py b/prowler/providers/aws/services/config/config_service.py index 5c876353..d94d0777 100644 --- a/prowler/providers/aws/services/config/config_service.py +++ b/prowler/providers/aws/services/config/config_service.py @@ -1,5 +1,7 @@ import threading -from dataclasses import dataclass +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 @@ -44,29 +46,29 @@ class Config: if "lastStatus" in recorder: self.recorders.append( Recorder( - recorder["name"], - recorder["recording"], - recorder["lastStatus"], - regional_client.region, + name=recorder["name"], + recording=recorder["recording"], + last_status=recorder["lastStatus"], + region=regional_client.region, ) ) else: self.recorders.append( Recorder( - recorder["name"], - recorder["recording"], - None, - regional_client.region, + name=recorder["name"], + recording=recorder["recording"], + last_status=None, + region=regional_client.region, ) ) # No config recorders in region if recorders_count == 0: self.recorders.append( Recorder( - self.audited_account, - None, - None, - regional_client.region, + name=self.audited_account, + recording=None, + last_status=None, + region=regional_client.region, ) ) @@ -76,21 +78,8 @@ class Config: ) -@dataclass -class Recorder: +class Recorder(BaseModel): name: str - recording: bool - last_status: str + recording: Optional[bool] + last_status: Optional[str] region: str - - def __init__( - self, - name, - recording, - last_status, - region, - ): - self.name = name - self.recording = recording - self.last_status = last_status - self.region = region diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_directory_log_forwarding_enabled/directoryservice_directory_log_forwarding_enabled.py b/prowler/providers/aws/services/directoryservice/directoryservice_directory_log_forwarding_enabled/directoryservice_directory_log_forwarding_enabled.py index a69ebcd2..4eba32c7 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_directory_log_forwarding_enabled/directoryservice_directory_log_forwarding_enabled.py +++ b/prowler/providers/aws/services/directoryservice/directoryservice_directory_log_forwarding_enabled/directoryservice_directory_log_forwarding_enabled.py @@ -11,6 +11,7 @@ class directoryservice_directory_log_forwarding_enabled(Check): report = Check_Report_AWS(self.metadata()) report.region = directory.region report.resource_id = directory.id + report.resource_tags = directory.tags if directory.log_subscriptions: report.status = "PASS" report.status_extended = f"Directory Service {directory.id} have log forwarding to CloudWatch enabled" diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_directory_monitor_notifications/directoryservice_directory_monitor_notifications.py b/prowler/providers/aws/services/directoryservice/directoryservice_directory_monitor_notifications/directoryservice_directory_monitor_notifications.py index c53a2cf3..95abe999 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_directory_monitor_notifications/directoryservice_directory_monitor_notifications.py +++ b/prowler/providers/aws/services/directoryservice/directoryservice_directory_monitor_notifications/directoryservice_directory_monitor_notifications.py @@ -11,6 +11,7 @@ class directoryservice_directory_monitor_notifications(Check): report = Check_Report_AWS(self.metadata()) report.region = directory.region report.resource_id = directory.id + report.resource_tags = directory.tags if directory.event_topics: report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_directory_snapshots_limit/directoryservice_directory_snapshots_limit.py b/prowler/providers/aws/services/directoryservice/directoryservice_directory_snapshots_limit/directoryservice_directory_snapshots_limit.py index 9475b086..de2d5503 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_directory_snapshots_limit/directoryservice_directory_snapshots_limit.py +++ b/prowler/providers/aws/services/directoryservice/directoryservice_directory_snapshots_limit/directoryservice_directory_snapshots_limit.py @@ -14,6 +14,7 @@ class directoryservice_directory_snapshots_limit(Check): report = Check_Report_AWS(self.metadata()) report.region = directory.region report.resource_id = directory.id + report.resource_tags = directory.tags if directory.snapshots_limits: if directory.snapshots_limits.manual_snapshots_limit_reached: report.status = "FAIL" diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_ldap_certificate_expiration/directoryservice_ldap_certificate_expiration.py b/prowler/providers/aws/services/directoryservice/directoryservice_ldap_certificate_expiration/directoryservice_ldap_certificate_expiration.py index cea68737..7ec163bf 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_ldap_certificate_expiration/directoryservice_ldap_certificate_expiration.py +++ b/prowler/providers/aws/services/directoryservice/directoryservice_ldap_certificate_expiration/directoryservice_ldap_certificate_expiration.py @@ -17,6 +17,7 @@ class directoryservice_ldap_certificate_expiration(Check): report = Check_Report_AWS(self.metadata()) report.region = directory.region report.resource_id = certificate.id + report.resource_tags = directory.tags remaining_days_to_expire = ( certificate.expiry_date_time diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_radius_server_security_protocol/directoryservice_radius_server_security_protocol.py b/prowler/providers/aws/services/directoryservice/directoryservice_radius_server_security_protocol/directoryservice_radius_server_security_protocol.py index 0110aa21..afc5a3a4 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_radius_server_security_protocol/directoryservice_radius_server_security_protocol.py +++ b/prowler/providers/aws/services/directoryservice/directoryservice_radius_server_security_protocol/directoryservice_radius_server_security_protocol.py @@ -15,6 +15,7 @@ class directoryservice_radius_server_security_protocol(Check): report = Check_Report_AWS(self.metadata()) report.region = directory.region report.resource_id = directory.id + report.resource_tags = directory.tags if ( directory.radius_settings.authentication_protocol == AuthenticationProtocol.MS_CHAPv2 diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_service.py b/prowler/providers/aws/services/directoryservice/directoryservice_service.py index d524de8b..fac7954d 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_service.py +++ b/prowler/providers/aws/services/directoryservice/directoryservice_service.py @@ -1,7 +1,7 @@ import threading from datetime import datetime from enum import Enum -from typing import Union +from typing import Optional, Union from pydantic import BaseModel @@ -24,6 +24,7 @@ class DirectoryService: self.__threading_call__(self.__describe_event_topics__) self.__threading_call__(self.__list_certificates__) self.__threading_call__(self.__get_snapshot_limits__) + self.__list_tags_for_resource__() def __get_session__(self): return self.session @@ -199,6 +200,20 @@ class DirectoryService: f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags_for_resource__(self): + logger.info("Directory Service - List Tags...") + try: + for directory in self.directories.values(): + regional_client = self.regional_clients[directory.region] + response = regional_client.list_tags_for_resource( + ResourceId=directory.id + )["Tags"] + directory.tags = response + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class SnapshotLimit(BaseModel): manual_snapshots_limit: int @@ -284,3 +299,4 @@ class Directory(BaseModel): snapshots_limits: SnapshotLimit = None radius_settings: RadiusSettings = None region: str + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.py b/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.py index ac87306a..291b3a80 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.py +++ b/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.py @@ -15,6 +15,7 @@ class directoryservice_supported_mfa_radius_enabled(Check): report = Check_Report_AWS(self.metadata()) report.region = directory.region report.resource_id = directory.id + report.resource_tags = directory.tags if directory.radius_settings.status == RadiusStatus.Completed: report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_encryption_enabled/dynamodb_accelerator_cluster_encryption_enabled.py b/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_encryption_enabled/dynamodb_accelerator_cluster_encryption_enabled.py index 4c6ca63d..2961c839 100644 --- a/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_encryption_enabled/dynamodb_accelerator_cluster_encryption_enabled.py +++ b/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_encryption_enabled/dynamodb_accelerator_cluster_encryption_enabled.py @@ -10,6 +10,7 @@ class dynamodb_accelerator_cluster_encryption_enabled(Check): report.resource_id = cluster.name report.resource_arn = cluster.arn report.region = cluster.region + report.resource_tags = cluster.tags report.status = "FAIL" report.status_extended = f"DynamoDB cluster {cluster.name} does not have encryption at rest enabled." if cluster.encryption: diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_service.py b/prowler/providers/aws/services/dynamodb/dynamodb_service.py index ea3ed112..b52674f3 100644 --- a/prowler/providers/aws/services/dynamodb/dynamodb_service.py +++ b/prowler/providers/aws/services/dynamodb/dynamodb_service.py @@ -1,5 +1,7 @@ import threading -from dataclasses import dataclass +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 @@ -18,6 +20,7 @@ class DynamoDB: self.__threading_call__(self.__list_tables__) self.__describe_table__() self.__describe_continuous_backups__() + self.__list_tags_for_resource__() def __get_session__(self): return self.session @@ -94,6 +97,20 @@ class DynamoDB: f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" ) + def __list_tags_for_resource__(self): + logger.info("DynamoDB - List Tags...") + try: + for table in self.tables: + regional_client = self.regional_clients[table.region] + response = regional_client.list_tags_of_resource(ResourceArn=table.arn)[ + "Tags" + ] + table.tags = response + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + ################## DynamoDB DAX class DAX: @@ -105,6 +122,7 @@ class DAX: self.regional_clients = generate_regional_clients(self.service, audit_info) self.clusters = [] self.__threading_call__(self.__describe_clusters__) + self.__list_tags_for_resource__() def __get_session__(self): return self.session @@ -137,10 +155,10 @@ class DAX: encryption = True self.clusters.append( Cluster( - cluster["ClusterArn"], - cluster["ClusterName"], - encryption, - regional_client.region, + arn=cluster["ClusterArn"], + name=cluster["ClusterName"], + encryption=encryption, + region=regional_client.region, ) ) except Exception as error: @@ -148,47 +166,32 @@ class DAX: f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags_for_resource__(self): + logger.info("DAX - List Tags...") + try: + for cluster in self.clusters: + regional_client = self.regional_clients[cluster.region] + response = regional_client.list_tags(ResourceName=cluster.name)["Tags"] + cluster.tags = response + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) -@dataclass -class Table: + +class Table(BaseModel): arn: str name: str - encryption_type: str - kms_arn: str - pitr: bool + encryption_type: Optional[str] + kms_arn: Optional[str] + pitr: bool = False region: str - - def __init__( - self, - arn, - name, - encryption_type, - kms_arn, - region, - ): - self.arn = arn - self.name = name - self.encryption_type = encryption_type - self.kms_arn = kms_arn - self.pitr = False - self.region = region + tags: Optional[list] = [] -@dataclass -class Cluster: +class Cluster(BaseModel): arn: str name: str - encryption: str + encryption: bool region: str - - def __init__( - self, - arn, - name, - encryption, - region, - ): - self.arn = arn - self.name = name - self.encryption = encryption - self.region = region + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled.py b/prowler/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled.py index 6fa68648..30a0ffe7 100644 --- a/prowler/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled.py +++ b/prowler/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled.py @@ -9,6 +9,7 @@ class dynamodb_tables_kms_cmk_encryption_enabled(Check): report = Check_Report_AWS(self.metadata()) report.resource_id = table.name report.resource_arn = table.arn + report.resource_tags = table.tags report.region = table.region report.status = "FAIL" report.status_extended = ( diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_tables_pitr_enabled/dynamodb_tables_pitr_enabled.py b/prowler/providers/aws/services/dynamodb/dynamodb_tables_pitr_enabled/dynamodb_tables_pitr_enabled.py index 048b516e..ff109d52 100644 --- a/prowler/providers/aws/services/dynamodb/dynamodb_tables_pitr_enabled/dynamodb_tables_pitr_enabled.py +++ b/prowler/providers/aws/services/dynamodb/dynamodb_tables_pitr_enabled/dynamodb_tables_pitr_enabled.py @@ -9,6 +9,7 @@ class dynamodb_tables_pitr_enabled(Check): report = Check_Report_AWS(self.metadata()) report.resource_id = table.name report.resource_arn = table.arn + report.resource_tags = table.tags report.region = table.region report.status = "FAIL" report.status_extended = f"DynamoDB table {table.name} does not have point-in-time recovery enabled." diff --git a/tests/providers/aws/services/cloudformation/cloudformation_service_test.py b/tests/providers/aws/services/cloudformation/cloudformation_service_test.py index f5ae21ef..cd8697a3 100644 --- a/tests/providers/aws/services/cloudformation/cloudformation_service_test.py +++ b/tests/providers/aws/services/cloudformation/cloudformation_service_test.py @@ -210,3 +210,7 @@ class Test_CloudFormation_Service: assert cloudformation.stacks[0].is_nested_stack is False assert cloudformation.stacks[0].root_nested_stack == "" assert cloudformation.stacks[0].region == AWS_REGION + assert cloudformation.stacks[0].tags == [ + {"Key": "Tag1", "Value": "Value1"}, + {"Key": "Tag2", "Value": "Value2"}, + ] diff --git a/tests/providers/aws/services/cloudformation/cloudformation_stacks_termination_protection_enabled/cloudformation_stacks_termination_protection_enabled_test.py b/tests/providers/aws/services/cloudformation/cloudformation_stacks_termination_protection_enabled/cloudformation_stacks_termination_protection_enabled_test.py index a40bce4b..03bb08f3 100644 --- a/tests/providers/aws/services/cloudformation/cloudformation_stacks_termination_protection_enabled/cloudformation_stacks_termination_protection_enabled_test.py +++ b/tests/providers/aws/services/cloudformation/cloudformation_stacks_termination_protection_enabled/cloudformation_stacks_termination_protection_enabled_test.py @@ -31,7 +31,7 @@ class Test_cloudformation_stacks_termination_protection_enabled: Stack( arn="arn:aws:cloudformation:eu-west-1:123456789012:stack/Test-Stack/796c8d26-b390-41d7-a23c-0702c4e78b60", name=stack_name, - outputs="", + outputs=[], region=AWS_REGION, ) ] @@ -68,7 +68,7 @@ class Test_cloudformation_stacks_termination_protection_enabled: Stack( arn="arn:aws:cloudformation:eu-west-1:123456789012:stack/Test-Stack/796c8d26-b390-41d7-a23c-0702c4e78b60", name=stack_name, - outputs="", + outputs=[], region=AWS_REGION, ) ] diff --git a/tests/providers/aws/services/cloudfront/cloudfront_service_test.py b/tests/providers/aws/services/cloudfront/cloudfront_service_test.py index f1ec5fb1..8f360339 100644 --- a/tests/providers/aws/services/cloudfront/cloudfront_service_test.py +++ b/tests/providers/aws/services/cloudfront/cloudfront_service_test.py @@ -133,6 +133,14 @@ def mock_make_api_call(self, operation_name, kwarg): }, "ETag": "", } + if operation_name == "ListTagsForResource": + return { + "Tags": { + "Items": [ + {"Key": "test", "Value": "test"}, + ] + } + } return make_api_call(self, operation_name, kwarg) @@ -247,3 +255,7 @@ class Test_CloudFront_Service: ].default_cache_config.field_level_encryption_id == "enabled" ) + + assert cloudfront.distributions[cloudfront_distribution_id].tags == [ + {"Key": "test", "Value": "test"}, + ] diff --git a/tests/providers/aws/services/cloudtrail/cloudtrail_service_test.py b/tests/providers/aws/services/cloudtrail/cloudtrail_service_test.py index f7fe02cd..2ef1f29d 100644 --- a/tests/providers/aws/services/cloudtrail/cloudtrail_service_test.py +++ b/tests/providers/aws/services/cloudtrail/cloudtrail_service_test.py @@ -63,7 +63,6 @@ class Test_Cloudtrail_Service: @mock_cloudtrail @mock_s3 def test_describe_trails(self): - cloudtrail_client_us_east_1 = client("cloudtrail", region_name="us-east-1") s3_client_us_east_1 = client("s3", region_name="us-east-1") cloudtrail_client_eu_west_1 = client("cloudtrail", region_name="eu-west-1") @@ -78,10 +77,20 @@ class Test_Cloudtrail_Service: CreateBucketConfiguration={"LocationConstraint": "eu-west-1"}, ) cloudtrail_client_us_east_1.create_trail( - Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + Name=trail_name_us, + S3BucketName=bucket_name_us, + IsMultiRegionTrail=False, + TagsList=[ + {"Key": "test", "Value": "test"}, + ], ) cloudtrail_client_eu_west_1.create_trail( - Name=trail_name_eu, S3BucketName=bucket_name_eu, IsMultiRegionTrail=False + Name=trail_name_eu, + S3BucketName=bucket_name_eu, + IsMultiRegionTrail=False, + TagsList=[ + {"Key": "test", "Value": "test"}, + ], ) audit_info = self.set_mocked_audit_info() cloudtrail = Cloudtrail(audit_info) @@ -101,6 +110,9 @@ class Test_Cloudtrail_Service: trail.s3_bucket == bucket_name_eu or trail.s3_bucket == bucket_name_us ) + assert trail.tags == [ + {"Key": "test", "Value": "test"}, + ] @mock_cloudtrail @mock_s3 diff --git a/tests/providers/aws/services/codeartifact/codeartifact_service_test.py b/tests/providers/aws/services/codeartifact/codeartifact_service_test.py index be01c1d4..0985935c 100644 --- a/tests/providers/aws/services/codeartifact/codeartifact_service_test.py +++ b/tests/providers/aws/services/codeartifact/codeartifact_service_test.py @@ -72,6 +72,13 @@ def mock_make_api_call(self, operation_name, kwarg): ], } + if operation_name == "ListTagsForResource": + return { + "tags": [ + {"key": "test", "value": "test"}, + ] + } + return make_api_call(self, operation_name, kwarg) @@ -116,6 +123,9 @@ class Test_CodeArtifact_Service: assert codeartifact.repositories assert codeartifact.repositories["test-repository"] assert codeartifact.repositories["test-repository"].name == "test-repository" + assert codeartifact.repositories["test-repository"].tags == [ + {"key": "test", "value": "test"}, + ] assert ( codeartifact.repositories["test-repository"].arn == f"arn:aws:codebuild:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:repository/test-repository" diff --git a/tests/providers/aws/services/config/config_recorder_all_regions_enabled/config_recorder_all_regions_enabled_test.py b/tests/providers/aws/services/config/config_recorder_all_regions_enabled/config_recorder_all_regions_enabled_test.py index 92d5a52c..88ecf1f9 100644 --- a/tests/providers/aws/services/config/config_recorder_all_regions_enabled/config_recorder_all_regions_enabled_test.py +++ b/tests/providers/aws/services/config/config_recorder_all_regions_enabled/config_recorder_all_regions_enabled_test.py @@ -13,6 +13,7 @@ class Test_config_recorder_all_regions_enabled: from prowler.providers.aws.services.config.config_service import Config current_audit_info.audited_partition = "aws" + current_audit_info.audited_account = "012345678912" current_audit_info.audited_regions = ["eu-west-1", "us-east-1"] with mock.patch( @@ -44,7 +45,7 @@ class Test_config_recorder_all_regions_enabled: from prowler.providers.aws.services.config.config_service import Config current_audit_info.audited_partition = "aws" - current_audit_info.audited_regions = ["eu-west-1", "us-east-1"] + current_audit_info.audited_regions = [AWS_REGION] with mock.patch( "prowler.providers.aws.services.config.config_recorder_all_regions_enabled.config_recorder_all_regions_enabled.config_client", @@ -57,7 +58,7 @@ class Test_config_recorder_all_regions_enabled: check = config_recorder_all_regions_enabled() result = check.execute() - assert len(result) == 2 + assert len(result) == 1 # Search for the recorder just created for recorder in result: if recorder.resource_id: @@ -85,7 +86,7 @@ class Test_config_recorder_all_regions_enabled: from prowler.providers.aws.services.config.config_service import Config current_audit_info.audited_partition = "aws" - current_audit_info.audited_regions = ["eu-west-1", "us-east-1"] + current_audit_info.audited_regions = [AWS_REGION] with mock.patch( "prowler.providers.aws.services.config.config_recorder_all_regions_enabled.config_recorder_all_regions_enabled.config_client", @@ -98,7 +99,7 @@ class Test_config_recorder_all_regions_enabled: check = config_recorder_all_regions_enabled() result = check.execute() - assert len(result) == 2 + assert len(result) == 1 # Search for the recorder just created for recorder in result: if recorder.resource_id: diff --git a/tests/providers/aws/services/directoryservice/directoryservice_service_test.py b/tests/providers/aws/services/directoryservice/directoryservice_service_test.py index 3163a9cd..4481f21d 100644 --- a/tests/providers/aws/services/directoryservice/directoryservice_service_test.py +++ b/tests/providers/aws/services/directoryservice/directoryservice_service_test.py @@ -92,7 +92,12 @@ def mock_make_api_call(self, operation_name, kwarg): "ManualSnapshotsLimitReached": True, } } - + if operation_name == "ListTagsForResource": + return { + "Tags": [ + {"Key": "string", "Value": "string"}, + ], + } return make_api_call(self, operation_name, kwarg) @@ -145,6 +150,9 @@ class Test_DirectoryService_Service: ) assert directoryservice.directories["d-12345a1b2"].name == "test-directory" assert directoryservice.directories["d-12345a1b2"].region == AWS_REGION + assert directoryservice.directories["d-12345a1b2"].tags == [ + {"Key": "string", "Value": "string"}, + ] assert ( directoryservice.directories[ "d-12345a1b2" diff --git a/tests/providers/aws/services/dynamodb/dynamodb_service_test.py b/tests/providers/aws/services/dynamodb/dynamodb_service_test.py index a1386b99..9af6c27a 100644 --- a/tests/providers/aws/services/dynamodb/dynamodb_service_test.py +++ b/tests/providers/aws/services/dynamodb/dynamodb_service_test.py @@ -121,6 +121,9 @@ class Test_DynamoDB_Service: {"AttributeName": "app", "KeyType": "RANGE"}, ], BillingMode="PAY_PER_REQUEST", + Tags=[ + {"Key": "test", "Value": "test"}, + ], )["TableDescription"] # DynamoDB client for this test class audit_info = self.set_mocked_audit_info() @@ -129,6 +132,9 @@ class Test_DynamoDB_Service: assert dynamo.tables[0].arn == table["TableArn"] assert dynamo.tables[0].name == "test1" assert dynamo.tables[0].region == AWS_REGION + assert dynamo.tables[0].tags == [ + {"Key": "test", "Value": "test"}, + ] # Test DynamoDB Describe Table @mock_dynamodb @@ -174,6 +180,9 @@ class Test_DynamoDB_Service: ReplicationFactor=3, IamRoleArn=iam_role_arn, SSESpecification={"Enabled": True}, + Tags=[ + {"Key": "test", "Value": "test"}, + ], ) dax_client.create_cluster( ClusterName="daxcluster2", @@ -181,6 +190,9 @@ class Test_DynamoDB_Service: ReplicationFactor=3, IamRoleArn=iam_role_arn, SSESpecification={"Enabled": True}, + Tags=[ + {"Key": "test", "Value": "test"}, + ], ) # DAX client for this test class audit_info = self.set_mocked_audit_info() @@ -190,3 +202,9 @@ class Test_DynamoDB_Service: assert dax.clusters[1].name == "daxcluster2" assert dax.clusters[0].region == AWS_REGION assert dax.clusters[1].region == AWS_REGION + assert dax.clusters[0].tags == [ + {"Key": "test", "Value": "test"}, + ] + assert dax.clusters[1].tags == [ + {"Key": "test", "Value": "test"}, + ]