Repository: aws-cloudformation/cfn-lint

Issue:
Add check for missing !Ref in condition
*cfn-lint version: (`cfn-lint --version`)*: cfn-lint 0.26.0

*Description of issue.*

It'd be helpful if cfn-lint had a check when parameter names or psuedo-parameters are used in conditions without a `!Ref` much like it checks for strings with `${PARAMETER}` without a `!Sub`. This would prevent accidental comparisons with a string literal. It might also be helpful to check for conditions that will always evaluate to true/false.

For example:

```yaml
---
AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  MyTestParameter:
    Type: String
    Default: ""

Conditions:
    CreateFooResource: !Not [!Equals [MyTestParameter, ""]]

Resources:
  Foo:
    Type: AWS::IAM::User
    Condition: CreateFooResource
    Properties: {}
```

It would be helpful to get a warning that `CreateFooResource` always evaluates to true or that it's comparing two string literals, one of which is a parameter name.


Discussion:

Resolving patch:
diff --git a/src/cfnlint/rules/conditions/EqualsIsUseful.py b/src/cfnlint/rules/conditions/EqualsIsUseful.py
new file mode 100644
--- /dev/null
+++ b/src/cfnlint/rules/conditions/EqualsIsUseful.py
@@ -0,0 +1,44 @@
+"""
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: MIT-0
+"""
+import json
+from cfnlint.rules import CloudFormationLintRule
+from cfnlint.rules import RuleMatch
+
+
+class EqualsIsUseful(CloudFormationLintRule):
+    """Validate that the Equals will return true/false and not always be true or false"""
+
+    id = 'W8003'
+    shortdesc = 'Fn::Equals will always return true or false'
+    description = 'Validate Fn::Equals to see if its comparing two strings or two equal items. While this works it may not be intended.'
+    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-equals'
+    tags = ['functions', 'equals']
+
+    function = 'Fn::Equals'
+
+    def _check_equal_values(self, values, path):
+        matches = []
+
+        if json.dumps(values[0]) == json.dumps(values[1]):
+            message = self.function + ' element will alway return true'
+            matches.append(RuleMatch(path, message))
+        elif isinstance(values[0], str) and isinstance(values[1], str):
+            message = self.function + ' element will alway return false'
+            matches.append(RuleMatch(path, message))
+        return matches
+
+    def match(self, cfn):
+        matches = []
+        # Build the list of functions
+        trees = cfn.search_deep_keys(self.function)
+
+        for tree in trees:
+            # Test when in Conditions
+            if tree[0] == 'Conditions':
+                value = tree[-1]
+                if isinstance(value, list) and len(value) == 2:
+                    matches.extend(self._check_equal_values(value, tree[:-1]))
+
+        return matches


Test patch:
diff --git a/test/fixtures/results/quickstart/nist_config_rules.json b/test/fixtures/results/quickstart/nist_config_rules.json
--- a/test/fixtures/results/quickstart/nist_config_rules.json
+++ b/test/fixtures/results/quickstart/nist_config_rules.json
@@ -1,4 +1,32 @@
 [
+    {
+        "Filename": "test/fixtures/templates/quickstart/nist_config_rules.yaml",
+        "Level": "Warning",
+        "Location": {
+            "End": {
+                "ColumnNumber": 17,
+                "LineNumber": 5
+            },
+            "Path": [
+                "Conditions",
+                "cApprovedAMIsRule",
+                "Fn::Not",
+                0,
+                "Fn::Equals"
+            ],
+            "Start": {
+                "ColumnNumber": 7,
+                "LineNumber": 5
+            }
+        },
+        "Message": "Fn::Equals element will alway return true",
+        "Rule": {
+            "Description": "Validate Fn::Equals to see if its comparing two strings or two equal items. While this works it may not be intended.",
+            "Id": "W8003",
+            "ShortDescription": "Fn::Equals will not always return true or false",
+            "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-equals"
+        }
+    },
     {
         "Filename": "test/fixtures/templates/quickstart/nist_config_rules.yaml",
         "Level": "Warning",
diff --git a/test/fixtures/templates/bad/conditions/equals_not_useful.yaml b/test/fixtures/templates/bad/conditions/equals_not_useful.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/bad/conditions/equals_not_useful.yaml
@@ -0,0 +1,11 @@
+---
+AWSTemplateFormatVersion: "2010-09-09"
+Parameters:
+  Environment:
+    Type: String
+    Default: dev
+Conditions:
+  IsTrue: !Equals ['true', 'true']
+  IsFalse: !Equals ['true', 'false']
+  IsDevEnvironment: !Equals [!Ref Environment, !Ref Environment]
+Resources: {}
diff --git a/test/unit/module/cfn_json/test_cfn_json.py b/test/unit/module/cfn_json/test_cfn_json.py
--- a/test/unit/module/cfn_json/test_cfn_json.py
+++ b/test/unit/module/cfn_json/test_cfn_json.py
@@ -24,7 +24,7 @@ def setUp(self):
         self.filenames = {
             "config_rule": {
                 "filename": 'test/fixtures/templates/quickstart/config-rules.json',
-                "failures": 4
+                "failures": 5
             },
             "iam": {
                 "filename": 'test/fixtures/templates/quickstart/iam.json',
diff --git a/test/unit/module/cfn_yaml/test_yaml.py b/test/unit/module/cfn_yaml/test_yaml.py
--- a/test/unit/module/cfn_yaml/test_yaml.py
+++ b/test/unit/module/cfn_yaml/test_yaml.py
@@ -28,7 +28,7 @@ def setUp(self):
             },
             "generic_bad": {
                 "filename": 'test/fixtures/templates/bad/generic.yaml',
-                "failures": 29
+                "failures": 30
             }
         }
 
diff --git a/test/unit/module/test_rules_collections.py b/test/unit/module/test_rules_collections.py
--- a/test/unit/module/test_rules_collections.py
+++ b/test/unit/module/test_rules_collections.py
@@ -50,7 +50,7 @@ def test_fail_run(self):
         filename = 'test/fixtures/templates/bad/generic.yaml'
         template = cfnlint.decode.cfn_yaml.load(filename)
         cfn = Template(filename, template, ['us-east-1'])
-        expected_err_count = 32
+        expected_err_count = 33
         matches = []
         matches.extend(self.rules.run(filename, cfn))
         assert len(matches) == expected_err_count, 'Expected {} failures, got {}'.format(
diff --git a/test/unit/rules/conditions/test_equals_is_useful.py b/test/unit/rules/conditions/test_equals_is_useful.py
new file mode 100644
--- /dev/null
+++ b/test/unit/rules/conditions/test_equals_is_useful.py
@@ -0,0 +1,25 @@
+"""
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: MIT-0
+"""
+from test.unit.rules import BaseRuleTestCase
+from cfnlint.rules.conditions.EqualsIsUseful import EqualsIsUseful  # pylint: disable=E0401
+
+
+class TestEqualsIsUseful(BaseRuleTestCase):
+    """Test template mapping configurations"""
+
+    def setUp(self):
+        """Setup"""
+        super(TestEqualsIsUseful, self).setUp()
+        self.collection.register(EqualsIsUseful())
+
+    success_templates = []
+
+    def test_file_positive(self):
+        """Test Positive"""
+        self.helper_file_positive()
+
+    def test_file_negative(self):
+        """Test failure"""
+        self.helper_file_negative('test/fixtures/templates/bad/conditions/equals_not_useful.yaml', 3)