From 8ed41e8ab0589d2d84853a422c63e80b9afc77af Mon Sep 17 00:00:00 2001 From: max-huneshagen Date: Thu, 9 Nov 2023 10:05:46 +0100 Subject: [PATCH] Create new stack name rule. --- cfripper/config/regex.py | 15 +++++++ cfripper/rules/__init__.py | 2 + cfripper/rules/stack_name_matches_regex.py | 41 +++++++++++++++++++ tests/rules/test_StackNameMatchesRegexRule.py | 39 ++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 cfripper/rules/stack_name_matches_regex.py create mode 100644 tests/rules/test_StackNameMatchesRegexRule.py diff --git a/cfripper/config/regex.py b/cfripper/config/regex.py index 39bb40e2..e5cebfc6 100644 --- a/cfripper/config/regex.py +++ b/cfripper/config/regex.py @@ -173,3 +173,18 @@ - sns:Get* """ REGEX_HAS_STAR_OR_STAR_AFTER_COLON = re.compile(r"^(\w*:)*[*?]+$") + + +""" +Check that stack name only consists of alphanumerical characters and hyphens. +Valid: +- abcdefg +- ABCDEFG +- abcdEFG +- aBc-DeFG +Invalid: +- abc_defg +- AB:cdefg +- !@£$$%aA +""" +REGEX_ALPHANUMERICAL_OR_HYPHEN = re.compile(r"^[A-Za-z0-9\-]+$") diff --git a/cfripper/rules/__init__.py b/cfripper/rules/__init__.py index 0522094f..23970fad 100644 --- a/cfripper/rules/__init__.py +++ b/cfripper/rules/__init__.py @@ -38,6 +38,7 @@ SQSQueuePolicyNotPrincipalRule, SQSQueuePolicyPublicRule, ) +from cfripper.rules.stack_name_matches_regex import StackNameMatchesRegexRule from cfripper.rules.wildcard_policies import ( GenericResourceWildcardPolicyRule, S3BucketPolicyWildcardActionRule, @@ -94,6 +95,7 @@ SQSQueuePolicyNotPrincipalRule, SQSQueuePolicyPublicRule, SQSQueuePolicyWildcardActionRule, + StackNameMatchesRegexRule, WildcardResourceRule, ) } diff --git a/cfripper/rules/stack_name_matches_regex.py b/cfripper/rules/stack_name_matches_regex.py new file mode 100644 index 00000000..4d19da85 --- /dev/null +++ b/cfripper/rules/stack_name_matches_regex.py @@ -0,0 +1,41 @@ +import re +from typing import Dict, Optional + +from pycfmodel.model.cf_model import CFModel + +from cfripper.config.regex import REGEX_ALPHANUMERICAL_OR_HYPHEN +from cfripper.model.enums import RuleMode, RuleRisk +from cfripper.model.result import Result +from cfripper.rules.base_rules import Rule + + +class StackNameMatchesRegexRule(Rule): + """ + Checks that a given stack follows the naming convention given by a regex. + """ + + RULE_MODE = RuleMode.DEBUG + RISK_VALUE = RuleRisk.LOW + REASON = ( + "The stack name {} does not follow the naming convention (only alphanumerical characters and hyphens allowed)." + ) + + def _stack_name_matches_regex(self, stack_name: str) -> bool: + """Check that stack name follows naming convention.""" + return bool(REGEX_ALPHANUMERICAL_OR_HYPHEN.match(stack_name)) + + def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: + result = Result() + stack_name = self._config.stack_name + if not stack_name: + return result + if not extras: + extras = {} + + if not self._stack_name_matches_regex(stack_name): + self.add_failure_to_result( + result, + self.REASON.format(stack_name), + context={"config": self._config, "extras": extras}, + ) + return result diff --git a/tests/rules/test_StackNameMatchesRegexRule.py b/tests/rules/test_StackNameMatchesRegexRule.py new file mode 100644 index 00000000..92bf6127 --- /dev/null +++ b/tests/rules/test_StackNameMatchesRegexRule.py @@ -0,0 +1,39 @@ +import pytest +from pycfmodel.model.cf_model import CFModel + +from cfripper.config.config import Config +from cfripper.rules import StackNameMatchesRegexRule + + +@pytest.mark.parametrize( + "stack_name, expected_result", + [ + ("justlowercase", True), + ("lowercase-with-hyphens", True), + ("lowercaseANDUPPERCASE", True), + ("lowercase-AND-UPPERCASE-with-hyphens", True), + ("including_underscore", False), + ("including space", False), + ("including-other-symbols!@£$%^&*()", False), + ], +) +def test_stack_name_matches_regex(stack_name, expected_result): + rule = StackNameMatchesRegexRule(Config(stack_name=stack_name, rules=["StackNameMatchesRegexRule"])) + assert rule._stack_name_matches_regex(stack_name) == expected_result + + +def test_works_with_extras(): + rule = StackNameMatchesRegexRule(Config(stack_name="some-valid-stack-name", rules=["StackNameMatchesRegexRule"])) + extras = {"stack": {"tags": [{"key": "project", "value": "some_project"}]}} + result = rule.invoke(cfmodel=CFModel(), extras=extras) + assert result.valid + + +def test_failure_is_added_for_invalid_stack_name(): + rule = StackNameMatchesRegexRule(Config(stack_name="some_invalid_stack_name", rules=["StackNameMatchesRegexRule"])) + result = rule.invoke(cfmodel=CFModel()) + assert result.failures + assert ( + result.failures[0].reason + == "The stack name some_invalid_stack_name does not follow the naming convention (only alphanumerical characters and hyphens allowed)." + )