Repository: aws-cloudformation/cfn-lint

Issue:
False alarm from new W4002
*cfn-lint version: 0.34.0*

[Here](https://gist.github.com/schmiddy/44a779032a930995d22ee2722a18f163) is an example template which causes a false alarm like this:

```
$ cfn-lint /tmp/example.yml 
W4002 As the resource "metadata" section contains reference to a "NoEcho" parameter DBUser, CloudFormation will display the parameter value in plaintext
/tmp/example.yml:21:7

W4002 As the resource "metadata" section contains reference to a "NoEcho" parameter DBPass, CloudFormation will display the parameter value in plaintext
/tmp/example.yml:21:7
```

The problem seems to be that the rule is looking for any mention of the parameter name, even as a text description that is not actually referencing the parameter.



Discussion:

Resolving patch:
diff --git a/src/cfnlint/rules/resources/NoEcho.py b/src/cfnlint/rules/resources/NoEcho.py
--- a/src/cfnlint/rules/resources/NoEcho.py
+++ b/src/cfnlint/rules/resources/NoEcho.py
@@ -2,6 +2,7 @@
 Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 SPDX-License-Identifier: MIT-0
 """
+import six
 from cfnlint.helpers import bool_compare
 from cfnlint.rules import CloudFormationLintRule
 from cfnlint.rules import RuleMatch
@@ -14,34 +15,58 @@ class NoEcho(CloudFormationLintRule):
     source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#parameters-section-structure-properties'
     tags = ['resources', 'NoEcho']
 
-    def match(self, cfn):
-        matches = []
+    def _get_no_echo_params(self, cfn):
+        """ Get no Echo Params"""
         no_echo_params = []
-        parameters = cfn.get_parameters()
-        for parameter_name, parameter_value in parameters.items():
+        for parameter_name, parameter_value in cfn.get_parameters().items():
             noecho = parameter_value.get('NoEcho', default=False)
             if bool_compare(noecho, True):
                 no_echo_params.append(parameter_name)
 
+        return no_echo_params
+
+    def _check_ref(self, cfn, no_echo_params):
+        """ Check Refs """
+        matches = []
+        refs = cfn.search_deep_keys('Ref')
+        for ref in refs:
+            if ref[-1] in no_echo_params:
+                if len(ref) > 3:
+                    if ref[0] == 'Resources' and ref[2] == 'Metadata':
+                        matches.append(RuleMatch(ref, 'As the resource "metadata" section contains ' +
+                                                 'reference to a "NoEcho" parameter ' +
+                                                 str(ref[-1]) +
+                                                 ', CloudFormation will display the parameter value in ' +
+                                                 'plaintext'))
+
+        return matches
+
+    def _check_sub(self, cfn, no_echo_params):
+        """ Check Subs """
+        matches = []
+        subs = cfn.search_deep_keys('Fn::Sub')
+        for sub in subs:
+            if isinstance(sub[-1], six.string_types):
+                params = cfn.get_sub_parameters(sub[-1])
+                for param in params:
+                    if param in no_echo_params:
+                        if len(sub) > 2:
+                            if sub[0] == 'Resources' and sub[2] == 'Metadata':
+
+                                matches.append(RuleMatch(sub[:-1], 'As the resource "metadata" section contains ' +
+                                                         'reference to a "NoEcho" parameter ' +
+                                                         str(param) +
+                                                         ', CloudFormation will display the parameter value in ' +
+                                                         'plaintext'))
+
+        return matches
+
+    def match(self, cfn):
+        matches = []
+        no_echo_params = self._get_no_echo_params(cfn)
         if not no_echo_params:
-            return no_echo_params
-
-        resource_properties = cfn.get_resources()
-        resource_dict = {key: resource_properties[key] for key in resource_properties if
-                         isinstance(resource_properties[key], dict)}
-        for resource_name, resource_values in resource_dict.items():
-            resource_values = {key: resource_values[key] for key in resource_values if
-                               isinstance(resource_values[key], dict)}
-            metadata = resource_values.get('Metadata', {})
-            if metadata is not None:
-                for prop_name, properties in metadata.items():
-                    if isinstance(properties, dict):
-                        for property_value in properties.values():
-                            for param in no_echo_params and no_echo_params:
-                                if str(property_value).find(str(param)) > -1:
-                                    path = ['Resources', resource_name, 'Metadata', prop_name]
-                                    matches.append(RuleMatch(path, 'As the resource "metadata" section contains '
-                                                                   'reference to a "NoEcho" parameter ' + str(param)
-                                                             + ', CloudFormation will display the parameter value in '
-                                                               'plaintext'))
+            return matches
+        matches.extend(self._check_ref(cfn, no_echo_params))
+        matches.extend(self._check_sub(cfn, no_echo_params))
+
         return matches


Test patch:
diff --git a/test/fixtures/results/quickstart/nist_application.json b/test/fixtures/results/quickstart/nist_application.json
--- a/test/fixtures/results/quickstart/nist_application.json
+++ b/test/fixtures/results/quickstart/nist_application.json
@@ -179,33 +179,6 @@
             "Source": "https://aws.amazon.com/blogs/devops/optimize-aws-cloudformation-templates/"
         }
     },
-    {
-        "Filename": "test/fixtures/templates/quickstart/nist_application.yaml",
-        "Level": "Warning",
-        "Location": {
-            "End": {
-                "ColumnNumber": 32,
-                "LineNumber": 221
-            },
-            "Path": [
-                "Resources",
-                "rAutoScalingConfigApp",
-                "Metadata",
-                "AWS::CloudFormation::Init"
-            ],
-            "Start": {
-                "ColumnNumber": 7,
-                "LineNumber": 221
-            }
-        },
-        "Message": "As the resource \"metadata\" section contains reference to a \"NoEcho\" parameter pDBPassword, CloudFormation will display the parameter value in plaintext",
-        "Rule": {
-            "Description": "Check if there is a NoEcho enabled parameter referenced within a resources Metadata section",
-            "Id": "W4002",
-            "ShortDescription": "Check for NoEcho References",
-            "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#parameters-section-structure-properties"
-        }
-    },
     {
         "Filename": "test/fixtures/templates/quickstart/nist_application.yaml",
         "Level": "Informational",
@@ -397,6 +370,42 @@
             "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html"
         }
     },
+    {
+        "Filename": "test/fixtures/templates/quickstart/nist_application.yaml",
+        "Level": "Warning",
+        "Location": {
+            "End": {
+                "ColumnNumber": 24,
+                "LineNumber": 343
+            },
+            "Path": [
+                "Resources",
+                "rAutoScalingConfigApp",
+                "Metadata",
+                "AWS::CloudFormation::Init",
+                "install_wordpress",
+                "files",
+                "/tmp/create-wp-config",
+                "content",
+                "Fn::Join",
+                1,
+                9,
+                "Ref",
+                "pDBPassword"
+            ],
+            "Start": {
+                "ColumnNumber": 21,
+                "LineNumber": 343
+            }
+        },
+        "Message": "As the resource \"metadata\" section contains reference to a \"NoEcho\" parameter pDBPassword, CloudFormation will display the parameter value in plaintext",
+        "Rule": {
+            "Description": "Check if there is a NoEcho enabled parameter referenced within a resources Metadata section",
+            "Id": "W4002",
+            "ShortDescription": "Check for NoEcho References",
+            "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#parameters-section-structure-properties"
+        }
+    },
     {
         "Filename": "test/fixtures/templates/quickstart/nist_application.yaml",
         "Level": "Informational",
diff --git a/test/fixtures/results/quickstart/non_strict/nist_application.json b/test/fixtures/results/quickstart/non_strict/nist_application.json
--- a/test/fixtures/results/quickstart/non_strict/nist_application.json
+++ b/test/fixtures/results/quickstart/non_strict/nist_application.json
@@ -179,33 +179,6 @@
             "Source": "https://aws.amazon.com/blogs/devops/optimize-aws-cloudformation-templates/"
         }
     },
-    {
-        "Filename": "test/fixtures/templates/quickstart/nist_application.yaml",
-        "Level": "Warning",
-        "Location": {
-            "End": {
-                "ColumnNumber": 32,
-                "LineNumber": 221
-            },
-            "Path": [
-                "Resources",
-                "rAutoScalingConfigApp",
-                "Metadata",
-                "AWS::CloudFormation::Init"
-            ],
-            "Start": {
-                "ColumnNumber": 7,
-                "LineNumber": 221
-            }
-        },
-        "Message": "As the resource \"metadata\" section contains reference to a \"NoEcho\" parameter pDBPassword, CloudFormation will display the parameter value in plaintext",
-        "Rule": {
-            "Description": "Check if there is a NoEcho enabled parameter referenced within a resources Metadata section",
-            "Id": "W4002",
-            "ShortDescription": "Check for NoEcho References",
-            "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#parameters-section-structure-properties"
-        }
-    },
     {
         "Filename": "test/fixtures/templates/quickstart/nist_application.yaml",
         "Level": "Informational",
@@ -397,6 +370,42 @@
             "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html"
         }
     },
+    {
+        "Filename": "test/fixtures/templates/quickstart/nist_application.yaml",
+        "Level": "Warning",
+        "Location": {
+            "End": {
+                "ColumnNumber": 24,
+                "LineNumber": 343
+            },
+            "Path": [
+                "Resources",
+                "rAutoScalingConfigApp",
+                "Metadata",
+                "AWS::CloudFormation::Init",
+                "install_wordpress",
+                "files",
+                "/tmp/create-wp-config",
+                "content",
+                "Fn::Join",
+                1,
+                9,
+                "Ref",
+                "pDBPassword"
+            ],
+            "Start": {
+                "ColumnNumber": 21,
+                "LineNumber": 343
+            }
+        },
+        "Message": "As the resource \"metadata\" section contains reference to a \"NoEcho\" parameter pDBPassword, CloudFormation will display the parameter value in plaintext",
+        "Rule": {
+            "Description": "Check if there is a NoEcho enabled parameter referenced within a resources Metadata section",
+            "Id": "W4002",
+            "ShortDescription": "Check for NoEcho References",
+            "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#parameters-section-structure-properties"
+        }
+    },
     {
         "Filename": "test/fixtures/templates/quickstart/nist_application.yaml",
         "Level": "Informational",
diff --git a/test/fixtures/templates/bad/noecho.yaml b/test/fixtures/templates/bad/noecho.yaml
--- a/test/fixtures/templates/bad/noecho.yaml
+++ b/test/fixtures/templates/bad/noecho.yaml
@@ -1,13 +1,19 @@
-AWSTemplateFormatVersion: '2010-09-09'
+AWSTemplateFormatVersion: "2010-09-09"
 Parameters:
   NoEchoParam:
     Type: String
     Default: NoEchoWasEchoed
     NoEcho: true
 Resources:
-  SNSTopicWithSecretName:
+  SNSTopicWithSecretNameInRef:
     Type: AWS::SNS::Topic
     Properties:
       DisplayName: testname
     Metadata:
       NoEchoParamInMetadata: !Ref NoEchoParam
+  SNSTopicWithSecretNameInSub:
+    Type: AWS::SNS::Topic
+    Properties:
+      DisplayName: testname
+    Metadata:
+      NoEchoParamInMetadata: !Sub "${NoEchoParam}"
diff --git a/test/unit/rules/resources/test_noecho.py b/test/unit/rules/resources/test_noecho.py
--- a/test/unit/rules/resources/test_noecho.py
+++ b/test/unit/rules/resources/test_noecho.py
@@ -19,4 +19,4 @@ def test_file_positive(self):
 
     def test_file_negative(self):
         """Test failure"""
-        self.helper_file_negative('test/fixtures/templates/bad/noecho.yaml', 1)
+        self.helper_file_negative('test/fixtures/templates/bad/noecho.yaml', 2)