Repository: aws-cloudformation/cfn-lint

Issue:
Trust Policy check counts Functions in length (E2508 Role trust policy JSON text cannot be longer than 2048 characters)
*cfn-lint version: (`cfn-lint --version`)*
cfn-lint 0.16.0

*Description of issue.*
When using functions in a Trust Policy, they get counted as characters and trigger the length check. This may cause false positives eg, if there is an "If" function in there, or false negatives, eg if the there is a Ref('short') that returns a long value.
 
Example template:
```
Parameters:
  # Testing with this set to No will deploy
  LimitAdminUse:
    AllowedValues:
      - 'Yes'
      - 'No'
    Default: 'No'
    Type: String
  UserName:
    Type: String
Conditions:
  IsLimitAdminUse: !Equals
    - !Ref 'LimitAdminUse'
    - 'Yes'
Resources:
  AdminRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Condition:
              Bool:
                aws:MultiFactorAuthPresent: 'true'
            Effect: Allow
            Principal:
              AWS: !If
                - IsLimitAdminUse
                # We repeat a lot, to get more characters, this could be all different usernames in a real template
                - - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                  - !Sub
                    - arn:aws:iam::${Account}:user/${User}
                    - Account: !Ref AWS::AccountId
                      User: !Ref UserName
                - !Sub
                  - arn:aws:iam::${Account}:root
                  - Account: !Ref AWS::AccountId
            Sid: ''
        Version: '2012-10-17'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess
      Path: /
```

* Feature request:
Getting this check to work reliably is hard, there are some mays to make it better.

1. Ignore the check if there are functions in the trust policy
2. (a bit better) Count every function as 0 characters (this should return the minimum number of characters)
2. (even better). Count every function that returns a value as 0, and handle If's as the maximum of its branches.



Discussion:
agreed.  I've been doing a lot of work on how to process conditions.  I think I have enough of the core engine built to do option 3.  I still need to do a few things so give me a little time
I got the code to fix this working.  I'm going to have to test it a little bit more and make sure I covered most of the scenarios.   This will work as you have describe in option 3.  

1. We will remove the Fn::Ifs by playing out the condition logic.
1. Then we will remove all other functions (besides Fn::Sub) replacing them with 0.  For Fn::Sub we will use the string but replace any variables with an empty string.
1. Then we will check the size of the resulting JSON

This way we can expand the logic around Fn::Sub to get even closer to reality.  Replace AccountId with a 12 digit number, etc.
That's awesome!

May I suggest treating Fn::Join similar to Fn::Sub (keeping the length of anything that is a real string, and maybe in the future count Ref's to an AccountId as 12).

The `Fn::Base64` is also pretty straight-forward to count, as this is always at least the length of the string given to it. More accurately it's the length of the string * 8\6 (8 bits per string characters, and 6 bits per base64 character)

The other functions are harder to do :)

Resolving patch:
diff --git a/src/cfnlint/__init__.py b/src/cfnlint/__init__.py
--- a/src/cfnlint/__init__.py
+++ b/src/cfnlint/__init__.py
@@ -408,7 +408,7 @@ def __eq__(self, item):
             ))
 
 
-class Template(object):
+class Template(object):  # pylint: disable=R0904
     """Class for a CloudFormation template"""
 
     # pylint: disable=dangerous-default-value
@@ -959,6 +959,64 @@ def is_resource_available(self, path, resource):
         # if resource condition isn't available then the resource is available
         return results
 
