Repository: getmoto/moto
Pull request #7731 (2024-05-30 08:18:59)

S3: Fix recursion for buckets in different partition
When a user creates a bucket in `cn-north-1`, but tries to retrieve a bucket via `us-east-1`, it currently throws a recursion error:
```
File "/moto/s3/models.py", line 1856, in get_bucket
    return s3_backends[account_id][self.partition].get_bucket(bucket_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/moto/s3/models.py", line 1856, in get_bucket
    return s3_backends[account_id][self.partition].get_bucket(bucket_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/moto/s3/models.py", line 1856, in get_bucket
    return s3_backends[account_id][self.partition].get_bucket(bucket_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  [Previous line repeated 968 more times]
```

We use the `bucket_accounts` to keep track of which bucket is in which account, but it didn't keep track of which partition the bucket is in.

Which meant it got into a loop:
 - get a bucket in `us-east-1`
 - bucket exists (in `aws-cn`) - call backend[account][self.partition == aws]
 - get a bucket in `us-east-1`
 - bucket exists (in `aws-cn`) - call backend[account][self.partition == aws]
 - .. etc

With this PR we keep track of the partition, and we call the backend for the correct partition:
 - get a bucket in `us-east-1`
 - bucket exists (in `aws-cn`) - call backend[account][aws-cn]

In the future we might add validation to stop cross-partition access, but for now we'll just allow it, to not break any workflows.


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/7731?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 94.33%. Comparing base [(`d3dbf15`)](https://app.codecov.io/gh/getmoto/moto/commit/d3dbf15a51ae77df513a6562366f27f37a4c2665?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`3915ec1`)](https://app.codecov.io/gh/getmoto/moto/commit/3915ec111139c8da42172117397dbcc271e11dca?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    #7731   +/-   ##
=======================================
  Coverage   94.33%   94.33%           
=======================================
  Files        1092     1092           
  Lines       93104    93104           
=======================================
  Hits        87834    87834           
  Misses       5270     5270           
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/7731/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/7731/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `29.54% <50.00%> (+<0.01%)` | :arrow_up: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/7731/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.30% <100.00%> (ø)` | |

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/7731?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).


Patch:
diff --git a/moto/s3/models.py b/moto/s3/models.py
index b18b6def8..0256da5f8 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -1062,7 +1062,7 @@ class FakeBucket(CloudFormationModel):
         self.default_lock_days: Optional[int] = 0
         self.default_lock_years: Optional[int] = 0
         self.ownership_rule: Optional[Dict[str, Any]] = None
-        s3_backends.bucket_accounts[name] = account_id
+        s3_backends.bucket_accounts[name] = (self.partition, account_id)
 
     @property
     def location(self) -> str:
@@ -1852,8 +1852,8 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
         if bucket_name in s3_backends.bucket_accounts:
             if not s3_allow_crossdomain_access():
                 raise AccessDeniedByLock
-            account_id = s3_backends.bucket_accounts[bucket_name]
-            return s3_backends[account_id][self.partition].get_bucket(bucket_name)
+            (partition, account_id) = s3_backends.bucket_accounts[bucket_name]
+            return s3_backends[account_id][partition].get_bucket(bucket_name)
 
         raise MissingBucket(bucket=bucket_name)
 
@@ -3028,9 +3028,9 @@ class S3BackendDict(BackendDict[S3Backend]):
     ):
         super().__init__(backend, service_name, use_boto3_regions, additional_regions)
 
-        # Maps bucket names to account IDs. This is used to locate the exact S3Backend
+        # Maps bucket names to (partition, account IDs). This is used to locate the exact S3Backend
         # holding the bucket and to maintain the common bucket namespace.
-        self.bucket_accounts: Dict[str, str] = {}
+        self.bucket_accounts: Dict[str, Tuple[str, str]] = {}
 
 
 s3_backends = S3BackendDict(


Test patch:
diff --git a/tests/test_ec2/test_flow_logs.py b/tests/test_ec2/test_flow_logs.py
index 4eca93221..071b9e3dd 100644
--- a/tests/test_ec2/test_flow_logs.py
+++ b/tests/test_ec2/test_flow_logs.py
@@ -9,16 +9,19 @@ from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 
 
 @mock_aws
-def test_create_flow_logs_s3():
-    s3 = boto3.resource("s3", region_name="us-west-1")
-    client = boto3.client("ec2", region_name="us-west-1")
+@pytest.mark.parametrize(
+    "region,partition", [("us-west-2", "aws"), ("cn-north-1", "aws-cn")]
+)
+def test_create_flow_logs_s3(region, partition):
+    s3 = boto3.resource("s3", region_name=region)
+    client = boto3.client("ec2", region_name=region)
 
     vpc = client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
 
     bucket_name = str(uuid4())
     bucket = s3.create_bucket(
         Bucket=bucket_name,
-        CreateBucketConfiguration={"LocationConstraint": "us-west-1"},
+        CreateBucketConfiguration={"LocationConstraint": region},
     )
 
     with pytest.raises(ClientError) as ex:
@@ -114,6 +117,32 @@ def test_create_multiple_flow_logs_s3():
     assert flow_log_1["LogDestination"] != flow_log_2["LogDestination"]
 
 
+@mock_aws
+def test_create_flow_logs_s3__bucket_in_different_partition():
+    s3 = boto3.resource("s3", region_name="cn-north-1")
+    client = boto3.client("ec2", region_name="us-west-1")
+
+    vpc = client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
+
+    bucket_name = str(uuid4())
+    bucket = s3.create_bucket(
+        Bucket=bucket_name,
+        CreateBucketConfiguration={"LocationConstraint": "cn-north-1"},
+    )
+
+    response = client.create_flow_logs(
+        ResourceType="VPC",
+        ResourceIds=[vpc["VpcId"]],
+        TrafficType="ALL",
+        LogDestinationType="s3",
+        LogDestination="arn:aws:s3:::" + bucket.name,
+    )["FlowLogIds"]
+    assert len(response) == 1
+
+    flow_logs = client.describe_flow_logs(FlowLogIds=[response[0]])["FlowLogs"]
+    assert len(flow_logs) == 1
+
+
 @mock_aws
 def test_create_flow_logs_cloud_watch():
     client = boto3.client("ec2", region_name="us-west-1")