Repository: getmoto/moto
Pull request #8933 (2025-05-27 14:15:59)

Allow duplicated Sids in ECR policies
## Motivation
A recent customer request in LocalStack hinted towards validation errors of an ECR policy.
After some investigation, it turns out that the ECR repository policy had duplicated Sids in different statements.
While this is discouraged, it works with ECR on AWS, so it should work with moto as well.

## Changes
* Remove requirement for sid uniqueness on ECR repository policies
* Add test to validate the special cases of ECR policy validation

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8933?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 92.86%. Comparing base [(`8d0bc7a`)](https://app.codecov.io/gh/getmoto/moto/commit/8d0bc7a20bcd196a4ba5593acc835efbf42649b7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`8b0b01d`)](https://app.codecov.io/gh/getmoto/moto/commit/8b0b01dab57ec40e082253acea088250de2036bc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #8933      +/-   ##
==========================================
- Coverage   92.86%   92.86%   -0.01%     
==========================================
  Files        1287     1287              
  Lines      112378   112379       +1     
==========================================
- Hits       104357   104356       -1     
- Misses       8021     8023       +2     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/8933/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/8933/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `28.58% <0.00%> (-0.01%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/8933/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `92.83% <100.00%> (-0.01%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/8933?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>

Patch:
diff --git a/moto/ecr/models.py b/moto/ecr/models.py
index e0fbe9402..77907c50e 100644
--- a/moto/ecr/models.py
+++ b/moto/ecr/models.py
@@ -816,6 +816,8 @@ class ECRBackend(BaseBackend):
             iam_policy_document_validator._validate_resource_exist = lambda: None  # type: ignore
             # the repository policy can have the old version 2008-10-17
             iam_policy_document_validator._validate_version = lambda: None  # type: ignore
+            # ECR does not have uniqueness requirements on Sids
+            iam_policy_document_validator._validate_sid_uniqueness = lambda: None  # type: ignore
             iam_policy_document_validator.validate()
         except MalformedPolicyDocument:
             raise InvalidParameterException(


Test patch:
diff --git a/tests/test_ecr/test_ecr_boto3.py b/tests/test_ecr/test_ecr_boto3.py
index 373d069ac..f4ed65c2d 100644
--- a/tests/test_ecr/test_ecr_boto3.py
+++ b/tests/test_ecr/test_ecr_boto3.py
@@ -1797,6 +1797,50 @@ def test_set_repository_policy():
     assert json.loads(response["policyText"]) == policy
 
 
+@mock_aws
+def test_set_repository_policy_validation():
+    """Test if ECR specific behavior for policy validation works (like allowing duplicated Sids)"""
+    client = boto3.client("ecr", region_name=ECR_REGION)
+    client.create_repository(repositoryName=ECR_REPO)
+
+    # test duplicated Sids (allowed for ECR) and missing resource
+    policy = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Sid": "duplicate",
+                "Effect": "Allow",
+                "Principal": {"AWS": f"arn:aws:iam::{ACCOUNT_ID}:root"},
+                "Action": ["ecr:DescribeImages"],
+            },
+            {
+                "Sid": "duplicate",
+                "Effect": "Allow",
+                "Principal": {"AWS": f"arn:aws:iam::{ACCOUNT_ID}:root"},
+                "Action": ["ecr:PutImage"],
+            },
+        ],
+    }
+
+    # when
+    client.set_repository_policy(repositoryName=ECR_REPO, policyText=json.dumps(policy))
+
+    # test older policy version (allowed for ECR)
+    policy = {
+        "Version": "2008-10-17",
+        "Statement": [
+            {
+                "Sid": "root",
+                "Effect": "Allow",
+                "Principal": {"AWS": f"arn:aws:iam::{ACCOUNT_ID}:root"},
+                "Action": ["ecr:DescribeImages"],
+            },
+        ],
+    }
+
+    client.set_repository_policy(repositoryName=ECR_REPO, policyText=json.dumps(policy))
+
+
 @mock_aws
 def test_set_repository_policy_error_not_exists():
     # given