From 652219b14f513354da8d16eeea9c407a6c105472 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Thu, 30 May 2024 19:25:16 +0200 Subject: [PATCH 01/28] make fixtures available globally --- conftest.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 conftest.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..871a7ebe --- /dev/null +++ b/conftest.py @@ -0,0 +1,68 @@ +import boto3 +import moto +import os +import pytest +from pacu import settings, Main +from pacu import core +from pacu.core.models import PacuSession +from sqlalchemy import orm, Column, create_engine +from sqlalchemy.orm import sessionmaker, Session +from sqlalchemy_utils import JSONType +from sqlalchemy.engine import Engine + + +settings.DATABASE_CONNECTION_PATH = "sqlite:///:memory:" + + +@pytest.fixture(scope="function") +def db() -> core.base: + core.base.engine: Engine = create_engine(settings.DATABASE_CONNECTION_PATH) + core.base.Session: sessionmaker = sessionmaker(bind=core.base.engine) + core.base.Base.metadata.create_all(core.base.engine) + yield core.base.Session() + + +@pytest.fixture(scope="function") +def pacu(db): + pacu = Main() + pacu.database = db + return pacu + + +@pytest.fixture(scope="function") +def active_session(db, pacu_session: PacuSession): + pacu_session.activate(db) + yield pacu_session + + +@pytest.fixture(scope="function") +def pacu_session(db: orm.session.Session): + query: orm.Query = db.query(PacuSession) + assert query.count() == 0 + + pacu_session = PacuSession() + db.add(pacu_session) + yield pacu_session + + +@pytest.fixture(scope="function") +def db_new_column(db: Session): + PacuSession.TestSvc = Column(JSONType, nullable=False, default=dict) + PacuSession.aws_data_field_names = PacuSession.aws_data_field_names + ("TestSvc",) + core.base.Session: sessionmaker = sessionmaker(bind=core.base.engine) + yield core.base.Session() + + +@pytest.fixture(scope="function") +def pacu_with_data(pacu: Main, active_session: PacuSession): + active_session.update(pacu.database, CloudWatch={"test_key": "test_value"}) + return pacu + + +@pytest.fixture +def aws_credentials(): + """Mocked AWS Credentials for moto.""" + os.environ["AWS_ACCESS_KEY_ID"] = "testing" + os.environ["AWS_SECRET_ACCESS_KEY"] = "testing" + os.environ["AWS_SECURITY_TOKEN"] = "testing" + os.environ["AWS_SESSION_TOKEN"] = "testing" From 5b10d005081dd409aaa17a2445f20cb4504b63c1 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Thu, 30 May 2024 19:26:42 +0200 Subject: [PATCH 02/28] add pacu modules to testpath --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 94f58a65..b7000195 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,3 +42,9 @@ importlib-metadata = "4.13.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +testpaths = [ + "tests", + "pacu/modules" +] From d88c24a983996743dfc875cf03bfbbd9c1f405bc Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Thu, 30 May 2024 19:27:12 +0200 Subject: [PATCH 03/28] use local conftest --- .../tests/conftest.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/conftest.py diff --git a/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/conftest.py b/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/conftest.py new file mode 100644 index 00000000..b9c961dd --- /dev/null +++ b/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/conftest.py @@ -0,0 +1,15 @@ +import pytest +import moto +import boto3 +import os + + +@pytest.fixture(scope="function") +def s3(aws_credentials): + with moto.mock_s3(): + yield boto3.client("s3", region_name="us-east-1") + + +@pytest.fixture +def environ(): + os.environ["PRINCIPAL"] = "asdf" From 3d4e67f3dbc455816832f121e8eb2bd0e2f7fd9f Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Thu, 30 May 2024 19:27:45 +0200 Subject: [PATCH 04/28] use conftest and formatting --- .../tests/test_app.py | 123 +++++++----------- 1 file changed, 48 insertions(+), 75 deletions(-) diff --git a/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/test_app.py b/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/test_app.py index d79f5119..05ec4c87 100644 --- a/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/test_app.py +++ b/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/test_app.py @@ -1,66 +1,61 @@ -import os import re -import boto3 -import moto + import pytest -from chalice.test import Client -from pacu.modules.cfn__resource_injection.cfn__resource_injection_lambda.app import app, add_role_dict, add_role_bytes, fetch, update +from pacu.modules.cfn__resource_injection.cfn__resource_injection_lambda.app import ( + add_role_dict, + add_role_bytes, + update, +) @pytest.fixture -def environ(): - os.environ['PRINCIPAL'] = 'asdf' +def upload_json(environ, s3): + s3.create_bucket(Bucket="test-bucket") + s3.put_object(Bucket="test-bucket", Key="test-key", Body=new_cfn_json) + return "test-bucket", "test-key", new_cfn_json -def test_add_role_dict(environ): +@pytest.mark.usefixtures("environ") +def test_add_role_dict(): cfn = { "AWSTemplateFormatVersion": "2010-09-09", "Description": "Test CloudFormation Template", "Globals": {}, "Outputs": {}, - "Resources": { - "OurBucket": { - "Properties": {}, - "Type": "AWS::S3::Bucket" - } - } + "Resources": {"OurBucket": {"Properties": {}, "Type": "AWS::S3::Bucket"}}, } assert { - 'AWSTemplateFormatVersion': '2010-09-09', - 'Description': 'Test CloudFormation Template', - 'Globals': {}, - 'Outputs': {}, - 'Resources': { - 'MaintenanceRole': { - 'Properties': { - 'AssumeRolePolicyDocument': '{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", ' - '"Principal": {"AWS": "asdf"}, "Action": "sts:AssumeRole"}]}', - 'Policies': [ - { - 'PolicyDocument': { - 'Statement': [ - { - 'Action': '*', - 'Effect': 'Allow', - 'Resource': '*' - } - ], - 'Version': '2012-10-17', - }, - 'PolicyName': 'default', - } - ] - }, - 'Type': 'AWS::IAM::Role', - }, - 'OurBucket': { - 'Properties': {}, - 'Type': 'AWS::S3::Bucket', - } - } - } == add_role_dict(cfn) + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Test CloudFormation Template", + "Globals": {}, + "Outputs": {}, + "Resources": { + "MaintenanceRole": { + "Properties": { + "AssumeRolePolicyDocument": '{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", ' + '"Principal": {"AWS": "asdf"}, "Action": "sts:AssumeRole"}]}', + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + {"Action": "*", "Effect": "Allow", "Resource": "*"} + ], + "Version": "2012-10-17", + }, + "PolicyName": "default", + } + ], + }, + "Type": "AWS::IAM::Role", + }, + "OurBucket": { + "Properties": {}, + "Type": "AWS::S3::Bucket", + }, + }, + } == add_role_dict(cfn) new_cfn_json = b""" @@ -134,49 +129,27 @@ def test_add_role_bytes_yaml(environ): add_role_bytes(new_cfn_yaml) -@pytest.fixture -def upload_json(environ, s3): - s3.create_bucket(Bucket='test-bucket') - s3.put_object(Bucket='test-bucket', Key='test-key', Body=new_cfn_json) - return 'test-bucket', 'test-key', new_cfn_json - - -@pytest.fixture -def aws_credentials(): - """Mocked AWS Credentials for moto.""" - os.environ['AWS_ACCESS_KEY_ID'] = 'testing' - os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' - os.environ['AWS_SECURITY_TOKEN'] = 'testing' - os.environ['AWS_SESSION_TOKEN'] = 'testing' - - -@pytest.fixture(scope='function') -def s3(aws_credentials): - with moto.mock_s3(): - yield boto3.client('s3', region_name='us-east-1') - - def test_update(s3, upload_json): bucket, key, _ = upload_json[0], upload_json[1], upload_json[2] - body = s3.get_object(Bucket=bucket, Key=key)['Body'].read() + body = s3.get_object(Bucket=bucket, Key=key)["Body"].read() assert clean_json(new_cfn_json.decode()) == clean_json(body.decode()) update(s3, bucket, key) - body = s3.get_object(Bucket=bucket, Key=key)['Body'].read() + body = s3.get_object(Bucket=bucket, Key=key)["Body"].read() assert clean_json(modified_cfn_json.decode()) == clean_json(body.decode()) def test_update_second_time(s3, upload_json): - bucket, key, body = upload_json[0], upload_json[1], upload_json[2] + bucket, key = upload_json[0], upload_json[1] update(s3, bucket, key) resp = s3.get_object(Bucket=bucket, Key=key) a = clean_json(modified_cfn_json.decode()) - b = clean_json(resp['Body'].read().decode()) + b = clean_json(resp["Body"].read().decode()) assert a == b def clean_json(s: str): - c = re.compile(r'\s') - return c.sub('', s) + c = re.compile(r"\s") + return c.sub("", s) From 8be81a66c0eba0cd649ab8d34b6e64949666eb3b Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Thu, 30 May 2024 19:28:19 +0200 Subject: [PATCH 05/28] move fixture to conftest.py --- tests/test_pacu_data_command.py | 134 +++++++++++++------------------- 1 file changed, 53 insertions(+), 81 deletions(-) diff --git a/tests/test_pacu_data_command.py b/tests/test_pacu_data_command.py index a4c9130d..073ef0e8 100644 --- a/tests/test_pacu_data_command.py +++ b/tests/test_pacu_data_command.py @@ -1,114 +1,86 @@ import pytest -from sqlalchemy import orm, create_engine -from sqlalchemy.orm import sessionmaker -from pacu import settings, Main -from pacu import core +from pacu import Main from pacu.core.models import PacuSession -@pytest.fixture(scope='function') -def db() -> core.base: - # Recreate the engine and session maker each run - core.base.engine: Engine = create_engine(settings.DATABASE_CONNECTION_PATH) - core.base.Session: sessionmaker = sessionmaker(bind=core.base.engine) - core.base.Base.metadata.create_all(core.base.engine) - yield core.base.Session() - -@pytest.fixture(scope='function') -def pacu(db): - pacu = Main() - pacu.database = db - return pacu - -@pytest.fixture(scope='function') -def active_session(db, pacu_session: PacuSession): - pacu_session.activate(db) - yield pacu_session - -@pytest.fixture(scope='function') -def pacu_session(db: orm.session.Session): - query: orm.Query = db.query(PacuSession) - assert query.count() == 0 - - pacu_session = PacuSession() - db.add(pacu_session) - yield pacu_session def test_parse_data_command_returns_help(pacu: Main, active_session: PacuSession): - msg = pacu._parse_data_command(['data', 'non-existent-service'], active_session) - assert 'Service not found. Please use the service name below.' in msg - assert 'APIGateway CloudTrail CloudWatch CodeBuild Cognito' in msg + msg = pacu._parse_data_command(["data", "non-existent-service"], active_session) + assert "Service not found. Please use the service name below." in msg + assert "APIGateway CloudTrail CloudWatch CodeBuild Cognito" in msg -def test_parse_data_command_returns_no_data_found(pacu: Main, active_session: PacuSession): - msg = pacu._parse_data_command(['data', 'CloudWatch'], active_session) - assert 'No data found' in msg +def test_parse_data_command_returns_no_data_found( + pacu: Main, active_session: PacuSession +): + msg = pacu._parse_data_command(["data", "CloudWatch"], active_session) + assert "No data found" in msg -def test_parse_data_command_returns_no_data_found_case_insensitive(pacu: Main, active_session: PacuSession): - msg = pacu._parse_data_command(['data', 'cloudwatch'], active_session) - assert 'No data found' in msg +def test_parse_data_command_returns_no_data_found_case_insensitive( + pacu: Main, active_session: PacuSession +): + msg = pacu._parse_data_command(["data", "cloudwatch"], active_session) + assert "No data found" in msg -@pytest.fixture(scope='function') -def pacu_with_data(pacu: Main, active_session: PacuSession): - active_session.update(pacu.database, CloudWatch={"test_key": "test_value"}) - return pacu +def test_parse_data_command_returns_data( + pacu_with_data: Main, active_session: PacuSession +): + msg = pacu_with_data._parse_data_command(["data", "CloudWatch"], active_session) + assert "test_key" in msg + assert "test_value" in msg -def test_parse_data_command_returns_data(pacu_with_data: Main, active_session: PacuSession): - msg = pacu_with_data._parse_data_command(['data', 'CloudWatch'], active_session) - assert 'test_key' in msg - assert 'test_value' in msg - - -def test_parse_data_command_returns_data_case_insensitive(pacu_with_data: Main, active_session: PacuSession): - msg = pacu_with_data._parse_data_command(['data', 'cloudwatch'], active_session) - assert 'test_key' in msg - assert 'test_value' in msg +def test_parse_data_command_returns_data_case_insensitive( + pacu_with_data: Main, active_session: PacuSession +): + msg = pacu_with_data._parse_data_command(["data", "cloudwatch"], active_session) + assert "test_key" in msg + assert "test_value" in msg service_data = { - 'lowercase_key': 'lowercase_key_value', - 'UPERCASE_KEY': 'upercase_key_value', - 'MixCase_Key': 'mixcase_key_value', - 'no_data_key': None - } + "lowercase_key": "lowercase_key_value", + "UPERCASE_KEY": "upercase_key_value", + "MixCase_Key": "mixcase_key_value", + "no_data_key": None, +} -def test_parse_data_command_sub_service_returns_help(pacu:Main): - msg = pacu._parse_data_command_sub_service(service_data, 'non_existent_sub_service') - assert 'Sub-service not found. Please use the sub-service name below.' in msg - assert 'lowercase_key\tUPERCASE_KEY\tMixCase_Key\tno_data_key' in msg +def test_parse_data_command_sub_service_returns_help(pacu: Main): + msg = pacu._parse_data_command_sub_service(service_data, "non_existent_sub_service") + assert "Sub-service not found. Please use the sub-service name below." in msg + assert "lowercase_key\tUPERCASE_KEY\tMixCase_Key\tno_data_key" in msg -def test_parse_data_command_sub_service_lowercase(pacu:Main): - msg = pacu._parse_data_command_sub_service(service_data, 'lowercase_key') +def test_parse_data_command_sub_service_lowercase(pacu: Main): + msg = pacu._parse_data_command_sub_service(service_data, "lowercase_key") assert '"lowercase_key_value"' == msg - msg = pacu._parse_data_command_sub_service(service_data, 'upercase_key') + msg = pacu._parse_data_command_sub_service(service_data, "upercase_key") assert '"upercase_key_value"' == msg - msg = pacu._parse_data_command_sub_service(service_data, 'mixcase_key') + msg = pacu._parse_data_command_sub_service(service_data, "mixcase_key") assert '"mixcase_key_value"' == msg - msg = pacu._parse_data_command_sub_service(service_data, 'no_data_key') - assert ' No data found.' == msg + msg = pacu._parse_data_command_sub_service(service_data, "no_data_key") + assert " No data found." == msg -def test_parse_data_command_sub_service_upercase(pacu:Main): - msg = pacu._parse_data_command_sub_service(service_data, 'LOWERCASE_KEY') +def test_parse_data_command_sub_service_upercase(pacu: Main): + msg = pacu._parse_data_command_sub_service(service_data, "LOWERCASE_KEY") assert '"lowercase_key_value"' == msg - msg = pacu._parse_data_command_sub_service(service_data, 'UPERCASE_KEY') + msg = pacu._parse_data_command_sub_service(service_data, "UPERCASE_KEY") assert '"upercase_key_value"' == msg - msg = pacu._parse_data_command_sub_service(service_data, 'MIXCASE_KEY') + msg = pacu._parse_data_command_sub_service(service_data, "MIXCASE_KEY") assert '"mixcase_key_value"' == msg - msg = pacu._parse_data_command_sub_service(service_data, 'NO_DATA_KEY') - assert ' No data found.' == msg + msg = pacu._parse_data_command_sub_service(service_data, "NO_DATA_KEY") + assert " No data found." == msg -def test_parse_data_command_sub_service_mixcase(pacu:Main): - msg = pacu._parse_data_command_sub_service(service_data, 'LowerCase_Key') +def test_parse_data_command_sub_service_mixcase(pacu: Main): + msg = pacu._parse_data_command_sub_service(service_data, "LowerCase_Key") assert '"lowercase_key_value"' == msg - msg = pacu._parse_data_command_sub_service(service_data, 'UperCase_Key') + msg = pacu._parse_data_command_sub_service(service_data, "UperCase_Key") assert '"upercase_key_value"' == msg - msg = pacu._parse_data_command_sub_service(service_data, 'MixCase_Key') + msg = pacu._parse_data_command_sub_service(service_data, "MixCase_Key") assert '"mixcase_key_value"' == msg - msg = pacu._parse_data_command_sub_service(service_data, 'No_Data_Key') - assert ' No data found.' == msg + msg = pacu._parse_data_command_sub_service(service_data, "No_Data_Key") + assert " No data found." == msg From c8606c2bad07619efc8a73596ab4dbf0492ea640 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Thu, 30 May 2024 19:28:44 +0200 Subject: [PATCH 06/28] move fixtures to conftest.py --- tests/test_pacu_session.py | 45 ++------------------------------------ 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/tests/test_pacu_session.py b/tests/test_pacu_session.py index 6b24607e..50308c70 100644 --- a/tests/test_pacu_session.py +++ b/tests/test_pacu_session.py @@ -1,40 +1,15 @@ +import os import pytest import sqlalchemy.exc -from sqlalchemy import orm, create_engine, Column -from sqlalchemy.engine import Engine -from sqlalchemy.orm import sessionmaker, Session -from sqlalchemy_utils import JSONType +from sqlalchemy import orm -from pacu import settings -from pacu import core from pacu.core.models import PacuSession, migrations -# Import base and models after settings are set up -settings.DATABASE_CONNECTION_PATH = "sqlite:///:memory:" - - -@pytest.fixture(scope='function') -def db() -> core.base: - # Recreate the engine and session maker each run - core.base.engine: Engine = create_engine(settings.DATABASE_CONNECTION_PATH) - core.base.Session: sessionmaker = sessionmaker(bind=core.base.engine) - core.base.Base.metadata.create_all(core.base.engine) - yield core.base.Session() - def test_sanity(db: orm.session.Session): assert PacuSession().__class__ == PacuSession -@pytest.fixture(scope='function') -def db_new_column(db: Session): - # Recreate the engine and session maker each run - PacuSession.TestSvc = Column(JSONType, nullable=False, default=dict) - PacuSession.aws_data_field_names = PacuSession.aws_data_field_names + ('TestSvc',) - core.base.Session: sessionmaker = sessionmaker(bind=core.base.engine) - yield core.base.Session() - - def test_migrations(db_new_column): with pytest.raises(sqlalchemy.exc.OperationalError): PacuSession.get_active_session(db_new_column) @@ -44,27 +19,11 @@ def test_migrations(db_new_column): assert PacuSession.get_active_session(db_new_column) is None -@pytest.fixture(scope='function') -def pacu_session(db: orm.session.Session): - query: orm.Query = db.query(PacuSession) - assert query.count() == 0 - - pacu_session = PacuSession() - db.add(pacu_session) - yield pacu_session - - def test_pacu_session_in_db(db, pacu_session: PacuSession): query: orm.Query = db.query(PacuSession) result: PacuSession = query.first() assert result.id == pacu_session.id -@pytest.fixture(scope='function') -def active_session(db, pacu_session: PacuSession): - pacu_session.activate(db) - yield pacu_session - - def test_active_session(active_session: PacuSession): assert active_session.is_active From 92532236a08f55b2a65d308c31ed1594fa897fe7 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Thu, 30 May 2024 19:29:26 +0200 Subject: [PATCH 07/28] init tests --- pacu/modules/cognito__attack/tests/__init__.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pacu/modules/cognito__attack/tests/__init__.py diff --git a/pacu/modules/cognito__attack/tests/__init__.py b/pacu/modules/cognito__attack/tests/__init__.py new file mode 100644 index 00000000..5d5a6fba --- /dev/null +++ b/pacu/modules/cognito__attack/tests/__init__.py @@ -0,0 +1,2 @@ +def test_cognito(): + assert True From 958e5d3b21ca15b9eeb2ba61de48a1e0faf37a5c Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Thu, 30 May 2024 19:42:00 +0200 Subject: [PATCH 08/28] rename file, to have unique test file names --- .../tests/{test_main.py => test_cfn__resource_injection.py} | 0 .../tests/{__init__.py => test_cognito__attack.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename pacu/modules/cfn__resource_injection/tests/{test_main.py => test_cfn__resource_injection.py} (100%) rename pacu/modules/cognito__attack/tests/{__init__.py => test_cognito__attack.py} (100%) diff --git a/pacu/modules/cfn__resource_injection/tests/test_main.py b/pacu/modules/cfn__resource_injection/tests/test_cfn__resource_injection.py similarity index 100% rename from pacu/modules/cfn__resource_injection/tests/test_main.py rename to pacu/modules/cfn__resource_injection/tests/test_cfn__resource_injection.py diff --git a/pacu/modules/cognito__attack/tests/__init__.py b/pacu/modules/cognito__attack/tests/test_cognito__attack.py similarity index 100% rename from pacu/modules/cognito__attack/tests/__init__.py rename to pacu/modules/cognito__attack/tests/test_cognito__attack.py From 8798c6a229e5b65518c646cf3670ab1424c072d6 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Thu, 30 May 2024 22:04:58 +0200 Subject: [PATCH 09/28] update aws_credentials fixture with cleanup --- conftest.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/conftest.py b/conftest.py index 871a7ebe..5044d667 100644 --- a/conftest.py +++ b/conftest.py @@ -62,7 +62,15 @@ def pacu_with_data(pacu: Main, active_session: PacuSession): @pytest.fixture def aws_credentials(): """Mocked AWS Credentials for moto.""" + + original_env = dict(os.environ) + os.environ["AWS_ACCESS_KEY_ID"] = "testing" os.environ["AWS_SECRET_ACCESS_KEY"] = "testing" os.environ["AWS_SECURITY_TOKEN"] = "testing" os.environ["AWS_SESSION_TOKEN"] = "testing" + + yield + + os.environ.clear() + os.environ.update(original_env) From 0eb3eabcce4746c8ef21e7eeb6d36433fc70cc80 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Fri, 31 May 2024 22:24:40 +0200 Subject: [PATCH 10/28] standalone test poc --- .../tests/test_cognito__attack.py | 95 ++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/pacu/modules/cognito__attack/tests/test_cognito__attack.py b/pacu/modules/cognito__attack/tests/test_cognito__attack.py index 5d5a6fba..24bb6d88 100644 --- a/pacu/modules/cognito__attack/tests/test_cognito__attack.py +++ b/pacu/modules/cognito__attack/tests/test_cognito__attack.py @@ -1,2 +1,93 @@ -def test_cognito(): - assert True +import unittest.mock + +import boto3 +import moto +from pacu import settings +from pacu.main import Main +from pacu.modules.cognito__attack.main import main +from pacu.modules.cognito__attack.tests.dataclasses import ( + AWSCredentials, + CognitoCredentials, +) +import builtins + + +EMAIL = "test@example.com" +USERNAME = "random" +PASSWORD = "XXXXXXXXXXXXX" + + +def test_me( + pacu: Main, +): + + with moto.mock_cognitoidp(): + + client = boto3.client( + "cognito-idp", + region_name=settings.REGION, + ) + + response = client.create_user_pool( + PoolName="TestUserPool", + UsernameAttributes=[ + "email", + ], + # Schema=[ + # { + # "AttributeName": "email", + # "AttributeDataType": "String", + # } + # ], + ) + + user_pool_id = response["UserPool"]["Id"] + + client_response = client.create_user_pool_client( + ClientName="AppClient", + GenerateSecret=False, + UserPoolId=user_pool_id, + ) + + cog = CognitoCredentials( + client=client, + user_pool_id=user_pool_id, + client_id=client_response["UserPoolClient"]["ClientId"], + client_name=client_response["UserPoolClient"]["ClientName"], + identity_pool_id=None, + ) + + with moto.mock_cognitoidentity(): + c = boto3.client( + "cognito-identity", + region_name=settings.REGION, + ) + + c_resposnse = c.create_identity_pool( + IdentityPoolName="TestIdentityPool", + AllowUnauthenticatedIdentities=False, + ) + + cog.identity_pool_id = c_resposnse["IdentityPoolId"] + + args = [ + "--username", + EMAIL, + "--email", + EMAIL, + "--password", + PASSWORD, + "--identity_pools", + cog.identity_pool_id, + "--user_pool_clients", + f"{cog.client_id}@{cog.user_pool_id}", + ] + + with unittest.mock.patch.object( + builtins, "input", side_effect=["n", "", "", "", ""] + ): + main(args=args, pacu_main=pacu) + + response = client.admin_get_user(UserPoolId=cog.user_pool_id, Username=EMAIL) + + user_status = response["UserStatus"] From f0542a25c55dbf95e962dfc1192c9d562b85ec32 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Fri, 31 May 2024 22:25:18 +0200 Subject: [PATCH 11/28] update conftest --- conftest.py | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/conftest.py b/conftest.py index 5044d667..472d0154 100644 --- a/conftest.py +++ b/conftest.py @@ -1,5 +1,3 @@ -import boto3 -import moto import os import pytest from pacu import settings, Main @@ -10,6 +8,7 @@ from sqlalchemy_utils import JSONType from sqlalchemy.engine import Engine +from pacu.modules.cognito__attack.tests.dataclasses import AWSCredentials settings.DATABASE_CONNECTION_PATH = "sqlite:///:memory:" @@ -23,10 +22,18 @@ def db() -> core.base: @pytest.fixture(scope="function") -def pacu(db): +def pacu(db, aws_credentials: AWSCredentials, active_session: PacuSession): pacu = Main() pacu.database = db - return pacu + + pacu.set_keys( + key_alias="pytest", + access_key_id=aws_credentials.access_key_id, + secret_access_key=aws_credentials.secret_access_key, + session_token=aws_credentials.session_token, + ) + + yield pacu @pytest.fixture(scope="function") @@ -40,7 +47,7 @@ def pacu_session(db: orm.session.Session): query: orm.Query = db.query(PacuSession) assert query.count() == 0 - pacu_session = PacuSession() + pacu_session = PacuSession(name="test") db.add(pacu_session) yield pacu_session @@ -56,21 +63,24 @@ def db_new_column(db: Session): @pytest.fixture(scope="function") def pacu_with_data(pacu: Main, active_session: PacuSession): active_session.update(pacu.database, CloudWatch={"test_key": "test_value"}) - return pacu + yield pacu -@pytest.fixture +@pytest.fixture(autouse=True) def aws_credentials(): """Mocked AWS Credentials for moto.""" - original_env = dict(os.environ) - - os.environ["AWS_ACCESS_KEY_ID"] = "testing" - os.environ["AWS_SECRET_ACCESS_KEY"] = "testing" - os.environ["AWS_SECURITY_TOKEN"] = "testing" - os.environ["AWS_SESSION_TOKEN"] = "testing" + value = "testing" + creds = AWSCredentials( + access_key_id=value, + secret_access_key=value, + session_token=value, + security_token=value, + ) - yield + os.environ["AWS_ACCESS_KEY_ID"] = creds.access_key_id + os.environ["AWS_SECRET_ACCESS_KEY"] = creds.secret_access_key + os.environ["AWS_SECURITY_TOKEN"] = creds.session_token + os.environ["AWS_SESSION_TOKEN"] = creds.session_token - os.environ.clear() - os.environ.update(original_env) + yield creds From 20299c350734c15899e7e069aca8ba6bd3fddc4b Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Fri, 31 May 2024 22:25:38 +0200 Subject: [PATCH 12/28] add region as setting --- pacu/settings.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pacu/settings.py b/pacu/settings.py index 30459df7..631b7a82 100644 --- a/pacu/settings.py +++ b/pacu/settings.py @@ -12,16 +12,18 @@ # at the time an error is written to the logs. Use with extreme caution. from pathlib import Path -ERROR_LOG_VERBOSITY = 'minimal' +ERROR_LOG_VERBOSITY = "minimal" -_home_dir = Path('~/.local/share/pacu') +REGION = "us-east-1" + +_home_dir = Path("~/.local/share/pacu") home_dir = _home_dir.expanduser().absolute() os.makedirs(home_dir, exist_ok=True, mode=0o700) -DATABASE_FILE_PATH = os.path.join(home_dir, 'sqlite.db') +DATABASE_FILE_PATH = os.path.join(home_dir, "sqlite.db") if os.path.isabs(DATABASE_FILE_PATH): - DATABASE_CONNECTION_PATH = 'sqlite:///' + DATABASE_FILE_PATH + DATABASE_CONNECTION_PATH = "sqlite:///" + DATABASE_FILE_PATH else: - DATABASE_CONNECTION_PATH = 'sqlite://' + DATABASE_FILE_PATH + DATABASE_CONNECTION_PATH = "sqlite://" + DATABASE_FILE_PATH From 302bf1fcd1ad6ac45b4fc02a9fb6cfcbc2f31aae Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Fri, 31 May 2024 22:25:56 +0200 Subject: [PATCH 13/28] aws_credentials are now a autouse fixture --- .../cfn__resource_injection_lambda/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/conftest.py b/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/conftest.py index b9c961dd..c89b87ec 100644 --- a/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/conftest.py +++ b/pacu/modules/cfn__resource_injection/cfn__resource_injection_lambda/tests/conftest.py @@ -5,7 +5,7 @@ @pytest.fixture(scope="function") -def s3(aws_credentials): +def s3(): with moto.mock_s3(): yield boto3.client("s3", region_name="us-east-1") From 771690e99ebc4928f14042a484ecbbc8b6c9de96 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Fri, 31 May 2024 22:26:13 +0200 Subject: [PATCH 14/28] add check for TokenType --- pacu/modules/cognito__attack/main.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pacu/modules/cognito__attack/main.py b/pacu/modules/cognito__attack/main.py index e99f2ed6..2689d983 100644 --- a/pacu/modules/cognito__attack/main.py +++ b/pacu/modules/cognito__attack/main.py @@ -500,9 +500,12 @@ def main(args, pacu_main: Main): "Your refresh token is: " + tokens["AuthenticationResult"]["RefreshToken"] ) - print( - "Your token type is: " + tokens["AuthenticationResult"]["TokenType"] - ) + + if "TokenType" in tokens["AuthenticationResult"]: + print( + "Your token type is: " + + tokens["AuthenticationResult"]["TokenType"] + ) attack_user["Username"] = username attack_user["Region"] = up_client["Region"] attack_user["UserPoolId"] = up_client["UserPoolId"] @@ -516,9 +519,11 @@ def main(args, pacu_main: Main): attack_user["Tokens"]["RefreshToken"] = tokens["AuthenticationResult"][ "RefreshToken" ] - attack_user["Tokens"]["TokenType"] = tokens["AuthenticationResult"][ - "TokenType" - ] + + if "TokenType" in tokens["AuthenticationResult"]: + attack_user["Tokens"]["TokenType"] = tokens["AuthenticationResult"][ + "TokenType" + ] credentials = get_identity_credentials( cognito_identity_pools, identity_client, @@ -944,7 +949,7 @@ def main(args, pacu_main: Main): for var in vars(args): if var == "regions": continue - if not getattr(args, var) and ARG_FIELD_MAPPER[var] in gathered_data: + if not getattr(args, var) and ARG_FIELD_MAPPER.get(var) in gathered_data: del gathered_data[ARG_FIELD_MAPPER[var]] cognito_data = deepcopy(session.Cognito) From 5ffe24110f98686c91118996dc38ca14df484494 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Fri, 31 May 2024 22:26:21 +0200 Subject: [PATCH 15/28] add conftest --- .../modules/cognito__attack/tests/conftest.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 pacu/modules/cognito__attack/tests/conftest.py diff --git a/pacu/modules/cognito__attack/tests/conftest.py b/pacu/modules/cognito__attack/tests/conftest.py new file mode 100644 index 00000000..8130ce29 --- /dev/null +++ b/pacu/modules/cognito__attack/tests/conftest.py @@ -0,0 +1,61 @@ +import pytest +import moto +import boto3 +from pacu.settings import REGION + +from pacu.modules.cognito__attack.tests.dataclasses import CognitoCredentials + + +@pytest.fixture +def mock_cognito_user_pool(): + + with moto.mock_cognitoidp(): + + client = boto3.client( + "cognito-idp", + region_name=REGION, + ) + + response = client.create_user_pool( + PoolName="TestUserPool", + UsernameAttributes=[ + "email", + ], + # Schema=[ + # { + # "AttributeName": "email", + # "AttributeDataType": "String", + # } + # ], + ) + + user_pool_id = response["UserPool"]["Id"] + + client_response = client.create_user_pool_client( + ClientName="AppClient", + GenerateSecret=False, + UserPoolId=user_pool_id, + ) + + cog = CognitoCredentials( + client=client, + user_pool_id=user_pool_id, + client_id=client_response["UserPoolClient"]["ClientId"], + client_name=client_response["UserPoolClient"]["ClientName"], + identity_pool_id=None, + ) + + with moto.mock_cognitoidentity(): + c = boto3.client( + "cognito-identity", + region_name=REGION, + ) + + c_resposnse = c.create_identity_pool( + IdentityPoolName="TestIdentityPool", + AllowUnauthenticatedIdentities=False, + ) + + cog.identity_pool_id = c_resposnse["IdentityPoolId"] + + yield cog From a58f1c660431b3d4e2f2a9d023e4696cd21de20f Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Fri, 31 May 2024 22:26:31 +0200 Subject: [PATCH 16/28] add dataclasses --- .../cognito__attack/tests/dataclasses.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 pacu/modules/cognito__attack/tests/dataclasses.py diff --git a/pacu/modules/cognito__attack/tests/dataclasses.py b/pacu/modules/cognito__attack/tests/dataclasses.py new file mode 100644 index 00000000..07bb4414 --- /dev/null +++ b/pacu/modules/cognito__attack/tests/dataclasses.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass +from typing import Any + + +@dataclass +class CognitoCredentials: + client: Any + user_pool_id: str + client_id: str + client_name: str + identity_pool_id: str + + +@dataclass +class AWSCredentials: + access_key_id: str + secret_access_key: str + session_token: str + security_token: str From 1dca34cdb0fe673023f053a629e7f7027d440c5b Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Wed, 5 Jun 2024 06:23:56 +0200 Subject: [PATCH 17/28] add minimal test --- .../tests/test_cognito__attack.py | 100 +++++------------- 1 file changed, 24 insertions(+), 76 deletions(-) diff --git a/pacu/modules/cognito__attack/tests/test_cognito__attack.py b/pacu/modules/cognito__attack/tests/test_cognito__attack.py index 24bb6d88..0db33f66 100644 --- a/pacu/modules/cognito__attack/tests/test_cognito__attack.py +++ b/pacu/modules/cognito__attack/tests/test_cognito__attack.py @@ -1,93 +1,41 @@ +import builtins import unittest.mock -import boto3 -import moto -from pacu import settings from pacu.main import Main from pacu.modules.cognito__attack.main import main -from pacu.modules.cognito__attack.tests.dataclasses import ( - AWSCredentials, - CognitoCredentials, -) -import builtins - +from pacu.modules.cognito__attack.tests.dataclasses import CognitoServiceConfig EMAIL = "test@example.com" USERNAME = "random" PASSWORD = "XXXXXXXXXXXXX" -def test_me( - pacu: Main, +def test_cognito__attack_minimal( + pacu: Main, mock_cognito_user_pool: CognitoServiceConfig ): - with moto.mock_cognitoidp(): - - client = boto3.client( - "cognito-idp", - region_name=settings.REGION, - ) - - response = client.create_user_pool( - PoolName="TestUserPool", - UsernameAttributes=[ - "email", - ], - # Schema=[ - # { - # "AttributeName": "email", - # "AttributeDataType": "String", - # } - # ], - ) - - user_pool_id = response["UserPool"]["Id"] - - client_response = client.create_user_pool_client( - ClientName="AppClient", - GenerateSecret=False, - UserPoolId=user_pool_id, - ) - - cog = CognitoCredentials( - client=client, - user_pool_id=user_pool_id, - client_id=client_response["UserPoolClient"]["ClientId"], - client_name=client_response["UserPoolClient"]["ClientName"], - identity_pool_id=None, - ) - - with moto.mock_cognitoidentity(): - c = boto3.client( - "cognito-identity", - region_name=settings.REGION, - ) - - c_resposnse = c.create_identity_pool( - IdentityPoolName="TestIdentityPool", - AllowUnauthenticatedIdentities=False, - ) + args = [ + "--username", + EMAIL, + "--email", + EMAIL, + "--password", + PASSWORD, + "--identity_pools", + mock_cognito_user_pool.identity_pool_id, + "--user_pool_clients", + f"{mock_cognito_user_pool.client_id}@{mock_cognito_user_pool.user_pool_id}", + ] - cog.identity_pool_id = c_resposnse["IdentityPoolId"] + input_list = ["n", "", "n", "n", "n"] - args = [ - "--username", - EMAIL, - "--email", - EMAIL, - "--password", - PASSWORD, - "--identity_pools", - cog.identity_pool_id, - "--user_pool_clients", - f"{cog.client_id}@{cog.user_pool_id}", - ] + with unittest.mock.patch.object(builtins, "input", side_effect=input_list): + main(args=args, pacu_main=pacu) - with unittest.mock.patch.object( - builtins, "input", side_effect=["n", "", "", "", ""] - ): - main(args=args, pacu_main=pacu) + response = mock_cognito_user_pool.client.admin_get_user( + UserPoolId=mock_cognito_user_pool.user_pool_id, Username=EMAIL + ) - response = client.admin_get_user(UserPoolId=cog.user_pool_id, Username=EMAIL) + user_status = response["UserStatus"] - user_status = response["UserStatus"] + assert user_status == "CONFIRMED" From 5a597335e4533c5701f091f1a752be15010fe214 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Wed, 5 Jun 2024 06:24:37 +0200 Subject: [PATCH 18/28] update fixture to provide with context --- .../modules/cognito__attack/tests/conftest.py | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/pacu/modules/cognito__attack/tests/conftest.py b/pacu/modules/cognito__attack/tests/conftest.py index 8130ce29..8e6f4700 100644 --- a/pacu/modules/cognito__attack/tests/conftest.py +++ b/pacu/modules/cognito__attack/tests/conftest.py @@ -3,7 +3,7 @@ import boto3 from pacu.settings import REGION -from pacu.modules.cognito__attack.tests.dataclasses import CognitoCredentials +from pacu.modules.cognito__attack.tests.dataclasses import CognitoServiceConfig @pytest.fixture @@ -37,25 +37,21 @@ def mock_cognito_user_pool(): UserPoolId=user_pool_id, ) - cog = CognitoCredentials( + with moto.mock_cognitoidentity(): + c = boto3.client( + "cognito-identity", + region_name=REGION, + ) + + c_resposnse = c.create_identity_pool( + IdentityPoolName="TestIdentityPool", + AllowUnauthenticatedIdentities=False, + ) + + yield CognitoServiceConfig( client=client, user_pool_id=user_pool_id, client_id=client_response["UserPoolClient"]["ClientId"], client_name=client_response["UserPoolClient"]["ClientName"], - identity_pool_id=None, - ) - - with moto.mock_cognitoidentity(): - c = boto3.client( - "cognito-identity", - region_name=REGION, + identity_pool_id=c_resposnse["IdentityPoolId"], ) - - c_resposnse = c.create_identity_pool( - IdentityPoolName="TestIdentityPool", - AllowUnauthenticatedIdentities=False, - ) - - cog.identity_pool_id = c_resposnse["IdentityPoolId"] - - yield cog From c0845921d645640c6eee1eaa0a6894ad42e91193 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Wed, 5 Jun 2024 06:25:02 +0200 Subject: [PATCH 19/28] add client type --- pacu/modules/cognito__attack/tests/dataclasses.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pacu/modules/cognito__attack/tests/dataclasses.py b/pacu/modules/cognito__attack/tests/dataclasses.py index 07bb4414..fd654722 100644 --- a/pacu/modules/cognito__attack/tests/dataclasses.py +++ b/pacu/modules/cognito__attack/tests/dataclasses.py @@ -1,10 +1,11 @@ +from botocore.client import BaseClient from dataclasses import dataclass from typing import Any @dataclass -class CognitoCredentials: - client: Any +class CognitoServiceConfig: + client: BaseClient user_pool_id: str client_id: str client_name: str From 545a5eb1acada0a88ad54c23647ae68066f213cc Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Wed, 5 Jun 2024 06:25:25 +0200 Subject: [PATCH 20/28] update imports --- pacu/modules/cognito__attack/tests/dataclasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pacu/modules/cognito__attack/tests/dataclasses.py b/pacu/modules/cognito__attack/tests/dataclasses.py index fd654722..571bd7a7 100644 --- a/pacu/modules/cognito__attack/tests/dataclasses.py +++ b/pacu/modules/cognito__attack/tests/dataclasses.py @@ -1,6 +1,6 @@ -from botocore.client import BaseClient from dataclasses import dataclass -from typing import Any + +from botocore.client import BaseClient @dataclass From 2a1fb5ede64055e3a9d91249c2206f78dd32b2df Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Wed, 5 Jun 2024 06:25:42 +0200 Subject: [PATCH 21/28] update imports --- pacu/modules/cognito__attack/tests/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pacu/modules/cognito__attack/tests/conftest.py b/pacu/modules/cognito__attack/tests/conftest.py index 8e6f4700..060490a0 100644 --- a/pacu/modules/cognito__attack/tests/conftest.py +++ b/pacu/modules/cognito__attack/tests/conftest.py @@ -1,9 +1,9 @@ -import pytest -import moto import boto3 -from pacu.settings import REGION +import moto +import pytest from pacu.modules.cognito__attack.tests.dataclasses import CognitoServiceConfig +from pacu.settings import REGION @pytest.fixture From dee66ad3c8b9b137ce3a3f02eb45e685632a0f42 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:09:43 +0200 Subject: [PATCH 22/28] sort imports --- pacu/modules/cognito__attack/main.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pacu/modules/cognito__attack/main.py b/pacu/modules/cognito__attack/main.py index 2689d983..621fb9c0 100644 --- a/pacu/modules/cognito__attack/main.py +++ b/pacu/modules/cognito__attack/main.py @@ -1,17 +1,19 @@ -import re +import argparse import base64 +import json +import re import webbrowser +from copy import deepcopy +from dataclasses import dataclass +from typing import Dict, List, Optional + import qrcode -import argparse -import json +from botocore.client import BaseClient +from botocore.exceptions import ClientError from pycognito.aws_srp import AWSSRP -from dataclasses import dataclass -from typing import List, Dict, Optional from pycognito.exceptions import SoftwareTokenMFAChallengeException -from copy import deepcopy -from botocore.exceptions import ClientError + from pacu import Main -from botocore.client import BaseClient # Using Spencer's iam_enum.py as a template From efa995014fb1d989cef854f4ec54588da9e71a20 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:09:57 +0200 Subject: [PATCH 23/28] remove schema --- pacu/modules/cognito__attack/tests/conftest.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pacu/modules/cognito__attack/tests/conftest.py b/pacu/modules/cognito__attack/tests/conftest.py index 060490a0..60769201 100644 --- a/pacu/modules/cognito__attack/tests/conftest.py +++ b/pacu/modules/cognito__attack/tests/conftest.py @@ -18,15 +18,7 @@ def mock_cognito_user_pool(): response = client.create_user_pool( PoolName="TestUserPool", - UsernameAttributes=[ - "email", - ], - # Schema=[ - # { - # "AttributeName": "email", - # "AttributeDataType": "String", - # } - # ], + AutoVerifiedAttributes=["email"], ) user_pool_id = response["UserPool"]["Id"] From ebb81b0429d838fa656a88c26378bcb39ac186f6 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Sat, 8 Jun 2024 19:52:50 +0200 Subject: [PATCH 24/28] add sanity test --- .../tests/test_cognito__attack.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pacu/modules/cognito__attack/tests/test_cognito__attack.py b/pacu/modules/cognito__attack/tests/test_cognito__attack.py index 0db33f66..3fe49a1c 100644 --- a/pacu/modules/cognito__attack/tests/test_cognito__attack.py +++ b/pacu/modules/cognito__attack/tests/test_cognito__attack.py @@ -39,3 +39,41 @@ def test_cognito__attack_minimal( user_status = response["UserStatus"] assert user_status == "CONFIRMED" + +def test_sanity(pacu: Main, mock_cognito_user_pool: CognitoServiceConfig): + conn = pacu.get_boto3_client("cognito-idp", REGION) + pool_id = mock_cognito_user_pool.user_pool_id + client_id = mock_cognito_user_pool.client_id + + conn.admin_create_user( + UserPoolId=pool_id, + Username=USERNAME, + TemporaryPassword=PASSWORD, + ) + + key = bytes(str(PASSWORD).encode("latin-1")) + msg = bytes(str(USERNAME + client_id).encode("latin-1")) + new_digest = hmac.new(key, msg, hashlib.sha256).digest() + secret_hash = base64.b64encode(new_digest).decode() + result = conn.initiate_auth( + ClientId=client_id, + AuthFlow="USER_SRP_AUTH", + AuthParameters={ + "USERNAME": USERNAME, + "SRP_A": uuid.uuid4().hex, + "SECRET_HASH": secret_hash, + }, + ) + + result = conn.respond_to_auth_challenge( + ClientId=client_id, + ChallengeName=result["ChallengeName"], + ChallengeResponses={ + "PASSWORD_CLAIM_SIGNATURE": str(uuid.uuid4()), + "PASSWORD_CLAIM_SECRET_BLOCK": result["Session"], + "TIMESTAMP": str(uuid.uuid4()), + "USERNAME": USERNAME, + }, + ) + + assert result["ResponseMetadata"]["HTTPStatusCode"] == 200 From 5827f95f1eb2df55a841578f9f6be2b562682f64 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Sat, 8 Jun 2024 19:53:09 +0200 Subject: [PATCH 25/28] update user pool --- pacu/modules/cognito__attack/tests/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pacu/modules/cognito__attack/tests/conftest.py b/pacu/modules/cognito__attack/tests/conftest.py index 60769201..ccadedbc 100644 --- a/pacu/modules/cognito__attack/tests/conftest.py +++ b/pacu/modules/cognito__attack/tests/conftest.py @@ -17,8 +17,7 @@ def mock_cognito_user_pool(): ) response = client.create_user_pool( - PoolName="TestUserPool", - AutoVerifiedAttributes=["email"], + PoolName="TestUserPool", UsernameAttributes=["email"] ) user_pool_id = response["UserPool"]["Id"] From c650612dcd0906e6a94d664b2eb394d586a78f95 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Sat, 8 Jun 2024 19:53:50 +0200 Subject: [PATCH 26/28] add imports for sanity check --- pacu/modules/cognito__attack/tests/test_cognito__attack.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pacu/modules/cognito__attack/tests/test_cognito__attack.py b/pacu/modules/cognito__attack/tests/test_cognito__attack.py index 3fe49a1c..4bc5453e 100644 --- a/pacu/modules/cognito__attack/tests/test_cognito__attack.py +++ b/pacu/modules/cognito__attack/tests/test_cognito__attack.py @@ -1,9 +1,14 @@ +import base64 import builtins +import hashlib +import hmac import unittest.mock +import uuid from pacu.main import Main from pacu.modules.cognito__attack.main import main from pacu.modules.cognito__attack.tests.dataclasses import CognitoServiceConfig +from pacu.settings import REGION EMAIL = "test@example.com" USERNAME = "random" From 7aba55af5d4bc1d03907a33065e1fd7c16d3ed3e Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Sat, 8 Jun 2024 19:54:05 +0200 Subject: [PATCH 27/28] update password --- pacu/modules/cognito__attack/tests/test_cognito__attack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pacu/modules/cognito__attack/tests/test_cognito__attack.py b/pacu/modules/cognito__attack/tests/test_cognito__attack.py index 4bc5453e..612dd1cb 100644 --- a/pacu/modules/cognito__attack/tests/test_cognito__attack.py +++ b/pacu/modules/cognito__attack/tests/test_cognito__attack.py @@ -12,7 +12,7 @@ EMAIL = "test@example.com" USERNAME = "random" -PASSWORD = "XXXXXXXXXXXXX" +PASSWORD = "1R@nd0mP4$$word" def test_cognito__attack_minimal( From 440b0e196b5c6e09e63d8d4969a91165fbeabab7 Mon Sep 17 00:00:00 2001 From: Eduard Schwarzkopf <48969167+EduardSchwarzkopf@users.noreply.github.com> Date: Sat, 8 Jun 2024 20:30:02 +0200 Subject: [PATCH 28/28] update tests --- .../tests/test_cognito__attack.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/pacu/modules/cognito__attack/tests/test_cognito__attack.py b/pacu/modules/cognito__attack/tests/test_cognito__attack.py index 612dd1cb..a55f783f 100644 --- a/pacu/modules/cognito__attack/tests/test_cognito__attack.py +++ b/pacu/modules/cognito__attack/tests/test_cognito__attack.py @@ -32,9 +32,15 @@ def test_cognito__attack_minimal( f"{mock_cognito_user_pool.client_id}@{mock_cognito_user_pool.user_pool_id}", ] - input_list = ["n", "", "n", "n", "n"] + user_input_list = [ + "random_token", # Enter verification code for user + "", # Enter attribute name to modify for user + "", # Enter attribute value to set for user + "", # Modify more custom attributes? (y/n) + "n", # Enter the number of the role you want to assume (or "n" to skip): + ] - with unittest.mock.patch.object(builtins, "input", side_effect=input_list): + with unittest.mock.patch.object(builtins, "input", side_effect=user_input_list): main(args=args, pacu_main=pacu) response = mock_cognito_user_pool.client.admin_get_user( @@ -44,27 +50,32 @@ def test_cognito__attack_minimal( user_status = response["UserStatus"] assert user_status == "CONFIRMED" + assert response["UserAttributes"][0]["Value"] == EMAIL + assert response["Username"] == response["UserAttributes"][1]["Value"] + -def test_sanity(pacu: Main, mock_cognito_user_pool: CognitoServiceConfig): +def test_cognito__attack_sanity( + pacu: Main, mock_cognito_user_pool: CognitoServiceConfig +): conn = pacu.get_boto3_client("cognito-idp", REGION) pool_id = mock_cognito_user_pool.user_pool_id client_id = mock_cognito_user_pool.client_id conn.admin_create_user( UserPoolId=pool_id, - Username=USERNAME, + Username=EMAIL, TemporaryPassword=PASSWORD, ) key = bytes(str(PASSWORD).encode("latin-1")) - msg = bytes(str(USERNAME + client_id).encode("latin-1")) + msg = bytes(str(EMAIL + client_id).encode("latin-1")) new_digest = hmac.new(key, msg, hashlib.sha256).digest() secret_hash = base64.b64encode(new_digest).decode() result = conn.initiate_auth( ClientId=client_id, AuthFlow="USER_SRP_AUTH", AuthParameters={ - "USERNAME": USERNAME, + "USERNAME": EMAIL, "SRP_A": uuid.uuid4().hex, "SECRET_HASH": secret_hash, }, @@ -77,7 +88,7 @@ def test_sanity(pacu: Main, mock_cognito_user_pool: CognitoServiceConfig): "PASSWORD_CLAIM_SIGNATURE": str(uuid.uuid4()), "PASSWORD_CLAIM_SECRET_BLOCK": result["Session"], "TIMESTAMP": str(uuid.uuid4()), - "USERNAME": USERNAME, + "USERNAME": EMAIL, }, )