def test_non_matching_filters_are_reported_normally( invalid_security_group_range): mock_config = Config( rules=["EC2SecurityGroupOpenToWorldRule"], aws_account_id="123456789", stack_name="mockstack", rules_config={ "EC2SecurityGroupOpenToWorldRule": RuleConfig(filters=[ Filter(rule_mode=RuleMode.WHITELISTED, eval={ "eq": [{ "ref": "config.stack_name" }, "anotherstack"] }) ], ) }, ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) result = processor.process_cf_template(invalid_security_group_range, mock_config) assert not result.valid assert result.failed_rules[0].rule == "EC2SecurityGroupOpenToWorldRule" assert ( result.failed_rules[0].reason == "Port(s) 0-79, 81-100 open to public IPs: (11.0.0.0/8) in security group 'SecurityGroup'" )
def test_filter_do_not_report_anything(invalid_security_group_range): mock_config = Config( rules=["EC2SecurityGroupOpenToWorldRule"], aws_account_id="123456789", stack_name="mockstack", rules_filters=[ Filter( rule_mode=RuleMode.ALLOWED, eval={ "and": [ { "eq": [{ "ref": "config.stack_name" }, "mockstack"] }, { "eq": [{ "ref": "open_ports" }, list(range(0, 101))] }, ] }, rules={"EC2SecurityGroupOpenToWorldRule"}, ) ], ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) result = processor.process_cf_template(invalid_security_group_range, mock_config) assert result.valid assert compare_lists_of_failures(result.failures, [])
def test_filter_works_as_expected(template_two_roles_dict, expected_result_two_roles): config = Config( rules=["CrossAccountTrustRule"], aws_account_id="123456789", stack_name="mockstack", rules_config={ "CrossAccountTrustRule": RuleConfig( filters=[ Filter( rule_mode=RuleMode.WHITELISTED, eval={ "and": [ {"eq": [{"ref": "config.stack_name"}, "mockstack"]}, {"eq": [{"ref": "logical_id"}, "RootRoleOne"]}, ] }, ) ], ) }, ) rules = [DEFAULT_RULES.get(rule)(config) for rule in config.rules] processor = RuleProcessor(*rules) result = processor.process_cf_template(template_two_roles_dict, config) assert not result.valid assert result.failed_rules[0] == expected_result_two_roles[-1]
def test_non_matching_filters_are_reported_normally( single_security_group_one_cidr_ingress): mock_config = Config( rules=["EC2SecurityGroupMissingEgressRule"], aws_account_id="123456789", stack_name="mockstack", rules_config={ "EC2SecurityGroupMissingEgressRule": RuleConfig(filters=[ Filter(rule_mode=RuleMode.WHITELISTED, eval={ "eq": [{ "ref": "config.stack_name" }, "anotherstack"] }) ], ) }, ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) result = processor.process_cf_template( single_security_group_one_cidr_ingress, mock_config) assert result.valid assert len(result.failed_rules) == 0 assert len(result.failed_monitored_rules) == 1 assert result.failed_monitored_rules[ 0].rule == "EC2SecurityGroupMissingEgressRule" assert ( result.failed_monitored_rules[0].reason == "Missing egress rule in sg means all traffic is allowed outbound. Make this explicit if it is desired configuration" )
def test_filter_do_not_report_anything(bad_template): mock_config = Config( rules=["EC2SecurityGroupIngressOpenToWorldRule"], aws_account_id="123456789", stack_name="mockstack", rules_config={ "EC2SecurityGroupIngressOpenToWorldRule": RuleConfig(filters=[ Filter( rule_mode=RuleMode.WHITELISTED, eval={ "and": [ { "eq": [{ "ref": "config.stack_name" }, "mockstack"] }, { "eq": [{ "ref": "ingress.FromPort" }, 46] }, ] }, ) ], ) }, ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) result = processor.process_cf_template(bad_template, mock_config) assert result.valid
def default_allow_all_config(): return Config( rules=DEFAULT_RULES, aws_account_id="123456789012", stack_name="mockstack", rules_filters=[ Filter( rule_mode=RuleMode.ALLOWED, eval={ "and": [ { "exists": { "ref": "config.stack_name" } }, { "eq": [{ "ref": "config.stack_name" }, "mockstack"] }, ] }, rules=set(DEFAULT_RULES.keys()), ), ], )
def test_filter_do_not_report_anything(single_security_group_one_cidr_ingress): mock_config = Config( rules=["EC2SecurityGroupMissingEgressRule"], aws_account_id="123456789", stack_name="mockstack", rules_config={ "EC2SecurityGroupMissingEgressRule": RuleConfig(filters=[ Filter( rule_mode=RuleMode.WHITELISTED, eval={"eq": [{ "ref": "config.stack_name" }, "mockstack"]}, ) ], ) }, ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) result = processor.process_cf_template( single_security_group_one_cidr_ingress, mock_config) assert result.valid
def test_exist_function_and_property_exists( template_cross_account_role_with_name): mock_config = Config( rules=["CrossAccountTrustRule"], aws_account_id="123456789", stack_name="mockstack", rules_config={ "CrossAccountTrustRule": RuleConfig(filters=[ Filter( rule_mode=RuleMode.WHITELISTED, eval={ "and": [ { "and": [ { "exists": { "ref": "resource.Properties.RoleName" } }, { "regex": [ "^prefix-.*$", { "ref": "resource.Properties.RoleName" } ] }, ] }, { "eq": [{ "ref": "principal" }, "arn:aws:iam::999999999:role/[email protected]" ] }, ] }, ), ]) }, ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) result = processor.process_cf_template( template_cross_account_role_with_name, mock_config) assert result.valid
def test_non_matching_filters_are_reported_normally(bad_template): mock_config = Config( rules=["EC2SecurityGroupIngressOpenToWorldRule"], aws_account_id="123456789", stack_name="mockstack", rules_filters=[ Filter( rule_mode=RuleMode.ALLOWED, eval={"eq": [{ "ref": "config.stack_name" }, "anotherstack"]}, rules={"EC2SecurityGroupIngressOpenToWorldRule"}, ) ], ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) result = processor.process_cf_template(bad_template, mock_config) assert not result.valid assert compare_lists_of_failures( result.failures, [ Failure( granularity=RuleGranularity.RESOURCE, reason= "Port(s) 46 open to public IPs: (11.0.0.0/8) in security group 'securityGroupIngress1'", risk_value=RuleRisk.MEDIUM, rule="EC2SecurityGroupIngressOpenToWorldRule", rule_mode=RuleMode.BLOCKING, actions=None, resource_ids={"securityGroupIngress1"}, resource_types={"AWS::EC2::SecurityGroupIngress"}, ), Failure( granularity=RuleGranularity.RESOURCE, reason= "Port(s) 46 open to public IPs: (::/0) in security group 'securityGroupIngress2'", risk_value=RuleRisk.MEDIUM, rule="EC2SecurityGroupIngressOpenToWorldRule", rule_mode=RuleMode.BLOCKING, actions=None, resource_ids={"securityGroupIngress2"}, resource_types={"AWS::EC2::SecurityGroupIngress"}, ), ], )
def test_non_matching_filters_are_reported_normally(template_two_roles_dict, expected_result_two_roles): mock_config = Config( rules=["CrossAccountTrustRule"], aws_account_id="123456789", stack_name="mockstack", rules_config={ "CrossAccountTrustRule": RuleConfig( filters=[ Filter(rule_mode=RuleMode.WHITELISTED, eval={"eq": [{"ref": "config.stack_name"}, "anotherstack"]}) ], ) }, ) rules = [DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules] processor = RuleProcessor(*rules) result = processor.process_cf_template(template_two_roles_dict, mock_config) assert not result.valid assert result.failed_rules == expected_result_two_roles
def test_filter_do_not_report_anything(template_two_roles_dict): mock_config = Config( rules=["CrossAccountTrustRule"], aws_account_id="123456789", stack_name="mockstack", rules_config={ "CrossAccountTrustRule": RuleConfig( filters=[ Filter(rule_mode=RuleMode.WHITELISTED, eval={"eq": [{"ref": "config.stack_name"}, "mockstack"]}) ], ) }, ) rules = [DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules] processor = RuleProcessor(*rules) result = processor.process_cf_template(template_two_roles_dict, mock_config) assert result.valid
def test_filter_do_not_report_anything(filter_eval_object, bad_template): mock_config = Config( rules=["EC2SecurityGroupIngressOpenToWorldRule"], aws_account_id="123456789", stack_name="mockstack", rules_filters=[ Filter( rule_mode=RuleMode.ALLOWED, eval=filter_eval_object, rules={"EC2SecurityGroupIngressOpenToWorldRule"}, ) ], ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) result = processor.process_cf_template(bad_template, mock_config) assert result.valid assert compare_lists_of_failures(result.failures, [])
def test_non_matching_filters_are_reported_normally( single_security_group_one_cidr_ingress): mock_config = Config( rules=["EC2SecurityGroupMissingEgressRule"], aws_account_id="123456789", stack_name="mockstack", rules_filters=[ Filter( rule_mode=RuleMode.ALLOWED, eval={"eq": [{ "ref": "config.stack_name" }, "anotherstack"]}, rules={"EC2SecurityGroupMissingEgressRule"}, ) ], ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) result = processor.process_cf_template( single_security_group_one_cidr_ingress, mock_config) assert not result.valid assert compare_lists_of_failures( result.failures, [ Failure( granularity=RuleGranularity.RESOURCE, reason= "Missing egress rule in sg means all traffic is allowed outbound. Make this explicit if it is desired configuration", risk_value=RuleRisk.MEDIUM, rule="EC2SecurityGroupMissingEgressRule", rule_mode=RuleMode.BLOCKING, actions=None, resource_ids={"sg"}, resource_types={"AWS::EC2::SecurityGroup"}, ) ], )
from cfripper.config.filter import Filter from cfripper.model.enums import RuleMode # FILTERS is here a single filter instead of list of filters FILTERS = Filter( rule_mode=RuleMode.ALLOWED, eval={ "and": [{ "eq": [{ "ref": "config.stack_name" }, "mockstack"] }, { "eq": [{ "ref": "logical_id" }, "RootRoleOne"] }] }, rules={"CrossAccountTrustRule"}, )
@pytest.fixture() def template_cross_account_role_no_name(): return get_cfmodel_from("config/cross_account_role_no_name.json").resolve() @pytest.fixture() def template_cross_account_role_with_name(): return get_cfmodel_from( "config/cross_account_role_with_name.json").resolve() @pytest.mark.parametrize( "filter, args, expected_result", [ (Filter(eval={"eq": ["string", "string"]}), {}, True), (Filter(eval={"eq": [1, 1]}), {}, True), (Filter(eval={"eq": [-1, -1]}), {}, True), (Filter(eval={"eq": [1.0, 1.0]}), {}, True), (Filter(eval={"eq": [-1.0, -1.0]}), {}, True), (Filter(eval={"eq": [True, True]}), {}, True), (Filter(eval={"eq": [False, False]}), {}, True), (Filter(eval={"eq": [[1, 2], [1, 2]]}), {}, True), (Filter(eval={"eq": ["string", "not_that_string"]}), {}, False), (Filter(eval={"eq": [1, 2]}), {}, False), (Filter(eval={"eq": [-1, 1]}), {}, False), (Filter(eval={"eq": [1.0, 2.0]}), {}, False), (Filter(eval={"eq": [1.0, -1.0]}), {}, False), (Filter(eval={"eq": [False, True]}), {}, False), (Filter(eval={"eq": [True, False]}), {}, False), (Filter(eval={"eq": [[1, 2], [2, 1]]}), {}, False),
def test_debug_filter(template_cross_account_role_with_name, caplog): logging.disable(logging.NOTSET) caplog.set_level(logging.DEBUG) mock_config = Config( rules=["CrossAccountTrustRule"], aws_account_id="123456789", stack_name="mockstack", rules_filters=[ Filter( reason="Test reason", rule_mode=RuleMode.ALLOWED, eval={ "and": [ { "and": [ { "exists": { "ref": "resource.Properties.RoleName" } }, { "regex": [ "^prefix-.*$", { "ref": "resource.Properties.RoleName" } ] }, ] }, { "eq": [{ "ref": "principal" }, "arn:aws:iam::999999999:role/[email protected]"] }, ] }, rules={"CrossAccountTrustRule"}, debug=True, ), ], ) rules = [ DEFAULT_RULES.get(rule)(mock_config) for rule in mock_config.rules ] processor = RuleProcessor(*rules) processor.process_cf_template(template_cross_account_role_with_name, mock_config) for line in [ "Filter: Test reason", "ref(resource.Properties.RoleName) -> prefix-test-root-role", "exists(prefix-test-root-role) -> True", "ref(resource.Properties.RoleName) -> prefix-test-root-role", "regex(^prefix-.*$, prefix-test-root-role) -> True", "ref(principal) -> arn:aws:iam::999999999:role/[email protected]", "eq(arn:aws:iam::999999999:role/[email protected], arn:aws:iam::999999999:role/[email protected]) -> True", "Filter result: True", ]: assert line in caplog.text
from cfripper.config.filter import Filter from cfripper.config.rule_config import RuleConfig from cfripper.model.enums import RuleMode # RULES_CONFIG is here a list of RuleConfig instead of a dict with the rule names as keys RULES_CONFIG = [ RuleConfig(filters=[ Filter( rule_mode=RuleMode.ALLOWED, eval={ "and": [ { "eq": [{ "ref": "config.stack_name" }, "mockstack"] }, { "eq": [{ "ref": "logical_id" }, "RootRoleOne"] }, ] }, ) ], ) ]
s = list(iterable) return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)) allow_http_ports_open_to_world_rules_config_filter = Filter( reason= "It can be acceptable to have Security Groups publicly available on ports 80 or 443.", rule_mode=RuleMode.ALLOWED, eval={ "and": [ { "exists": { "ref": "open_ports" } }, { "or": [{ "eq": [list(subset_allowed_ports), { "ref": "open_ports" }] } for subset_allowed_ports in powerset(ALLOWED_PORTS) if subset_allowed_ports] }, ] }, rules={ "EC2SecurityGroupOpenToWorldRule", "EC2SecurityGroupIngressOpenToWorldRule" }, )