From 040cb1adec85efd69501886cd9973f98b902a60c Mon Sep 17 00:00:00 2001 From: MahdiPresario001 Date: Tue, 23 Jun 2026 12:41:32 +0200 Subject: [PATCH] s3 bucket fix --- validator/src/gm_validator/main.py | 8 +++++++- validator/tests/test_s3_anonymous.py | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/validator/src/gm_validator/main.py b/validator/src/gm_validator/main.py index 27c79c8..012d74e 100644 --- a/validator/src/gm_validator/main.py +++ b/validator/src/gm_validator/main.py @@ -145,7 +145,12 @@ def _build_s3_client(config: ValidatorConfig) -> Any: When ``config.s3_anonymous`` is set the client also signs no requests (``botocore.UNSIGNED``) — required for OVH public-read buckets or any - bucket reachable without IAM credentials. + bucket reachable without IAM credentials — and switches to + ``virtual``-hosted addressing (``bucket.endpoint/key``). OVH serves + public-read objects only via the virtual-hosted host; an *unsigned* + path-style request (``endpoint/bucket/key``) is rejected with HTTP 400 + regardless of the object ACL. Signed (keyed) reads work either way, so + only the anonymous path needs the switch. boto3-stubs types ``boto3.client()`` as an overload set keyed on the Literal service name; passing the remaining args via ``**kwargs`` @@ -157,6 +162,7 @@ def _build_s3_client(config: ValidatorConfig) -> Any: request_checksum_calculation="when_required", response_checksum_validation="when_required", signature_version=botocore.UNSIGNED if config.s3_anonymous else None, + s3={"addressing_style": "virtual"} if config.s3_anonymous else {}, ) if config.s3_endpoint_url: return boto3.client( diff --git a/validator/tests/test_s3_anonymous.py b/validator/tests/test_s3_anonymous.py index 99ae48b..7b345f0 100644 --- a/validator/tests/test_s3_anonymous.py +++ b/validator/tests/test_s3_anonymous.py @@ -197,3 +197,27 @@ def test_client_pins_checksums_to_when_required(anonymous: bool, endpoint_url: s assert captured.config is not None, "expected a 'config' kwarg on boto3.client" assert _checksum_calculation(captured.config) == "when_required" assert _checksum_validation(captured.config) == "when_required" + + +def _addressing_style(config: Config) -> object: + """Read Config.s3['addressing_style'] (the OVH virtual-hosted switch).""" + s3 = getattr(config, "s3", None) or {} + return s3.get("addressing_style") + + +def test_anonymous_client_uses_virtual_addressing() -> None: + """Anonymous mode must use virtual-hosted addressing. OVH serves + public-read objects only at ``bucket.endpoint/key``; an unsigned + path-style request (``endpoint/bucket/key``) is rejected with HTTP 400 + regardless of the object ACL.""" + captured = _capture_client_call(_make_config(s3_anonymous=True)) + assert captured.config is not None + assert _addressing_style(captured.config) == "virtual" + + +def test_signed_client_keeps_default_addressing() -> None: + """Signed mode keeps default (path) addressing — the keyed gm-bucket + reads work path-style and must not be perturbed.""" + captured = _capture_client_call(_make_config(s3_anonymous=False)) + assert captured.config is not None + assert _addressing_style(captured.config) is None