Repository: aws-cloudformation/cfn-lint

Issue:
Unsupported AllowedValues in parameter Cidr linter  
The Paramter Cidr linter checks for an AllowedPattern, but it's also possible that a Cidr parameter is controlled with `AllowedValues`. This makes the check on the `AllowedPattern` obsolete:

https://github.com/awslabs/cfn-python-lint/blob/master/src/cfnlint/rules/parameters/Cidr.py#L65

Besides that the allowed values itself are not checked.

Example:

```
  EgressOracle:
    Type: "String"
    AllowedValues:
      - "XXX.XXX.XXX.XXX/28" # Testing
      - "YYY.YYY.YYY.YYY/28" # Production
    Description: "Egress (Outbound) CIDR for Oracle connectivity (port 1521)"
```

This raises the 
```
AllowedPattern for Parameter should be specified at Parameters/EgressOracle. Example "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$"
```

While there are AllowedValues that specify it. But those aren't checked, so this would be "OK" according to the current code:

```
  EgressOracle:
    Type: "String"
    AllowedValues:
      - "XXX.XXX.XXX.XXX" # Testing
      - "YYY.YYY.YYY.YYY" # Production
    Description: "Egress (Outbound) CIDR for Oracle connectivity (port 1521)"
```

There should be support for `AllowedValues`:
* Don't raise the AllowedPattern Warning if there are AllowedValues
* If there are AllowedValues, check those values for valid Cidr's




Discussion:
@kddejong / @cmmeyer Can I be assigned to this issue? We're working on this, so PR will be coming soon.

(Just making issues of all remaining things that we encounter in our stacks to get a clear overview) 
makes sense to me.  thanks @fatbasstard 

Resolving patch:
diff --git a/src/cfnlint/rules/parameters/Cidr.py b/src/cfnlint/rules/parameters/Cidr.py
--- a/src/cfnlint/rules/parameters/Cidr.py
+++ b/src/cfnlint/rules/parameters/Cidr.py
@@ -19,7 +19,7 @@
 
 
 class Cidr(CloudFormationLintRule):
-    """Check Availibility Zone parameter checks """
+    """Check Availability Zone parameter checks """
     id = 'W2509'
     shortdesc = 'CIDR Parameters have allowed values'
     description = 'Check if a parameter is being used as a CIDR. ' \
@@ -49,8 +49,8 @@ def __init__(self):
             'AWS::EC2::SecurityGroup.Ingress',
         ]
 
-        for resoruce_type_spec in resource_type_specs:
-            self.resource_property_types.append(resoruce_type_spec)
+        for resource_type_spec in resource_type_specs:
+            self.resource_property_types.append(resource_type_spec)
         for property_type_spec in property_type_specs:
             self.resource_sub_property_types.append(property_type_spec)
 
@@ -62,9 +62,10 @@ def check_cidr_ref(self, value, path, parameters, resources):
         if value in parameters:
             parameter = parameters.get(value, {})
             allowed_pattern = parameter.get('AllowedPattern', None)
-            if not allowed_pattern:
+            allowed_values = parameter.get('AllowedValues', None)
+            if not allowed_pattern and not allowed_values:
                 param_path = ['Parameters', value]
-                message = 'AllowedPattern for Parameter should be specified at {1}. Example "{0}"'
+                message = 'AllowedPattern and/or AllowedValues for Parameter should be specified at {1}. Example for AllowedPattern "{0}"'
                 matches.append(RuleMatch(param_path, message.format(self.cidr_regex, ('/'.join(param_path)))))
 
         return matches
