diff --git a/prowler/providers/aws/aws_provider.py b/prowler/providers/aws/aws_provider.py index a5c33d6d..fba1f608 100644 --- a/prowler/providers/aws/aws_provider.py +++ b/prowler/providers/aws/aws_provider.py @@ -240,6 +240,8 @@ def get_checks_from_input_arn(audit_resources: list, provider: str) -> set: service = "efs" elif service == "logs": service = "cloudwatch" + elif service == "cognito": + service = "cognito-idp" # Check if Prowler has checks in service try: list_modules(provider, service) diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json index c81befe3..ffb910e2 100644 --- a/prowler/providers/aws/aws_regions_by_service.json +++ b/prowler/providers/aws/aws_regions_by_service.json @@ -2195,6 +2195,36 @@ "aws-us-gov": [] } }, + "cognito": { + "regions": { + "aws": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "il-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2" + ], + "aws-cn": [], + "aws-us-gov": [ + "us-gov-west-1" + ] + } + }, "cognito-identity": { "regions": { "aws": [ @@ -10683,4 +10713,4 @@ } } } -} \ No newline at end of file +} diff --git a/prowler/providers/aws/services/cognito/__init__.py b/prowler/providers/aws/services/cognito/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/aws/services/cognito/cognito_idp_client.py b/prowler/providers/aws/services/cognito/cognito_idp_client.py new file mode 100644 index 00000000..f71f16af --- /dev/null +++ b/prowler/providers/aws/services/cognito/cognito_idp_client.py @@ -0,0 +1,4 @@ +from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info +from prowler.providers.aws.services.cognito.cognito_service import CognitoIDP + +cognito_idp_client = CognitoIDP(current_audit_info) diff --git a/prowler/providers/aws/services/cognito/cognito_service.py b/prowler/providers/aws/services/cognito/cognito_service.py new file mode 100644 index 00000000..735846de --- /dev/null +++ b/prowler/providers/aws/services/cognito/cognito_service.py @@ -0,0 +1,122 @@ +from datetime import datetime +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.lib.service.service import AWSService + + +################## CognitoIDP +class CognitoIDP(AWSService): + def __init__(self, audit_info): + super().__init__("cognito-idp", audit_info) + self.user_pools = {} + self.__threading_call__(self.__list_user_pools__) + self.__describe_user_pools__() + self.__get_user_pool_mfa_config__() + + def __list_user_pools__(self, regional_client): + logger.info("Cognito - Listing User Pools...") + try: + user_pools_paginator = regional_client.get_paginator("list_user_pools") + for page in user_pools_paginator.paginate(MaxResults=60): + for user_pool in page["UserPools"]: + arn = f"arn:{self.audited_partition}:cognito-idp:{regional_client.region}:{self.audited_account}:userpool/{user_pool['Id']}" + if not self.audit_resources or ( + is_resource_filtered(arn, self.audit_resources) + ): + try: + self.user_pools[arn] = UserPool( + id=user_pool["Id"], + arn=arn, + name=user_pool["Name"], + region=regional_client.region, + last_modified=user_pool["LastModifiedDate"], + creation_date=user_pool["CreationDate"], + status=user_pool.get("Status", "Disabled"), + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __describe_user_pools__(self): + logger.info("Cognito - Describing User Pools...") + try: + for user_pool in self.user_pools.values(): + try: + user_pool_details = self.regional_clients[ + user_pool.region + ].describe_user_pool(UserPoolId=user_pool.id)["UserPool"] + user_pool.password_policy = user_pool_details.get( + "Policies", {} + ).get("PasswordPolicy", {}) + user_pool.deletion_protection = user_pool_details.get( + "DeletionProtection", "INACTIVE" + ) + user_pool.advanced_security_mode = user_pool_details.get( + "UserPoolAddOns", {} + ).get("AdvancedSecurityMode", "OFF") + user_pool.tags = [user_pool_details.get("UserPoolTags", "")] + except Exception as error: + logger.error( + f"{user_pool.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{user_pool.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __get_user_pool_mfa_config__(self): + logger.info("Cognito - Getting User Pool MFA Configuration...") + try: + for user_pool in self.user_pools.values(): + try: + mfa_config = self.regional_clients[ + user_pool.region + ].get_user_pool_mfa_config(UserPoolId=user_pool.id) + if mfa_config["MfaConfiguration"] != "OFF": + user_pool.mfa_config = MFAConfig( + sms_authentication=mfa_config.get( + "SmsMfaConfiguration", {} + ), + software_token_mfa_authentication=mfa_config.get( + "SoftwareTokenMfaConfiguration", {} + ), + status=mfa_config["MfaConfiguration"], + ) + except Exception as error: + logger.error( + f"{user_pool.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{user_pool.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class MFAConfig(BaseModel): + sms_authentication: Optional[dict] + software_token_mfa_authentication: Optional[dict] + status: str + + +class UserPool(BaseModel): + id: str + arn: str + name: str + region: str + advanced_security_mode: str = "OFF" + deletion_protection: str = "INACTIVE" + last_modified: datetime + creation_date: datetime + status: str + password_policy: Optional[dict] + mfa_config: Optional[MFAConfig] + tags: Optional[list] = [] diff --git a/tests/lib/check/check_test.py b/tests/lib/check/check_test.py index e115c509..593f57da 100644 --- a/tests/lib/check/check_test.py +++ b/tests/lib/check/check_test.py @@ -256,6 +256,10 @@ def mock_recover_checks_from_aws_provider_rds_service(*_): ] +def mock_recover_checks_from_aws_provider_cognito_service(*_): + return [] + + class Test_Check: def test_load_check_metadata(self): test_cases = [ @@ -565,6 +569,19 @@ class Test_Check: recovered_checks = get_checks_from_input_arn(audit_resources, provider) assert recovered_checks == expected_checks + @patch( + "prowler.lib.check.check.recover_checks_from_provider", + new=mock_recover_checks_from_aws_provider_cognito_service, + ) + def test_get_checks_from_input_arn_cognito(self): + audit_resources = [ + f"arn:aws:cognito-idp:us-east-1:{AWS_ACCOUNT_NUMBER}:userpool/test" + ] + provider = "aws" + expected_checks = [] + recovered_checks = get_checks_from_input_arn(audit_resources, provider) + assert recovered_checks == expected_checks + @patch( "prowler.lib.check.check.recover_checks_from_provider", new=mock_recover_checks_from_aws_provider_ec2_service, diff --git a/tests/providers/aws/services/cognito/cognito_service_test.py b/tests/providers/aws/services/cognito/cognito_service_test.py new file mode 100644 index 00000000..e1279555 --- /dev/null +++ b/tests/providers/aws/services/cognito/cognito_service_test.py @@ -0,0 +1,117 @@ +from boto3 import client +from moto import mock_cognitoidp + +from prowler.providers.aws.services.cognito.cognito_service import CognitoIDP +from tests.providers.aws.audit_info_utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + AWS_REGION_US_EAST_1, + set_mocked_aws_audit_info, +) + + +class Test_Cognito_Service: + # Test Cognito Service + @mock_cognitoidp + def test_service(self): + audit_info = set_mocked_aws_audit_info( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + cognito = CognitoIDP(audit_info) + assert cognito.service == "cognito-idp" + + # Test Cognito client + @mock_cognitoidp + def test_client(self): + audit_info = set_mocked_aws_audit_info( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + cognito = CognitoIDP(audit_info) + for regional_client in cognito.regional_clients.values(): + assert regional_client.__class__.__name__ == "CognitoIdentityProvider" + + # Test Cognito session + @mock_cognitoidp + def test__get_session__(self): + audit_info = set_mocked_aws_audit_info( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + cognito = CognitoIDP(audit_info) + assert cognito.session.__class__.__name__ == "Session" + + # Test Cognito Session + @mock_cognitoidp + def test_audited_account(self): + audit_info = set_mocked_aws_audit_info( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + cognito = CognitoIDP(audit_info) + assert cognito.audited_account == AWS_ACCOUNT_NUMBER + + @mock_cognitoidp + def test_list_user_pools(self): + user_pool_name_1 = "user_pool_test_1" + user_pool_name_2 = "user_pool_test_2" + audit_info = set_mocked_aws_audit_info( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + cognito_client_eu_west_1 = client("cognito-idp", region_name="eu-west-1") + cognito_client_us_east_1 = client("cognito-idp", region_name="us-east-1") + cognito_client_eu_west_1.create_user_pool(PoolName=user_pool_name_1) + cognito_client_us_east_1.create_user_pool(PoolName=user_pool_name_2) + cognito = CognitoIDP(audit_info) + assert len(cognito.user_pools) == 2 + for user_pool in cognito.user_pools.values(): + assert ( + user_pool.name == user_pool_name_1 or user_pool.name == user_pool_name_2 + ) + assert user_pool.region == "eu-west-1" or user_pool.region == "us-east-1" + + @mock_cognitoidp + def test_describe_user_pools(self): + user_pool_name_1 = "user_pool_test_1" + audit_info = set_mocked_aws_audit_info( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + cognito_client_eu_west_1 = client("cognito-idp", region_name="eu-west-1") + user_pool_id = cognito_client_eu_west_1.create_user_pool( + PoolName=user_pool_name_1 + )["UserPool"]["Id"] + cognito = CognitoIDP(audit_info) + assert len(cognito.user_pools) == 1 + for user_pool in cognito.user_pools.values(): + assert user_pool.name == user_pool_name_1 + assert user_pool.region == "eu-west-1" + assert user_pool.id == user_pool_id + assert user_pool.password_policy is not None + assert user_pool.deletion_protection is not None + assert user_pool.advanced_security_mode is not None + assert user_pool.tags is not None + + @mock_cognitoidp + def test_get_user_pool_mfa_config(self): + user_pool_name_1 = "user_pool_test_1" + audit_info = set_mocked_aws_audit_info( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + cognito_client_eu_west_1 = client("cognito-idp", region_name="eu-west-1") + user_pool_id = cognito_client_eu_west_1.create_user_pool( + PoolName=user_pool_name_1 + )["UserPool"]["Id"] + cognito_client_eu_west_1.set_user_pool_mfa_config( + UserPoolId=user_pool_id, + SoftwareTokenMfaConfiguration={"Enabled": True}, + MfaConfiguration="ON", + ) + cognito = CognitoIDP(audit_info) + assert len(cognito.user_pools) == 1 + for user_pool in cognito.user_pools.values(): + assert user_pool.name == user_pool_name_1 + assert user_pool.region == "eu-west-1" + assert user_pool.id == user_pool_id + assert user_pool.mfa_config is not None + assert user_pool.mfa_config.sms_authentication == {} + assert user_pool.mfa_config.software_token_mfa_authentication == { + "Enabled": True + } + assert user_pool.mfa_config.status == "ON" diff --git a/util/update_aws_services_regions.py b/util/update_aws_services_regions.py index a0506eca..7e352653 100644 --- a/util/update_aws_services_regions.py +++ b/util/update_aws_services_regions.py @@ -48,6 +48,10 @@ for page in get_parameters_by_path_paginator.paginate( logging.info("Updating subservices and the services not present in the original matrix") # macie2 --> macie regions_by_service["services"]["macie2"] = regions_by_service["services"]["macie"] +# cognito --> cognito-idp +regions_by_service["services"]["cognito"] = regions_by_service["services"][ + "cognito-idp" +] # opensearch --> es regions_by_service["services"]["opensearch"] = regions_by_service["services"]["es"] # elbv2 --> elb