Repository: aws-cloudformation/cfn-lint

Issue:
[E2503] fails incorrectly when intrinsic function used in Protocol value 
*cfn-lint version: 0.4.2*

*Description of issue.*

This is valid, and conforms to the spec, but rule throws an error:

```yaml
Parameters:
  TestParam:
    Type: String
    Default: TCP
Conditions:
  TestCond: !Equals ['a', 'a']
Resources:
  OpenShiftMasterELB:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    Properties:
      Subnets:
        - subnet-1234abcd
      SecurityGroups:
        - sg-1234abcd
      Listeners:
        # Fails on Protocol
        - InstancePort: '1'
          InstanceProtocol: !Ref TestParam
          LoadBalancerPort: '1'
          Protocol: !Ref TestParam
        # Also fails on Protocol
        - InstancePort: '2'
          InstanceProtocol: !If [TestCond, TCP, SSL]
          LoadBalancerPort: '2'
          Protocol: !If [TestCond, TCP, SSL]
        # Works
        - InstancePort: '3'
          InstanceProtocol: !If [TestCond, TCP, SSL]
          LoadBalancerPort: '3'
          Protocol: TCP
```


Discussion:

Resolving patch:
diff --git a/src/cfnlint/rules/resources/elb/Elb.py b/src/cfnlint/rules/resources/elb/Elb.py
--- a/src/cfnlint/rules/resources/elb/Elb.py
+++ b/src/cfnlint/rules/resources/elb/Elb.py
@@ -30,13 +30,21 @@ class Elb(CloudFormationLintRule):
     def match(self, cfn):
         """Check ELB Resource Parameters"""
 
+        def is_intrinsic(input_obj):
+            """Checks if a given input looks like an intrinsic function"""
+
+            if isinstance(input_obj, dict) and len(input_obj) == 1:
+                if list(input_obj.keys())[0] == 'Ref' or list(input_obj.keys())[0].startswith('Fn::'):
+                    return True
+            return False
+
         matches = list()
 
         results = cfn.get_resource_properties(['AWS::ElasticLoadBalancingV2::Listener'])
         for result in results:
             protocol = result['Value'].get('Protocol')
             if protocol:
-                if protocol not in ['HTTP', 'HTTPS', 'TCP']:
+                if protocol not in ['HTTP', 'HTTPS', 'TCP'] and not is_intrinsic(protocol):
                     message = 'Protocol is invalid for {0}'
                     path = result['Path'] + ['Protocol']
                     matches.append(RuleMatch(path, message.format(('/'.join(result['Path'])))))
@@ -53,7 +61,7 @@ def match(self, cfn):
                 for index, listener in enumerate(result['Value']):
                     protocol = listener.get('Protocol')
                     if protocol:
-                        if protocol not in ['HTTP', 'HTTPS', 'TCP', 'SSL']:
+                        if protocol not in ['HTTP', 'HTTPS', 'TCP', 'SSL'] and not is_intrinsic(protocol):
                             message = 'Protocol is invalid for {0}'
                             path = result['Path'] + [index, 'Protocol']
                             matches.append(RuleMatch(path, message.format(('/'.join(result['Path'])))))


Test patch:
diff --git a/test/fixtures/templates/bad/properties_elb.yaml b/test/fixtures/templates/bad/properties_elb.yaml
--- a/test/fixtures/templates/bad/properties_elb.yaml
+++ b/test/fixtures/templates/bad/properties_elb.yaml
@@ -17,6 +17,11 @@ Parameters:
     AllowedValues:
       - internal
       - internet-facing
+  TestParam:
+    Type: String
+    Default: TCP
+Conditions:
+  TestCond: !Equals ['a', 'a']
 Resources:
   LoadBalancer:
     Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
@@ -108,6 +113,22 @@ Resources:
       AvailabilityZones:
         Fn::GetAZs: ''
       Listeners:
+      - InstancePort: '4'
+        InstanceProtocol: !If [TestCond, TCP, SSL]
+        LoadBalancerPort: '4'
+        Protocol: [test]
+      - InstancePort: '5'
+        InstanceProtocol: !If [TestCond, TCP, SSL]
+        LoadBalancerPort: '5'
+        Protocol: ererere
+      - InstancePort: '4'
+        InstanceProtocol: !If [TestCond, TCP, SSL]
+        LoadBalancerPort: '4'
+        Protocol: {"arbKey": ""}
+      - InstancePort: '4'
+        InstanceProtocol: !If [TestCond, TCP, SSL]
+        LoadBalancerPort: '4'
+        Protocol: {"arbKey": "", "moreArb": ""}
       - LoadBalancerPort: '80'
         InstancePort: '80'
         Protocol: TCP
diff --git a/test/fixtures/templates/good/properties_elb.yaml b/test/fixtures/templates/good/properties_elb.yaml
--- a/test/fixtures/templates/good/properties_elb.yaml
+++ b/test/fixtures/templates/good/properties_elb.yaml
@@ -21,6 +21,11 @@ Parameters:
     Type: List<AWS::EC2::SecurityGroup::Id>
   CertARN:
     Type: String
+  TestParam:
+    Type: String
+    Default: TCP
+Conditions:
+  TestCond: !Equals ['a', 'a']
 Resources:
   LoadBalancer:
     Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
@@ -114,6 +119,14 @@ Resources:
       AvailabilityZones:
         Fn::GetAZs: ''
       Listeners:
+      - InstancePort: '1'
+        InstanceProtocol: !Ref TestParam
+        LoadBalancerPort: '1'
+        Protocol: !Ref TestParam
+      - InstancePort: '2'
+        InstanceProtocol: !If [TestCond, TCP, SSL]
+        LoadBalancerPort: '2'
+        Protocol: !If [TestCond, TCP, SSL]
       - LoadBalancerPort: '80'
         InstancePort: '80'
         Protocol: TCP
diff --git a/test/rules/resources/elb/test_elb.py b/test/rules/resources/elb/test_elb.py
--- a/test/rules/resources/elb/test_elb.py
+++ b/test/rules/resources/elb/test_elb.py
@@ -34,4 +34,4 @@ def test_file_positive(self):
 
     def test_file_negative(self):
         """Test failure"""
-        self.helper_file_negative('fixtures/templates/bad/properties_elb.yaml', 2)
+        self.helper_file_negative('fixtures/templates/bad/properties_elb.yaml', 6)