diff --git a/prowler/providers/aws/aws_provider.py b/prowler/providers/aws/aws_provider.py index bcf8d1d8a7..de82b0342a 100644 --- a/prowler/providers/aws/aws_provider.py +++ b/prowler/providers/aws/aws_provider.py @@ -70,6 +70,25 @@ from prowler.providers.common.provider import Provider class AwsProvider(Provider): + """ + AwsProvider class is the main class for the AWS provider. + + This class is responsible for initializing the AWS provider, setting up the AWS session, validating the AWS + credentials, assuming an IAM role, getting the AWS Organizations metadata, and setting the AWS identity. + + Attributes: + _type (str): The provider type. + _identity (AWSIdentityInfo): The AWS provider identity information. + _session (AWSSession): The AWS provider session. + _organizations_metadata (AWSOrganizationsInfo): The AWS Organizations metadata. + _audit_resources (list): The list of resources to audit. + _audit_config (dict): The audit configuration. + _scan_unused_services (bool): A boolean indicating whether to scan unused services. + _enabled_regions (set): The set of enabled regions. + _mutelist (AWSMutelist): The AWS provider mutelist. + audit_metadata (Audit_Metadata): The audit metadata. + """ + _type: str = "aws" _identity: AWSIdentityInfo _session: AWSSession @@ -111,7 +130,7 @@ class AwsProvider(Provider): Args: - retries_max_attempts: The maximum number of retries for the AWS client. - role_arn: The ARN of the IAM role to assume. - - session_duration: The duration of the session in seconds. + - session_duration: The duration of the session in seconds, between 900 and 43200. - external_id: The external ID to use when assuming the IAM role. - role_session_name: The name of the session when assuming the IAM role. - mfa: A boolean indicating whether MFA is enabled. @@ -136,6 +155,37 @@ class AwsProvider(Provider): - ArgumentTypeError: If the input external ID is invalid. - ArgumentTypeError: If the input role session name is invalid. + Usage: + - Boto3 is used so we follow their credential setup process: + - Authentication: Make sure you have properly configured your AWS CLI with a valid Access Key and Region or declare the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables. + - aws configure + or + - export AWS_ACCESS_KEY_ID="ASXXXXXXX" + export AWS_SECRET_ACCESS_KEY="XXXXXXXXX" + export AWS_SESSION_TOKEN="XXXXXXXXX" + - To create a new aws object you can use: + - aws = AwsProvider() + - aws = AwsProvider(aws_access_key_id="ASXXXXXXX", aws_secret_access_key="XXXXXXXXX", aws_session_token="XXXXXXXXX") + - Profile: If you have multiple profiles in your AWS CLI configuration, you can specify the profile to use: + - aws = AwsProvider(profile="profile_name") + - MFA: If you have MFA enabled you can specify it: + - aws = AwsProvider(mfa=True) + * Note: If you have MFA enabled you will be prompted to enter the MFA ARN and the MFA TOTP code. + * Note: Take into account that you can use static credentials or a profile, with the combination of MFA. + + - Assume Role: *Requires authentication.* Prowler can be used against multiple accounts using IAM Assume Role features depending on each use case: + - Set up a custom profile inside your AWS CLI configuration file: + - [profile profile_name] + role_arn = arn:aws:iam::123456789012:role/role_name + - aws = AwsProvider(profile="profile_name") + - Use role_arn directly: + - aws = AwsProvider(role_arn="arn:aws:iam::123456789012:role/role_name") + - Use role_arn with session duration(in seconds, by default 3600) and external ID: + - aws = AwsProvider(role_arn="arn:aws:iam::123456789012:role/role_name", session_duration=3600, external_id="external_id") + - Use custom role session name: + - aws = AwsProvider(role_arn="arn:aws:iam::123456789012:role/role_name", role_session_name="custom_session_name") + * Note: You can use the combination of MFA with Assume Role. + - aws = AwsProvider(role_arn="arn:aws:iam::123456789012:role/role_name", mfa=True) """ logger.info("Initializing AWS provider ...") @@ -426,6 +476,21 @@ class AwsProvider(Provider): regions: set, profile_region: str, ) -> AWSIdentityInfo: + """ + set_identity sets the AWS provider identity information. + + Args: + - caller_identity: The AWS caller identity information. + - profile: The AWS CLI profile name. + - regions: A set of regions to audit. + - profile_region: The AWS CLI profile region. + + Returns: + - AWSIdentityInfo: The AWS provider identity information. + + Raises: + - AWSInvalidProviderIdError: If the AWS provider ID is invalid. + """ logger.info(f"Original AWS Caller Identity UserId: {caller_identity.user_id}") logger.info(f"Original AWS Caller Identity ARN: {caller_identity.arn}") @@ -449,6 +514,22 @@ class AwsProvider(Provider): aws_secret_access_key: str = None, aws_session_token: Optional[str] = None, ) -> Session: + """ + setup_session sets up an AWS session using the provided credentials. + + Args: + - mfa: A boolean indicating whether MFA is enabled. + - profile: The name of the AWS CLI profile to use. + - aws_access_key_id: The AWS access key ID. + - aws_secret_access_key: The AWS secret access key. + - aws_session_token: The AWS session token, optional. + + Returns: + - Session: The AWS session. + + Raises: + - AWSSetUpSessionError: If an error occurs during the setup process. + """ try: logger.debug("Creating original session ...") @@ -590,6 +671,21 @@ class AwsProvider(Provider): return refreshed_credentials def print_credentials(self): + """ + Print the AWS credentials. + + This method prints the AWS credentials used by the provider. + + Example output: + ``` + Using the AWS credentials below: + AWS-CLI Profile: default + AWS Regions: all + AWS Account: 123456789012 + User Id: AIDAJDPLRKLG7EXAMPLE + Caller Identity ARN: arn:aws:iam::123456789012:user/prowler + ``` + """ # Beautify audited regions, set "all" if there is no filter region regions = ( ", ".join(self._identity.audited_regions) @@ -681,6 +777,12 @@ class AwsProvider(Provider): def get_checks_from_input_arn(self) -> set: """ get_checks_from_input_arn gets the list of checks from the input arns + + Returns: + - set: set of strings representing the checks from the input arns + + Example: + checks = get_checks_from_input_arn() """ checks_from_arn = set() is_subservice_in_checks = False @@ -747,7 +849,18 @@ class AwsProvider(Provider): # TODO: This can be moved to another class since it doesn't need self def get_regions_from_audit_resources(self, audit_resources: list) -> set: - """get_regions_from_audit_resources gets the regions from the audit resources arns""" + """get_regions_from_audit_resources gets the regions from the audit resources arns + + Args: + - audit_resources: list of ARNs of the resources to audit + + Returns: + - set: set of strings representing the regions from the audit resources arns + + Example: + audit_resources = ["arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"] + regions = get_regions_from_audit_resources(audit_resources) + """ audited_regions = set() for resource in audit_resources: region = resource.split(":")[3] @@ -807,7 +920,18 @@ class AwsProvider(Provider): raise error def get_default_region(self, service: str) -> str: - """get_default_region returns the default region based on the profile and audited service regions""" + """get_default_region returns the default region based on the profile and audited service regions + + Args: + - service: The AWS service name + + Returns: + - str: The default region for the given service + + Example: + service = "ec2" + default_region = get_default_region(service) + """ try: service_regions = AwsProvider.get_available_aws_service_regions( service, self._identity.partition, self._identity.audited_regions @@ -828,7 +952,14 @@ class AwsProvider(Provider): raise error def get_global_region(self) -> str: - """get_global_region returns the global region based on the audited partition""" + """get_global_region returns the global region based on the audited partition + + Returns: + - str: The global region for the audited partition + + Example: + global_region = get_global_region()a + """ global_region = "us-east-1" if self._identity.partition == "aws-cn": global_region = "cn-north-1" @@ -840,7 +971,14 @@ class AwsProvider(Provider): @staticmethod def input_role_mfa_token_and_code() -> AWSMFAInfo: - """input_role_mfa_token_and_code ask for the AWS MFA ARN and TOTP and returns it.""" + """input_role_mfa_token_and_code ask for the AWS MFA ARN and TOTP and returns it. + + Returns: + - AWSMFAInfo: An object containing the MFA ARN and TOTP code + + Example: + mfa_info = input_role_mfa_token_and_code() + """ mfa_ARN = input("Enter ARN of MFA: ") mfa_TOTP = input("Enter MFA code: ") return AWSMFAInfo(arn=mfa_ARN, totp=mfa_TOTP) @@ -848,6 +986,12 @@ class AwsProvider(Provider): def set_session_config(self, retries_max_attempts: int) -> Config: """ set_session_config returns a botocore Config object with the Prowler user agent and the default retrier configuration if nothing is passed as argument + + Args: + - retries_max_attempts: The maximum number of retries for the standard retrier config + + Returns: + - Config: The botocore Config object """ # Set the maximum retries for the standard retrier config default_session_config = Config( @@ -874,6 +1018,13 @@ class AwsProvider(Provider): ) -> AWSCredentials: """ assume_role assumes the IAM roles passed with the given session and returns AWSCredentials + + Args: + - session: The AWS session object + - assumed_role_info: The AWSAssumeRoleInfo object + + Returns: + - AWSCredentials: The AWS credentials for the assumed role """ try: role_session_name = ( @@ -925,7 +1076,14 @@ class AwsProvider(Provider): ) def get_aws_enabled_regions(self, current_session: Session) -> set: - """get_aws_enabled_regions returns a set of enabled AWS regions""" + """get_aws_enabled_regions returns a set of enabled AWS regions + + Args: + - current_session: The AWS session object + + Returns: + - set: set of strings representing the enabled AWS regions + """ try: # EC2 Client to check enabled regions service = "ec2" @@ -949,6 +1107,12 @@ class AwsProvider(Provider): # TODO: review this function # Maybe this should be done within the AwsProvider and not in __main__.py def get_checks_to_execute_by_audit_resources(self) -> set[str]: + """ + get_checks_to_execute_by_audit_resources gets the checks to execute based on the audit resources + + Returns: + - set: set of strings representing the checks to execute + """ # Once the provider is set and we have the eventual checks from arn, it is time to exclude the others try: checks = set() @@ -1370,6 +1534,19 @@ def read_aws_regions_file() -> dict: # TODO: This can be moved to another class since it doesn't need self def get_aws_region_for_sts(session_region: str, regions: set[str]) -> str: + """ + Get the AWS region for the STS Assume Role operation. + + Args: + - session_region (str): The region configured in the AWS session. + - regions (set[str]): The regions passed with the -f/--region/--filter-region option. + + Returns: + str: The AWS region for the STS Assume Role operation + + Example: + aws_region = get_aws_region_for_sts(session_region, regions) + """ # If there is no region passed with -f/--region/--filter-region if regions is None or len(regions) == 0: # If you have a region configured in your AWS config or credentials file