diff --git a/docs/tutorials/aws/role-assumption.md b/docs/tutorials/aws/role-assumption.md index 977868c1..df52f349 100644 --- a/docs/tutorials/aws/role-assumption.md +++ b/docs/tutorials/aws/role-assumption.md @@ -23,6 +23,15 @@ prowler aws -R arn:aws:iam:::role/ prowler aws -T/--session-duration -I/--external-id -R arn:aws:iam:::role/ ``` +## Custom Role Session Name + +Prowler can use your custom Role Session name with: +```console +prowler aws --role-session-name +``` + +> It defaults to `ProwlerAssessmentSession` + ## STS Endpoint Region If you are using Prowler in AWS regions that are not enabled by default you need to use the argument `--sts-endpoint-region` to point the AWS STS API calls `assume-role` and `get-caller-identity` to the non-default region, e.g.: `prowler aws --sts-endpoint-region eu-south-2`. diff --git a/prowler/providers/aws/aws_provider.py b/prowler/providers/aws/aws_provider.py index fba1f608..53cf5ad8 100644 --- a/prowler/providers/aws/aws_provider.py +++ b/prowler/providers/aws/aws_provider.py @@ -113,9 +113,15 @@ def assume_role( sts_endpoint_region: str = None, ) -> dict: try: + role_session_name = ( + assumed_role_info.role_session_name + if assumed_role_info.role_session_name + else "ProwlerAssessmentSession" + ) + assume_role_arguments = { "RoleArn": assumed_role_info.role_arn, - "RoleSessionName": "ProwlerAsessmentSession", + "RoleSessionName": role_session_name, "DurationSeconds": assumed_role_info.session_duration, } diff --git a/prowler/providers/aws/lib/arguments/arguments.py b/prowler/providers/aws/lib/arguments/arguments.py index 0d5742b3..9864e9fa 100644 --- a/prowler/providers/aws/lib/arguments/arguments.py +++ b/prowler/providers/aws/lib/arguments/arguments.py @@ -1,5 +1,5 @@ from argparse import ArgumentTypeError, Namespace -from re import search +from re import fullmatch, search from prowler.providers.aws.aws_provider import get_aws_available_regions from prowler.providers.aws.lib.arn.arn import arn_type @@ -27,6 +27,13 @@ def init_parser(self): help="ARN of the role to be assumed", # Pending ARN validation ) + aws_auth_subparser.add_argument( + "--role-session-name", + nargs="?", + default="ProwlerAssessmentSession", + help="An identifier for the assumed role session. Defaults to ProwlerAssessmentSession", + type=validate_role_session_name, + ) aws_auth_subparser.add_argument( "--sts-endpoint-region", nargs="?", @@ -203,3 +210,16 @@ def validate_bucket(bucket_name): raise ArgumentTypeError( "Bucket name must be valid (https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)" ) + + +def validate_role_session_name(session_name): + """ + validates that the role session name is valid + Documentation: https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html + """ + if fullmatch("[\w+=,.@-]{2,64}", session_name): + return session_name + else: + raise ArgumentTypeError( + "Role Session Name must be 2-64 characters long and consist only of upper- and lower-case alphanumeric characters with no spaces. You can also include underscores or any of the following characters: =,.@-" + ) diff --git a/prowler/providers/aws/lib/audit_info/audit_info.py b/prowler/providers/aws/lib/audit_info/audit_info.py index 65f884c0..30dc0392 100644 --- a/prowler/providers/aws/lib/audit_info/audit_info.py +++ b/prowler/providers/aws/lib/audit_info/audit_info.py @@ -30,6 +30,7 @@ current_audit_info = AWS_Audit_Info( session_duration=None, external_id=None, mfa_enabled=None, + role_session_name=None, ), mfa_enabled=None, audit_resources=None, diff --git a/prowler/providers/aws/lib/audit_info/models.py b/prowler/providers/aws/lib/audit_info/models.py index 59a2b15a..6e25db4d 100644 --- a/prowler/providers/aws/lib/audit_info/models.py +++ b/prowler/providers/aws/lib/audit_info/models.py @@ -20,6 +20,7 @@ class AWS_Assume_Role: session_duration: int external_id: str mfa_enabled: bool + role_session_name: str @dataclass diff --git a/prowler/providers/common/audit_info.py b/prowler/providers/common/audit_info.py index 421ab272..15c02cf0 100644 --- a/prowler/providers/common/audit_info.py +++ b/prowler/providers/common/audit_info.py @@ -85,6 +85,7 @@ Azure Identity Type: {Fore.YELLOW}[{audit_info.identity.identity_type}]{Style.RE current_audit_info.assumed_role_info.role_arn = input_role input_session_duration = arguments.get("session_duration") input_external_id = arguments.get("external_id") + input_role_session_name = arguments.get("role_session_name") # STS Endpoint Region sts_endpoint_region = arguments.get("sts_endpoint_region") @@ -153,6 +154,9 @@ Azure Identity Type: {Fore.YELLOW}[{audit_info.identity.identity_type}]{Style.RE ) current_audit_info.assumed_role_info.external_id = input_external_id current_audit_info.assumed_role_info.mfa_enabled = input_mfa + current_audit_info.assumed_role_info.role_session_name = ( + input_role_session_name + ) # Check if role arn is valid try: diff --git a/tests/lib/cli/parser_test.py b/tests/lib/cli/parser_test.py index efef44b9..78d69924 100644 --- a/tests/lib/cli/parser_test.py +++ b/tests/lib/cli/parser_test.py @@ -5,7 +5,10 @@ import pytest from mock import patch from prowler.lib.cli.parser import ProwlerArgumentParser -from prowler.providers.aws.lib.arguments.arguments import validate_bucket +from prowler.providers.aws.lib.arguments.arguments import ( + validate_bucket, + validate_role_session_name, +) from prowler.providers.azure.lib.arguments.arguments import validate_azure_region prowler_command = "prowler" @@ -1012,6 +1015,13 @@ class Test_Parser: parsed = self.parser.parse(command) assert parsed.sts_endpoint_region == sts_endpoint_region + def test_aws_parser_role_session_name(self): + argument = "--role-session-name" + role_session_name = "ProwlerAssessmentSession" + command = [prowler_command, argument, role_session_name] + parsed = self.parser.parse(command) + assert parsed.role_session_name == role_session_name + def test_parser_azure_auth_sp(self): argument = "--sp-env-auth" command = [prowler_command, "azure", argument] @@ -1164,3 +1174,25 @@ class Test_Parser: valid_bucket_names = ["bucket-name" "test" "test-test-test"] for bucket_name in valid_bucket_names: assert validate_bucket(bucket_name) == bucket_name + + def test_validate_role_session_name_invalid_role_names(self): + bad_role_names = [ + "role name", + "adasD*", + "test#", + "role-name?", + ] + for role_name in bad_role_names: + with pytest.raises(ArgumentTypeError) as argument_error: + validate_role_session_name(role_name) + + assert argument_error.type == ArgumentTypeError + assert ( + argument_error.value.args[0] + == "Role Session Name must be 2-64 characters long and consist only of upper- and lower-case alphanumeric characters with no spaces. You can also include underscores or any of the following characters: =,.@-" + ) + + def test_validate_role_session_name_valid_role_names(self): + valid_role_names = ["prowler-role" "test@" "test=test+test,."] + for role_name in valid_role_names: + assert validate_role_session_name(role_name) == role_name diff --git a/tests/providers/aws/aws_provider_test.py b/tests/providers/aws/aws_provider_test.py index 69d59728..eed14ce3 100644 --- a/tests/providers/aws/aws_provider_test.py +++ b/tests/providers/aws/aws_provider_test.py @@ -32,7 +32,7 @@ class Test_AWS_Provider: @mock_iam @mock_sts def test_aws_provider_user_without_mfa(self): - # sessionName = "ProwlerAsessmentSession" + # sessionName = "ProwlerAssessmentSession" # Boto 3 client to create our user iam_client = boto3.client("iam", region_name=AWS_REGION_US_EAST_1) # IAM user @@ -56,6 +56,7 @@ class Test_AWS_Provider: session_duration=None, external_id=None, mfa_enabled=False, + role_session_name="ProwlerAssessmentSession", ), original_session=session, ) @@ -75,6 +76,7 @@ class Test_AWS_Provider: session_duration=None, external_id=None, mfa_enabled=False, + role_session_name="ProwlerAssessmentSession", ) @mock_iam @@ -103,6 +105,7 @@ class Test_AWS_Provider: session_duration=None, external_id=None, mfa_enabled=False, + role_session_name="ProwlerAssessmentSession", ), original_session=session, profile_region=AWS_REGION_US_EAST_1, @@ -123,6 +126,7 @@ class Test_AWS_Provider: session_duration=None, external_id=None, mfa_enabled=False, + role_session_name="ProwlerAssessmentSession", ) @mock_iam @@ -132,7 +136,7 @@ class Test_AWS_Provider: role_name = "test-role" role_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:role/{role_name}" session_duration_seconds = 900 - sessionName = "ProwlerAsessmentSession" + sessionName = "ProwlerAssessmentSession" # Boto 3 client to create our user iam_client = boto3.client("iam", region_name=AWS_REGION_US_EAST_1) @@ -157,6 +161,7 @@ class Test_AWS_Provider: session_duration=session_duration_seconds, external_id=None, mfa_enabled=True, + role_session_name="ProwlerAssessmentSession", ), original_session=session, profile_region=AWS_REGION_US_EAST_1, @@ -210,7 +215,7 @@ class Test_AWS_Provider: role_name = "test-role" role_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:role/{role_name}" session_duration_seconds = 900 - sessionName = "ProwlerAsessmentSession" + sessionName = "ProwlerAssessmentSession" # Boto 3 client to create our user iam_client = boto3.client("iam", region_name=AWS_REGION_US_EAST_1) @@ -235,6 +240,7 @@ class Test_AWS_Provider: session_duration=session_duration_seconds, external_id=None, mfa_enabled=False, + role_session_name="ProwlerAssessmentSession", ), original_session=session, profile_region=AWS_REGION_US_EAST_1, @@ -282,7 +288,7 @@ class Test_AWS_Provider: session_duration_seconds = 900 AWS_REGION_US_EAST_1 = AWS_REGION_EU_WEST_1 sts_endpoint_region = AWS_REGION_US_EAST_1 - sessionName = "ProwlerAsessmentSession" + sessionName = "ProwlerAssessmentSession" # Boto 3 client to create our user iam_client = boto3.client("iam", region_name=AWS_REGION_US_EAST_1) @@ -307,6 +313,7 @@ class Test_AWS_Provider: session_duration=session_duration_seconds, external_id=None, mfa_enabled=False, + role_session_name="ProwlerAssessmentSession", ), original_session=session, profile_region=AWS_REGION_US_EAST_1, diff --git a/tests/providers/common/audit_info_test.py b/tests/providers/common/audit_info_test.py index 57021e62..25835d04 100644 --- a/tests/providers/common/audit_info_test.py +++ b/tests/providers/common/audit_info_test.py @@ -116,6 +116,7 @@ class Test_Set_Audit_Info: session_duration=None, external_id=None, mfa_enabled=None, + role_session_name="ProwlerAssessmentSession", ), audited_regions=["eu-west-2", "eu-west-1"], organizations_metadata=None,