+    def get_object_without_nested_conditions(self, obj, path):
+        """
+            Get a list of object values without conditions included.
+            Evaluates deep into the object removing any nested conditions as well
+        """
+        results = []
+        scenarios = self.get_condition_scenarios_below_path(path)
+        if not isinstance(obj, (dict, list)):
+            return results
+
+        if not scenarios:
+            if isinstance(obj, dict):
+                if len(obj) == 1:
+                    if obj.get('Ref') == 'AWS::NoValue':
+                        return results
+            return [{
+                'Scenario': None,
+                'Object': obj
+            }]
+
+        def get_value(value, scenario):  # pylint: disable=R0911
+            """ Get the value based on the scenario resolving nesting """
+            if isinstance(value, dict):
+                if len(value) == 1:
+                    if 'Fn::If' in value:
+                        if_values = value.get('Fn::If')
+                        if len(if_values) == 3:
+                            if_path = scenario.get(if_values[0], None)
+                            if if_path is not None:
+                                if if_path:
+                                    return get_value(if_values[1], scenario)
+                                return get_value(if_values[2], scenario)
+                    elif value.get('Ref') == 'AWS::NoValue':
+                        return None
+
+                new_object = {}
+                for k, v in value.items():
+                    new_object[k] = get_value(v, scenario)
+                return new_object
+            if isinstance(value, list):
+                new_list = []
+                for item in value:
+                    new_value = get_value(item, scenario)
+                    if new_value is not None:
+                        new_list.append(get_value(item, scenario))
+
+                return new_list
+
+            return value
+
+        for scenario in scenarios:
+            results.append({
+                'Scenario': scenario,
+                'Object': get_value(obj, scenario)
+            })
+
+        return results
+
     def get_value_from_scenario(self, obj, scenario):
         """
             Get object values from a provided scenario
@@ -1072,6 +1130,25 @@ def get_object_without_conditions(self, obj):
 
         return results
 
+    def get_condition_scenarios_below_path(self, path, include_if_in_function=False):
+        """
+            get Condition Scenarios from below path
+        """
+        fn_ifs = self.search_deep_keys('Fn::If')
+        results = {}
+        for fn_if in fn_ifs:
+            if len(fn_if) >= len(path):
+                if path == fn_if[0:len(path)]:
+                    # This needs to handle items only below the Path
+                    result = self.get_conditions_from_path(self.template, fn_if[0:-1], False, include_if_in_function)
+                    for condition_name, condition_values in result.items():
+                        if condition_name in results:
+                            results[condition_name].union(condition_values)
+                        else:
+                            results[condition_name] = condition_values
+
+        return self.conditions.get_scenarios(results.keys())
+
     def get_conditions_scenarios_from_object(self, objs):
         """
             Get condition from objects
@@ -1114,7 +1191,7 @@ def get_conditions_from_property(value):
 
         return self.conditions.get_scenarios(list(con))
 
-    def get_conditions_from_path(self, text, path):
+    def get_conditions_from_path(self, text, path, include_resource_conditions=True, include_if_in_function=True, only_last=False):
         """
             Parent function to handle resources with conditions.
             Input:
@@ -1126,18 +1203,19 @@ def get_conditions_from_path(self, text, path):
                     {'condition': {True}}
         """
 
-        results = self._get_conditions_from_path(text, path)
-        if len(path) >= 2:
-            if path[0] in ['Resources', 'Outputs']:
-                condition = text.get(path[0], {}).get(path[1], {}).get('Condition')
-                if condition:
-                    if not results.get(condition):
-                        results[condition] = set()
-                    results[condition].add(True)
+        results = self._get_conditions_from_path(text, path, include_if_in_function, only_last)
+        if include_resource_conditions:
+            if len(path) >= 2:
+                if path[0] in ['Resources', 'Outputs']:
+                    condition = text.get(path[0], {}).get(path[1], {}).get('Condition')
+                    if condition:
+                        if not results.get(condition):
+                            results[condition] = set()
+                        results[condition].add(True)
 
         return results
 
