Repository: aws-cloudformation/cfn-lint

Issue:
Linter does not catch MalformedPolicyDocument syntax error
*cfn-lint version: 0.25.0*

*Description of issue.*
`cfn-lint` fails to catch the following syntax error pre-deployment, which is caught by CloudFormation during deployment:
```
Syntax errors in policy. (Service: AmazonIdentityManagement; Status Code: 400; Error Code: MalformedPolicyDocument; Request ID: ccd05e19-aa60-4b26-bfcc-8fbafca40c61; Proxy: null)
```

The policy in question has the following template:
```yaml
APILambdaExecutionPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: Allows API Gateway triggered Lambdas access to service resources
      Path: /
      ManagedPolicyName:
        Fn::Sub: ${Project}-APILambdaExecutionPolicy-${AWS::Region}
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - dynamodb:*Get*
          - dynamodb:*Item*
          - dynamodb:Describe*
          - dynamodb:Query
          Resource:
          - Fn::GetAtt:
            - DynamoDB
            - Outputs.TaskDBTableARN
          - Fn::Sub: ${DynamoDB.Outputs.TaskDBTableARN}/*
          - Effect: Allow
            Action:
            - cloudwatch:PutMetricAlarm
            - cloudwatch:PutMetricData
            Resource:
            - '*'
          - Effect: Allow
            Action:
            - xray:PutTraceSegments
            - xray:PutTelemetryRecords
            Resource:
            - '*'
          - Effect: Allow
            Action:
            - sqs:GetQueueUrl
            - sqs:SendMessage
            Resource:
            - Fn::GetAtt:
              - SQS
              - Outputs.QueueARN
          - Effect: Allow
            Action:
            - sns:Publish
            Resource:
            - arn:aws:sns:*:*:*  
```





Discussion:
To clarify, is the syntax error for this template just the indentation?

Resolving patch:
diff --git a/src/cfnlint/rules/resources/iam/Policy.py b/src/cfnlint/rules/resources/iam/Policy.py
--- a/src/cfnlint/rules/resources/iam/Policy.py
+++ b/src/cfnlint/rules/resources/iam/Policy.py
@@ -5,7 +5,7 @@
 import json
 from datetime import date
 import six
-from cfnlint.helpers import convert_dict
+from cfnlint.helpers import convert_dict, FUNCTIONS_SINGLE
 from cfnlint.rules import CloudFormationLintRule
 from cfnlint.rules import RuleMatch
 
@@ -154,6 +154,23 @@ def _check_policy_statement(self, branch, statement, is_identity_policy, resourc
                 matches.append(
                     RuleMatch(branch[:], message))
 
+        resources = statement.get('Resource', [])
+        if isinstance(resources, six.string_types):
+            resources = [resources]
+
+        for index, resource in enumerate(resources):
+            if isinstance(resource, dict):
+                if len(resource) == 1:
+                    for k in resource.keys():
+                        if k not in FUNCTIONS_SINGLE:
+                            message = 'IAM Policy statement Resource incorrectly formatted'
+                            matches.append(
+                                RuleMatch(branch[:] + ['Resource', index], message))
+                else:
+                    message = 'IAM Policy statement Resource incorrectly formatted'
+                    matches.append(
+                        RuleMatch(branch[:] + ['Resource', index], message))
+
         return(matches)
 
     def match_resource_properties(self, properties, resourcetype, path, cfn):


Test patch:
diff --git a/test/fixtures/templates/bad/properties_iam_policy.yaml b/test/fixtures/templates/bad/resources/iam/iam_policy.yaml
similarity index 77%
rename from test/fixtures/templates/bad/properties_iam_policy.yaml
rename to test/fixtures/templates/bad/resources/iam/iam_policy.yaml
--- a/test/fixtures/templates/bad/properties_iam_policy.yaml
+++ b/test/fixtures/templates/bad/resources/iam/iam_policy.yaml
@@ -22,6 +22,15 @@ Resources:
         - Resource: '*'
           Effect: 'NotAllow'
           Principal: [123456789012]
+        - Effect: Allow
+          Action:
+          - cloudwatch:*
+          Resource:
+          - Effect: Allow
+          - Effect: Allow
+            Action:
+            - cloudwatch:*
+            Resource: '*'
   rIamPolicy:
     Type: AWS::IAM::Policy
     Properties:
diff --git a/test/unit/module/config/test_config_mixin.py b/test/unit/module/config/test_config_mixin.py
--- a/test/unit/module/config/test_config_mixin.py
+++ b/test/unit/module/config/test_config_mixin.py
@@ -158,7 +158,7 @@ def test_config_expand_ignore_templates(self, yaml_mock):
         # test defaults
         self.assertNotIn(
             'test/fixtures/templates/bad/resources/iam/resource_policy.yaml', config.templates)
-        self.assertEqual(len(config.templates), 4)
+        self.assertEqual(len(config.templates), 5)
 
     @patch('cfnlint.config.ConfigFileArgs._read_config', create=True)
     def test_config_merge(self, yaml_mock):
diff --git a/test/unit/rules/resources/iam/test_iam_policy.py b/test/unit/rules/resources/iam/test_iam_policy.py
--- a/test/unit/rules/resources/iam/test_iam_policy.py
+++ b/test/unit/rules/resources/iam/test_iam_policy.py
@@ -24,7 +24,7 @@ def test_file_positive(self):
 
     def test_file_negative(self):
         """Test failure"""
-        self.helper_file_negative('test/fixtures/templates/bad/properties_iam_policy.yaml', 10)
+        self.helper_file_negative('test/fixtures/templates/bad/resources/iam/iam_policy.yaml', 12)
 
     def test_file_resource_negative(self):
         """Test failure"""