diff --git a/CHANGELOG.md b/CHANGELOG.md index 40cc85b9..51096148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. ## [0.23.3] - 2021-02-11 +### Additions +- All rules now support filter contexts! ### Improvements - Update `WildcardResourceRule` to allow for certain resources to be excluded. diff --git a/cfripper/rules/cloudformation_authentication.py b/cfripper/rules/cloudformation_authentication.py index de045af8..c28a4ee5 100644 --- a/cfripper/rules/cloudformation_authentication.py +++ b/cfripper/rules/cloudformation_authentication.py @@ -37,6 +37,14 @@ class CloudFormationAuthenticationRule(Rule): Ref: "PasswordAuth" ... ```` + + 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` | `Resource` | Resource that is being addressed | """ REASON = "Hardcoded credentials in {}" @@ -46,5 +54,10 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: result = Result() for logical_id, resource in cfmodel.Resources.items(): if resource.has_hardcoded_credentials(): - self.add_failure_to_result(result, self.REASON.format(logical_id), resource_ids={logical_id}) + self.add_failure_to_result( + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={"config": self._config, "extras": extras, "logical_id": logical_id, "resource": resource}, + ) return result diff --git a/cfripper/rules/ebs_volume_has_sse.py b/cfripper/rules/ebs_volume_has_sse.py index e8606c58..88330f42 100644 --- a/cfripper/rules/ebs_volume_has_sse.py +++ b/cfripper/rules/ebs_volume_has_sse.py @@ -32,6 +32,14 @@ class EBSVolumeHasSSERule(Rule): } } ```` + + 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` | `Resource` | EC2 Volume that is being addressed | """ REASON = "EBS volume {} should have server-side encryption enabled" @@ -42,5 +50,15 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: for logical_id, resource in cfmodel.Resources.items(): if resource.Type == "AWS::EC2::Volume": if resource.Properties.get("Encrypted") != "true": - self.add_failure_to_result(result, self.REASON.format(logical_id), resource_ids={logical_id}) + self.add_failure_to_result( + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + }, + ) return result diff --git a/cfripper/rules/hardcoded_RDS_password.py b/cfripper/rules/hardcoded_RDS_password.py index 97c3e1d3..54eae9f9 100644 --- a/cfripper/rules/hardcoded_RDS_password.py +++ b/cfripper/rules/hardcoded_RDS_password.py @@ -46,6 +46,14 @@ class HardcodedRDSPasswordRule(Rule): MasterUserPassword: !Ref 'MasterUserPassword' ... ```` + + 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` | `Resource` | Resource that is being addressed | """ REASON_DEFAULT = "Default RDS {} password parameter (readable in plain-text) for {}." @@ -60,7 +68,7 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: for logical_id, resource in cfmodel.Resources.items(): # flag insecure RDS Clusters. if resource.Type == "AWS::RDS::DBCluster": - failure_added = self._failure_added(result, logical_id, resource) + failure_added = self._failure_added(result, logical_id, resource, extras) if not failure_added: password_protected_cluster_ids.append(logical_id) @@ -76,20 +84,28 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: ): continue - self._failure_added(result, logical_id, resource) + self._failure_added(result, logical_id, resource, extras) return result - def _failure_added(self, result: Result, logical_id: str, resource: GenericResource) -> bool: + def _failure_added( + self, result: Result, logical_id: str, resource: GenericResource, extras: Optional[Dict] = None + ) -> bool: master_user_password = resource.Properties.get("MasterUserPassword", Parameter.NO_ECHO_NO_DEFAULT) resource_type = resource.Type.replace("AWS::RDS::DB", "") if master_user_password == Parameter.NO_ECHO_WITH_DEFAULT: self.add_failure_to_result( - result, self.REASON_DEFAULT.format(resource_type, logical_id), resource_ids={logical_id} + result, + self.REASON_DEFAULT.format(resource_type, logical_id), + resource_ids={logical_id}, + context={"config": self._config, "extras": extras, "logical_id": logical_id, "resource": resource}, ) return True elif master_user_password not in (Parameter.NO_ECHO_NO_DEFAULT, Parameter.NO_ECHO_WITH_VALUE): self.add_failure_to_result( - result, self.REASON_MISSING_NOECHO.format(resource_type, logical_id), resource_ids={logical_id}, + result, + self.REASON_MISSING_NOECHO.format(resource_type, logical_id), + resource_ids={logical_id}, + context={"config": self._config, "extras": extras, "logical_id": logical_id, "resource": resource}, ) return True diff --git a/cfripper/rules/iam_roles.py b/cfripper/rules/iam_roles.py index 2f2daf9c..d2c4cbb4 100644 --- a/cfripper/rules/iam_roles.py +++ b/cfripper/rules/iam_roles.py @@ -15,6 +15,16 @@ class IAMRolesOverprivilegedRule(Rule): """ Rule that checks for wildcards in resources for a set of actions and restricts managed policies. + + 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` | `IAMRole` | Resource that is being addressed | + |`statement` | `Optional[Statement]` | Statement being checked found in the Resource | + |`action` | `Optional[str]` | Action containing insecure permission with forbidden prefix | """ GRANULARITY = RuleGranularity.RESOURCE @@ -23,11 +33,11 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: result = Result() for logical_id, resource in cfmodel.Resources.items(): if isinstance(resource, IAMRole): - self.check_managed_policies(result, logical_id, resource) - self.check_inline_policies(result, logical_id, resource) + self.check_managed_policies(result, logical_id, resource, extras) + self.check_inline_policies(result, logical_id, resource, extras) return result - def check_managed_policies(self, result: Result, logical_id: str, role: IAMRole): + def check_managed_policies(self, result: Result, logical_id: str, role: IAMRole, extras: Optional[Dict] = None): """Run the managed policies against a blacklist.""" if not role.Properties.ManagedPolicyArns: return @@ -38,9 +48,17 @@ def check_managed_policies(self, result: Result, logical_id: str, role: IAMRole) result, f"Role {logical_id} has forbidden Managed Policy {managed_policy_arn}", resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": role, + "statement": None, + "action": None, + }, ) - def check_inline_policies(self, result: Result, logical_id: str, role: IAMRole): + def check_inline_policies(self, result: Result, logical_id: str, role: IAMRole, extras: Optional[Dict] = None): """Check conditional and non-conditional inline policies.""" if not role.Properties.Policies: return @@ -56,6 +74,14 @@ def check_inline_policies(self, result: Result, logical_id: str, role: IAMRole): f"Role '{logical_id}' contains an insecure permission '{action}' in policy " f"'{policy.PolicyName}'", resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": role, + "statement": statement, + "action": action, + }, ) @@ -63,6 +89,14 @@ class IAMRoleWildcardActionOnPolicyRule(Rule): """ Checks for use of wildcard characters in all IAM Role policies (including `AssumeRolePolicyDocument`) and [AWS Managed Policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html). + + 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` | `Union[IAMRole, IAMManagedPolicy]` | Resource that is being addressed | """ GRANULARITY = RuleGranularity.RESOURCE @@ -75,7 +109,15 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: # check AssumeRolePolicyDocument. if resource.Properties.AssumeRolePolicyDocument.allowed_actions_with(REGEX_WILDCARD_POLICY_ACTION): self.add_failure_to_result( - result, self.REASON.format(logical_id, "AssumeRolePolicy"), resource_ids={logical_id}, + result, + self.REASON.format(logical_id, "AssumeRolePolicy"), + resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + }, ) # check other policies of the IAM role. @@ -86,6 +128,12 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: result, self.REASON.format(logical_id, f"{policy.PolicyName} policy"), resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + }, ) # check AWS::IAM::ManagedPolicy. @@ -93,6 +141,9 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: REGEX_WILDCARD_POLICY_ACTION ): self.add_failure_to_result( - result, self.REASON.format(logical_id, "AWS::IAM::ManagedPolicy"), resource_ids={logical_id}, + result, + self.REASON.format(logical_id, "AWS::IAM::ManagedPolicy"), + resource_ids={logical_id}, + context={"config": self._config, "extras": extras, "logical_id": logical_id, "resource": resource}, ) return result diff --git a/cfripper/rules/kms_key_wildcard_principal.py b/cfripper/rules/kms_key_wildcard_principal.py index 93d4cfab..073f3fb7 100644 --- a/cfripper/rules/kms_key_wildcard_principal.py +++ b/cfripper/rules/kms_key_wildcard_principal.py @@ -16,6 +16,16 @@ class KMSKeyWildcardPrincipalRule(Rule): """ Check for wildcards in principals in KMS Policies. + + 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` | `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 | """ GRANULARITY = RuleGranularity.RESOURCE @@ -38,6 +48,16 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: ) else: self.add_failure_to_result( - result, self.REASON.format(logical_id), resource_ids={logical_id} + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + "statement": statement, + "principal": principal, + }, ) return result diff --git a/cfripper/rules/managed_policy_on_user.py b/cfripper/rules/managed_policy_on_user.py index e7cf72ea..683e4a96 100644 --- a/cfripper/rules/managed_policy_on_user.py +++ b/cfripper/rules/managed_policy_on_user.py @@ -60,6 +60,14 @@ class ManagedPolicyOnUserRule(Rule): } } ``` + + 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` | `IAMManagedPolicy` | Resource that is being addressed | """ REASON = "IAM managed policy {} should not apply directly to users. Should be on group" @@ -69,5 +77,10 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: result = Result() for logical_id, resource in cfmodel.Resources.items(): if isinstance(resource, IAMManagedPolicy) and resource.Properties.Users: - self.add_failure_to_result(result, self.REASON.format(logical_id), resource_ids={logical_id}) + self.add_failure_to_result( + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={"config": self._config, "extras": extras, "logical_id": logical_id, "resource": resource}, + ) return result diff --git a/cfripper/rules/policy_on_user.py b/cfripper/rules/policy_on_user.py index 95e4a596..e969fa06 100644 --- a/cfripper/rules/policy_on_user.py +++ b/cfripper/rules/policy_on_user.py @@ -60,6 +60,14 @@ class PolicyOnUserRule(Rule): } } ``` + + 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` | `IAMPolicy` | Resource that is being addressed | """ GRANULARITY = RuleGranularity.RESOURCE @@ -69,5 +77,10 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: result = Result() for logical_id, resource in cfmodel.Resources.items(): if isinstance(resource, IAMPolicy) and resource.Properties.Users: - self.add_failure_to_result(result, self.REASON.format(logical_id), resource_ids={logical_id}) + self.add_failure_to_result( + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={"config": self._config, "extras": extras, "logical_id": logical_id, "resource": resource}, + ) return result diff --git a/cfripper/rules/s3_bucket_policy.py b/cfripper/rules/s3_bucket_policy.py index 013d39be..39b2e441 100644 --- a/cfripper/rules/s3_bucket_policy.py +++ b/cfripper/rules/s3_bucket_policy.py @@ -22,6 +22,17 @@ class S3BucketPolicyPrincipalRule(PrincipalCheckingRule, ResourceSpecificRule): Fix: All principals connected to S3 Bucket Policies should be known. CFRipper checks that **all** principals meet the requirements expected. The list of valid accounts is defined in `valid_principals`, which is set in the config. + + 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` | `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 | + |`account_id` | str | Account ID found in the principal | """ GRANULARITY = RuleGranularity.RESOURCE @@ -46,6 +57,17 @@ def resource_invoke(self, resource: S3BucketPolicy, logical_id: str, extras: Opt ) else: self.add_failure_to_result( - result, self.REASON.format(logical_id, account_id), resource_ids={logical_id}, + result, + self.REASON.format(logical_id, account_id), + resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + "statement": statement, + "principal": principal, + "account_id": account_id, + }, ) return result diff --git a/cfripper/rules/s3_public_access.py b/cfripper/rules/s3_public_access.py index 358378fc..8378cd4a 100644 --- a/cfripper/rules/s3_public_access.py +++ b/cfripper/rules/s3_public_access.py @@ -22,6 +22,15 @@ class S3BucketPublicReadAclAndListStatementRule(Rule): Fix: Unless the bucket is hosting static content and needs to be accessed publicly, these bucket policies should be locked down. + + 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` | `S3BucketPolicy` | Resource that is being addressed | + |`bucket_name` | str | Name of the S3 bucket being analysed | """ GRANULARITY = RuleGranularity.RESOURCE @@ -38,7 +47,18 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: bucket_name = bucket_name[len("UNDEFINED_PARAM_") :] bucket = cfmodel.Resources.get(bucket_name) if bucket and bucket.Properties.get("AccessControl") == "PublicRead": - self.add_failure_to_result(result, self.REASON.format(logical_id), resource_ids={logical_id}) + self.add_failure_to_result( + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + "bucket_name": bucket_name, + }, + ) return result @@ -52,6 +72,14 @@ class S3BucketPublicReadWriteAclRule(Rule): Fix: Remove any configuration that looks like `"AccessControl": "PublicReadWrite"` from your S3 bucket policy. + + 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` | `Resource` | S3 Bucket that is being addressed | """ GRANULARITY = RuleGranularity.RESOURCE @@ -66,5 +94,10 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: and hasattr(resource, "Properties") and resource.Properties.get("AccessControl") == "PublicReadWrite" ): - self.add_failure_to_result(result, self.REASON.format(logical_id), resource_ids={logical_id}) + self.add_failure_to_result( + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={"config": self._config, "extras": extras, "logical_id": logical_id, "resource": resource}, + ) return result diff --git a/cfripper/rules/sns_topic_policy.py b/cfripper/rules/sns_topic_policy.py index c91a9e83..ba738397 100644 --- a/cfripper/rules/sns_topic_policy.py +++ b/cfripper/rules/sns_topic_policy.py @@ -17,6 +17,15 @@ class SNSTopicPolicyNotPrincipalRule(ResourceSpecificRule): AWS **strongly** recommends against using `NotPrincipal` in the same policy statement as `"Effect": "Allow"`. Doing so grants the permissions specified in the policy statement to all principals except the one named in the `NotPrincipal` element. By doing this, you might grant access to anonymous (unauthenticated) users. + + 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` | `SNSTopicPolicy` | Resource that is being addressed | + |`statement` | `Statement` | Statement being checked found in the Resource | """ GRANULARITY = RuleGranularity.RESOURCE @@ -27,7 +36,18 @@ def resource_invoke(self, resource: SNSTopicPolicy, logical_id: str, extras: Opt result = Result() for statement in resource.Properties.PolicyDocument._statement_as_list(): if statement.NotPrincipal: - self.add_failure_to_result(result, self.REASON.format(logical_id), resource_ids={logical_id}) + self.add_failure_to_result( + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + "statement": statement, + }, + ) return result diff --git a/cfripper/rules/sqs_queue_policy.py b/cfripper/rules/sqs_queue_policy.py index 1eb9264c..05ddf481 100644 --- a/cfripper/rules/sqs_queue_policy.py +++ b/cfripper/rules/sqs_queue_policy.py @@ -21,6 +21,15 @@ class SQSQueuePolicyNotPrincipalRule(ResourceSpecificRule): AWS **strongly** recommends against using `NotPrincipal` in the same policy statement as `"Effect": "Allow"`. Doing so grants the permissions specified in the policy statement to all principals except the one named in the `NotPrincipal` element. By doing this, you might grant access to anonymous (unauthenticated) users. + + 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` | `SQSQueuePolicy` | Resource that is being addressed | + |`statement` | `Statement` | Statement being checked found in the Resource | """ GRANULARITY = RuleGranularity.RESOURCE @@ -31,7 +40,18 @@ def resource_invoke(self, resource: SQSQueuePolicy, logical_id: str, extras: Opt result = Result() for statement in resource.Properties.PolicyDocument._statement_as_list(): if statement.NotPrincipal: - self.add_failure_to_result(result, self.REASON.format(logical_id), resource_ids={logical_id}) + self.add_failure_to_result( + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + "statement": statement, + }, + ) return result @@ -41,6 +61,15 @@ class SQSQueuePolicyPublicRule(ResourceSpecificRule): Risk: This is deemed a potential security risk as anyone would be able to interact with your queue. + + 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` | `SQSQueuePolicy` | Resource that is being addressed | + |`statement` | `Statement` | Statement being checked found in the Resource | """ REASON = "SQS Queue policy {} should not be public" @@ -59,7 +88,18 @@ def resource_invoke(self, resource: SQSQueuePolicy, logical_id: str, extras: Opt f"because there are conditions: {statement.Condition}" ) else: - self.add_failure_to_result(result, self.REASON.format(logical_id), resource_ids={logical_id}) + self.add_failure_to_result( + result, + self.REASON.format(logical_id), + resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + "statement": statement, + }, + ) return result diff --git a/cfripper/rules/wildcard_policies.py b/cfripper/rules/wildcard_policies.py index 1e3e2ae6..64c015e2 100644 --- a/cfripper/rules/wildcard_policies.py +++ b/cfripper/rules/wildcard_policies.py @@ -44,7 +44,15 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: REGEX_HAS_STAR_OR_STAR_AFTER_COLON ): self.add_failure_to_result( - result, self.REASON.format(self.AWS_RESOURCE.__name__, logical_id), resource_ids={logical_id}, + result, + self.REASON.format(self.AWS_RESOURCE.__name__, logical_id), + resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + }, ) return result @@ -53,6 +61,14 @@ class S3BucketPolicyWildcardActionRule(GenericWildcardPolicyRule): """ Checks for use of the wildcard `*` character in the Actions of Policy Documents of S3 Bucket Policies. This rule is a subclass of `GenericWildcardPolicyRule`. + + 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` | `S3BucketPolicy` | Resource that is being addressed | """ AWS_RESOURCE = S3BucketPolicy @@ -62,6 +78,14 @@ class SNSTopicPolicyWildcardActionRule(GenericWildcardPolicyRule): """ Checks for use of the wildcard `*` character in the Actions of Policy Documents of SQS Queue Policies. This rule is a subclass of `GenericWildcardPolicyRule`. + + 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` | `SNSTopicPolicy` | Resource that is being addressed | """ AWS_RESOURCE = SNSTopicPolicy @@ -71,6 +95,14 @@ class SQSQueuePolicyWildcardActionRule(GenericWildcardPolicyRule): """ Checks for use of the wildcard `*` character in the Actions of Policy Documents of SQS Queue Policies. This rule is a subclass of `GenericWildcardPolicyRule`. + + 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` | `SQSQueuePolicy` | Resource that is being addressed | """ AWS_RESOURCE = SQSQueuePolicy diff --git a/cfripper/rules/wildcard_principals.py b/cfripper/rules/wildcard_principals.py index 81baa9ac..e2a66fd0 100644 --- a/cfripper/rules/wildcard_principals.py +++ b/cfripper/rules/wildcard_principals.py @@ -22,10 +22,6 @@ class GenericWildcardPrincipalRule(PrincipalCheckingRule): - """ - Checks for wildcard principals in resources. - """ - REASON_WILCARD_PRINCIPAL = "{} should not allow wildcard in principals or account-wide principals (principal: '{}')" GRANULARITY = RuleGranularity.RESOURCE @@ -36,16 +32,18 @@ def invoke(self, cfmodel: CFModel, extras: Optional[Dict] = None) -> Result: result = Result() for logical_id, resource in cfmodel.Resources.items(): if isinstance(resource, (IAMManagedPolicy, IAMPolicy, S3BucketPolicy, SNSTopicPolicy, SQSQueuePolicy)): - self.check_for_wildcards(result, logical_id, resource.Properties.PolicyDocument) + self.check_for_wildcards(result, logical_id, resource.Properties.PolicyDocument, extras) elif isinstance(resource, (IAMRole, IAMUser)): if isinstance(resource, IAMRole): - self.check_for_wildcards(result, logical_id, resource.Properties.AssumeRolePolicyDocument) + self.check_for_wildcards(result, logical_id, resource.Properties.AssumeRolePolicyDocument, extras) if resource.Properties and resource.Properties.Policies: for policy in resource.Properties.Policies: - self.check_for_wildcards(result, logical_id, policy.PolicyDocument) + self.check_for_wildcards(result, logical_id, policy.PolicyDocument, extras) return result - def check_for_wildcards(self, result: Result, logical_id: str, resource: PolicyDocument): + def check_for_wildcards( + self, result: Result, logical_id: str, resource: PolicyDocument, extras: Optional[Dict] = None + ): for statement in resource._statement_as_list(): if statement.Effect == "Allow" and statement.principals_with(self.FULL_REGEX): for principal in statement.get_principal_list(): @@ -68,6 +66,15 @@ def check_for_wildcards(self, result: Result, logical_id: str, resource: PolicyD result, self.REASON_WILCARD_PRINCIPAL.format(logical_id, principal), resource_ids={logical_id}, + context={ + "config": self._config, + "extras": extras, + "logical_id": logical_id, + "resource": resource, + "statement": statement, + "principal": principal, + "account_id": account_id, + }, ) def resource_is_whitelisted(self, logical_id): @@ -86,6 +93,17 @@ class PartialWildcardPrincipalRule(GenericWildcardPrincipalRule): Fix: Where possible, restrict the access to only the required resources. For example, instead of `Principal: "*"`, include a list of the roles that need access. + + 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` | `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 | + |`account_id` | str | Account ID found in the principal | """ REASON_WILCARD_PRINCIPAL = "{} should not allow wildcard in principals or account-wide principals (principal: '{}')" @@ -110,6 +128,17 @@ class FullWildcardPrincipalRule(GenericWildcardPrincipalRule): Fix: Where possible, restrict the access to only the required resources. For example, instead of `Principal: "*"`, include a list of the roles that need access. + + 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` | `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 | + |`account_id` | str | Account ID found in the principal | """ REASON_WILCARD_PRINCIPAL = "{} should not allow wildcards in principals (principal: '{}')" diff --git a/docs/rule_config.md b/docs/rule_config.md index e0182dc5..b1c9993a 100644 --- a/docs/rule_config.md +++ b/docs/rule_config.md @@ -1,28 +1,16 @@ Allows to overwrite the default behaviour of the rule, such as changing the rule mode and risk value. It accepts a more - granular configuration using the filter. - +granular configuration using the filter. + {{ inline_source('cfripper.config.rule_config.RuleConfig') }} ## Filters -When adding a failure or warning it will check if there is a filter that matches the current context and set the new +When adding a failure or warning it will check if there is a filter that matches the current context and set the new risk or mode. Context depends on each rule and is available inside each rule's documentation. The object accepts a reason parameter to say why that filter exists. {{ inline_source('cfripper.config.filter.Filter') }} -!!! warning - Only available for the following rules: - - - CrossAccountCheckingRule - - CrossAccountTrustRule - - EC2SecurityGroupIngressOpenToWorldRule - - EC2SecurityGroupMissingEgressRule - - EC2SecurityGroupOpenToWorldRule - - KMSKeyCrossAccountTrustRule - - S3CrossAccountTrustRule - - WildcardResourceRule - ### Filter preference Following the cascade style, takes preference always the last value set following this structure: @@ -30,30 +18,31 @@ Following the cascade style, takes preference always the last value set followin ``` Rule Standard -> Rule Config -> Filter #1 -> ... -> Filter #N ``` - ### Implemented filter functions -| Function | Description | Example | -|:-------------------:|:---------------------------------------------------------------------------:|:---------------------------------------:| -| `eq` | Same as a == b | `{"eq": ["string", "string"]}` | -| `ne` | Same as a != b | `{"ne": ["string", "not_that_string"]}` | -| `lt` | Same as a < b | `{"lt": [0, 1]}` | -| `gt` | Same as a > b | `{"gt": [1, 0]}` | -| `le` | Same as a <= b | `{"le": [1, 1]}` | -| `ge` | Same as a >= b | `{"ge": [1, 1]}` | -| `not` | Same as not a | `{"not": True}` | -| `or` | True if any arg is True | `{"or": [False, True]}` | -| `and` | True if all args are True | `{"and": [True, True]}` | -| `in` | Same as a in b | `{"in": ["b", ["a", "b"]]}` | -| `regex` | True if b match pattern a (case sensitive) | `{"regex": [r"^\d+$", "5"]}` | -| `regex:ignorecase` | True if b match pattern a (case insensitive) | `{"regex:ignorecase": [r"^AA$", "aa"]}` | -| `exists` | True if a is not None | `{"exists": None}` | -| `empty` | True if len(a) equals 0 | `{"empty": []}` | -| `ref` | Get the value at any depth of the context based on the path described by a. | `{"ref": "param_a.param_b"}` | + +| Function | Description | Example | +| :----------------: | :-------------------------------------------------------------------------: | :-------------------------------------: | +| `eq` | Same as a == b | `{"eq": ["string", "string"]}` | +| `ne` | Same as a != b | `{"ne": ["string", "not_that_string"]}` | +| `lt` | Same as a < b | `{"lt": [0, 1]}` | +| `gt` | Same as a > b | `{"gt": [1, 0]}` | +| `le` | Same as a <= b | `{"le": [1, 1]}` | +| `ge` | Same as a >= b | `{"ge": [1, 1]}` | +| `not` | Same as not a | `{"not": True}` | +| `or` | True if any arg is True | `{"or": [False, True]}` | +| `and` | True if all args are True | `{"and": [True, True]}` | +| `in` | Same as a in b | `{"in": ["b", ["a", "b"]]}` | +| `regex` | True if b match pattern a (case sensitive) | `{"regex": [r"^\d+$", "5"]}` | +| `regex:ignorecase` | True if b match pattern a (case insensitive) | `{"regex:ignorecase": [r"^AA$", "aa"]}` | +| `exists` | True if a is not None | `{"exists": None}` | +| `empty` | True if len(a) equals 0 | `{"empty": []}` | +| `ref` | Get the value at any depth of the context based on the path described by a. | `{"ref": "param_a.param_b"}` | ### Examples Disable the rule if the role name is prefixed with `sandbox-` and the principal equals `arn:aws:iam::123456789012:role/test-role`. + ```python3 Filter( reason="", @@ -66,4 +55,3 @@ Filter( }, ) ``` - diff --git a/requirements.txt b/requirements.txt index 45119eed..7f944617 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,16 +4,39 @@ # # make freeze # -boto3==1.16.25 # via cfripper (setup.py) -botocore==1.19.25 # via boto3, s3transfer -cfn-flip==1.2.3 # via cfripper (setup.py) -click==7.1.2 # via cfn-flip, cfripper (setup.py) -jmespath==0.10.0 # via boto3, botocore -pycfmodel==0.8.1 # via cfripper (setup.py) -pydantic==1.7.2 # via pycfmodel -pydash==4.7.6 # via cfripper (setup.py) -python-dateutil==2.8.1 # via botocore -pyyaml==5.3.1 # via cfn-flip, cfripper (setup.py) -s3transfer==0.3.3 # via boto3 -six==1.15.0 # via cfn-flip, python-dateutil -urllib3==1.26.2 # via botocore +boto3==1.16.25 + # via cfripper (setup.py) +botocore==1.19.25 + # via + # boto3 + # s3transfer +cfn-flip==1.2.3 + # via cfripper (setup.py) +click==7.1.2 + # via + # cfn-flip + # cfripper (setup.py) +jmespath==0.10.0 + # via + # boto3 + # botocore +pycfmodel==0.8.2 + # via cfripper (setup.py) +pydantic==1.7.2 + # via pycfmodel +pydash==4.7.6 + # via cfripper (setup.py) +python-dateutil==2.8.1 + # via botocore +pyyaml==5.3.1 + # via + # cfn-flip + # cfripper (setup.py) +s3transfer==0.3.3 + # via boto3 +six==1.15.0 + # via + # cfn-flip + # python-dateutil +urllib3==1.26.2 + # via botocore diff --git a/setup.py b/setup.py index cba7c1af..4b46e3ba 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ "boto3>=1.4.7,<2", "cfn_flip>=1.2.0", "click~=7.1.1", - "pycfmodel>=0.8.1", + "pycfmodel>=0.8.2", "pydash~=4.7.6", "PyYAML>=4.2b1", ] diff --git a/tests/conftest.py b/tests/conftest.py index 2c2a78c2..1669ef5a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,12 @@ import pytest +from cfripper.config.config import Config +from cfripper.config.filter import Filter +from cfripper.config.rule_config import RuleConfig +from cfripper.model.enums import RuleMode +from cfripper.rules import DEFAULT_RULES + @pytest.fixture(scope="session", autouse=True) def default_session_fixture(request): @@ -14,3 +20,28 @@ def default_session_fixture(request): @pytest.fixture def test_files_location() -> Path: return Path(__file__).parent / "test_files" + + +@pytest.fixture() +def default_allow_all_config(): + return Config( + rules=DEFAULT_RULES, + aws_account_id="123456789", + stack_name="mockstack", + rules_config={ + rule: RuleConfig( + filters=[ + Filter( + rule_mode=RuleMode.WHITELISTED, + eval={ + "and": [ + {"exists": {"ref": "config.stack_name"}}, + {"eq": [{"ref": "config.stack_name"}, "mockstack"]}, + ] + }, + ), + ] + ) + for rule in DEFAULT_RULES + }, + ) diff --git a/tests/rules/test_CloudFormationAuthenticationRule.py b/tests/rules/test_CloudFormationAuthenticationRule.py index d2f25db2..bae366f2 100644 --- a/tests/rules/test_CloudFormationAuthenticationRule.py +++ b/tests/rules/test_CloudFormationAuthenticationRule.py @@ -46,3 +46,9 @@ def test_rule_ignores_where_auth_not_mentioned(neutral_template): assert result.valid assert len(result.failed_rules) == 0 assert len(result.failed_monitored_rules) == 0 + + +def test_rule_supports_filter_config(bad_template, default_allow_all_config): + rule = CloudFormationAuthenticationRule(default_allow_all_config) + result = rule.invoke(bad_template) + assert result.valid diff --git a/tests/rules/test_EBSVolumeHasSSERule.py b/tests/rules/test_EBSVolumeHasSSERule.py index 96a26f06..ccddf6ab 100644 --- a/tests/rules/test_EBSVolumeHasSSERule.py +++ b/tests/rules/test_EBSVolumeHasSSERule.py @@ -33,3 +33,9 @@ def test_failures_are_raised(bad_template): assert len(result.failed_monitored_rules) == 0 assert result.failed_rules[0].rule == "EBSVolumeHasSSERule" assert result.failed_rules[0].reason == "EBS volume TestVolume should have server-side encryption enabled" + + +def test_rule_supports_filter_config(bad_template, default_allow_all_config): + rule = EBSVolumeHasSSERule(default_allow_all_config) + result = rule.invoke(bad_template) + assert result.valid diff --git a/tests/rules/test_FullWildcardPrincipal.py b/tests/rules/test_FullWildcardPrincipal.py index 5e234582..0d085133 100644 --- a/tests/rules/test_FullWildcardPrincipal.py +++ b/tests/rules/test_FullWildcardPrincipal.py @@ -32,3 +32,9 @@ def test_failures_are_raised(bad_template): assert len(result.failed_monitored_rules) == 0 assert result.failed_rules[0].rule == "FullWildcardPrincipalRule" assert result.failed_rules[0].reason == "PolicyA should not allow wildcards in principals (principal: '*')" + + +def test_rule_supports_filter_config(bad_template, default_allow_all_config): + rule = FullWildcardPrincipalRule(default_allow_all_config) + result = rule.invoke(bad_template) + assert result.valid diff --git a/tests/rules/test_HardcodedRDSPasswordRule.py b/tests/rules/test_HardcodedRDSPasswordRule.py index a6c14b87..a6f616c0 100644 --- a/tests/rules/test_HardcodedRDSPasswordRule.py +++ b/tests/rules/test_HardcodedRDSPasswordRule.py @@ -89,3 +89,9 @@ def test_failures_are_raised_for_bad_instances_and_bad_clusters(bad_template_clu ) assert result.failed_rules[1].rule == "HardcodedRDSPasswordRule" assert result.failed_rules[1].reason == "RDS Instance password parameter missing NoEcho for BadDb33." + + +def test_rule_supports_filter_config(bad_template_clusters_with_bad_instances, default_allow_all_config): + rule = HardcodedRDSPasswordRule(default_allow_all_config) + result = rule.invoke(bad_template_clusters_with_bad_instances) + assert result.valid diff --git a/tests/rules/test_IAMRoleWildcardActionOnPolicyRule.py b/tests/rules/test_IAMRoleWildcardActionOnPolicyRule.py index d6cae0e8..afb9ff04 100644 --- a/tests/rules/test_IAMRoleWildcardActionOnPolicyRule.py +++ b/tests/rules/test_IAMRoleWildcardActionOnPolicyRule.py @@ -78,3 +78,9 @@ def test_valid_iam_role_no_errors(iam_managed_policy_good_template_with_allow_an assert result.valid assert len(result.failed_rules) == 0 assert len(result.failed_monitored_rules) == 0 + + +def test_rule_supports_filter_config(iam_managed_policy_bad_template, default_allow_all_config): + rule = IAMRoleWildcardActionOnPolicyRule(default_allow_all_config) + result = rule.invoke(iam_managed_policy_bad_template) + assert result.valid diff --git a/tests/rules/test_IAMRolesOverprivilegedRule.py b/tests/rules/test_IAMRolesOverprivilegedRule.py index c6f40b07..8593e912 100644 --- a/tests/rules/test_IAMRolesOverprivilegedRule.py +++ b/tests/rules/test_IAMRolesOverprivilegedRule.py @@ -109,3 +109,9 @@ def test_with_invalid_role_inline_policy_fn_if(invalid_role_inline_policy_fn_if) result.failed_rules[0].reason == "Role 'RootRole' contains an insecure permission 'ec2:DeleteVpc' in policy 'ProdCredentialStoreAccessPolicy'" ) + + +def test_rule_supports_filter_config(invalid_role_managed_policy, default_allow_all_config): + rule = IAMRolesOverprivilegedRule(default_allow_all_config) + result = rule.invoke(invalid_role_managed_policy) + assert result.valid diff --git a/tests/rules/test_ManagedPolicyOnUserRule.py b/tests/rules/test_ManagedPolicyOnUserRule.py index e7fb6ac3..6f01033a 100644 --- a/tests/rules/test_ManagedPolicyOnUserRule.py +++ b/tests/rules/test_ManagedPolicyOnUserRule.py @@ -35,3 +35,9 @@ def test_failures_are_raised(bad_template): result.failed_rules[0].reason == "IAM managed policy DirectManagedPolicy should not apply directly to users. Should be on group" ) + + +def test_rule_supports_filter_config(bad_template, default_allow_all_config): + rule = ManagedPolicyOnUserRule(default_allow_all_config) + result = rule.invoke(bad_template) + assert result.valid diff --git a/tests/rules/test_PartialWildcardPrincipal.py b/tests/rules/test_PartialWildcardPrincipal.py index c83a6671..5edc51cd 100644 --- a/tests/rules/test_PartialWildcardPrincipal.py +++ b/tests/rules/test_PartialWildcardPrincipal.py @@ -74,3 +74,9 @@ def test_aws_elb_allow_template(aws_elb_allow_template): rule = PartialWildcardPrincipalRule(None) result = rule.invoke(aws_elb_allow_template) assert result.valid + + +def test_rule_supports_filter_config(bad_template, default_allow_all_config): + rule = PartialWildcardPrincipalRule(default_allow_all_config) + result = rule.invoke(bad_template) + assert result.valid diff --git a/tests/rules/test_PolicyOnUserRule.py b/tests/rules/test_PolicyOnUserRule.py index adbcdcb9..07adb4fc 100644 --- a/tests/rules/test_PolicyOnUserRule.py +++ b/tests/rules/test_PolicyOnUserRule.py @@ -33,3 +33,9 @@ def test_failures_are_raised(bad_template: CFModel): assert len(result.failed_monitored_rules) == 0 assert result.failed_rules[0].rule == "PolicyOnUserRule" assert result.failed_rules[0].reason == "IAM policy Policy should not apply directly to users. Should be on group" + + +def test_rule_supports_filter_config(bad_template, default_allow_all_config): + rule = PolicyOnUserRule(default_allow_all_config) + result = rule.invoke(bad_template) + assert result.valid diff --git a/tests/rules/test_PrivilegeEscalationRule.py b/tests/rules/test_PrivilegeEscalationRule.py index 26bfe550..8c8e0a94 100644 --- a/tests/rules/test_PrivilegeEscalationRule.py +++ b/tests/rules/test_PrivilegeEscalationRule.py @@ -51,3 +51,9 @@ def test_valid_privilege_escalation_on_s3_bucket_policy(valid_privilege_escalati rule = PrivilegeEscalationRule(None) result = rule.invoke(valid_privilege_escalation_on_s3_bucket_policy) assert result.valid + + +def test_rule_supports_filter_config(privilege_escalation_role_cf, default_allow_all_config): + rule = PrivilegeEscalationRule(default_allow_all_config) + result = rule.invoke(privilege_escalation_role_cf) + assert result.valid diff --git a/tests/rules/test_S3BucketPolicyPrincipalRule.py b/tests/rules/test_S3BucketPolicyPrincipalRule.py index a1050a9b..30fc3ebd 100644 --- a/tests/rules/test_S3BucketPolicyPrincipalRule.py +++ b/tests/rules/test_S3BucketPolicyPrincipalRule.py @@ -21,3 +21,9 @@ def test_failures_are_raised(bad_template): assert result.failed_rules[0].reason == "S3 Bucket S3BucketPolicy policy has non-whitelisted principals 1234556" assert result.failed_rules[1].rule == "S3BucketPolicyPrincipalRule" assert result.failed_rules[1].reason == "S3 Bucket S3BucketPolicy policy has non-whitelisted principals 1234557" + + +def test_rule_supports_filter_config(bad_template, default_allow_all_config): + rule = S3BucketPolicyPrincipalRule(default_allow_all_config) + result = rule.invoke(bad_template) + assert result.valid diff --git a/tests/rules/test_S3BucketPublicReadAclAndListStatementRule.py b/tests/rules/test_S3BucketPublicReadAclAndListStatementRule.py index f0af1e8b..a5a57cc3 100644 --- a/tests/rules/test_S3BucketPublicReadAclAndListStatementRule.py +++ b/tests/rules/test_S3BucketPublicReadAclAndListStatementRule.py @@ -23,3 +23,9 @@ def test_s3_read_plus_list(s3_read_plus_list): == "S3 Bucket S3BucketPolicy should not have a public read acl and list bucket statement" ) assert result.failed_rules[0].rule_mode == RuleMode.BLOCKING + + +def test_rule_supports_filter_config(s3_read_plus_list, default_allow_all_config): + rule = S3BucketPublicReadAclAndListStatementRule(default_allow_all_config) + result = rule.invoke(s3_read_plus_list) + assert result.valid diff --git a/tests/rules/test_S3BucketPublicReadWriteAclRule.py b/tests/rules/test_S3BucketPublicReadWriteAclRule.py index 469e7eb0..bcc0d0c2 100644 --- a/tests/rules/test_S3BucketPublicReadWriteAclRule.py +++ b/tests/rules/test_S3BucketPublicReadWriteAclRule.py @@ -19,3 +19,9 @@ def test_failures_are_raised(bad_template): assert len(result.failed_monitored_rules) == 0 assert result.failed_rules[0].rule == "S3BucketPublicReadWriteAclRule" assert result.failed_rules[0].reason == "S3 Bucket S3Bucket should not have a public read-write acl" + + +def test_rule_supports_filter_config(bad_template, default_allow_all_config): + rule = S3BucketPublicReadWriteAclRule(default_allow_all_config) + result = rule.invoke(bad_template) + assert result.valid diff --git a/tests/rules/test_S3CrossAccountTrustRule.py b/tests/rules/test_S3CrossAccountTrustRule.py index 16b54079..5856642d 100644 --- a/tests/rules/test_S3CrossAccountTrustRule.py +++ b/tests/rules/test_S3CrossAccountTrustRule.py @@ -76,3 +76,9 @@ def test_s3_bucket_cross_account_from_aws_service(s3_bucket_cross_account_from_a assert result.valid assert len(result.failed_rules) == 0 assert len(result.failed_monitored_rules) == 0 + + +def test_rule_supports_filter_config(s3_bucket_cross_account_and_normal, default_allow_all_config): + rule = S3CrossAccountTrustRule(default_allow_all_config) + result = rule.invoke(s3_bucket_cross_account_and_normal) + assert result.valid diff --git a/tests/rules/test_SNSTopicDangerousPolicyActionsRule.py b/tests/rules/test_SNSTopicDangerousPolicyActionsRule.py index 1a25e309..a4bb6890 100644 --- a/tests/rules/test_SNSTopicDangerousPolicyActionsRule.py +++ b/tests/rules/test_SNSTopicDangerousPolicyActionsRule.py @@ -23,3 +23,9 @@ def test_sns_dangerous_policy_actions(sqs_policy): result.failed_rules[0].reason == "SNS Topic policy mysnspolicyA should not not include the following dangerous " "actions: ['sns:AddPermission', 'sns:RemovePermission', 'sns:TagResource', 'sns:UntagResource']" ) + + +def test_rule_supports_filter_config(sqs_policy, default_allow_all_config): + rule = SNSTopicDangerousPolicyActionsRule(default_allow_all_config) + result = rule.invoke(sqs_policy) + assert result.valid diff --git a/tests/rules/test_SNSTopicPolicyNotPrincipalRule.py b/tests/rules/test_SNSTopicPolicyNotPrincipalRule.py index 4fdd16ae..14deb3ae 100644 --- a/tests/rules/test_SNSTopicPolicyNotPrincipalRule.py +++ b/tests/rules/test_SNSTopicPolicyNotPrincipalRule.py @@ -21,3 +21,9 @@ def test_sns_topic_not_principal(sns_topic_not_principal): result.failed_rules[0].reason == "SNS Topic policy mysnspolicyA should not allow Allow and NotPrincipal at the same time" ) + + +def test_rule_supports_filter_config(sns_topic_not_principal, default_allow_all_config): + rule = SNSTopicPolicyNotPrincipalRule(default_allow_all_config) + result = rule.invoke(sns_topic_not_principal) + assert result.valid diff --git a/tests/rules/test_SQSDangerousPolicyActionsRule.py b/tests/rules/test_SQSDangerousPolicyActionsRule.py index 08132610..b59d7e28 100644 --- a/tests/rules/test_SQSDangerousPolicyActionsRule.py +++ b/tests/rules/test_SQSDangerousPolicyActionsRule.py @@ -42,3 +42,9 @@ def test_sqs_dangerous_policy_actions(sqs_policy): == "SQS Queue policy QueuePolicyPublic4 should not not include the following dangerous actions: " "['sqs:AddPermission', 'sqs:CreateQueue', 'sqs:DeleteQueue', 'sqs:RemovePermission', 'sqs:TagQueue']" ) + + +def test_rule_supports_filter_config(sqs_policy, default_allow_all_config): + rule = SQSDangerousPolicyActionsRule(default_allow_all_config) + result = rule.invoke(sqs_policy) + assert result.valid diff --git a/tests/rules/test_SQSQueuePolicyNotPrincipalRule.py b/tests/rules/test_SQSQueuePolicyNotPrincipalRule.py index 9dc93cf0..4667bef3 100644 --- a/tests/rules/test_SQSQueuePolicyNotPrincipalRule.py +++ b/tests/rules/test_SQSQueuePolicyNotPrincipalRule.py @@ -21,3 +21,9 @@ def test_sqs_policy_not_principal(sqs_policy_not_principal): result.failed_rules[0].reason == "SQS Queue policy QueuePolicyWithNotPrincipal should not allow Allow and NotPrincipal at the same time" ) + + +def test_rule_supports_filter_config(sqs_policy_not_principal, default_allow_all_config): + rule = SQSQueuePolicyNotPrincipalRule(default_allow_all_config) + result = rule.invoke(sqs_policy_not_principal) + assert result.valid diff --git a/tests/rules/test_SQSQueuePolicyPublicRule.py b/tests/rules/test_SQSQueuePolicyPublicRule.py index 2b873d91..32e5d283 100644 --- a/tests/rules/test_SQSQueuePolicyPublicRule.py +++ b/tests/rules/test_SQSQueuePolicyPublicRule.py @@ -26,3 +26,9 @@ def test_sqs_policy_public(sqs_policy_public): assert result.failed_rules[2].reason == "SQS Queue policy QueuePolicyPublic3 should not be public" assert result.failed_rules[3].rule == "SQSQueuePolicyPublicRule" assert result.failed_rules[3].reason == "SQS Queue policy QueuePolicyPublic4 should not be public" + + +def test_rule_supports_filter_config(sqs_policy_public, default_allow_all_config): + rule = SQSQueuePolicyPublicRule(default_allow_all_config) + result = rule.invoke(sqs_policy_public) + assert result.valid diff --git a/tests/rules/test_WildcardPoliciesRule.py b/tests/rules/test_WildcardPoliciesRule.py index 6d07be31..d488ec40 100644 --- a/tests/rules/test_WildcardPoliciesRule.py +++ b/tests/rules/test_WildcardPoliciesRule.py @@ -75,3 +75,9 @@ def test_sns_topic_with_wildcards(sns_topic_with_wildcards): assert len(result.failed_rules) == 1 assert result.failed_rules[0].rule == "SNSTopicPolicyWildcardActionRule" assert result.failed_rules[0].reason == "The SNSTopicPolicy mysnspolicy1 should not allow a `*` action" + + +def test_rule_supports_filter_config(sns_topic_with_wildcards, default_allow_all_config): + rule = SNSTopicPolicyWildcardActionRule(default_allow_all_config) + result = rule.invoke(sns_topic_with_wildcards) + assert result.valid