diff --git a/src/cfnlint/rules/parameters/CidrAllowedValues.py b/src/cfnlint/rules/parameters/CidrAllowedValues.py
new file mode 100644
--- /dev/null
+++ b/src/cfnlint/rules/parameters/CidrAllowedValues.py
@@ -0,0 +1,125 @@
+"""
+  Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy of this
+  software and associated documentation files (the "Software"), to deal in the Software
+  without restriction, including without limitation the rights to use, copy, modify,
+  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""
+import re
+from cfnlint import CloudFormationLintRule
+from cfnlint import RuleMatch
+
+
+class CidrAllowedValues(CloudFormationLintRule):
+    """Check Availability Zone parameter checks """
+    id = 'E2004'
+    shortdesc = 'CIDR Allowed Values should be a Cidr Range'
+    description = 'Check if a parameter is being used as a CIDR. ' \
+                  'If it is make sure allowed values are proper CIDRs'
+    tags = ['base', 'parameters', 'cidr']
+
+    # pylint: disable=C0301
+    cidr_regex = r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
+
+    def __init__(self):
+        """Init"""
+        resource_type_specs = [
+            'AWS::EC2::Subnet',
+            'AWS::EC2::Vpc',
+            'AWS::RDS::DBSecurityGroupIngress',
+            'AWS::EC2::NetworkAclEntry',
+            'AWS::EC2::SecurityGroupIngress',
+            'AWS::EC2::SecurityGroupEgress',
+            'AWS::Redshift::ClusterSecurityGroupIngress',
+            'AWS::EC2::VPCCidrBlock',
+        ]
+
+        property_type_specs = [
+            'AWS::RDS::DBSecurityGroup.Ingress',
+            'AWS::EC2::SecurityGroup.Egress',
+            'AWS::SES::ReceiptFilter.IpFilter',
+            'AWS::EC2::SecurityGroup.Ingress',
+        ]
+
+        for resource_type_spec in resource_type_specs:
+            self.resource_property_types.append(resource_type_spec)
+        for property_type_spec in property_type_specs:
+            self.resource_sub_property_types.append(property_type_spec)
+
+    # pylint: disable=W0613
+    def check_cidr_ref(self, value, path, parameters, resources):
+        """Check ref for VPC"""
+        matches = list()
+
+        if value in parameters:
+            parameter = parameters.get(value, {})
+            allowed_values = parameter.get('AllowedValues', None)
+            if allowed_values:
+                for cidr in allowed_values:
+                    pattern = re.compile(self.cidr_regex)
+                    if not pattern.match(cidr):
+                        cidr_path = ['Parameters', value]
+                        message = 'Cidr should be a Cidr Range based string for {0}'
+                        matches.append(RuleMatch(cidr_path, message.format(cidr)))
+
+        return matches
+
+    def check(self, properties, resource_type, path, cfn):
+        """Check itself"""
+        matches = list()
+
+        matches.extend(
+            cfn.check_value(
+                properties, 'CIDRIP', path,
+                check_value=None, check_ref=self.check_cidr_ref,
+                check_mapping=None, check_split=None, check_join=None
+            )
+        )
+        matches.extend(
+            cfn.check_value(
+                properties, 'Cidr', path,
+                check_value=None, check_ref=self.check_cidr_ref,
+                check_mapping=None, check_split=None, check_join=None
+            )
+        )
+        matches.extend(
+            cfn.check_value(
+                properties, 'CidrBlock', path,
+                check_value=None, check_ref=self.check_cidr_ref,
+                check_mapping=None, check_split=None, check_join=None
+            )
+        )
+        matches.extend(
+            cfn.check_value(
+                properties, 'CidrIp', path,
+                check_value=None, check_ref=self.check_cidr_ref,
+                check_mapping=None, check_split=None, check_join=None
+            )
+        )
+
+        return matches
+
+    def match_resource_sub_properties(self, properties, property_type, path, cfn):
+        """Match for sub properties"""
+        matches = list()
+
+        matches.extend(self.check(properties, property_type, path, cfn))
+
+        return matches
+
+    def match_resource_properties(self, properties, resource_type, path, cfn):
+        """Check CloudFormation Properties"""
+        matches = list()
+
+        matches.extend(self.check(properties, resource_type, path, cfn))
+
+        return matches


Test patch:
diff --git a/test/rules/parameters/test_cidr_allowed_values.py b/test/rules/parameters/test_cidr_allowed_values.py
new file mode 100644
--- /dev/null
+++ b/test/rules/parameters/test_cidr_allowed_values.py
@@ -0,0 +1,38 @@
+"""
+  Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy of this
+  software and associated documentation files (the "Software"), to deal in the Software
+  without restriction, including without limitation the rights to use, copy, modify,
+  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""
+from cfnlint.rules.parameters.CidrAllowedValues import CidrAllowedValues  # pylint: disable=E0401
+from .. import BaseRuleTestCase
+
+
+class TestParameterCidrAllowedValues(BaseRuleTestCase):
+    """Test template parameter configurations"""
+    def setUp(self):
+        """Setup"""
+        super(TestParameterCidrAllowedValues, self).setUp()
+        self.collection.register(CidrAllowedValues())
+
+    success_templates = [
+        'templates/good/properties_ec2_vpc.yaml',
+    ]
+
+    def test_file_positive(self):
+        """Test Positive"""
+        self.helper_file_positive()
+
+    def test_file_negative(self):
+        """Test failure"""
+        self.helper_file_negative('templates/bad/properties_ec2_network.yaml', 3)
diff --git a/test/templates/bad/properties_ec2_network.yaml b/test/templates/bad/properties_ec2_network.yaml
--- a/test/templates/bad/properties_ec2_network.yaml
+++ b/test/templates/bad/properties_ec2_network.yaml
@@ -5,6 +5,12 @@ Description: >
 Parameters:
   cidrBlock:
     Type: Number
