Repository: aws-cloudformation/cfn-lint

Issue:
Feature Request: `AWS::IAM::Role` - recognize if 2 `Sid`s in an InlinePolicy are the same
### Is this feature request related to a new rule or cfn-lint capabilities?

_No response_

### Describe the feature you'd like to request

Currently cfn-lint doesn't recognize in an AWS::IAM::Role Inline Policy if 2 SIDs are the same. Example:

```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Test

Resources:
  ScheduleRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: scheduler.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: "InlinePolicy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Sid: "KMSAccess" # <----------
                Effect: Allow
                Action:
                  - kms:Decrypt
                  - kms:GenerateDataKey
                  - kms:Encrypt
                Resource: "*"
              - Sid: "KMSAccess" # <----------
                Effect: Allow
                Action:
                  - kms:Decrypt
                  - kms:GenerateDataKey
                  - kms:Encrypt
                Resource: "*"
```

This will result in the following CloudFormation exception:

```
Resource handler returned message:
"Statement IDs (SID) in a single policy must be unique.
(Service: Iam, Status Code: 400, Request ID: c0...)"
(RequestToken: b7..., HandlerErrorCode: InvalidRequest)
```

### Describe the solution you'd like

It would be great if cfn-lint would generate an error for this.

### Additional context

_No response_

### Is this something that you'd be interested in working on?

- [ ] 👋 I may be able to implement this feature request

### Would this feature include a breaking change?

- [ ] ⚠️ This feature might incur a breaking change

Discussion:
@r-heimann: @kddejong Thank you very much!

Solution patch:
diff --git a/src/cfnlint/data/schemas/other/iam/policy_identity.json b/src/cfnlint/data/schemas/other/iam/policy_identity.json
index b305ea62f..e3a2bc374 100644
--- a/src/cfnlint/data/schemas/other/iam/policy_identity.json
+++ b/src/cfnlint/data/schemas/other/iam/policy_identity.json
@@ -61,6 +61,9 @@
    "type": [
     "object",
     "array"
+   ],
+   "uniqueKeys": [
+    "Sid"
    ]
   },
   "Version": {
diff --git a/src/cfnlint/data/schemas/other/iam/policy_resource.json b/src/cfnlint/data/schemas/other/iam/policy_resource.json
index 92581514d..85755185c 100644
--- a/src/cfnlint/data/schemas/other/iam/policy_resource.json
+++ b/src/cfnlint/data/schemas/other/iam/policy_resource.json
@@ -73,6 +73,9 @@
    "type": [
     "object",
     "array"
+   ],
+   "uniqueKeys": [
+    "Sid"
    ]
   },
   "Version": {


Test patch:
diff --git a/test/unit/rules/resources/iam/test_identity_policy.py b/test/unit/rules/resources/iam/test_identity_policy.py
index ced3940fc..592beea8c 100644
--- a/test/unit/rules/resources/iam/test_identity_policy.py
+++ b/test/unit/rules/resources/iam/test_identity_policy.py
@@ -208,3 +208,33 @@ class TestIdentityPolicies(TestCase):
         self.assertListEqual(
             list(errs[0].path), ["Statement", 0, "Condition", "iam:PassedToService"]
         )
+
+    def test_duplicate_sid(self):
+        validator = CfnTemplateValidator()
+
+        policy = {
+            "Version": "2012-10-17",
+            "Statement": [
+                {
+                    "Sid": "All",
+                    "Effect": "Allow",
+                    "Action": "*",
+                    "Resource": "*",
+                },
+                {
+                    "Sid": "All",
+                    "Effect": "Allow",
+                    "Action": "*",
+                    "Resource": "*",
+                },
+            ],
+        }
+
+        errs = list(
+            self.rule.validate(
+                validator=validator, policy=policy, schema={}, policy_type=None
+            )
+        )
+        self.assertEqual(len(errs), 1, errs)
+        self.assertEqual(errs[0].message, "array items are not unique for keys ['Sid']")
+        self.assertListEqual(list(errs[0].path), ["Statement"])