Repository: aws-cloudformation/cfn-lint

Issue:
E2507 - Fn::If not allowed in Policy Statement list
*cfn-lint version: 0.4.2*

In our environments, we have a standardized deployment which has grown organically over time. This has meant that we have used `Fn::If` and `Condition` all over the place. 

On example is conditionally adding a policy statement to a Managed Policy object. 

This is valid cloudformation today and has been deployed across a number of accounts, but the cfn-lint throws an error saying it's not valid.

**Template**

```
Parameters:
  SomeOptionalParameter:
    Type: String
    Description: Name of some optional bucket
    Default: ""

...
Conditions:
  SomeCondition: !Not [!Equals [!Ref SomeOptionalParameter, ""]]
...
Resources:
  SomeManagedPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      Description: "Example Managed Policy"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "s3:ListBucket"
              - "s3:GetObject"
            Resource: '*'
          - Fn::If:
            - SomeCondition
            - Effect: "Allow"
              Action:
                - "s3:PutObject"
              Resource:
                - SomeOptionalParameter
            - Ref: AWS::NoValue

```

**Output**
```
E2507 IAM Policy statement missing Effect
/home/user/repos/example.yaml:468:9

E2507 IAM Policy statement missing Action or NotAction
/home/user/repos/example.yaml:468:9

E2507 IAM Policy statement missing Resource or NotResource
/home/user/repos/example.yaml:468:9

E2507 IAM Policy statement key Fn::If isn't valid
/home/user/repos/example.yaml:559:13
```

Another interesting part is, the line numbers in the first 3 is of the `Statement` line, while the last one is the actual offending entry.

We'd really love to fail our deployments on cfn-lint, but this one is blocking us wrongly. :)


Discussion:
let me take a look.  Thanks for reporting the issue.

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
@@ -27,7 +27,7 @@ class Policy(CloudFormationLintRule):
     source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html'
     tags = ['properties', 'iam']
 
-    def _check_policy_document(self, branch, policy):
+    def _check_policy_document(self, cfn, branch, policy):
         """Check policy document"""
         matches = list()
 
@@ -50,11 +50,11 @@ def _check_policy_document(self, branch, policy):
                     RuleMatch(branch[:] + [parent_key], message))
             if parent_key == 'Statement':
                 if isinstance(parent_value, (list)):
-                    for index, statement in enumerate(parent_value):
+                    values = cfn.get_values(policy, 'Statement', branch[:])
+                    for value in values:
                         matches.extend(
                             self._check_policy_statement(
-                                branch[:] + [parent_key, index],
-                                statement
+                                value['Path'], value['Value']
                             )
                         )
                 else:
@@ -112,13 +112,13 @@ def _check_policy_statement(self, branch, statement):
 
         return(matches)
 
-    def _check_policy(self, branch, policy):
+    def _check_policy(self, cfn, branch, policy):
         """Checks a policy"""
         matches = list()
         policy_document = policy.get('PolicyDocument', {})
         matches.extend(
             self._check_policy_document(
-                branch + ['PolicyDocument'], policy_document))
+                cfn, branch + ['PolicyDocument'], policy_document))
 
         return matches
 
@@ -140,16 +140,17 @@ def match(self, cfn):
             tree = ['Resources', resource_name, 'Properties']
             properties = resource_values.get('Properties', {})
             if properties:
-                policy_document = properties.get('PolicyDocument', None)
-                if policy_document:
-                    matches.extend(
-                        self._check_policy_document(
-                            tree[:] + ['PolicyDocument'], policy_document))
-                policy_documents = properties.get('Policies', [])
-                for index, policy_document in enumerate(policy_documents):
-                    matches.extend(
-                        self._check_policy(
-                            tree[:] + ['Policies', index],
-                            policy_document))
+                if properties.get('PolicyDocument'):
+                    values = cfn.get_values(properties, 'PolicyDocument', tree)
+                    for value in values:
+                        matches.extend(
+                            self._check_policy_document(
+                                cfn, value['Path'], value['Value']))
+                if properties.get('Policies'):
+                    values = cfn.get_values(properties, 'Policies', tree)
+                    for value in values:
+                        matches.extend(
+                            self._check_policy(
+                                cfn, value['Path'], value['Value']))
 
         return matches


Test patch:
diff --git a/test/fixtures/templates/good/resources/iam/policy.yaml b/test/fixtures/templates/good/resources/iam/policy.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/good/resources/iam/policy.yaml
@@ -0,0 +1,30 @@
+---
+AWSTemplateFormatVersion: "2010-09-09"
+Parameters:
+  SomeOptionalParameter:
+    Type: String
+    Description: Name of some optional bucket
+    Default: ""
+Conditions:
+  SomeCondition: !Not [!Equals [!Ref SomeOptionalParameter, ""]]
+Resources:
+  SomeManagedPolicy:
+    Type: "AWS::IAM::ManagedPolicy"
+    Properties:
+      Description: "Example Managed Policy"
+      PolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Effect: "Allow"
+            Action:
+              - "s3:ListBucket"
+              - "s3:GetObject"
+            Resource: '*'
+          - Fn::If:
+            - SomeCondition
+            - Effect: "Allow"
+              Action:
+                - "s3:PutObject"
+              Resource:
+                - SomeOptionalParameter
+            - Ref: AWS::NoValue
diff --git a/test/rules/resources/iam/test_iam_policy.py b/test/rules/resources/iam/test_iam_policy.py
--- a/test/rules/resources/iam/test_iam_policy.py
+++ b/test/rules/resources/iam/test_iam_policy.py
@@ -24,6 +24,9 @@ def setUp(self):
         """Setup"""
         super(TestPropertyIamPolicies, self).setUp()
         self.collection.register(Policy())
+        self.success_templates = [
+            'fixtures/templates/good/resources/iam/policy.yaml'
+        ]
 
     def test_file_positive(self):
         """Test Positive"""