+  cidrBlockAllowedValues:
+    Type: String
+    AllowedValues:
+      - '127.0.0.1'
+      - '8.8.8.8'
+      - 'notacidr'
   vpcTenancy:
     Type: String
     AllowedValues:
@@ -23,6 +29,11 @@ Resources:
     Properties:
       InstanceTenancy: !Ref vpcTenancy
       CidrBlock: 10.0.0.3
+  myVpc3:
+    Type: AWS::EC2::VPC
+    Properties:
+      InstanceTenancy: !Ref vpcTenancy
+      CidrBlock: !Ref cidrBlockAllowedValues
   mySubnet2-1:
     Type: AWS::EC2::Subnet
     Properties:
diff --git a/test/templates/good/properties_ec2_vpc.yaml b/test/templates/good/properties_ec2_vpc.yaml
--- a/test/templates/good/properties_ec2_vpc.yaml
+++ b/test/templates/good/properties_ec2_vpc.yaml
@@ -3,9 +3,20 @@ AWSTemplateFormatVersion: "2010-09-09"
 Description: >
   AWS EC2 Good Template
 Parameters:
-  cidrBlock:
+  cidrBlockAllowedPattern:
     Type: String
     AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
+  cidrBlockAllowedValues:
+    Type: String
+    AllowedValues:
+      - '127.0.0.1/32'
+      - '8.8.8.8/32'
+  cidrBlockAllowedPatternValues:
+    Type: String
+    AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
+    AllowedValues:
+      - '127.0.0.1/32'
+      - '8.8.8.8/32'
   vpcTenancy:
     Type: String
     AllowedValues:
@@ -15,23 +26,33 @@ Resources:
   myVpc1:
     Type: AWS::EC2::VPC
     Properties:
-      CidrBlock: !Ref cidrBlock
+      CidrBlock: !Ref cidrBlockAllowedPattern
       InstanceTenancy: default
   myVpc2:
     Type: AWS::EC2::VPC
     Properties:
       InstanceTenancy: !Ref vpcTenancy
-      CidrBlock: !Ref cidrBlock
+      CidrBlock: !Ref cidrBlockAllowedPattern
+  myVpc3:
+    Type: AWS::EC2::VPC
+    Properties:
+      InstanceTenancy: !Ref vpcTenancy
+      CidrBlock: !Ref cidrBlockAllowedValues
+  myVpc4:
+    Type: AWS::EC2::VPC
+    Properties:
+      InstanceTenancy: !Ref vpcTenancy
+      CidrBlock: !Ref cidrBlockAllowedPatternValues
   mySubnet21:
     Type: AWS::EC2::Subnet
     Properties:
       CidrBlock:
         Fn::Select:
         - 0
-        - !Cidr [!Ref cidrBlock, 3, 16]
+        - !Cidr [!Ref cidrBlockAllowedPattern, 3, 16]
       VpcId: vpc-1234567
   mySubnet22:
     Type: AWS::EC2::Subnet
     Properties:
-      CidrBlock: !Ref cidrBlock
+      CidrBlock: !Ref cidrBlockAllowedPattern
       VpcId: vpc-1234567