From e8a1378ad0943cc0e80bcfd4381a20645b5bbb99 Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Thu, 2 Mar 2023 13:56:22 +0100 Subject: [PATCH] feat(tags): add resource tags to G-R services (#2009) --- .../aws/services/glacier/glacier_service.py | 17 ++++++++++ .../glacier_vaults_policy_public_access.py | 4 +-- .../guardduty_is_enabled.py | 1 + .../guardduty_no_high_severity_findings.py | 1 + .../services/guardduty/guardduty_service.py | 27 ++++++++++++++-- .../iam_disable_30_days_credentials.py | 1 + .../iam_disable_45_days_credentials.py | 1 + .../iam_disable_90_days_credentials.py | 1 + ...ross_service_confused_deputy_prevention.py | 1 + .../providers/aws/services/iam/iam_service.py | 22 +++++++++++++ .../iam_user_hardware_mfa_enabled.py | 1 + .../kms/kms_cmk_are_used/kms_cmk_are_used.py | 1 + .../kms_cmk_rotation_enabled.py | 1 + .../kms_key_not_publicly_accessible.py | 1 + .../providers/aws/services/kms/kms_service.py | 16 ++++++++++ .../aws/services/macie/macie_service.py | 22 ++++--------- .../services/opensearch/opensearch_service.py | 17 ++++++++++ ...h_service_domains_audit_logging_enabled.py | 1 + ...vice_domains_cloudwatch_logging_enabled.py | 1 + ...vice_domains_encryption_at_rest_enabled.py | 1 + ...e_domains_https_communications_enforced.py | 1 + ..._domains_internal_user_database_enabled.py | 1 + ...domains_node_to_node_encryption_enabled.py | 1 + ...service_domains_not_publicly_accessible.py | 1 + ..._to_the_latest_service_software_version.py | 1 + ...s_use_cognito_authentication_for_kibana.py | 1 + .../rds_instance_backup_enabled.py | 1 + .../rds_instance_deletion_protection.py | 1 + ...ds_instance_enhanced_monitoring_enabled.py | 1 + ...ds_instance_integration_cloudwatch_logs.py | 1 + ..._instance_minor_version_upgrade_enabled.py | 1 + .../rds_instance_multi_az.py | 1 + .../rds_instance_no_public_access.py | 1 + .../rds_instance_storage_encrypted.py | 1 + .../providers/aws/services/rds/rds_service.py | 6 ++++ .../rds_snapshots_public_access.py | 2 ++ .../redshift_cluster_audit_logging.py | 1 + .../redshift_cluster_automated_snapshot.py | 1 + .../redshift_cluster_automatic_upgrades.py | 1 + .../redshift_cluster_public_access.py | 1 + .../aws/services/redshift/redshift_service.py | 3 ++ ...te53_domains_privacy_protection_enabled.py | 2 +- .../route53_domains_transferlock_enabled.py | 2 +- ...hosted_zones_cloudwatch_logging_enabled.py | 1 + .../aws/services/route53/route53_service.py | 32 +++++++++++++++++++ .../services/glacier/glacier_service_test.py | 4 +++ .../guardduty_is_enabled_test.py | 12 +++++-- ...uardduty_no_high_severity_findings_test.py | 7 +++- .../guardduty/guardduty_service_test.py | 5 ++- .../aws/services/iam/iam_service_test.py | 24 ++++++++++++++ .../aws/services/kms/kms_service_test.py | 9 +++++- .../macie_is_enabled/macie_is_enabled_test.py | 12 +++---- .../aws/services/macie/macie_service_test.py | 4 +-- .../opensearch/opensearch_service_test.py | 9 ++++++ .../aws/services/rds/rds_service_test.py | 6 ++++ .../redshift/redshift_service_test.py | 6 ++++ .../services/route53/route53_service_test.py | 14 +++++++- .../route53/route53domains_service_test.py | 9 ++++++ 58 files changed, 287 insertions(+), 38 deletions(-) diff --git a/prowler/providers/aws/services/glacier/glacier_service.py b/prowler/providers/aws/services/glacier/glacier_service.py index cb0a40ae..3a28b712 100644 --- a/prowler/providers/aws/services/glacier/glacier_service.py +++ b/prowler/providers/aws/services/glacier/glacier_service.py @@ -1,5 +1,6 @@ import json import threading +from typing import Optional from botocore.client import ClientError from pydantic import BaseModel @@ -20,6 +21,7 @@ class Glacier: self.vaults = {} self.__threading_call__(self.__list_vaults__) self.__threading_call__(self.__get_vault_access_policy__) + self.__list_tags_for_vault__() def __get_session__(self): return self.session @@ -79,9 +81,24 @@ class Glacier: f" {error}" ) + def __list_tags_for_vault__(self): + logger.info("Glacier - List Tags...") + try: + for vault in self.vaults.values(): + regional_client = self.regional_clients[vault.region] + response = regional_client.list_tags_for_vault(vaultName=vault.name)[ + "Tags" + ] + vault.tags = [response] + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class Vault(BaseModel): name: str arn: str region: str access_policy: dict = {} + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access.py b/prowler/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access.py index b64cc16e..321b2c7c 100644 --- a/prowler/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access.py +++ b/prowler/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access.py @@ -10,7 +10,7 @@ class glacier_vaults_policy_public_access(Check): report.region = vault.region report.resource_id = vault.name report.resource_arn = vault.arn - + report.resource_tags = vault.tags report.status = "PASS" report.status_extended = ( f"Vault {vault.name} has policy which does not allow access to everyone" @@ -19,10 +19,8 @@ class glacier_vaults_policy_public_access(Check): public_access = False if vault.access_policy: for statement in vault.access_policy["Statement"]: - # Only check allow statements if statement["Effect"] == "Allow": - if ( "*" in statement["Principal"] or ( diff --git a/prowler/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled.py b/prowler/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled.py index 0ce6b959..d7a30944 100644 --- a/prowler/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled.py +++ b/prowler/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled.py @@ -10,6 +10,7 @@ class guardduty_is_enabled(Check): report.region = detector.region report.resource_id = detector.id report.resource_arn = detector.arn + report.resource_tags = detector.tags report.status = "PASS" report.status_extended = f"GuardDuty detector {detector.id} enabled" if detector.status is None: diff --git a/prowler/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings.py b/prowler/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings.py index 7a320478..67eb5b88 100644 --- a/prowler/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings.py +++ b/prowler/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings.py @@ -10,6 +10,7 @@ class guardduty_no_high_severity_findings(Check): report.region = detector.region report.resource_id = detector.id report.resource_arn = detector.arn + report.resource_tags = detector.tags report.status = "PASS" report.status_extended = f"GuardDuty detector {detector.id} does not have high severity findings." if len(detector.findings) > 0: diff --git a/prowler/providers/aws/services/guardduty/guardduty_service.py b/prowler/providers/aws/services/guardduty/guardduty_service.py index c711155a..c9344ea6 100644 --- a/prowler/providers/aws/services/guardduty/guardduty_service.py +++ b/prowler/providers/aws/services/guardduty/guardduty_service.py @@ -1,4 +1,5 @@ import threading +from typing import Optional from pydantic import BaseModel @@ -12,12 +13,15 @@ class GuardDuty: def __init__(self, audit_info): self.service = "guardduty" self.session = audit_info.audit_session + self.audited_account = audit_info.audited_account self.audit_resources = audit_info.audit_resources + self.audited_partition = audit_info.audited_partition self.regional_clients = generate_regional_clients(self.service, audit_info) self.detectors = [] self.__threading_call__(self.__list_detectors__) self.__get_detector__(self.regional_clients) self.__list_findings__(self.regional_clients) + self.__list_tags_for_resource__() def __get_session__(self): return self.session @@ -40,8 +44,11 @@ class GuardDuty: if not self.audit_resources or ( is_resource_filtered(detector, self.audit_resources) ): + arn = f"arn:{self.audited_partition}:guardduty:{regional_client.region}:{self.audited_account}:detector/{detector}" self.detectors.append( - Detector(id=detector, region=regional_client.region) + Detector( + id=detector, arn=arn, region=regional_client.region + ) ) except Exception as error: logger.error( @@ -93,11 +100,25 @@ class GuardDuty: f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags_for_resource__(self): + logger.info("Guardduty - List Tags...") + try: + for detector in self.detectors: + regional_client = self.regional_clients[detector.region] + response = regional_client.list_tags_for_resource( + ResourceArn=detector.arn + )["Tags"] + detector.tags = [response] + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class Detector(BaseModel): id: str - # there is no arn for a guardduty detector but we want it filled for the reports - arn: str = "" + arn: str region: str status: bool = None findings: list = [] + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials.py b/prowler/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials.py index e5627d44..846eae26 100644 --- a/prowler/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials.py +++ b/prowler/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials.py @@ -13,6 +13,7 @@ class iam_disable_30_days_credentials(Check): report = Check_Report_AWS(self.metadata()) report.resource_id = user.name report.resource_arn = user.arn + report.resource_tags = user.tags report.region = iam_client.region if user.password_last_used: time_since_insertion = ( diff --git a/prowler/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials.py b/prowler/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials.py index fa723f54..0115e4b4 100644 --- a/prowler/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials.py +++ b/prowler/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials.py @@ -13,6 +13,7 @@ class iam_disable_45_days_credentials(Check): report = Check_Report_AWS(self.metadata()) report.resource_id = user.name report.resource_arn = user.arn + report.resource_tags = user.tags report.region = iam_client.region if user.password_last_used: time_since_insertion = ( diff --git a/prowler/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials.py b/prowler/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials.py index 6c453110..47350258 100644 --- a/prowler/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials.py +++ b/prowler/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials.py @@ -13,6 +13,7 @@ class iam_disable_90_days_credentials(Check): report = Check_Report_AWS(self.metadata()) report.resource_id = user.name report.resource_arn = user.arn + report.resource_tags = user.tags report.region = iam_client.region if user.password_last_used: time_since_insertion = ( diff --git a/prowler/providers/aws/services/iam/iam_role_cross_service_confused_deputy_prevention/iam_role_cross_service_confused_deputy_prevention.py b/prowler/providers/aws/services/iam/iam_role_cross_service_confused_deputy_prevention/iam_role_cross_service_confused_deputy_prevention.py index 1ab98c5d..9546b1f5 100644 --- a/prowler/providers/aws/services/iam/iam_role_cross_service_confused_deputy_prevention/iam_role_cross_service_confused_deputy_prevention.py +++ b/prowler/providers/aws/services/iam/iam_role_cross_service_confused_deputy_prevention/iam_role_cross_service_confused_deputy_prevention.py @@ -12,6 +12,7 @@ class iam_role_cross_service_confused_deputy_prevention(Check): report.region = iam_client.region report.resource_arn = role.arn report.resource_id = role.name + report.resource_tags = role.tags report.status = "FAIL" report.status_extended = f"IAM Service Role {role.name} prevents against a cross-service confused deputy attack" for statement in role.assume_role_policy["Statement"]: diff --git a/prowler/providers/aws/services/iam/iam_service.py b/prowler/providers/aws/services/iam/iam_service.py index 6e5edc2b..4d2cf5e5 100644 --- a/prowler/providers/aws/services/iam/iam_service.py +++ b/prowler/providers/aws/services/iam/iam_service.py @@ -59,6 +59,7 @@ class IAM: self.__list_policies_version__(self.policies) self.saml_providers = self.__list_saml_providers__() self.server_certificates = self.__list_server_certificates__() + self.__list_tags_for_resource__() def __get_client__(self): return self.client @@ -408,6 +409,25 @@ class IAM: finally: return server_certificates + def __list_tags_for_resource__(self): + logger.info("IAM - List Tags...") + try: + for role in self.roles: + response = self.client.list_role_tags(RoleName=role.name)["Tags"] + role.tags = response + except Exception as error: + logger.error( + f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + try: + for user in self.users: + response = self.client.list_user_tags(UserName=user.name)["Tags"] + user.tags = response + except Exception as error: + logger.error( + f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class MFADevice(BaseModel): serial_number: str @@ -421,6 +441,7 @@ class User(BaseModel): password_last_used: Optional[datetime] attached_policies: list[dict] = [] inline_policies: list[str] = [] + tags: Optional[list] = [] class Role(BaseModel): @@ -428,6 +449,7 @@ class Role(BaseModel): arn: str assume_role_policy: dict is_service_role: bool + tags: Optional[list] = [] class Group(BaseModel): diff --git a/prowler/providers/aws/services/iam/iam_user_hardware_mfa_enabled/iam_user_hardware_mfa_enabled.py b/prowler/providers/aws/services/iam/iam_user_hardware_mfa_enabled/iam_user_hardware_mfa_enabled.py index 94d03759..2507bc29 100644 --- a/prowler/providers/aws/services/iam/iam_user_hardware_mfa_enabled/iam_user_hardware_mfa_enabled.py +++ b/prowler/providers/aws/services/iam/iam_user_hardware_mfa_enabled/iam_user_hardware_mfa_enabled.py @@ -11,6 +11,7 @@ class iam_user_hardware_mfa_enabled(Check): report = Check_Report_AWS(self.metadata()) report.resource_id = user.name report.resource_arn = user.arn + report.resource_tags = user.tags report.region = iam_client.region if user.mfa_devices: report.status = "PASS" diff --git a/prowler/providers/aws/services/kms/kms_cmk_are_used/kms_cmk_are_used.py b/prowler/providers/aws/services/kms/kms_cmk_are_used/kms_cmk_are_used.py index 2726577f..20653cb5 100644 --- a/prowler/providers/aws/services/kms/kms_cmk_are_used/kms_cmk_are_used.py +++ b/prowler/providers/aws/services/kms/kms_cmk_are_used/kms_cmk_are_used.py @@ -12,6 +12,7 @@ class kms_cmk_are_used(Check): report.region = key.region report.resource_id = key.id report.resource_arn = key.arn + report.resource_tags = key.tags if key.state != "Enabled": if key.state == "PendingDeletion": report.status = "PASS" diff --git a/prowler/providers/aws/services/kms/kms_cmk_rotation_enabled/kms_cmk_rotation_enabled.py b/prowler/providers/aws/services/kms/kms_cmk_rotation_enabled/kms_cmk_rotation_enabled.py index 38fa0563..0a002231 100644 --- a/prowler/providers/aws/services/kms/kms_cmk_rotation_enabled/kms_cmk_rotation_enabled.py +++ b/prowler/providers/aws/services/kms/kms_cmk_rotation_enabled/kms_cmk_rotation_enabled.py @@ -8,6 +8,7 @@ class kms_cmk_rotation_enabled(Check): for key in kms_client.keys: report = Check_Report_AWS(self.metadata()) report.region = key.region + report.resource_tags = key.tags # Only check enabled CMKs keys if ( key.manager == "CUSTOMER" diff --git a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py index e14405da..6e6714d1 100644 --- a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py +++ b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py @@ -14,6 +14,7 @@ class kms_key_not_publicly_accessible(Check): report.status_extended = f"KMS key {key.id} is not exposed to Public." report.resource_id = key.id report.resource_arn = key.arn + report.resource_tags = key.tags report.region = key.region # If the "Principal" element value is set to { "AWS": "*" } and the policy statement is not using any Condition clauses to filter the access, the selected AWS KMS master key is publicly accessible. if key.policy and "Statement" in key.policy: diff --git a/prowler/providers/aws/services/kms/kms_service.py b/prowler/providers/aws/services/kms/kms_service.py index a8d9ad4f..b363ae01 100644 --- a/prowler/providers/aws/services/kms/kms_service.py +++ b/prowler/providers/aws/services/kms/kms_service.py @@ -23,6 +23,7 @@ class KMS: self.__describe_key__() self.__get_key_rotation_status__() self.__get_key_policy__() + self.__list_resource_tags__() def __get_session__(self): return self.session @@ -109,6 +110,20 @@ class KMS: f"{regional_client.region} -- {error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" ) + def __list_resource_tags__(self): + logger.info("KMS - List Tags...") + for key in self.keys: + try: + regional_client = self.regional_clients[key.region] + response = regional_client.list_resource_tags( + KeyId=key.id, + )["Tags"] + key.tags = response + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class Key(BaseModel): id: str @@ -120,3 +135,4 @@ class Key(BaseModel): policy: Optional[dict] spec: Optional[str] region: str + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/macie/macie_service.py b/prowler/providers/aws/services/macie/macie_service.py index 4cbc8b27..2c444270 100644 --- a/prowler/providers/aws/services/macie/macie_service.py +++ b/prowler/providers/aws/services/macie/macie_service.py @@ -1,5 +1,6 @@ import threading -from dataclasses import dataclass + +from pydantic import BaseModel from prowler.lib.logger import logger from prowler.providers.aws.aws_provider import generate_regional_clients @@ -32,8 +33,8 @@ class Macie: try: self.sessions.append( Session( - regional_client.get_macie_session()["status"], - regional_client.region, + status=regional_client.get_macie_session()["status"], + region=regional_client.region, ) ) @@ -41,8 +42,8 @@ class Macie: if "Macie is not enabled" in str(error): self.sessions.append( Session( - "DISABLED", - regional_client.region, + status="DISABLED", + region=regional_client.region, ) ) else: @@ -51,15 +52,6 @@ class Macie: ) -@dataclass -class Session: +class Session(BaseModel): status: str region: str - - def __init__( - self, - status, - region, - ): - self.status = status - self.region = region diff --git a/prowler/providers/aws/services/opensearch/opensearch_service.py b/prowler/providers/aws/services/opensearch/opensearch_service.py index ce38ed4f..a406bd78 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service.py @@ -1,5 +1,6 @@ import threading from json import loads +from typing import Optional from pydantic import BaseModel @@ -19,6 +20,7 @@ class OpenSearchService: self.__threading_call__(self.__list_domain_names__) self.__describe_domain_config__(self.regional_clients) self.__describe_domain__(self.regional_clients) + self.__list_tags__() def __get_session__(self): return self.session @@ -129,6 +131,20 @@ class OpenSearchService: f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags__(self): + logger.info("OpenSearch - List Tags...") + for domain in self.opensearch_domains: + try: + regional_client = self.regional_clients[domain.region] + response = regional_client.list_tags( + ARN=domain.arn, + )["TagList"] + domain.tags = response + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class PublishingLoggingOption(BaseModel): name: str @@ -150,3 +166,4 @@ class OpenSearchDomain(BaseModel): internal_user_database: bool = None update_available: bool = None version: str = None + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_audit_logging_enabled/opensearch_service_domains_audit_logging_enabled.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_audit_logging_enabled/opensearch_service_domains_audit_logging_enabled.py index 2af9040c..32fec778 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_audit_logging_enabled/opensearch_service_domains_audit_logging_enabled.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_audit_logging_enabled/opensearch_service_domains_audit_logging_enabled.py @@ -12,6 +12,7 @@ class opensearch_service_domains_audit_logging_enabled(Check): report.region = domain.region report.resource_id = domain.name report.resource_arn = domain.arn + report.resource_tags = domain.tags report.status = "FAIL" report.status_extended = ( f"Opensearch domain {domain.name} AUDIT_LOGS disabled" diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_cloudwatch_logging_enabled/opensearch_service_domains_cloudwatch_logging_enabled.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_cloudwatch_logging_enabled/opensearch_service_domains_cloudwatch_logging_enabled.py index 7880ec3a..0c954b1a 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_cloudwatch_logging_enabled/opensearch_service_domains_cloudwatch_logging_enabled.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_cloudwatch_logging_enabled/opensearch_service_domains_cloudwatch_logging_enabled.py @@ -12,6 +12,7 @@ class opensearch_service_domains_cloudwatch_logging_enabled(Check): report.region = domain.region report.resource_id = domain.name report.resource_arn = domain.arn + report.resource_tags = domain.tags report.status = "FAIL" report.status_extended = f"Opensearch domain {domain.name} SEARCH_SLOW_LOGS and INDEX_SLOW_LOGS disabled" has_SEARCH_SLOW_LOGS = False diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_encryption_at_rest_enabled/opensearch_service_domains_encryption_at_rest_enabled.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_encryption_at_rest_enabled/opensearch_service_domains_encryption_at_rest_enabled.py index 3a8bfb44..a4ef1e61 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_encryption_at_rest_enabled/opensearch_service_domains_encryption_at_rest_enabled.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_encryption_at_rest_enabled/opensearch_service_domains_encryption_at_rest_enabled.py @@ -12,6 +12,7 @@ class opensearch_service_domains_encryption_at_rest_enabled(Check): report.region = domain.region report.resource_id = domain.name report.resource_arn = domain.arn + report.resource_tags = domain.tags report.status = "PASS" report.status_extended = ( f"Opensearch domain {domain.name} has encryption at-rest enabled" diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_https_communications_enforced/opensearch_service_domains_https_communications_enforced.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_https_communications_enforced/opensearch_service_domains_https_communications_enforced.py index bc346889..32b15cf1 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_https_communications_enforced/opensearch_service_domains_https_communications_enforced.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_https_communications_enforced/opensearch_service_domains_https_communications_enforced.py @@ -12,6 +12,7 @@ class opensearch_service_domains_https_communications_enforced(Check): report.region = domain.region report.resource_id = domain.name report.resource_arn = domain.arn + report.resource_tags = domain.tags report.status = "PASS" report.status_extended = ( f"Opensearch domain {domain.name} has enforce HTTPS enabled" diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_internal_user_database_enabled/opensearch_service_domains_internal_user_database_enabled.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_internal_user_database_enabled/opensearch_service_domains_internal_user_database_enabled.py index 1041ba5f..a571b423 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_internal_user_database_enabled/opensearch_service_domains_internal_user_database_enabled.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_internal_user_database_enabled/opensearch_service_domains_internal_user_database_enabled.py @@ -12,6 +12,7 @@ class opensearch_service_domains_internal_user_database_enabled(Check): report.region = domain.region report.resource_id = domain.name report.resource_arn = domain.arn + report.resource_tags = domain.tags report.status = "PASS" report.status_extended = f"Opensearch domain {domain.name} does not have internal user database enabled" if domain.internal_user_database: diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_node_to_node_encryption_enabled/opensearch_service_domains_node_to_node_encryption_enabled.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_node_to_node_encryption_enabled/opensearch_service_domains_node_to_node_encryption_enabled.py index 054095b8..875604b1 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_node_to_node_encryption_enabled/opensearch_service_domains_node_to_node_encryption_enabled.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_node_to_node_encryption_enabled/opensearch_service_domains_node_to_node_encryption_enabled.py @@ -12,6 +12,7 @@ class opensearch_service_domains_node_to_node_encryption_enabled(Check): report.region = domain.region report.resource_id = domain.name report.resource_arn = domain.arn + report.resource_tags = domain.tags report.status = "PASS" report.status_extended = ( f"Opensearch domain {domain.name} has node-to-node encryption enabled" diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py index 174f5cce..858a9168 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py @@ -12,6 +12,7 @@ class opensearch_service_domains_not_publicly_accessible(Check): report.region = domain.region report.resource_id = domain.name report.resource_arn = domain.arn + report.resource_tags = domain.tags report.status = "PASS" report.status_extended = ( f"Opensearch domain {domain.name} does not allow anonymous access" diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_updated_to_the_latest_service_software_version/opensearch_service_domains_updated_to_the_latest_service_software_version.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_updated_to_the_latest_service_software_version/opensearch_service_domains_updated_to_the_latest_service_software_version.py index 260b530d..bc1b4aee 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_updated_to_the_latest_service_software_version/opensearch_service_domains_updated_to_the_latest_service_software_version.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_updated_to_the_latest_service_software_version/opensearch_service_domains_updated_to_the_latest_service_software_version.py @@ -12,6 +12,7 @@ class opensearch_service_domains_updated_to_the_latest_service_software_version( report.region = domain.region report.resource_id = domain.name report.resource_arn = domain.arn + report.resource_tags = domain.tags report.status = "PASS" report.status_extended = f"Opensearch domain {domain.name} with version {domain.version} does not have internal updates available" if domain.update_available: diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_use_cognito_authentication_for_kibana/opensearch_service_domains_use_cognito_authentication_for_kibana.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_use_cognito_authentication_for_kibana/opensearch_service_domains_use_cognito_authentication_for_kibana.py index 4c1f82dc..48b7ae73 100644 --- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_use_cognito_authentication_for_kibana/opensearch_service_domains_use_cognito_authentication_for_kibana.py +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_use_cognito_authentication_for_kibana/opensearch_service_domains_use_cognito_authentication_for_kibana.py @@ -12,6 +12,7 @@ class opensearch_service_domains_use_cognito_authentication_for_kibana(Check): report.region = domain.region report.resource_id = domain.name report.resource_arn = domain.arn + report.resource_tags = domain.tags report.status = "PASS" report.status_extended = f"Opensearch domain {domain.name} has Amazon Cognito authentication for Kibana enabled" if not domain.cognito_options: diff --git a/prowler/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled.py b/prowler/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled.py index 8048c085..8e141edb 100644 --- a/prowler/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled.py +++ b/prowler/providers/aws/services/rds/rds_instance_backup_enabled/rds_instance_backup_enabled.py @@ -9,6 +9,7 @@ class rds_instance_backup_enabled(Check): report = Check_Report_AWS(self.metadata()) report.region = db_instance.region report.resource_id = db_instance.id + report.resource_tags = db_instance.tags if db_instance.backup_retention_period > 0: report.status = "PASS" report.status_extended = f"RDS Instance {db_instance.id} has backup enabled with retention period {db_instance.backup_retention_period} days." diff --git a/prowler/providers/aws/services/rds/rds_instance_deletion_protection/rds_instance_deletion_protection.py b/prowler/providers/aws/services/rds/rds_instance_deletion_protection/rds_instance_deletion_protection.py index fd6779c3..4d21002d 100644 --- a/prowler/providers/aws/services/rds/rds_instance_deletion_protection/rds_instance_deletion_protection.py +++ b/prowler/providers/aws/services/rds/rds_instance_deletion_protection/rds_instance_deletion_protection.py @@ -9,6 +9,7 @@ class rds_instance_deletion_protection(Check): report = Check_Report_AWS(self.metadata()) report.region = db_instance.region report.resource_id = db_instance.id + report.resource_tags = db_instance.tags if db_instance.deletion_protection: report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/rds/rds_instance_enhanced_monitoring_enabled/rds_instance_enhanced_monitoring_enabled.py b/prowler/providers/aws/services/rds/rds_instance_enhanced_monitoring_enabled/rds_instance_enhanced_monitoring_enabled.py index b3df5b49..a2ad2bf8 100644 --- a/prowler/providers/aws/services/rds/rds_instance_enhanced_monitoring_enabled/rds_instance_enhanced_monitoring_enabled.py +++ b/prowler/providers/aws/services/rds/rds_instance_enhanced_monitoring_enabled/rds_instance_enhanced_monitoring_enabled.py @@ -9,6 +9,7 @@ class rds_instance_enhanced_monitoring_enabled(Check): report = Check_Report_AWS(self.metadata()) report.region = db_instance.region report.resource_id = db_instance.id + report.resource_tags = db_instance.tags if db_instance.enhanced_monitoring_arn: report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/rds/rds_instance_integration_cloudwatch_logs/rds_instance_integration_cloudwatch_logs.py b/prowler/providers/aws/services/rds/rds_instance_integration_cloudwatch_logs/rds_instance_integration_cloudwatch_logs.py index 58a06183..cedb9b11 100644 --- a/prowler/providers/aws/services/rds/rds_instance_integration_cloudwatch_logs/rds_instance_integration_cloudwatch_logs.py +++ b/prowler/providers/aws/services/rds/rds_instance_integration_cloudwatch_logs/rds_instance_integration_cloudwatch_logs.py @@ -9,6 +9,7 @@ class rds_instance_integration_cloudwatch_logs(Check): report = Check_Report_AWS(self.metadata()) report.region = db_instance.region report.resource_id = db_instance.id + report.resource_tags = db_instance.tags if db_instance.cloudwatch_logs: report.status = "PASS" report.status_extended = f"RDS Instance {db_instance.id} is shipping {' '.join(db_instance.cloudwatch_logs)} to CloudWatch Logs." diff --git a/prowler/providers/aws/services/rds/rds_instance_minor_version_upgrade_enabled/rds_instance_minor_version_upgrade_enabled.py b/prowler/providers/aws/services/rds/rds_instance_minor_version_upgrade_enabled/rds_instance_minor_version_upgrade_enabled.py index 9d77c87e..65bca2e4 100644 --- a/prowler/providers/aws/services/rds/rds_instance_minor_version_upgrade_enabled/rds_instance_minor_version_upgrade_enabled.py +++ b/prowler/providers/aws/services/rds/rds_instance_minor_version_upgrade_enabled/rds_instance_minor_version_upgrade_enabled.py @@ -9,6 +9,7 @@ class rds_instance_minor_version_upgrade_enabled(Check): report = Check_Report_AWS(self.metadata()) report.region = db_instance.region report.resource_id = db_instance.id + report.resource_tags = db_instance.tags if db_instance.auto_minor_version_upgrade: report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/rds/rds_instance_multi_az/rds_instance_multi_az.py b/prowler/providers/aws/services/rds/rds_instance_multi_az/rds_instance_multi_az.py index 7fd2f386..004b0891 100644 --- a/prowler/providers/aws/services/rds/rds_instance_multi_az/rds_instance_multi_az.py +++ b/prowler/providers/aws/services/rds/rds_instance_multi_az/rds_instance_multi_az.py @@ -9,6 +9,7 @@ class rds_instance_multi_az(Check): report = Check_Report_AWS(self.metadata()) report.region = db_instance.region report.resource_id = db_instance.id + report.resource_tags = db_instance.tags if db_instance.multi_az: report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/rds/rds_instance_no_public_access/rds_instance_no_public_access.py b/prowler/providers/aws/services/rds/rds_instance_no_public_access/rds_instance_no_public_access.py index 4daf84da..86990eb5 100644 --- a/prowler/providers/aws/services/rds/rds_instance_no_public_access/rds_instance_no_public_access.py +++ b/prowler/providers/aws/services/rds/rds_instance_no_public_access/rds_instance_no_public_access.py @@ -9,6 +9,7 @@ class rds_instance_no_public_access(Check): report = Check_Report_AWS(self.metadata()) report.region = db_instance.region report.resource_id = db_instance.id + report.resource_tags = db_instance.tags if not db_instance.public: report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/rds/rds_instance_storage_encrypted/rds_instance_storage_encrypted.py b/prowler/providers/aws/services/rds/rds_instance_storage_encrypted/rds_instance_storage_encrypted.py index cc67d13d..bb6f2d7a 100644 --- a/prowler/providers/aws/services/rds/rds_instance_storage_encrypted/rds_instance_storage_encrypted.py +++ b/prowler/providers/aws/services/rds/rds_instance_storage_encrypted/rds_instance_storage_encrypted.py @@ -9,6 +9,7 @@ class rds_instance_storage_encrypted(Check): report = Check_Report_AWS(self.metadata()) report.region = db_instance.region report.resource_id = db_instance.id + report.resource_tags = db_instance.tags if db_instance.encrypted: report.status = "PASS" report.status_extended = f"RDS Instance {db_instance.id} is encrypted." diff --git a/prowler/providers/aws/services/rds/rds_service.py b/prowler/providers/aws/services/rds/rds_service.py index 727bd5cf..96705084 100644 --- a/prowler/providers/aws/services/rds/rds_service.py +++ b/prowler/providers/aws/services/rds/rds_service.py @@ -74,6 +74,7 @@ class RDS: ), multi_az=instance["MultiAZ"], region=regional_client.region, + tags=instance.get("TagList"), ) ) except Exception as error: @@ -100,6 +101,7 @@ class RDS: id=snapshot["DBSnapshotIdentifier"], instance_id=snapshot["DBInstanceIdentifier"], region=regional_client.region, + tags=snapshot.get("TagList"), ) ) except Exception as error: @@ -144,6 +146,7 @@ class RDS: id=snapshot["DBClusterSnapshotIdentifier"], cluster_id=snapshot["DBClusterIdentifier"], region=regional_client.region, + tags=snapshot.get("TagList"), ) ) except Exception as error: @@ -183,6 +186,7 @@ class DBInstance(BaseModel): enhanced_monitoring_arn: Optional[str] multi_az: bool region: str + tags: Optional[list] = [] class DBSnapshot(BaseModel): @@ -190,6 +194,7 @@ class DBSnapshot(BaseModel): instance_id: str public: bool = False region: str + tags: Optional[list] = [] class ClusterSnapshot(BaseModel): @@ -197,3 +202,4 @@ class ClusterSnapshot(BaseModel): cluster_id: str public: bool = False region: str + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/rds/rds_snapshots_public_access/rds_snapshots_public_access.py b/prowler/providers/aws/services/rds/rds_snapshots_public_access/rds_snapshots_public_access.py index 9afac973..d0e6e319 100644 --- a/prowler/providers/aws/services/rds/rds_snapshots_public_access/rds_snapshots_public_access.py +++ b/prowler/providers/aws/services/rds/rds_snapshots_public_access/rds_snapshots_public_access.py @@ -9,6 +9,7 @@ class rds_snapshots_public_access(Check): report = Check_Report_AWS(self.metadata()) report.region = db_snap.region report.resource_id = db_snap.id + report.resource_tags = db_snap.tags if db_snap.public: report.status = "FAIL" report.status_extended = ( @@ -26,6 +27,7 @@ class rds_snapshots_public_access(Check): report = Check_Report_AWS(self.metadata()) report.region = db_snap.region report.resource_id = db_snap.id + report.resource_tags = db_snap.tags if db_snap.public: report.status = "FAIL" report.status_extended = f"RDS Cluster Snapshot {db_snap.id} is public." diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_audit_logging/redshift_cluster_audit_logging.py b/prowler/providers/aws/services/redshift/redshift_cluster_audit_logging/redshift_cluster_audit_logging.py index c9cbf8b2..d2683ace 100644 --- a/prowler/providers/aws/services/redshift/redshift_cluster_audit_logging/redshift_cluster_audit_logging.py +++ b/prowler/providers/aws/services/redshift/redshift_cluster_audit_logging/redshift_cluster_audit_logging.py @@ -10,6 +10,7 @@ class redshift_cluster_audit_logging(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"Redshift Cluster {cluster.arn} has audit logging enabled" diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_automated_snapshot/redshift_cluster_automated_snapshot.py b/prowler/providers/aws/services/redshift/redshift_cluster_automated_snapshot/redshift_cluster_automated_snapshot.py index 5f91998f..b3298c37 100644 --- a/prowler/providers/aws/services/redshift/redshift_cluster_automated_snapshot/redshift_cluster_automated_snapshot.py +++ b/prowler/providers/aws/services/redshift/redshift_cluster_automated_snapshot/redshift_cluster_automated_snapshot.py @@ -10,6 +10,7 @@ class redshift_cluster_automated_snapshot(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"Redshift Cluster {cluster.arn} has automated snapshots" diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_automatic_upgrades/redshift_cluster_automatic_upgrades.py b/prowler/providers/aws/services/redshift/redshift_cluster_automatic_upgrades/redshift_cluster_automatic_upgrades.py index 733f6da6..6423ccf8 100644 --- a/prowler/providers/aws/services/redshift/redshift_cluster_automatic_upgrades/redshift_cluster_automatic_upgrades.py +++ b/prowler/providers/aws/services/redshift/redshift_cluster_automatic_upgrades/redshift_cluster_automatic_upgrades.py @@ -10,6 +10,7 @@ class redshift_cluster_automatic_upgrades(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"Redshift Cluster {cluster.arn} has AllowVersionUpgrade enabled" diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_public_access/redshift_cluster_public_access.py b/prowler/providers/aws/services/redshift/redshift_cluster_public_access/redshift_cluster_public_access.py index 49dcdfeb..e2d810ab 100644 --- a/prowler/providers/aws/services/redshift/redshift_cluster_public_access/redshift_cluster_public_access.py +++ b/prowler/providers/aws/services/redshift/redshift_cluster_public_access/redshift_cluster_public_access.py @@ -10,6 +10,7 @@ class redshift_cluster_public_access(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"Redshift Cluster {cluster.arn} is not publicly accessible" diff --git a/prowler/providers/aws/services/redshift/redshift_service.py b/prowler/providers/aws/services/redshift/redshift_service.py index c70219be..b88af24f 100644 --- a/prowler/providers/aws/services/redshift/redshift_service.py +++ b/prowler/providers/aws/services/redshift/redshift_service.py @@ -1,4 +1,5 @@ import threading +from typing import Optional from pydantic import BaseModel @@ -45,6 +46,7 @@ class Redshift: cluster_to_append = Cluster( id=cluster["ClusterIdentifier"], region=regional_client.region, + tags=cluster.get("Tags"), ) if ( "PubliclyAccessible" in cluster @@ -114,3 +116,4 @@ class Cluster(BaseModel): logging_enabled: bool = None bucket: str = None cluster_snapshots: bool = None + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/route53/route53_domains_privacy_protection_enabled/route53_domains_privacy_protection_enabled.py b/prowler/providers/aws/services/route53/route53_domains_privacy_protection_enabled/route53_domains_privacy_protection_enabled.py index 32219264..575ec004 100644 --- a/prowler/providers/aws/services/route53/route53_domains_privacy_protection_enabled/route53_domains_privacy_protection_enabled.py +++ b/prowler/providers/aws/services/route53/route53_domains_privacy_protection_enabled/route53_domains_privacy_protection_enabled.py @@ -12,7 +12,7 @@ class route53_domains_privacy_protection_enabled(Check): report = Check_Report_AWS(self.metadata()) report.resource_id = domain.name report.region = domain.region - + report.resource_tags = domain.tags if domain.admin_privacy: report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/route53/route53_domains_transferlock_enabled/route53_domains_transferlock_enabled.py b/prowler/providers/aws/services/route53/route53_domains_transferlock_enabled/route53_domains_transferlock_enabled.py index d4916098..4eefc518 100644 --- a/prowler/providers/aws/services/route53/route53_domains_transferlock_enabled/route53_domains_transferlock_enabled.py +++ b/prowler/providers/aws/services/route53/route53_domains_transferlock_enabled/route53_domains_transferlock_enabled.py @@ -12,7 +12,7 @@ class route53_domains_transferlock_enabled(Check): report = Check_Report_AWS(self.metadata()) report.resource_id = domain.name report.region = domain.region - + report.resource_tags = domain.tags if domain.status_list and "clientTransferProhibited" in domain.status_list: report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/route53/route53_public_hosted_zones_cloudwatch_logging_enabled/route53_public_hosted_zones_cloudwatch_logging_enabled.py b/prowler/providers/aws/services/route53/route53_public_hosted_zones_cloudwatch_logging_enabled/route53_public_hosted_zones_cloudwatch_logging_enabled.py index 5a52a7b5..ecace09b 100644 --- a/prowler/providers/aws/services/route53/route53_public_hosted_zones_cloudwatch_logging_enabled/route53_public_hosted_zones_cloudwatch_logging_enabled.py +++ b/prowler/providers/aws/services/route53/route53_public_hosted_zones_cloudwatch_logging_enabled/route53_public_hosted_zones_cloudwatch_logging_enabled.py @@ -10,6 +10,7 @@ class route53_public_hosted_zones_cloudwatch_logging_enabled(Check): if not hosted_zone.private_zone: report = Check_Report_AWS(self.metadata()) report.resource_id = hosted_zone.id + report.resource_tags = hosted_zone.tags report.region = hosted_zone.region if ( hosted_zone.logging_config diff --git a/prowler/providers/aws/services/route53/route53_service.py b/prowler/providers/aws/services/route53/route53_service.py index 8328bdca..8f342269 100644 --- a/prowler/providers/aws/services/route53/route53_service.py +++ b/prowler/providers/aws/services/route53/route53_service.py @@ -1,3 +1,5 @@ +from typing import Optional + from pydantic import BaseModel from prowler.lib.logger import logger @@ -21,6 +23,7 @@ class Route53: self.region = self.client.region self.__list_hosted_zones__() self.__list_query_logging_configs__() + self.__list_tags_for_resource__() def __get_session__(self): return self.session @@ -74,6 +77,19 @@ class Route53: f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags_for_resource__(self): + logger.info("Route53Domains - List Tags...") + for hosted_zone in self.hosted_zones.values(): + try: + response = self.client.list_tags_for_resource( + ResourceType="hostedzone", ResourceId=hosted_zone.id + )["ResourceTagSet"] + hosted_zone.tags = response.get("Tags") + except Exception as error: + logger.error( + f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class LoggingConfig(BaseModel): cloudwatch_log_group_arn: str @@ -86,6 +102,7 @@ class HostedZone(BaseModel): private_zone: bool logging_config: LoggingConfig = None region: str + tags: Optional[list] = [] ################## Route53Domains @@ -102,6 +119,7 @@ class Route53Domains: self.client = self.session.client(self.service, self.region) self.__list_domains__() self.__get_domain_detail__() + self.__list_tags_for_domain__() def __get_session__(self): return self.session @@ -136,9 +154,23 @@ class Route53Domains: f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __list_tags_for_domain__(self): + logger.info("Route53Domains - List Tags...") + for domain in self.domains.values(): + try: + response = self.client.list_tags_for_domain( + DomainName=domain.name, + )["TagList"] + domain.tags = response + except Exception as error: + logger.error( + f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class Domain(BaseModel): name: str region: str admin_privacy: bool = False status_list: list[str] = None + tags: Optional[list] = [] diff --git a/tests/providers/aws/services/glacier/glacier_service_test.py b/tests/providers/aws/services/glacier/glacier_service_test.py index 78551a77..23cbb017 100644 --- a/tests/providers/aws/services/glacier/glacier_service_test.py +++ b/tests/providers/aws/services/glacier/glacier_service_test.py @@ -54,6 +54,9 @@ def mock_make_api_call(self, operation_name, kwarg): if operation_name == "GetVaultAccessPolicy": return {"policy": {"Policy": json.dumps(vault_json_policy)}} + if operation_name == "ListTagsForVault": + return {"Tags": {"test": "test"}} + return make_api_call(self, operation_name, kwarg) @@ -99,6 +102,7 @@ class Test_Glacier_Service: == f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault" ) assert glacier.vaults[vault_name].region == AWS_REGION + assert glacier.vaults[vault_name].tags == [{"test": "test"}] def test__get_vault_access_policy__(self): # Set partition for the service diff --git a/tests/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled_test.py b/tests/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled_test.py index d2924eb7..838d5c9d 100644 --- a/tests/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled_test.py +++ b/tests/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled_test.py @@ -8,6 +8,9 @@ AWS_REGION = "eu-west-1" AWS_ACCOUNT_NUMBER = "123456789012" detector_id = str(uuid4()) +detector_arn = ( + f"arn:aws:guardduty:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" +) class Test_guardduty_is_enabled: @@ -33,6 +36,7 @@ class Test_guardduty_is_enabled: Detector( id=detector_id, region=AWS_REGION, + arn=detector_arn, status=True, ) ) @@ -50,7 +54,7 @@ class Test_guardduty_is_enabled: assert result[0].status == "PASS" assert search("enabled", result[0].status_extended) assert result[0].resource_id == detector_id - assert result[0].resource_arn == "" + assert result[0].resource_arn == detector_arn def test_guardduty_configured_but_suspended(self): guardduty_client = mock.MagicMock @@ -58,6 +62,7 @@ class Test_guardduty_is_enabled: guardduty_client.detectors.append( Detector( id=detector_id, + arn=detector_arn, region=AWS_REGION, status=False, ) @@ -76,7 +81,7 @@ class Test_guardduty_is_enabled: assert result[0].status == "FAIL" assert search("configured but suspended", result[0].status_extended) assert result[0].resource_id == detector_id - assert result[0].resource_arn == "" + assert result[0].resource_arn == detector_arn def test_guardduty_not_configured(self): guardduty_client = mock.MagicMock @@ -84,6 +89,7 @@ class Test_guardduty_is_enabled: guardduty_client.detectors.append( Detector( id=detector_id, + arn=detector_arn, region=AWS_REGION, ) ) @@ -101,4 +107,4 @@ class Test_guardduty_is_enabled: assert result[0].status == "FAIL" assert search("not configured", result[0].status_extended) assert result[0].resource_id == detector_id - assert result[0].resource_arn == "" + assert result[0].resource_arn == detector_arn diff --git a/tests/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings_test.py b/tests/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings_test.py index a61aa8f9..3912e6dd 100644 --- a/tests/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings_test.py +++ b/tests/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings_test.py @@ -32,6 +32,7 @@ class Test_guardduty_no_high_severity_findings: guardduty_client.detectors.append( Detector( id=detector_id, + arn="", region=AWS_REGION, ) ) @@ -58,7 +59,11 @@ class Test_guardduty_no_high_severity_findings: guardduty_client.detectors = [] guardduty_client.detectors.append( Detector( - id=detector_id, region=AWS_REGION, status=False, findings=[str(uuid4())] + id=detector_id, + region=AWS_REGION, + arn="", + status=False, + findings=[str(uuid4())], ) ) with mock.patch( diff --git a/tests/providers/aws/services/guardduty/guardduty_service_test.py b/tests/providers/aws/services/guardduty/guardduty_service_test.py index 4dcf2037..08f50055 100644 --- a/tests/providers/aws/services/guardduty/guardduty_service_test.py +++ b/tests/providers/aws/services/guardduty/guardduty_service_test.py @@ -16,6 +16,8 @@ make_api_call = botocore.client.BaseClient._make_api_call def mock_make_api_call(self, operation_name, kwarg): if operation_name == "ListFindings": return {"FindingIds": ["86c1d16c9ec63f634ccd087ae0d427ba1"]} + if operation_name == "ListTagsForResource": + return {"Tags": {"test": "test"}} return make_api_call(self, operation_name, kwarg) @@ -77,7 +79,7 @@ class Test_GuardDuty_Service: # Test GuardDuty session def test__list_detectors__(self): guardduty_client = client("guardduty", region_name=AWS_REGION) - response = guardduty_client.create_detector(Enable=True) + response = guardduty_client.create_detector(Enable=True, Tags={"test": "test"}) audit_info = self.set_mocked_audit_info() guardduty = GuardDuty(audit_info) @@ -85,6 +87,7 @@ class Test_GuardDuty_Service: assert len(guardduty.detectors) == 1 assert guardduty.detectors[0].id == response["DetectorId"] assert guardduty.detectors[0].region == AWS_REGION + assert guardduty.detectors[0].tags == [{"test": "test"}] @mock_guardduty # Test GuardDuty session diff --git a/tests/providers/aws/services/iam/iam_service_test.py b/tests/providers/aws/services/iam/iam_service_test.py index 85287e66..9d512570 100644 --- a/tests/providers/aws/services/iam/iam_service_test.py +++ b/tests/providers/aws/services/iam/iam_service_test.py @@ -247,10 +247,16 @@ class Test_IAM_Service: service_role = iam_client.create_role( RoleName="test-1", AssumeRolePolicyDocument=dumps(service_policy_document), + Tags=[ + {"Key": "test", "Value": "test"}, + ], )["Role"] role = iam_client.create_role( RoleName="test-2", AssumeRolePolicyDocument=dumps(policy_document), + Tags=[ + {"Key": "test", "Value": "test"}, + ], )["Role"] # IAM client for this test class @@ -258,6 +264,12 @@ class Test_IAM_Service: iam = IAM(audit_info) assert len(iam.roles) == len(iam_client.list_roles()["Roles"]) + assert iam.roles[0].tags == [ + {"Key": "test", "Value": "test"}, + ] + assert iam.roles[1].tags == [ + {"Key": "test", "Value": "test"}, + ] assert is_service_role(service_role) assert not is_service_role(role) @@ -287,15 +299,27 @@ class Test_IAM_Service: # Create 2 IAM Users iam_client.create_user( UserName="user1", + Tags=[ + {"Key": "test", "Value": "test"}, + ], ) iam_client.create_user( UserName="user2", + Tags=[ + {"Key": "test", "Value": "test"}, + ], ) # IAM client for this test class audit_info = self.set_mocked_audit_info() iam = IAM(audit_info) assert len(iam.users) == len(iam_client.list_users()["Users"]) + assert iam.users[0].tags == [ + {"Key": "test", "Value": "test"}, + ] + assert iam.users[1].tags == [ + {"Key": "test", "Value": "test"}, + ] # Test IAM Get Account Summary @mock_iam diff --git a/tests/providers/aws/services/kms/kms_service_test.py b/tests/providers/aws/services/kms/kms_service_test.py index e6796c49..93a8bc7e 100644 --- a/tests/providers/aws/services/kms/kms_service_test.py +++ b/tests/providers/aws/services/kms/kms_service_test.py @@ -88,7 +88,11 @@ class Test_ACM_Service: # Generate KMS Client kms_client = client("kms", region_name=AWS_REGION) # Create KMS keys - key1 = kms_client.create_key()["KeyMetadata"] + key1 = kms_client.create_key( + Tags=[ + {"TagKey": "test", "TagValue": "test"}, + ], + )["KeyMetadata"] # KMS client for this test class audit_info = self.set_mocked_audit_info() kms = KMS(audit_info) @@ -97,6 +101,9 @@ class Test_ACM_Service: assert kms.keys[0].state == key1["KeyState"] assert kms.keys[0].origin == key1["Origin"] assert kms.keys[0].manager == key1["KeyManager"] + assert kms.keys[0].tags == [ + {"TagKey": "test", "TagValue": "test"}, + ] # Test KMS Get rotation status @mock_kms diff --git a/tests/providers/aws/services/macie/macie_is_enabled/macie_is_enabled_test.py b/tests/providers/aws/services/macie/macie_is_enabled/macie_is_enabled_test.py index d270aed4..ad21520e 100644 --- a/tests/providers/aws/services/macie/macie_is_enabled/macie_is_enabled_test.py +++ b/tests/providers/aws/services/macie/macie_is_enabled/macie_is_enabled_test.py @@ -8,8 +8,8 @@ class Test_macie_is_enabled: macie_client = mock.MagicMock macie_client.sessions = [ Session( - "DISABLED", - "eu-west-1", + status="DISABLED", + region="eu-west-1", ) ] with mock.patch( @@ -33,8 +33,8 @@ class Test_macie_is_enabled: macie_client = mock.MagicMock macie_client.sessions = [ Session( - "ENABLED", - "eu-west-1", + status="ENABLED", + region="eu-west-1", ) ] with mock.patch( @@ -58,8 +58,8 @@ class Test_macie_is_enabled: macie_client = mock.MagicMock macie_client.sessions = [ Session( - "PAUSED", - "eu-west-1", + status="PAUSED", + region="eu-west-1", ) ] with mock.patch( diff --git a/tests/providers/aws/services/macie/macie_service_test.py b/tests/providers/aws/services/macie/macie_service_test.py index e505ea06..42520c10 100644 --- a/tests/providers/aws/services/macie/macie_service_test.py +++ b/tests/providers/aws/services/macie/macie_service_test.py @@ -66,8 +66,8 @@ class Test_Macie_Service: macie = Macie(current_audit_info) macie.sessions = [ Session( - "ENABLED", - "eu-west-1", + status="ENABLED", + region="eu-west-1", ) ] assert len(macie.sessions) == 1 diff --git a/tests/providers/aws/services/opensearch/opensearch_service_test.py b/tests/providers/aws/services/opensearch/opensearch_service_test.py index c9c39c5e..87544112 100644 --- a/tests/providers/aws/services/opensearch/opensearch_service_test.py +++ b/tests/providers/aws/services/opensearch/opensearch_service_test.py @@ -82,6 +82,12 @@ def mock_make_api_call(self, operation_name, kwarg): "AdvancedSecurityOptions": {"InternalUserDatabaseEnabled": True}, } } + if operation_name == "ListTags": + return { + "TagList": [ + {"Key": "test", "Value": "test"}, + ] + } return make_api_call(self, operation_name, kwarg) @@ -183,3 +189,6 @@ class Test_OpenSearchService_Service: assert opensearch.opensearch_domains[0].internal_user_database assert opensearch.opensearch_domains[0].update_available assert opensearch.opensearch_domains[0].version == "opensearch-version1" + assert opensearch.opensearch_domains[0].tags == [ + {"Key": "test", "Value": "test"}, + ] diff --git a/tests/providers/aws/services/rds/rds_service_test.py b/tests/providers/aws/services/rds/rds_service_test.py index 0d20b607..0f0158f0 100644 --- a/tests/providers/aws/services/rds/rds_service_test.py +++ b/tests/providers/aws/services/rds/rds_service_test.py @@ -82,6 +82,9 @@ class Test_RDS_Service: BackupRetentionPeriod=10, EnableCloudwatchLogsExports=["audit", "error"], MultiAZ=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], ) # RDS client for this test class audit_info = self.set_mocked_audit_info() @@ -101,6 +104,9 @@ class Test_RDS_Service: assert rds.db_instances[0].deletion_protection assert rds.db_instances[0].auto_minor_version_upgrade assert rds.db_instances[0].multi_az + assert rds.db_instances[0].tags == [ + {"Key": "test", "Value": "test"}, + ] # Test RDS Describe DB Snapshots @mock_rds diff --git a/tests/providers/aws/services/redshift/redshift_service_test.py b/tests/providers/aws/services/redshift/redshift_service_test.py index fca6c5f3..f20d6ec7 100644 --- a/tests/providers/aws/services/redshift/redshift_service_test.py +++ b/tests/providers/aws/services/redshift/redshift_service_test.py @@ -110,6 +110,9 @@ class Test_Redshift_Service: MasterUsername="user", MasterUserPassword="password", PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], ) audit_info = self.set_mocked_audit_info() redshift = Redshift(audit_info) @@ -126,6 +129,9 @@ class Test_Redshift_Service: redshift.clusters[0].allow_version_upgrade == response["Cluster"]["AllowVersionUpgrade"] ) + assert redshift.clusters[0].tags == [ + {"Key": "test", "Value": "test"}, + ] @mock_redshift def test_describe_logging_status(self): diff --git a/tests/providers/aws/services/route53/route53_service_test.py b/tests/providers/aws/services/route53/route53_service_test.py index 2d869c31..b0c0f0cd 100644 --- a/tests/providers/aws/services/route53/route53_service_test.py +++ b/tests/providers/aws/services/route53/route53_service_test.py @@ -18,7 +18,16 @@ def mock_make_api_call(self, operation_name, kwarg): """We have to mock every AWS API call using Boto3""" if operation_name == "DescribeDirectories": return {} - + if operation_name == "ListTagsForResource": + return { + "ResourceTagSet": { + "ResourceType": "hostedzone", + "ResourceId": "test", + "Tags": [ + {"Key": "test", "Value": "test"}, + ], + } + } return make_api_call(self, operation_name, kwarg) @@ -107,6 +116,9 @@ class Test_Route53_Service: == log_group_arn ) assert route53.hosted_zones[hosted_zone_id].region == AWS_REGION + assert route53.hosted_zones[hosted_zone_id].tags == [ + {"Key": "test", "Value": "test"}, + ] @mock_route53 @mock_logs diff --git a/tests/providers/aws/services/route53/route53domains_service_test.py b/tests/providers/aws/services/route53/route53domains_service_test.py index 25e72ab1..7f270dae 100644 --- a/tests/providers/aws/services/route53/route53domains_service_test.py +++ b/tests/providers/aws/services/route53/route53domains_service_test.py @@ -28,6 +28,12 @@ def mock_make_api_call(self, operation_name, kwarg): ], "NextPageMarker": "string", } + if operation_name == "ListTagsForDomain": + return { + "TagList": [ + {"Key": "test", "Value": "test"}, + ] + } if operation_name == "GetDomainDetail": return { "DomainName": "test.domain.com", @@ -117,3 +123,6 @@ class Test_Route53_Service: "clientTransferProhibited" in route53domains.domains[domain_name].status_list ) + assert route53domains.domains[domain_name].tags == [ + {"Key": "test", "Value": "test"}, + ]