Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
-------------------

Expand Down
50 changes: 28 additions & 22 deletions crate/operator/grand_central.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,37 +555,43 @@ 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.
:param labels: Kubernetes labels to apply to the resource.
: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


Expand Down
12 changes: 9 additions & 3 deletions tests/test_create_grand_central.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading