8000 rework s3 virtual host addressing · localstack/localstack@b36e946 · GitHub
[go: up one dir, main page]

Skip to content

Commit b36e946

Browse files
committed
rework s3 virtual host addressing
1 parent b4a5fc5 commit b36e946

File tree

2 files changed

+35
-11
lines changed

2 files changed

+35
-11
lines changed

localstack/services/s3/virtual_host.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
from urllib.parse import urlsplit, urlunsplit
44

5-
from localstack.config import LEGACY_S3_PROVIDER
5+
from localstack import config
66
from localstack.constants import LOCALHOST_HOSTNAME
77
from localstack.http import Request, Response
88
from localstack.http.proxy import Proxy
@@ -13,13 +13,15 @@
1313

1414
LOG = logging.getLogger(__name__)
1515

16-
# virtual-host style: https://{bucket-name}.s3.{region}.localhost.localstack.cloud.com/{key-name}
17-
VHOST_REGEX_PATTERN = f"<regex('.*'):bucket>.s3.<regex('({AWS_REGION_REGEX}\\.)?'):region>{LOCALHOST_HOSTNAME}<regex('(?::\\d+)?'):port>"
16+
# virtual-host style: https://{bucket-name}.s3.{region?}.{domain}:{port?}/{key-name}
17+
# ex: https://{bucket-name}.s3.{region}.localhost.localstack.cloud.com:4566/{key-name}
18+
# ex: https://{bucket-name}.s3.{region}.amazonaws.com/{key-name}
19+
VHOS 10000 T_REGEX_PATTERN = f"<regex('.*'):bucket>.s3.<regex('({AWS_REGION_REGEX}\\.)?'):region><regex('.*'):domain><regex('(?::\\d+)?'):port>"
1820

1921
# path addressed request with the region in the hostname
2022
# https://s3.{region}.localhost.localstack.cloud.com/{bucket-name}/{key-name}
2123
PATH_WITH_REGION_PATTERN = (
22-
f"s3.<regex('({AWS_REGION_REGEX}\\.)'):region>{LOCALHOST_HOSTNAME}<regex('(?::\\d+)?'):port>"
24+
f"s3.<regex('({AWS_REGION_REGEX}\\.)'):region><regex('.*'):domain><regex('(?::\\d+)?'):port>"
2325
)
2426

2527

@@ -31,7 +33,7 @@ class S3VirtualHostProxyHandler:
3133

3234
def __call__(self, request: Request, **kwargs) -> Response:
3335
# TODO region pattern currently not working -> removing it from url
34-
rewritten_url = self._rewrite_url(request.url, kwargs.get("bucket"), kwargs.get("region"))
36+
rewritten_url = self._rewrite_url(url=request.url, **kwargs)
3537

3638
LOG.debug(f"Rewritten original host url: {request.url} to path-style url: {rewritten_url}")
3739

@@ -53,16 +55,18 @@ def __call__(self, request: Request, **kwargs) -> Response:
5355
return forwarded
5456

5557
@staticmethod
56-
def _rewrite_url(url: str, bucket: str, region: str) -> str:
58+
def _rewrite_url(url: str, domain: str, bucket: str, region: str, port: str, **kwargs) -> str:
5759
"""
5860
Rewrites the url so that it can be forwarded to moto. Used for vhost-style and for any url that contains the region.
5961
6062
For vhost style: removes the bucket-name from the host-name and adds it as path
61-
E.g. http://my-bucket.s3.localhost.localstack.cloud:4566 -> http://s3.localhost.localstack.cloud:4566/my-bucket
63+
E.g. https://bucket.s3.localhost.localstack.cloud:4566 -> https://s3.localhost.localstack.cloud:4566/bucket
64+
E.g. https://bucket.s3.amazonaws.com -> https://s3.localhost.localstack.cloud:4566/bucket
6265
6366
If the region is contained in the host-name we remove it (for now) as moto cannot handle the region correctly
6467
6568
:param url: the original url
69+
:param domain: the domain name
6670
:param bucket: the bucket name
6771
:param region: the region name
6872
:return: re-written url as string
@@ -79,10 +83,17 @@ def _rewrite_url(url: str, bucket: str, region: str) -> str:
7983
if region:
8084
netloc = netloc.replace(f"{region}", "")
8185

86+
# the user can specify whatever domain & port he wants in the Host header
87+
# we need to make sure we're redirecting the request to our edge URL, possibly s3.localhost.localstack.cloud
88+
host = f"{domain}:{port}" if port else domain
89+
edge_host = f"{LOCALHOST_HOSTNAME}:{config.get_edge_port_http()}"
90+
if host != edge_host:
91+
netloc = netloc.replace(host, edge_host)
92+
8293
return urlunsplit((splitted.scheme, netloc, path, splitted.query, splitted.fragment))
8394

8495

85-
@hooks.on_infra_ready(should_load=not LEGACY_S3_PROVIDER)
96+
@hooks.on_infra_ready(should_load=not config.LEGACY_S3_PROVIDER)
8697
def register_virtual_host_routes():
8798
"""
8899
Registers the S3 virtual host handler into the edge router.

tests/integration/s3/test_s3.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6034,17 +6034,30 @@ def _get_static_hosting_transformers(snapshot):
60346034

60356035

60366036
class TestS3Routing:
6037-
def test_access_favicon_via_aws_endpoints(self, s3_bucket, s3_client):
6037+
@pytest.mark.only_localstack
6038+
@pytest.mark.parametrize(
6039+
"domain, use_virtual_address",
6040+
[
6041+
("s3.amazonaws.com", False),
6042+
("s3.amazonaws.com", True),
6043+
("s3.us-west-2.amazonaws.com", False),
6044+
("s3.us-west-2.amazonaws.com", True),
6045+
],
6046+
)
6047+
def test_access_favicon_via_aws_endpoints(
6048+
self, s3_bucket, s3_client, domain, use_virtual_address
6049+
):
60386050
"""Assert that /favicon.ico objects can be created/accessed/deleted using amazonaws host headers"""
60396051

60406052
s3_key = "favicon.ico"
60416053
content = b"test 123"
60426054
s3_client.put_object(Bucket=s3_bucket, Key=s3_key, Body=content)
60436055
s3_client.head_object(Bucket=s3_bucket, Key=s3_key)
60446056

6045-
url = f"{config.get_edge_url()}/{s3_key}"
6057+
path = s3_key if use_virtual_address else f"{s3_bucket}/{s3_key}"
6058+
url = f"{config.get_edge_url()}/{path}"
60466059
headers = aws_stack.mock_aws_request_headers("s3")
6047-
headers["host"] = f"{s3_bucket}.s3.amazonaws.com"
6060+
headers["host"] = f"{s3_bucket}.{domain}" if use_virtual_address else domain
60486061

60496062
# get object via *.amazonaws.com host header
60506063
result = requests.get(url, headers=headers)

0 commit comments

Comments
 (0)
0