-    def _get_conditions_from_path(self, text, path):
+    def _get_conditions_from_path(self, text, path, include_if_in_function=True, only_last=False):
         """
             Get the conditions and their True/False value for the path provided
             Input:
@@ -1170,7 +1248,7 @@ def get_condition_name(value, num=None):
 
         try:
             # Found a condition at the root of the Path
-            if path[0] == 'Fn::If':
+            if path[0] == 'Fn::If' and ((len(path) == 1 and only_last) or not only_last):
                 condition = text.get('Fn::If')
                 if len(path) > 1:
                     if path[1] in [1, 2]:
@@ -1179,7 +1257,9 @@ def get_condition_name(value, num=None):
                     get_condition_name(condition)
             # Iterate if the Path has more than one value
             if len(path) > 1:
-                child_results = self._get_conditions_from_path(text[path[0]], path[1:])
+                if (path[0] in cfnlint.helpers.FUNCTIONS and path[0] != 'Fn::If') and not include_if_in_function:
+                    return results
+                child_results = self._get_conditions_from_path(text[path[0]], path[1:], include_if_in_function, only_last)
                 for c_r_k, c_r_v in child_results.items():
                     if not results.get(c_r_k):
                         results[c_r_k] = set()
diff --git a/src/cfnlint/rules/resources/iam/Limits.py b/src/cfnlint/rules/resources/iam/Limits.py
deleted file mode 100644
--- a/src/cfnlint/rules/resources/iam/Limits.py
+++ /dev/null
@@ -1,108 +0,0 @@
-"""
-  Copyright 2019 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 datetime
-import json
-
-from cfnlint import CloudFormationLintRule
-from cfnlint import RuleMatch
-
-
-class Limits(CloudFormationLintRule):
-    """Check if IAM limits are not breached"""
-    id = 'E2508'
-    shortdesc = 'Check IAM resource limits'
-    description = 'See if IAM resources do not breach limits'
-    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html'
-    tags = ['resources', 'iam']
-
-    def _serialize_date(self, obj):
-        if isinstance(obj, datetime.date):
-            return obj.isoformat()
-        raise TypeError('Object of type {} is not JSON serializable'.format(obj.__class__.__name__))
-
-    def check_instance_profile_roles(self, properties, path, cfn):
-        """Check InstanceProfile.Roles is within limits"""
-        matches = []
-
-        if 'Roles' not in properties:
-            return matches
-
-        property_sets = cfn.get_object_without_conditions(properties)
-        for property_set in property_sets:
-            roles = property_set.get('Object').get('Roles')
-            if isinstance(roles, list):
-                if len(roles) > 1:
-                    if property_set['Scenario'] is None:
-                        matches.append(
-                            RuleMatch(
-                                path + ['Roles'],
-                                'InstanceProfile can only have one role attached'
-                            )
-                        )
-                    else:
-                        scenario_text = ' and '.join(['when condition "%s" is %s' % (k, v) for (k, v) in property_set['Scenario'].items()])
-                        message = 'InstanceProfile can only have one role attached when {0}'
-                        matches.append(
-                            RuleMatch(
-                                path + ['Roles'],
-                                message.format(scenario_text),
-                            )
-                        )
-
-        return matches
-
-    def check_role_assume_role_policy_document(self, properties, path, _):
-        """Check Role.AssumeRolePolicyDocument is within limits"""
-        matches = []
-
-        if 'AssumeRolePolicyDocument' not in properties:
-            return matches
-
-        if len(json.dumps(properties['AssumeRolePolicyDocument'], default=self._serialize_date)) > 2048:
-            matches.append(
-                RuleMatch(
-                    path + ['AssumeRolePolicyDocument'],
-                    'Role trust policy JSON text cannot be longer than 2048 characters',
-                )
-            )
-
-        return matches
-
-    def match(self, cfn):
-        """Check that IAM resources are below limits"""
-        matches = []
-        types = {
-            'AWS::IAM::User': [
-            ],
-            'AWS::IAM::Group': [
-            ],
-            'AWS::IAM::Role': [
-                self.check_role_assume_role_policy_document,
-            ],
-            'AWS::IAM::InstanceProfile': [
-            ],
-        }
-
-        for resource_type, check_list in types.items():
-            resources = cfn.get_resources(resource_type=resource_type)
-            for resource_name, resource_object in resources.items():
-                path = ['Resources', resource_name, 'Properties']
-                properties = resource_object.get('Properties', {})
-                for check in check_list:
-                    matches.extend(check(properties, path, cfn))
-
-        return matches
diff --git a/src/cfnlint/rules/resources/properties/JsonSize.py b/src/cfnlint/rules/resources/properties/JsonSize.py
new file mode 100644
--- /dev/null
+++ b/src/cfnlint/rules/resources/properties/JsonSize.py
@@ -0,0 +1,132 @@
+"""
+  Copyright 2019 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 datetime
+import json
+import re
+import six
+import cfnlint.helpers
+from cfnlint import CloudFormationLintRule
+from cfnlint import RuleMatch
+from cfnlint.helpers import RESOURCE_SPECS
+
+
+class JsonSize(CloudFormationLintRule):
+    """Check if JSON Object Size is within the specified length"""
+    id = 'E3502'
+    shortdesc = 'Check if a JSON Object is within size limits'
+    description = 'Validate properties that are JSON values so that their length is within the limits'
+    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html'
+    tags = ['resources', 'limits', 'json']
+
+    def initialize(self, cfn):
+        """Initialize the rule"""
+        for resource_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes'):
+            self.resource_property_types.append(resource_type_spec)
+        for property_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes'):
+            self.resource_sub_property_types.append(property_type_spec)
+
+    def _serialize_date(self, obj):
+        if isinstance(obj, datetime.date):
+            return obj.isoformat()
+        raise TypeError('Object of type {} is not JSON serializable'.format(obj.__class__.__name__))
+
+    def check_value(self, value, path, prop, cfn, specs):
+        """Check Role.AssumeRolePolicyDocument is within limits"""
+        matches = []
+
+        def remove_functions(obj):
+            """ Replaces intrinsic functions with string """
+            if isinstance(obj, dict):
+                new_obj = {}
+                if len(obj) == 1:
+                    for k, v in obj.items():
+                        if k in cfnlint.helpers.FUNCTIONS:
+                            if k == 'Fn::Sub':
+                                if isinstance(v, six.string_types):
+                                    return re.sub(r'\${.*}', '', v)
+                                if isinstance(v, list):
+                                    return re.sub(r'\${.*}', '', v[0])
+                        else:
+                            new_obj[k] = remove_functions(v)
+                            return new_obj
+                else:
+                    for k, v in obj.items():
+                        new_obj[k] = remove_functions(v)
+                    return new_obj
+            elif isinstance(obj, list):
+                new_list = []
+                for v in obj:
+                    new_list.append(remove_functions(v))
+                return new_list
+
+            return obj
+
+        scenarios = cfn.get_object_without_nested_conditions(value, path)
+        json_max_size = specs.get('JsonMax')
+        for scenario in scenarios:
+            if len(json.dumps(remove_functions(scenario['Object']), default=self._serialize_date)) > json_max_size:
+                if scenario['Scenario']:
+                    message = 'Role trust policy JSON text cannot be longer than {0} characters when {1}'
+                    scenario_text = ' and '.join(['when condition "%s" is %s' % (k, v) for (k, v) in scenario['Scenario'].items()])
+                    matches.append(RuleMatch(path + [prop], message.format(json_max_size, scenario_text)))
+                else:
+                    message = 'Role trust policy JSON text cannot be longer than {0} characters'
+                    matches.append(
+                        RuleMatch(
+                            path + [prop],
+                            message.format(json_max_size),
+                        )
+                    )
+
+        return matches
+
+    def check(self, cfn, properties, specs, path):
+        """Check itself"""
+        matches = []
+        for p_value, p_path in properties.items_safe(path[:]):
+            for prop in p_value:
+                if prop in specs:
+                    value = specs.get(prop).get('Value', {})
+                    if value:
+                        value_type = value.get('ValueType', '')
+                        primitive_type = specs.get(prop).get('PrimitiveType')
+                        if primitive_type == 'Json':
+                            matches.extend(
+                                self.check_value(
+                                    p_value, p_path, prop, cfn,
+                                    RESOURCE_SPECS.get(cfn.regions[0]).get('ValueTypes').get(value_type, {})
+                                )
+                            )
+        return matches
+
+    def match_resource_sub_properties(self, properties, property_type, path, cfn):
+        """Match for sub properties"""
+        matches = list()
+
+        specs = RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes').get(property_type, {}).get('Properties', {})
+        matches.extend(self.check(cfn, properties, specs, path))
+
+        return matches
+
+    def match_resource_properties(self, properties, resource_type, path, cfn):
+        """Check CloudFormation Properties"""
+        matches = list()
+
+        specs = RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes').get(resource_type, {}).get('Properties', {})
+        matches.extend(self.check(cfn, properties, specs, path))
+
+        return matches


Test patch:
diff --git a/test/fixtures/templates/bad/resources/properties/json_size.yaml b/test/fixtures/templates/bad/resources/properties/json_size.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/bad/resources/properties/json_size.yaml
@@ -0,0 +1,136 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: Test bad ManagedPolicyArns setup
+Conditions:
+  isProduction: !Equals [!Ref 'AWS::Region', 'us-east1']
+Resources:
+  IamRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+          Effect: Allow
+          Principal:
+            Service: cloudformation.amazonaws.com
+          Action: sts:AssumeRole
+  IamRoleConditions:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Fn::If:
+        - isProduction
+        - Version: '2012-10-17'
+          Statement:
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
+        - Version: '2012-10-17'
+          Statement:
+          - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
+            Effect: Allow
+            Principal:
+              Service: cloudformation.amazonaws.com
+            Action: sts:AssumeRole
diff --git a/test/fixtures/templates/bad/resources_iam_role_assume_role_policy_document.yaml b/test/fixtures/templates/bad/resources_iam_role_assume_role_policy_document.yaml
deleted file mode 100644
--- a/test/fixtures/templates/bad/resources_iam_role_assume_role_policy_document.yaml
+++ /dev/null
@@ -1,64 +0,0 @@
-AWSTemplateFormatVersion: "2010-09-09"
-Description: Test bad ManagedPolicyArns setup
-Resources:
-  IamRole:
-    Type: AWS::IAM::Role
-    Properties:
-      AssumeRolePolicyDocument:
-        Version: '2012-10-17'
-        Statement:
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
-        - Sid: VeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongTextVeryLongText
-          Effect: Allow
-          Principal:
-            Service: cloudformation.amazonaws.com
-          Action: sts:AssumeRole
diff --git a/test/fixtures/templates/good/resources/properties/json_size.yaml b/test/fixtures/templates/good/resources/properties/json_size.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/good/resources/properties/json_size.yaml
@@ -0,0 +1,118 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: Test bad ManagedPolicyArns setup
+Parameters:
+  UserName:
+    Type: String
+Conditions:
+  cPrimaryRegion: !Equals ['us-east-1', !Ref 'AWS::Region']
+  IsLimitAdminUse: !Equals ['us-east-1', !Ref 'AWS::Region']
+Resources:
+  IamRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument: {}
+      ManagedPolicyArns:
+      - arn:aws:iam::aws:policy/AdministratorAccess
+      - arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess
+      - arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess
+      - arn:aws:iam::aws:policy/AmazonKinesisReadOnlyAccess
+      - arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess
+      - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
+      - arn:aws:iam::aws:policy/AutoScalingConsoleReadOnlyAccess
+      - arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess
+      - arn:aws:iam::aws:policy/AWSCodeBuildReadOnlyAccess
+      - !If [cPrimaryRegion, 'arn:aws:iam::aws:policy/IAMReadOnlyAccess', !Ref 'AWS::NoValue']
+  IamGroup:
+    Type: AWS::IAM::Group
+    Properties:
+      ManagedPolicyArns:
+        Fn::If:
+        - cPrimaryRegion
+        - - arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess
+        - - arn:aws:iam::aws:policy/AdministratorAccess
+  AdminRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+          - Action: sts:AssumeRole
+            Condition:
+              Bool:
+                aws:MultiFactorAuthPresent: 'true'
+            Effect: Allow
+            Principal:
+              AWS: !If
+                - IsLimitAdminUse
+                # Too many characters but filtered down to ok because the Subs are replaces
+                - - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                  - !Sub
+                    - arn:aws:iam::${Account}:user/${User}
+                    - Account: !Ref AWS::AccountId
+                      User: !Ref UserName
+                - !Sub
+                  - arn:aws:iam::${Account}:root
+                  - Account: !Ref AWS::AccountId
+            Sid: ''
+        Version: '2012-10-17'
+      ManagedPolicyArns:
+        - arn:aws:iam::aws:policy/AdministratorAccess
+      Path: /
diff --git a/test/integration/test_patched_specs.py b/test/integration/test_patched_specs.py
--- a/test/integration/test_patched_specs.py
+++ b/test/integration/test_patched_specs.py
@@ -114,7 +114,7 @@ def test_property_value_types(self):
         for v_name, v_values in self.spec.get('ValueTypes').items():
             list_count = 0
             for p_name, p_values in v_values.items():
-                self.assertIn(p_name, ['Ref', 'GetAtt', 'AllowedValues', 'AllowedPattern', 'AllowedPatternRegex', 'ListMin', 'ListMax'])
+                self.assertIn(p_name, ['Ref', 'GetAtt', 'AllowedValues', 'AllowedPattern', 'AllowedPatternRegex', 'ListMin', 'ListMax', 'JsonMax'])
                 if p_name in ['ListMin', 'ListMax']:
                     list_count += 1
                 if p_name == 'Ref':
diff --git a/test/module/test_template.py b/test/module/test_template.py
--- a/test/module/test_template.py
+++ b/test/module/test_template.py
@@ -722,3 +722,203 @@ def test_get_object_without_nested_conditions(self):
             else:
                 # Blanket assertion error
                 self.assertDictEqual(result['Scenario'], {})
+
+    def test_get_object_without_nested_conditions_iam(self):
+        """ Test Getting condition names in an object/list """
+        template = {
+            'Conditions': {
+                'isProduction': {'Fn::Equals': [{'Ref': 'myEnvironment'}, 'prod']},
+                'isDevelopment': {'Fn::Equals': [{'Ref': 'myEnvironment'}, 'dev']}
+            },
+            'Resources': {
+                'Role': {
+                    'Type': 'AWS::IAM::Role',
+                    'Properties': {
+                        'AssumeRolePolicyDocument': {
+                            'Version': '2012-10-17',
+                            'Statement': [
+                                {
+                                    'Sid': '',
+                                    'Action': 'sts:AssumeRole',
+                                    'Effect': 'Allow',
+                                    'Principal': {
+                                        'AWS': {
+                                            'Fn::If': [
+                                                'isProduction',
+                                                '123456789012',
+                                                '210987654321'
+                                            ]
+                                        }
+                                    }
+                                }
+                            ]
+                        },
+                        'ManagedPolicyArns': [
+                            'arn:aws:iam::aws:policy/AdministratorAccess'
+                        ],
+                        'Path': '/'
+                    }
+                }
+            }
+        }
+
+        template = Template('test.yaml', template)
+
+        results = template.get_object_without_nested_conditions(
+            template.template.get('Resources', {}).get('Role', {}).get('Properties', {}).get('AssumeRolePolicyDocument', {}),
+            ['Resources', 'Role', 'Properties', 'AssumeRolePolicyDocument']
+        )
+
+        self.assertEqual(len(results), 2)
+        for result in results:
+            if not result['Scenario']['isProduction']:
+                self.assertDictEqual(
+                    result['Object'],
+                    {
+                        'Version': '2012-10-17',
+                        'Statement': [
+                            {
+                                'Sid': '',
+                                'Action': 'sts:AssumeRole',
+                                'Effect': 'Allow',
+                                'Principal': {
+                                    'AWS': '210987654321'
+                                }
+                            }
+                        ]
+                    }
+                )
+            if result['Scenario']['isProduction']:
+                self.assertDictEqual(
+                    result['Object'],
+                    {
+                        'Version': '2012-10-17',
+                        'Statement': [
+                            {
+                                'Sid': '',
+                                'Action': 'sts:AssumeRole',
+                                'Effect': 'Allow',
+                                'Principal': {
+                                    'AWS': '123456789012'
+                                }
+                            }
+                        ]
+                    }
+                )
+
+    def test_get_object_without_nested_conditions_basic(self):
+        """ Test Getting condition names in an object/list """
+        template = {
+            'Resources': {
+                'Test': 'Test'
+            }
+        }
+
+        template = Template('test.yaml', template)
+
+        results = template.get_object_without_nested_conditions(
+            template.template.get('Resources', {}).get('Test', {}),
+            ['Resources', 'Test']
+        )
+        self.assertEqual(results, [])
+
+    def test_get_object_without_nested_conditions_ref(self):
+        """ Test Getting condition names in an object/list """
+        template = {
+            'Resources': {
+                'Test': {
+                    'Ref': 'AWS::NoValue'
+                }
+            }
+        }
+
+        template = Template('test.yaml', template)
+
+        results = template.get_object_without_nested_conditions(
+            template.template.get('Resources', {}).get('Test', {}),
+            ['Resources', 'Test']
+        )
+        self.assertEqual(results, [])
+        results = template.get_object_without_nested_conditions(
+            template.template.get('Resources', {}),
+            ['Resources']
+        )
+        self.assertEqual(results, [{'Object': {'Test': {'Ref': 'AWS::NoValue'}}, 'Scenario': None}])
+
+    def test_get_condition_scenarios_below_path(self):
+        """ Test Getting condition names in an object/list """
+        template = {
+            'Parameters': {
+                'Environment': {
+                    'Type': 'String'
+                }
+            },
+            'Conditions': {
+                'isPrimaryRegion': {'Fn::Equals': [{'Ref': 'AWS::Region'}, 'us-east-1']},
+                'isProduction': {'Fn::Equals': [{'Ref': 'Environment'}, 'prod']},
+            },
+            'Resources': {
+                'Role': {
+                    'Type': 'AWS::IAM::Role',
+                    'Properties': {
+                        'AssumeRolePolicyDocument': {
+                            'Version': '2012-10-17',
+                            'Statement': [
+                                {
+                                    'Sid': '',
+                                    'Action': 'sts:AssumeRole',
+                                    'Effect': 'Allow',
+                                    'Principal': {
+                                        'AWS': {
+                                            'Fn::If': [
+                                                'isProduction',
+                                                {
+                                                    'Fn::Sub': [
+                                                        '{account}',
+                                                        {
+                                                            'account': {
+                                                                'Fn::If': [
+                                                                    'isPrimaryRegion',
+                                                                    '123456789012',
+                                                                    'ABCDEFGHIJKL'
+                                                                ]
+                                                            }
+                                                        }
+                                                    ]
+                                                },
+                                                '210987654321'
+                                            ]
+                                        }
+                                    }
+                                }
+                            ]
+                        },
+                        'ManagedPolicyArns': [
+                            'arn:aws:iam::aws:policy/AdministratorAccess'
+                        ],
+                        'Path': '/'
+                    }
+                }
+            }
+        }
+
+        template = Template('test.yaml', template)
+        results = template.get_condition_scenarios_below_path(
+            ['Resources', 'Role', 'Properties', 'AssumeRolePolicyDocument']
+        )
+        self.assertEqualListOfDicts(
+            results,
+            [{'isProduction': True}, {'isProduction': False}]
+        )
+        results = template.get_condition_scenarios_below_path(
+            ['Resources', 'Role', 'Properties', 'AssumeRolePolicyDocument'], True
+        )
+        self.assertEqualListOfDicts(
+            results,
+            [
+                {'isPrimaryRegion': False, 'isProduction': False},
+                {'isPrimaryRegion': False, 'isProduction': True},
+                {'isPrimaryRegion': True, 'isProduction': False},
+                {'isPrimaryRegion': True, 'isProduction': True},
+            ]
+        )
diff --git a/test/rules/resources/iam/test_iam_limits.py b/test/rules/resources/properties/test_json_size.py
similarity index 76%
rename from test/rules/resources/iam/test_iam_limits.py
rename to test/rules/resources/properties/test_json_size.py
--- a/test/rules/resources/iam/test_iam_limits.py
+++ b/test/rules/resources/properties/test_json_size.py
@@ -14,16 +14,19 @@
   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.resources.iam.Limits import Limits  # pylint: disable=E0401
+from cfnlint.rules.resources.properties.JsonSize import JsonSize  # pylint: disable=E0401
 from ... import BaseRuleTestCase
 
 
-class TestPropertyIamLimits(BaseRuleTestCase):
-    """Test IAM Limits"""
+class TestJsonSize(BaseRuleTestCase):
+    """Test Json Size"""
     def setUp(self):
         """Setup"""
-        super(TestPropertyIamLimits, self).setUp()
-        self.collection.register(Limits())
+        super(TestJsonSize, self).setUp()
+        self.collection.register(JsonSize())
+        self.success_templates = [
+            'test/fixtures/templates/good/resources/properties/json_size.yaml'
+        ]
 
     def test_file_positive(self):
         """Test Positive"""
@@ -31,4 +34,4 @@ def test_file_positive(self):
 
     def test_role_assume_role_policy_document(self):
         """Test failure"""
-        self.helper_file_negative('test/fixtures/templates/bad/resources_iam_role_assume_role_policy_document.yaml', 1)
+        self.helper_file_negative('test/fixtures/templates/bad/resources/properties/json_size.yaml', 2)