diff --git a/CHANGES.rst b/CHANGES.rst index f170b1ad..ae2c6099 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,9 @@ Unreleased * Fixed suspend/resume getting stuck on clusters without backups configured. +* Fixed the ``grand-central-cors`` when ``spec.cluster.settings.http.cors.allow-origin`` + is not set (which might be the case on older clusters) + 2.62.0 (2026-07-15) ------------------- diff --git a/crate/operator/grand_central.py b/crate/operator/grand_central.py index fea2fed0..4f37358d 100644 --- a/crate/operator/grand_central.py +++ b/crate/operator/grand_central.py @@ -555,8 +555,12 @@ def get_grand_central_middleware_cors( Build the ``grand-central-cors`` Traefik Middleware manifest. The CrateDB setting accepts a comma-separated list of origins; these are - split into individual entries. Falls back to ``["*"]`` when the setting - is absent. + split into individual entries. When the setting is absent, all origins + are allowed via ``accessControlAllowOriginListRegex`` rather than a + literal ``"*"`` in ``accessControlAllowOriginList``: Traefik's headers + middleware returns the literal ``"*"`` for the latter, which browsers + reject on credentialed requests, whereas a regex match reflects the + actual request Origin. :param owner_references: Owner references to set on the resource. :param name: The CrateDB custom resource name defining the CrateDB cluster. @@ -564,28 +568,30 @@ def get_grand_central_middleware_cors( :param spec: The ``spec`` section of the CrateDB custom resource, used to read ``cluster.settings.http.cors.allow-origin``. """ - raw_origin = ( - spec["cluster"].get("settings", {}).get("http.cors.allow-origin") or "*" - ) - origin_list = [o.strip() for o in raw_origin.split(",") if o.strip()] + raw_origin = spec["cluster"].get("settings", {}).get("http.cors.allow-origin") + + headers: Dict[str, Any] = { + "accessControlAllowCredentials": True, + "accessControlAllowMethods": [ + "GET", + "POST", + "PUT", + "PATCH", + "OPTIONS", + "DELETE", + ], + "accessControlAllowHeaders": ["Content-Type", "Authorization"], + "accessControlMaxAge": 7200, + } + if raw_origin: + headers["accessControlAllowOriginList"] = [ + o.strip() for o in raw_origin.split(",") if o.strip() + ] + else: + headers["accessControlAllowOriginListRegex"] = [".*"] body = _build_middleware_base(name, _MIDDLEWARE_CORS, labels, owner_references) - body["spec"] = { - "headers": { - "accessControlAllowOriginList": origin_list, - "accessControlAllowCredentials": True, - "accessControlAllowMethods": [ - "GET", - "POST", - "PUT", - "PATCH", - "OPTIONS", - "DELETE", - ], - "accessControlAllowHeaders": ["Content-Type", "Authorization"], - "accessControlMaxAge": 7200, - } - } + body["spec"] = {"headers": headers} return body diff --git a/tests/test_create_grand_central.py b/tests/test_create_grand_central.py index b019f5bc..b78b72a7 100644 --- a/tests/test_create_grand_central.py +++ b/tests/test_create_grand_central.py @@ -383,9 +383,15 @@ async def test_create_grand_central_traefik( ] assert cors_headers["accessControlAllowCredentials"] is True assert cors_headers["accessControlMaxAge"] == 7200 - assert cors_headers["accessControlAllowOriginList"] == [ - "*" - ], "Expected default origin list ['*'] when no cors setting is configured" + assert cors_headers.get("accessControlAllowOriginList") is None, ( + "Expected no literal accessControlAllowOriginList when no cors setting " + "is configured, since Traefik returns the literal '*' for it, which " + "browsers reject on credentialed requests" + ) + assert cors_headers["accessControlAllowOriginListRegex"] == [".*"], ( + "Expected a regex match-all when no cors setting is configured, so " + "Traefik reflects the request Origin" + ) # No nginx Ingress should be created await assert_wait_for(