From 92a89632fb144911f4f9880683112ac9d1ffd4b7 Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 3 Mar 2022 17:08:32 +0100 Subject: [PATCH] Created new GenericCrossAccountTrustRule (#208) * stopped using _statement_as_list() in CrossAccountCheckingRule in favor of statement_as_list() * created GenericCrossAccountTrustRule * added tests for the new rule * updated changelog and version * updated documentation regarding future deprecations * fix changelog wrong date for 1.4.2 * covering cases for OpenSearch Domain * covering cases for ElasticSearch Domain * covering cases for ksm key - skipped tests pending pycfmodel 0.17.0 * covering cases for S3 buckets * covering cases for KMS Keys by bumping pycfmodel to 0.17.0 * move GenericCrossAccountTrustRule tests into its own file * update documentation * test one side only * updated cross account documentation * fix typo * add test to check that the new generic rule ignores roles * parametrize tests * PR suggestions * improve test imports Co-authored-by: Ramon --- CHANGELOG.md | 12 +- cfripper/__version__.py | 2 +- cfripper/rules/__init__.py | 2 + cfripper/rules/cross_account_trust.py | 87 ++- requirements.txt | 2 +- setup.py | 2 +- tests/rules/test_CrossAccountTrustRule.py | 182 +++---- .../test_GenericCrossAccountTrustRule.py | 510 ++++++++++++++++++ .../generic_resource_no_policies.json | 12 + ...ic_resource_with_cross_account_policy.json | 34 ++ .../generic_resources_no_policies.json | 18 + ...resources_with_cross_account_policies.json | 61 +++ ...ed_cross_account_policy_and_no_policy.json | 39 ++ .../iam_role_to_jump_to_another_account.yaml | 31 ++ .../invalid_generic_resource.json | 31 ++ .../invalid_generic_resources.json | 55 ++ .../mixed_invalid_generic_resources.json | 36 ++ 17 files changed, 991 insertions(+), 125 deletions(-) create mode 100644 tests/rules/test_GenericCrossAccountTrustRule.py create mode 100644 tests/test_templates/rules/CrossAccountTrustRule/generic_resource_no_policies.json create mode 100644 tests/test_templates/rules/CrossAccountTrustRule/generic_resource_with_cross_account_policy.json create mode 100644 tests/test_templates/rules/CrossAccountTrustRule/generic_resources_no_policies.json create mode 100644 tests/test_templates/rules/CrossAccountTrustRule/generic_resources_with_cross_account_policies.json create mode 100644 tests/test_templates/rules/CrossAccountTrustRule/generic_resources_with_mixed_cross_account_policy_and_no_policy.json create mode 100644 tests/test_templates/rules/CrossAccountTrustRule/iam_role_to_jump_to_another_account.yaml create mode 100644 tests/test_templates/rules/CrossAccountTrustRule/invalid_generic_resource.json create mode 100644 tests/test_templates/rules/CrossAccountTrustRule/invalid_generic_resources.json create mode 100644 tests/test_templates/rules/CrossAccountTrustRule/mixed_invalid_generic_resources.json diff --git a/CHANGELOG.md b/CHANGELOG.md index f78dc372..e38480ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,17 @@ # Changelog All notable changes to this project will be documented in this file. -## [1.4.2] - 2022-2-24 +## [1.5.0] +### Updates +- Created `GenericCrossAccountTrustRule` in order to check for CrossAccount issues for generic resources. +- Added documentation regarding the deprecation of `S3CrossAccountTrustRule`, `KMSKeyCrossAccountTrustRule`, `ElasticsearchDomainCrossAccountTrustRule` and `OpenSearchDomainCrossAccountTrustRule`. +- Covering cases for already mapped models in rules inherited from `CrossAccountCheckingRule` with the new `GenericCrossAccountTrustRule`. +### Improvements +- Bump `pycfmodel` to `0.17.0` +### Fixes +- Stopped using `_statement_as_list()` when retrieving statements in `CrossAccountCheckingRule` in favor of `statement_as_list()`. + +## [1.4.2] - 2022-2-28 ### Fixes - Fix how `make install-dev` works, it will install dependencies from `make install` first. ### Improvements diff --git a/cfripper/__version__.py b/cfripper/__version__.py index 40df11f6..a526ac27 100644 --- a/cfripper/__version__.py +++ b/cfripper/__version__.py @@ -1,3 +1,3 @@ -VERSION = (1, 4, 2) +VERSION = (1, 5, 0) __version__ = ".".join(map(str, VERSION)) diff --git a/cfripper/rules/__init__.py b/cfripper/rules/__init__.py index 6a170f6a..f4e9963a 100644 --- a/cfripper/rules/__init__.py +++ b/cfripper/rules/__init__.py @@ -5,6 +5,7 @@ CrossAccountCheckingRule, CrossAccountTrustRule, ElasticsearchDomainCrossAccountTrustRule, + GenericCrossAccountTrustRule, KMSKeyCrossAccountTrustRule, OpenSearchDomainCrossAccountTrustRule, S3CrossAccountTrustRule, @@ -55,6 +56,7 @@ EC2SecurityGroupOpenToWorldRule, ElasticsearchDomainCrossAccountTrustRule, FullWildcardPrincipalRule, + GenericCrossAccountTrustRule, HardcodedRDSPasswordRule, IAMRolesOverprivilegedRule, IAMRoleWildcardActionOnPolicyRule, diff --git a/cfripper/rules/cross_account_trust.py b/cfripper/rules/cross_account_trust.py index a629ec2d..507c8600 100644 --- a/cfripper/rules/cross_account_trust.py +++ b/cfripper/rules/cross_account_trust.py @@ -2,6 +2,7 @@ "CrossAccountCheckingRule", "CrossAccountTrustRule", "ElasticsearchDomainCrossAccountTrustRule", + "GenericCrossAccountTrustRule", "KMSKeyCrossAccountTrustRule", "OpenSearchDomainCrossAccountTrustRule", "S3CrossAccountTrustRule", @@ -53,7 +54,7 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: properties = resource.Properties policy_document = getattr(properties, self.PROPERTY_WITH_POLICYDOCUMENT) if policy_document: - for statement in policy_document._statement_as_list(): + for statement in policy_document.statement_as_list(): filters_available_context = { "config": self._config, "extras": extras, @@ -105,6 +106,52 @@ def _do_statement_check( ) +class GenericCrossAccountTrustRule(CrossAccountCheckingRule): + """ + Checks if the trust policy of every resource grants permissions to principals from other accounts. + Do not use whole accounts as principals. + It doesn't check if policies allow permissions to assume roles in other accounts. + + Risk: + It might allow other AWS identities to escalate privileges. + + Fix: + If cross account permissions are required, the stack should be added to the allowlist for this rule. + Otherwise, the access should be removed from the CloudFormation definition. + + Filters context: + | Parameter | Type | Description | + |:-----------:|:-----------:|:--------------------------------------------------------------:| + |`config` | `str` | `config` variable available inside the rule | + |`extras` | `str` | `extras` variable available inside the rule | + |`logical_id` | `str` | ID used in CloudFormation to refer the resource being analysed | + |`resource` | `Generic` | Resource that is being addressed | + |`statement` | `Statement` | Statement being checked found in the Resource | + |`principal` | `str` | AWS Principal being checked found in the statement | + |`account_id` | `str` | Account ID found in the principal | + """ + + REASON = "{} has forbidden cross-account with {}" + + def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: + result = Result() + for logical_id, resource in cfmodel.Resources.items(): + policy_documents = resource.policy_documents + if policy_documents: + for document in policy_documents: + for statement in document.policy_document.statement_as_list(): + filters_available_context = { + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + "statement": statement, + } + self._do_statement_check(result, logical_id, statement, filters_available_context) + + return result + + class CrossAccountTrustRule(CrossAccountCheckingRule): """ Checks if the trust policy of a role grants permissions to principals from other accounts. @@ -120,9 +167,9 @@ class CrossAccountTrustRule(CrossAccountCheckingRule): Filters context: | Parameter | Type | Description | |:-----------:|:-----------:|:--------------------------------------------------------------:| - |`config` | str | `config` variable available inside the rule | - |`extras` | str | `extras` variable available inside the rule | - |`logical_id` | str | ID used in Cloudformation to refer the resource being analysed | + |`config` | `str` | `config` variable available inside the rule | + |`extras` | `str` | `extras` variable available inside the rule | + |`logical_id` | `str` | ID used in CloudFormation to refer the resource being analysed | |`resource` | `IAMRole` | Resource that is being addressed | |`statement` | `Statement` | Statement being checked found in the Resource | |`principal` | `str` | AWS Principal being checked found in the statement | @@ -136,6 +183,8 @@ class CrossAccountTrustRule(CrossAccountCheckingRule): class S3CrossAccountTrustRule(CrossAccountCheckingRule): """ + To be replaced by GenericCrossAccountTrustRule. + Check for cross account access in S3 bucket policies. Cross account access by default should not be allowed. Risk: @@ -148,9 +197,9 @@ class S3CrossAccountTrustRule(CrossAccountCheckingRule): Filters context: | Parameter | Type | Description | |:-----------:|:----------------:|:--------------------------------------------------------------:| - |`config` | str | `config` variable available inside the rule | - |`extras` | str | `extras` variable available inside the rule | - |`logical_id` | str | ID used in Cloudformation to refer the resource being analysed | + |`config` | `str` | `config` variable available inside the rule | + |`extras` | `str` | `extras` variable available inside the rule | + |`logical_id` | `str` | ID used in CloudFormation to refer the resource being analysed | |`resource` | `S3BucketPolicy` | Resource that is being addressed | |`statement` | `Statement` | Statement being checked found in the Resource | |`principal` | `str` | AWS Principal being checked found in the statement | @@ -164,6 +213,8 @@ class S3CrossAccountTrustRule(CrossAccountCheckingRule): class KMSKeyCrossAccountTrustRule(CrossAccountCheckingRule): """ + To be replaced by GenericCrossAccountTrustRule. + Checks for KMS keys that allow cross-account principals to get access to the key. Risk: @@ -176,9 +227,9 @@ class KMSKeyCrossAccountTrustRule(CrossAccountCheckingRule): Filters context: | Parameter | Type | Description | |:-----------:|:-----------:|:--------------------------------------------------------------:| - |`config` | str | `config` variable available inside the rule | - |`extras` | str | `extras` variable available inside the rule | - |`logical_id` | str | ID used in Cloudformation to refer the resource being analysed | + |`config` | `str` | `config` variable available inside the rule | + |`extras` | `str` | `extras` variable available inside the rule | + |`logical_id` | `str` | ID used in CloudFormation to refer the resource being analysed | |`resource` | `KMSKey` | Resource that is being addressed | |`statement` | `Statement` | Statement being checked found in the Resource | |`principal` | `str` | AWS Principal being checked found in the statement | @@ -192,6 +243,8 @@ class KMSKeyCrossAccountTrustRule(CrossAccountCheckingRule): class ElasticsearchDomainCrossAccountTrustRule(CrossAccountCheckingRule): """ + To be replaced by GenericCrossAccountTrustRule. + Checks for Elasticsearch domains that allow cross-account principals to get access. Risk: @@ -204,9 +257,9 @@ class ElasticsearchDomainCrossAccountTrustRule(CrossAccountCheckingRule): Filters context: | Parameter | Type | Description | |:-----------:|:-----------:|:--------------------------------------------------------------:| - |`config` | str | `config` variable available inside the rule | - |`extras` | str | `extras` variable available inside the rule | - |`logical_id` | str | ID used in Cloudformation to refer the resource being analysed | + |`config` | `str` | `config` variable available inside the rule | + |`extras` | `str` | `extras` variable available inside the rule | + |`logical_id` | `str` | ID used in CloudFormation to refer the resource being analysed | |`resource` | `ESDomain` | Resource that is being addressed | |`statement` | `Statement` | Statement being checked found in the Resource | |`principal` | `str` | AWS Principal being checked found in the statement | @@ -220,6 +273,8 @@ class ElasticsearchDomainCrossAccountTrustRule(CrossAccountCheckingRule): class OpenSearchDomainCrossAccountTrustRule(CrossAccountCheckingRule): """ + To be replaced by GenericCrossAccountTrustRule. + Checks for OpenSearch domains that allow cross-account principals to get access. Risk: @@ -232,9 +287,9 @@ class OpenSearchDomainCrossAccountTrustRule(CrossAccountCheckingRule): Filters context: | Parameter | Type | Description | |:-----------:|:------------------:|:--------------------------------------------------------------:| - |`config` | str | `config` variable available inside the rule | - |`extras` | str | `extras` variable available inside the rule | - |`logical_id` | str | ID used in Cloudformation to refer the resource being analysed | + |`config` | `str` | `config` variable available inside the rule | + |`extras` | `str` | `extras` variable available inside the rule | + |`logical_id` | `str` | ID used in CloudFormation to refer the resource being analysed | |`resource` | `OpenSearchDomain` | Resource that is being addressed | |`statement` | `Statement` | Statement being checked found in the Resource | |`principal` | `str` | AWS Principal being checked found in the statement | diff --git a/requirements.txt b/requirements.txt index adce3a22..a8407742 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ cfn-flip==1.3.0 click==7.1.2 jmespath==0.10.0 pluggy==0.13.1 -pycfmodel==0.16.3 +pycfmodel==0.17.0 pydantic==1.9.0 pydash==4.7.6 python-dateutil==2.8.2 diff --git a/setup.py b/setup.py index 9965a31f..63b638a6 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ "cfn_flip>=1.2.0", "click~=7.1.1", "pluggy~=0.13.1", - "pycfmodel>=0.16.3", + "pycfmodel>=0.17.0", "pydash~=4.7.6", "PyYAML>=4.2b1", ] diff --git a/tests/rules/test_CrossAccountTrustRule.py b/tests/rules/test_CrossAccountTrustRule.py index 3ec5b1a7..cf66e4db 100644 --- a/tests/rules/test_CrossAccountTrustRule.py +++ b/tests/rules/test_CrossAccountTrustRule.py @@ -35,42 +35,34 @@ def template_valid_with_canonical_id(): return get_cfmodel_from("rules/CrossAccountTrustRule/valid_with_canonical_id.json").resolve() -@pytest.fixture() def template_valid_with_sts(): return get_cfmodel_from("rules/CrossAccountTrustRule/valid_with_sts.yml").resolve() -@pytest.fixture() def template_valid_with_sts_es_domain(): return get_cfmodel_from("rules/CrossAccountTrustRule/valid_with_sts_es_domain.yml").resolve() -@pytest.fixture() def template_valid_with_sts_opensearch_domain(): return get_cfmodel_from("rules/CrossAccountTrustRule/valid_with_sts_opensearch_domain.yml").resolve() -@pytest.fixture() def template_invalid_with_sts(): return get_cfmodel_from("rules/CrossAccountTrustRule/invalid_with_sts.yml").resolve() -@pytest.fixture() def template_invalid_with_sts_es_domain(): return get_cfmodel_from("rules/CrossAccountTrustRule/invalid_with_sts_es_domain.yml").resolve() -@pytest.fixture() def template_invalid_with_sts_opensearch_domain(): return get_cfmodel_from("rules/CrossAccountTrustRule/invalid_with_sts_opensearch_domain.yml").resolve() -@pytest.fixture() def template_es_domain_without_access_policies(): return get_cfmodel_from("rules/CrossAccountTrustRule/es_domain_without_access_policies.yml").resolve() -@pytest.fixture() def template_opensearch_domain_without_access_policies(): return get_cfmodel_from("rules/CrossAccountTrustRule/opensearch_domain_without_access_policies.yml").resolve() @@ -266,38 +258,36 @@ def test_kms_cross_account_success(principal): rule = KMSKeyCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) model = get_cfmodel_from("rules/CrossAccountTrustRule/kms_basic.yml").resolve(extra_params={"Principal": principal}) result = rule.invoke(model) - assert result.valid assert compare_lists_of_failures(result.failures, []) -def test_sts_valid(template_valid_with_sts): - rule = KMSKeyCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) - result = rule.invoke(template_valid_with_sts) - - assert result.valid - assert compare_lists_of_failures(result.failures, []) - - -def test_sts_failure(template_invalid_with_sts): +@pytest.mark.parametrize( + "template,is_valid,failures", + [ + (template_valid_with_sts(), True, []), + ( + template_invalid_with_sts(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="KmsMasterKey has forbidden cross-account policy allow with arn:aws:sts::999999999:assumed-role/test-role/session for an KMS Key Policy", + risk_value=RuleRisk.MEDIUM, + rule="KMSKeyCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"KmsMasterKey"}, + ) + ], + ), + ], +) +def test_kms_key_cross_account_sts(template, is_valid, failures): rule = KMSKeyCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) - result = rule.invoke(template_invalid_with_sts) - - assert not result.valid - assert compare_lists_of_failures( - result.failures, - [ - Failure( - granularity=RuleGranularity.RESOURCE, - reason="KmsMasterKey has forbidden cross-account policy allow with arn:aws:sts::999999999:assumed-role/test-role/session for an KMS Key Policy", - risk_value=RuleRisk.MEDIUM, - rule="KMSKeyCrossAccountTrustRule", - rule_mode=RuleMode.BLOCKING, - actions=None, - resource_ids={"KmsMasterKey"}, - ) - ], - ) + result = rule.invoke(template) + assert result.valid == is_valid + assert compare_lists_of_failures(result.failures, failures) @pytest.mark.parametrize( @@ -343,46 +333,37 @@ def test_es_domain_cross_account_success(principal): extra_params={"Principal": principal} ) result = rule.invoke(model) - assert result.valid assert compare_lists_of_failures(result.failures, []) -def test_sts_valid_es_domain(template_valid_with_sts_es_domain): - rule = ElasticsearchDomainCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) - result = rule.invoke(template_valid_with_sts_es_domain) - - assert result.valid - assert compare_lists_of_failures(result.failures, []) - - -def test_sts_failure_es_domain(template_invalid_with_sts_es_domain): - rule = ElasticsearchDomainCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) - result = rule.invoke(template_invalid_with_sts_es_domain) - - assert not result.valid - assert compare_lists_of_failures( - result.failures, - [ - Failure( - granularity=RuleGranularity.RESOURCE, - reason="TestDomain has forbidden cross-account policy allow with arn:aws:sts::999999999:assumed-role/test-role/session for an ES domain policy.", - risk_value=RuleRisk.MEDIUM, - rule="ElasticsearchDomainCrossAccountTrustRule", - rule_mode=RuleMode.BLOCKING, - actions=None, - resource_ids={"TestDomain"}, - ) - ], - ) - - -def test_es_domain_without_access_policies(template_es_domain_without_access_policies): +@pytest.mark.parametrize( + "template,is_valid,failures", + [ + (template_es_domain_without_access_policies(), True, []), + (template_valid_with_sts_es_domain(), True, []), + ( + template_invalid_with_sts_es_domain(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="TestDomain has forbidden cross-account policy allow with arn:aws:sts::999999999:assumed-role/test-role/session for an ES domain policy.", + risk_value=RuleRisk.MEDIUM, + rule="ElasticsearchDomainCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"TestDomain"}, + ) + ], + ), + ], +) +def test_elasticsearch_domain_cross_account_rule_with_set_principals(template, is_valid, failures): rule = ElasticsearchDomainCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) - result = rule.invoke(template_es_domain_without_access_policies) - - assert result.valid - assert compare_lists_of_failures(result.failures, []) + result = rule.invoke(template) + assert result.valid == is_valid + assert compare_lists_of_failures(result.failures, failures) @pytest.mark.parametrize( @@ -428,43 +409,34 @@ def test_opensearch_domain_cross_account_success(principal): extra_params={"Principal": principal} ) result = rule.invoke(model) - - assert result.valid - assert compare_lists_of_failures(result.failures, []) - - -def test_sts_valid_opensearch_domain(template_valid_with_sts_opensearch_domain): - rule = OpenSearchDomainCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) - result = rule.invoke(template_valid_with_sts_opensearch_domain) - assert result.valid assert compare_lists_of_failures(result.failures, []) -def test_sts_failure_opensearch_domain(template_invalid_with_sts_opensearch_domain): - rule = OpenSearchDomainCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) - result = rule.invoke(template_invalid_with_sts_opensearch_domain) - - assert not result.valid - assert compare_lists_of_failures( - result.failures, - [ - Failure( - granularity=RuleGranularity.RESOURCE, - reason="TestDomain has forbidden cross-account policy allow with arn:aws:sts::999999999:assumed-role/test-role/session for an OpenSearch domain policy.", - risk_value=RuleRisk.MEDIUM, - rule="OpenSearchDomainCrossAccountTrustRule", - rule_mode=RuleMode.BLOCKING, - actions=None, - resource_ids={"TestDomain"}, - ) - ], - ) - - -def test_opensearch_domain_without_access_policies(template_opensearch_domain_without_access_policies): +@pytest.mark.parametrize( + "template,is_valid,failures", + [ + (template_opensearch_domain_without_access_policies(), True, []), + (template_valid_with_sts_opensearch_domain(), True, []), + ( + template_invalid_with_sts_opensearch_domain(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="TestDomain has forbidden cross-account policy allow with arn:aws:sts::999999999:assumed-role/test-role/session for an OpenSearch domain policy.", + risk_value=RuleRisk.MEDIUM, + rule="OpenSearchDomainCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"TestDomain"}, + ) + ], + ), + ], +) +def test_opensearch_domain_with_different_principals_in_rule_config(template, is_valid, failures): rule = OpenSearchDomainCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) - result = rule.invoke(template_opensearch_domain_without_access_policies) - - assert result.valid - assert compare_lists_of_failures(result.failures, []) + result = rule.invoke(template) + assert result.valid == is_valid + assert compare_lists_of_failures(result.failures, failures) diff --git a/tests/rules/test_GenericCrossAccountTrustRule.py b/tests/rules/test_GenericCrossAccountTrustRule.py new file mode 100644 index 00000000..56197715 --- /dev/null +++ b/tests/rules/test_GenericCrossAccountTrustRule.py @@ -0,0 +1,510 @@ +import pytest + +from cfripper.config.config import Config +from cfripper.model.enums import RuleGranularity, RuleMode, RuleRisk +from cfripper.model.result import Failure +from cfripper.rules import GenericCrossAccountTrustRule +from tests.utils import compare_lists_of_failures, get_cfmodel_from + + +@pytest.fixture() +def s3_bucket_cross_account(): + return get_cfmodel_from("rules/S3CrossAccountTrustRule/s3_bucket_cross_account.json").resolve() + + +@pytest.fixture() +def s3_bucket_cross_account_from_aws_service(): + return get_cfmodel_from("rules/S3CrossAccountTrustRule/s3_bucket_cross_account_from_aws_service.json").resolve() + + +@pytest.fixture() +def s3_bucket_cross_account_and_normal(): + return get_cfmodel_from("rules/S3CrossAccountTrustRule/s3_bucket_cross_account_and_normal.json").resolve() + + +def template_valid_with_sts(): + return get_cfmodel_from("rules/CrossAccountTrustRule/valid_with_sts.yml").resolve() + + +def template_valid_with_sts_es_domain(): + return get_cfmodel_from("rules/CrossAccountTrustRule/valid_with_sts_es_domain.yml").resolve() + + +def template_valid_with_sts_opensearch_domain(): + return get_cfmodel_from("rules/CrossAccountTrustRule/valid_with_sts_opensearch_domain.yml").resolve() + + +def template_invalid_with_sts(): + return get_cfmodel_from("rules/CrossAccountTrustRule/invalid_with_sts.yml").resolve() + + +def template_invalid_with_sts_es_domain(): + return get_cfmodel_from("rules/CrossAccountTrustRule/invalid_with_sts_es_domain.yml").resolve() + + +def template_invalid_with_sts_opensearch_domain(): + return get_cfmodel_from("rules/CrossAccountTrustRule/invalid_with_sts_opensearch_domain.yml").resolve() + + +def template_es_domain_without_access_policies(): + return get_cfmodel_from("rules/CrossAccountTrustRule/es_domain_without_access_policies.yml").resolve() + + +def template_opensearch_domain_without_access_policies(): + return get_cfmodel_from("rules/CrossAccountTrustRule/opensearch_domain_without_access_policies.yml").resolve() + + +def template_generic_resource_no_policies(): + return get_cfmodel_from("rules/CrossAccountTrustRule/generic_resource_no_policies.json").resolve() + + +def template_two_generic_resources_no_policies(): + return get_cfmodel_from("rules/CrossAccountTrustRule/generic_resources_no_policies.json").resolve() + + +def template_generic_resource_with_cross_account_policy(): + return get_cfmodel_from("rules/CrossAccountTrustRule/generic_resource_with_cross_account_policy.json").resolve() + + +def template_generic_resources_with_cross_account_policies(): + return get_cfmodel_from("rules/CrossAccountTrustRule/generic_resources_with_cross_account_policies.json").resolve() + + +def template_generic_resources_with_mixed_cross_account_policy_and_no_policy(): + return get_cfmodel_from( + "rules/CrossAccountTrustRule/generic_resources_with_mixed_cross_account_policy_and_no_policy.json" + ).resolve() + + +def template_invalid_generic_resource(): + return get_cfmodel_from("rules/CrossAccountTrustRule/invalid_generic_resource.json").resolve() + + +def template_invalid_generic_resources(): + return get_cfmodel_from("rules/CrossAccountTrustRule/invalid_generic_resources.json").resolve() + + +def template_mixed_invalid_generic_resources(): + return get_cfmodel_from("rules/CrossAccountTrustRule/mixed_invalid_generic_resources.json").resolve() + + +@pytest.fixture() +def template_iam_role_to_jump_to_another_account(): + return get_cfmodel_from("rules/CrossAccountTrustRule/iam_role_to_jump_to_another_account.yaml").resolve() + + +@pytest.fixture() +def template_one_role(): + return get_cfmodel_from("rules/CrossAccountTrustRule/iam_root_role_cross_account.json").resolve() + + +def test_iam_role_to_jump_to_another_account(template_iam_role_to_jump_to_another_account): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789")) + result = rule.invoke(template_iam_role_to_jump_to_another_account) + assert result.valid + assert compare_lists_of_failures(result.failures, []) + + +def test_iam_role_is_ignored_in_generic_rule(template_one_role): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789")) + result = rule.invoke(template_one_role) + assert result.valid + assert compare_lists_of_failures(result.failures, []) + + +def test_s3_bucket_cross_account_with_generic(s3_bucket_cross_account): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789")) + result = rule.invoke(s3_bucket_cross_account) + + assert not result.valid + assert compare_lists_of_failures( + result.failures, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="S3BucketPolicyAccountAccess has forbidden cross-account with arn:aws:iam::987654321:root", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"S3BucketPolicyAccountAccess"}, + ) + ], + ) + + +def test_s3_bucket_cross_account_and_normal_with_generic(s3_bucket_cross_account_and_normal): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789012")) + result = rule.invoke(s3_bucket_cross_account_and_normal) + + assert not result.valid + assert compare_lists_of_failures( + result.failures, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="S3BucketPolicyAccountAccess has forbidden cross-account with arn:aws:iam::666555444333:root", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"S3BucketPolicyAccountAccess"}, + ) + ], + ) + + +def test_s3_bucket_cross_account_and_normal_with_org_aws_account_with_generic(s3_bucket_cross_account_and_normal): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789012", aws_principals=["666555444333"])) + result = rule.invoke(s3_bucket_cross_account_and_normal) + + assert not result.valid + assert compare_lists_of_failures( + result.failures, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="S3BucketPolicyAccountAccess has forbidden cross-account with arn:aws:iam::666555444333:root", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"S3BucketPolicyAccountAccess"}, + ) + ], + ) + + +def test_s3_bucket_cross_account_for_current_account_with_generic(s3_bucket_cross_account): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="987654321")) + result = rule.invoke(s3_bucket_cross_account) + + assert result.valid + assert compare_lists_of_failures(result.failures, []) + + +def test_s3_bucket_cross_account_from_aws_service_with_generic(s3_bucket_cross_account_from_aws_service): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789")) + result = rule.invoke(s3_bucket_cross_account_from_aws_service) + + assert result.valid + assert compare_lists_of_failures(result.failures, []) + + +def test_generic_rule_supports_filter_config(s3_bucket_cross_account_and_normal, default_allow_all_config): + rule = GenericCrossAccountTrustRule(default_allow_all_config) + result = rule.invoke(s3_bucket_cross_account_and_normal) + + assert result.valid + assert compare_lists_of_failures(result.failures, []) + + +@pytest.mark.parametrize( + "principal", ["arn:aws:iam::123456789:root", "arn:aws:iam::123456789:not-root", "arn:aws:iam::123456789:not-root*"], +) +def test_generic_cross_account_for_opensearch_domain_with_principal_params(principal): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) + model = get_cfmodel_from("rules/CrossAccountTrustRule/opensearch_domain_basic.yml").resolve( + extra_params={"Principal": principal} + ) + result = rule.invoke(model) + assert result.valid + assert compare_lists_of_failures(result.failures, []) + + +@pytest.mark.parametrize( + "principal", + [ + "arn:aws:iam::999999999:root", + "arn:aws:iam::*:root", + "arn:aws:iam::*:*", + "arn:aws:iam::*:root*", + "arn:aws:iam::*:not-root*", + "*", + ], +) +def test_generic_cross_account_for_opensearch_domain_different_principals(principal): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) + model = get_cfmodel_from("rules/CrossAccountTrustRule/opensearch_domain_basic.yml").resolve( + extra_params={"Principal": principal} + ) + result = rule.invoke(model) + assert not result.valid + assert compare_lists_of_failures( + result.failures, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason=f"TestDomain has forbidden cross-account with {principal}", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"TestDomain"}, + ) + ], + ) + + +@pytest.mark.parametrize( + "template,is_valid,failures", + [ + (template_es_domain_without_access_policies(), True, []), + (template_valid_with_sts_es_domain(), True, []), + ( + template_invalid_with_sts_es_domain(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="TestDomain has forbidden cross-account with arn:aws:sts::999999999:assumed-role/test-role/session", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"TestDomain"}, + ) + ], + ), + (template_valid_with_sts(), True, []), + ( + template_invalid_with_sts(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="KmsMasterKey has forbidden cross-account with arn:aws:sts::999999999:assumed-role/test-role/session", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"KmsMasterKey"}, + ) + ], + ), + (template_opensearch_domain_without_access_policies(), True, []), + (template_valid_with_sts_opensearch_domain(), True, []), + ( + template_invalid_with_sts_opensearch_domain(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="TestDomain has forbidden cross-account with arn:aws:sts::999999999:assumed-role/test-role/session", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"TestDomain"}, + ) + ], + ), + (template_generic_resource_no_policies(), True, []), + (template_two_generic_resources_no_policies(), True, []), + ( + template_invalid_generic_resource(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="NonexistentResource has forbidden cross-account with arn:aws:sts::999999999:assumed-role/test-role/session", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"NonexistentResource"}, + ) + ], + ), + ( + template_invalid_generic_resources(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="NonexistentResource has forbidden cross-account with arn:aws:sts::999999999:assumed-role/test-role/session", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"NonexistentResource"}, + ), + Failure( + granularity=RuleGranularity.RESOURCE, + reason="NonexistentResourceSecond has forbidden cross-account with arn:aws:sts::999999999:assumed-role/test-role/session", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"NonexistentResourceSecond"}, + ), + ], + ), + ( + template_mixed_invalid_generic_resources(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="NonexistentResourceSecond has forbidden cross-account with arn:aws:sts::999999999:assumed-role/test-role/session", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"NonexistentResourceSecond"}, + ), + ], + ), + (template_generic_resource_no_policies(), True, []), + (template_two_generic_resources_no_policies(), True, []), + ( + template_generic_resource_with_cross_account_policy(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="NonexistentResource has forbidden cross-account with arn:aws:iam::999999999:role/someuser@bla.com", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"NonexistentResource"}, + ) + ], + ), + ( + template_generic_resources_with_cross_account_policies(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="NonexistentResource has forbidden cross-account with arn:aws:iam::999999999:role/someuser@bla.com", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"NonexistentResource"}, + ), + Failure( + granularity=RuleGranularity.RESOURCE, + reason="NonexistentResourceTwo has forbidden cross-account with arn:aws:iam::999999999:role/someuser@bla.com", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"NonexistentResourceTwo"}, + ), + ], + ), + ( + template_generic_resources_with_mixed_cross_account_policy_and_no_policy(), + False, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason="NonexistentResourceTwo has forbidden cross-account with arn:aws:iam::999999999:role/someuser@bla.com", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"NonexistentResourceTwo"}, + ) + ], + ), + ], +) +def test_generic_cross_account_rule_for_resources_with_set_principals(template, is_valid, failures): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) + result = rule.invoke(template) + assert result.valid == is_valid + assert compare_lists_of_failures(result.failures, failures) + + +@pytest.mark.parametrize( + "principal", ["arn:aws:iam::123456789:root", "arn:aws:iam::123456789:not-root", "arn:aws:iam::123456789:not-root*"], +) +def test_generic_cross_account_es_domain_cross_account_success(principal): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) + model = get_cfmodel_from("rules/CrossAccountTrustRule/es_domain_basic.yml").resolve( + extra_params={"Principal": principal} + ) + result = rule.invoke(model) + assert result.valid + assert compare_lists_of_failures(result.failures, []) + + +@pytest.mark.parametrize( + "principal", + [ + "arn:aws:iam::999999999:root", + "arn:aws:iam::*:root", + "arn:aws:iam::*:*", + "arn:aws:iam::*:root*", + "arn:aws:iam::*:not-root*", + "*", + ], +) +def test_generic_cross_account_rule_es_domain_cross_account_failure(principal): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) + model = get_cfmodel_from("rules/CrossAccountTrustRule/es_domain_basic.yml").resolve( + extra_params={"Principal": principal} + ) + result = rule.invoke(model) + assert not result.valid + assert compare_lists_of_failures( + result.failures, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason=f"TestDomain has forbidden cross-account with {principal}", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"TestDomain"}, + ) + ], + ) + + +@pytest.mark.parametrize( + "principal", ["arn:aws:iam::123456789:root", "arn:aws:iam::123456789:not-root", "arn:aws:iam::123456789:not-root*"], +) +def test_generic_cross_account_with_kms_key_success(principal): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) + model = get_cfmodel_from("rules/CrossAccountTrustRule/kms_basic.yml").resolve(extra_params={"Principal": principal}) + result = rule.invoke(model) + assert result.valid + assert compare_lists_of_failures(result.failures, []) + + +@pytest.mark.parametrize( + "principal", + [ + "arn:aws:iam::999999999:root", + "arn:aws:iam::*:root", + "arn:aws:iam::*:*", + "arn:aws:iam::*:root*", + "arn:aws:iam::*:not-root*", + "*", + ], +) +def test_generic_cross_account_with_kms_key_failure(principal): + rule = GenericCrossAccountTrustRule(Config(aws_account_id="123456789", aws_principals=["999999999"])) + model = get_cfmodel_from("rules/CrossAccountTrustRule/kms_basic.yml").resolve(extra_params={"Principal": principal}) + result = rule.invoke(model) + assert not result.valid + assert compare_lists_of_failures( + result.failures, + [ + Failure( + granularity=RuleGranularity.RESOURCE, + reason=f"KMSKey has forbidden cross-account with {principal}", + risk_value=RuleRisk.MEDIUM, + rule="GenericCrossAccountTrustRule", + rule_mode=RuleMode.BLOCKING, + actions=None, + resource_ids={"KMSKey"}, + ) + ], + ) diff --git a/tests/test_templates/rules/CrossAccountTrustRule/generic_resource_no_policies.json b/tests/test_templates/rules/CrossAccountTrustRule/generic_resource_no_policies.json new file mode 100644 index 00000000..e054128f --- /dev/null +++ b/tests/test_templates/rules/CrossAccountTrustRule/generic_resource_no_policies.json @@ -0,0 +1,12 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Non existent resource without policies", + "Resources": { + "NonexistentResource": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value" + } + } + } +} diff --git a/tests/test_templates/rules/CrossAccountTrustRule/generic_resource_with_cross_account_policy.json b/tests/test_templates/rules/CrossAccountTrustRule/generic_resource_with_cross_account_policy.json new file mode 100644 index 00000000..57fcaf6d --- /dev/null +++ b/tests/test_templates/rules/CrossAccountTrustRule/generic_resource_with_cross_account_policy.json @@ -0,0 +1,34 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Non existent resource without policies", + "Resources": { + "NonexistentResource": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + "Policies": [ + { + "PolicyName": "APolicyName", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + "arn:aws:iam::123456789:user/someuser@bla.com", + "arn:aws:iam::123456789:user/someuser@bla.com", + "arn:aws:iam::123456789:root", + "arn:aws:iam::999999999:role/someuser@bla.com", + "arn:aws:iam::123456789:user/someuser@bla.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + } + ] + } + } + } +} diff --git a/tests/test_templates/rules/CrossAccountTrustRule/generic_resources_no_policies.json b/tests/test_templates/rules/CrossAccountTrustRule/generic_resources_no_policies.json new file mode 100644 index 00000000..da076a10 --- /dev/null +++ b/tests/test_templates/rules/CrossAccountTrustRule/generic_resources_no_policies.json @@ -0,0 +1,18 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Non existent resource without policies", + "Resources": { + "NonexistentResource": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value" + } + }, + "NonexistentResourceTwo": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value" + } + } + } +} diff --git a/tests/test_templates/rules/CrossAccountTrustRule/generic_resources_with_cross_account_policies.json b/tests/test_templates/rules/CrossAccountTrustRule/generic_resources_with_cross_account_policies.json new file mode 100644 index 00000000..af7f068a --- /dev/null +++ b/tests/test_templates/rules/CrossAccountTrustRule/generic_resources_with_cross_account_policies.json @@ -0,0 +1,61 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Non existent resource without policies", + "Resources": { + "NonexistentResource": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + "Policies": [ + { + "PolicyName": "APolicyName", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + "arn:aws:iam::123456789:user/someuser@bla.com", + "arn:aws:iam::123456789:user/someuser@bla.com", + "arn:aws:iam::123456789:root", + "arn:aws:iam::999999999:role/someuser@bla.com", + "arn:aws:iam::123456789:user/someuser@bla.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + } + ] + } + }, + "NonexistentResourceTwo": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + "PoliciesOrSimilar": [ + { + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + "arn:aws:iam::123456789:user/someuser@bla.com", + "arn:aws:iam::123456789:user/someuser@bla.com", + "arn:aws:iam::123456789:root", + "arn:aws:iam::999999999:role/someuser@bla.com", + "arn:aws:iam::123456789:user/someuser@bla.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + } + ] + } + } + } +} diff --git a/tests/test_templates/rules/CrossAccountTrustRule/generic_resources_with_mixed_cross_account_policy_and_no_policy.json b/tests/test_templates/rules/CrossAccountTrustRule/generic_resources_with_mixed_cross_account_policy_and_no_policy.json new file mode 100644 index 00000000..cdf1fcf0 --- /dev/null +++ b/tests/test_templates/rules/CrossAccountTrustRule/generic_resources_with_mixed_cross_account_policy_and_no_policy.json @@ -0,0 +1,39 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Non existent resource without policies", + "Resources": { + "NonexistentResource": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + } + }, + "NonexistentResourceTwo": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + "PoliciesOrSimilar": [ + { + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + "arn:aws:iam::123456789:user/someuser@bla.com", + "arn:aws:iam::123456789:user/someuser@bla.com", + "arn:aws:iam::123456789:root", + "arn:aws:iam::999999999:role/someuser@bla.com", + "arn:aws:iam::123456789:user/someuser@bla.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + } + ] + } + } + } +} diff --git a/tests/test_templates/rules/CrossAccountTrustRule/iam_role_to_jump_to_another_account.yaml b/tests/test_templates/rules/CrossAccountTrustRule/iam_role_to_jump_to_another_account.yaml new file mode 100644 index 00000000..02bb2fb9 --- /dev/null +++ b/tests/test_templates/rules/CrossAccountTrustRule/iam_role_to_jump_to_another_account.yaml @@ -0,0 +1,31 @@ +AWSTemplateFormatVersion: "2010-09-09" + +Resources: + ValidIAMRole: + Type: 'AWS::IAM::Role' + Properties: + RoleName: "valid-ec2-role" + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - "sts:AssumeRole" + + PolicyToManageRDSEverywhere: + Type: "AWS::IAM::ManagedPolicy" + Properties: + ManagedPolicyName: "TestRDSFullAdmin" + Description: "Policy to manage RDS" + Roles: + - !Ref ValidIAMRole + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "sts:AssumeRole" + Resource: "arn:aws:iam::222222222222:role/*" diff --git a/tests/test_templates/rules/CrossAccountTrustRule/invalid_generic_resource.json b/tests/test_templates/rules/CrossAccountTrustRule/invalid_generic_resource.json new file mode 100644 index 00000000..3082e770 --- /dev/null +++ b/tests/test_templates/rules/CrossAccountTrustRule/invalid_generic_resource.json @@ -0,0 +1,31 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Non existent resource without policies", + "Resources": { + "NonexistentResource": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + "Policies": [ + { + "PolicyName": "APolicyName", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + "arn:aws:iam::123456789:role/test-role", + "arn:aws:sts::999999999:assumed-role/test-role/session" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + } + ] + } + } + } +} diff --git a/tests/test_templates/rules/CrossAccountTrustRule/invalid_generic_resources.json b/tests/test_templates/rules/CrossAccountTrustRule/invalid_generic_resources.json new file mode 100644 index 00000000..75fae078 --- /dev/null +++ b/tests/test_templates/rules/CrossAccountTrustRule/invalid_generic_resources.json @@ -0,0 +1,55 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Non existent resource without policies", + "Resources": { + "NonexistentResource": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + "Policies": [ + { + "PolicyName": "APolicyName", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + "arn:aws:iam::123456789:role/test-role", + "arn:aws:sts::999999999:assumed-role/test-role/session" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + } + ] + } + }, + "NonexistentResourceSecond": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + "arn:aws:sts::999999999:assumed-role/test-role/session", + "arn:aws:iam::123456789:role/test-role" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + } + ] + } + } + } +} diff --git a/tests/test_templates/rules/CrossAccountTrustRule/mixed_invalid_generic_resources.json b/tests/test_templates/rules/CrossAccountTrustRule/mixed_invalid_generic_resources.json new file mode 100644 index 00000000..f45d804e --- /dev/null +++ b/tests/test_templates/rules/CrossAccountTrustRule/mixed_invalid_generic_resources.json @@ -0,0 +1,36 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Non existent resource without policies", + "Resources": { + "NonexistentResource": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + } + }, + "NonexistentResourceSecond": { + "Type": "AWS::Non::Existent", + "Properties": { + "PropertyOne": "test-value", + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + "arn:aws:sts::999999999:assumed-role/test-role/session", + "arn:aws:iam::123456789:role/test-role" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + } + ] + } + } + } +}