Skip to content

Commit 8d48f80

Browse files
blarghmateyclaude
andcommitted
fix(apisix): send OIDC hosts to a canonical https origin before openid-connect (#5525)
* fix(apisix): send OIDC hosts to a canonical https origin before openid-connect The shared-plugin defaults already carry APISIX's `redirect` plugin with http_to_https, but `redirect` has a lower priority than openid-connect (2599), and APISIX dispatches a phase in descending priority. On every OIDC-protected route the upgrade therefore never runs: openid-connect has already answered. That matters because APISIX derives only a relative redirect_uri and lua-resty-openidc 1.8.0 -- the version APISIX 3.17 pins -- makes it absolute from ngx.var.scheme and ngx.var.http_host. Both are raw connection values, so a plain-HTTP request sends Keycloak an http:// redirect_uri and a request carrying `Host: <host>:443` sends an authority with the port still on it. Keycloak registers bare-host https URIs only and rejects both with error="invalid_redirect_uri", killing the login at the authorization endpoint before any callback exists for the recovery function to rescue. Reproduced against production on both api.learn and nb.learn, so this is not specific to the host the Keycloak log happens to show most. Worse than the failed logins: with the upgrade shadowed, APISIX answers plain -HTTP requests to these hosts with an OIDC session cookie over cleartext and without the Secure attribute. Redirecting is what fixes this rather than setting X-Forwarded-Proto and X-Forwarded-Host. lua-resty-openidc does prefer those headers over the ngx vars, but on this deployment none of Forwarded / X-Forwarded-Proto / X-Forwarded-Host reaches it -- sending each against production leaves the redirect_uri unchanged -- so pinning them would be a no-op that reads like a fix. APISIX keys a plugin config by plugin name, so a route can carry exactly one serverless-pre-function, and both this and the error-callback recovery have to run ahead of openid-connect. They are consequently one plugin with two functions rather than two plugins, which is why the builder is renamed. serverless/init.lua runs `functions` in array order and stops at the first returning a code, so the origin is normalised before recovery decides whether to redirect back into a login flow -- otherwise recovery would target an http:// origin and fail again. Leaving port 80 answering with a redirect is only safe because every ACME ClusterIssuer on the cluster solves via dns01/Route53; an issuer switched to http-01 would need its challenge path carved out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G9yBC9UqoVwiPtxqLNgUD9 * fix(apisix): validate the redirect status, correct the 308 rationale Copilot review on #5525, both points verified against source before acting. ngx.redirect accepts only 301/302/303/307/308 and raises a Lua error on anything else (ngx_http_lua_control.c:209-219). The block this setting travels in is not part of serverless-pre-function's schema, so APISIX will not reject a bad value either -- it would first surface as a 500 on live traffic. The `Literal` annotation alone does not prevent that: this repo's mypy hook runs without the project installed, so a cross-module call resolves to Any and the bad literal passes. Validating in the builder moves the failure to `pulumi preview`, with tests over both the accepted and rejected sets. The 308 rationale was also wrong. APISIX's `redirect` plugin is method-dependent -- 301 for GET/HEAD, 308 for everything else (redirect.lua:208-215) -- so it would not have downgraded a POST, and the previous comment claimed a behavioural fix where there is only a simplification to one uniform status. Corrected in the Lua comment and the docstring. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G9yBC9UqoVwiPtxqLNgUD9 --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 423f915 commit 8d48f80

14 files changed

Lines changed: 652 additions & 60 deletions

File tree

src/ol_infrastructure/applications/jupyterhub/deployment.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
OLApisixRouteConfig,
2424
OLApisixSharedPlugins,
2525
OLApisixSharedPluginsConfig,
26-
oidc_error_callback_recovery_plugin,
26+
oidc_gateway_pre_function_plugin,
2727
stale_session_cookie_cleanup_plugin,
2828
)
2929
from ol_infrastructure.components.services.cert_manager import (
@@ -187,11 +187,17 @@ def provision_jupyterhub_deployment( # noqa: PLR0913
187187
# host belongs to mit-learn, which clears it from its own
188188
# routes. Safe to delete once the old cookies have aged out.
189189
stale_session_cookie_cleanup_plugin(),
190-
# Same expired-authentication-session callbacks that api.learn
191-
# and mitxonline see, at this host's much smaller volume (2/day
192-
# on nb.learn.mit.edu). Attached for consistency of behaviour
193-
# across the OIDC-protected hosts rather than for the volume.
194-
oidc_error_callback_recovery_plugin(),
190+
# Two pre-openid-connect fixes, necessarily one plugin (APISIX
191+
# allows a single serverless-pre-function per config). The
192+
# expired-authentication-session callbacks are the same ones
193+
# api.learn and mitxonline see, at this host's much smaller
194+
# volume (2/day). The canonical-origin redirect matters more
195+
# here than anywhere else: nb.learn and authoring.nb.learn are
196+
# the two hosts Keycloak actually rejects today, 61 hits over
197+
# 7 days between them, because they take direct plain-HTTP
198+
# traffic that the `redirect` default plugin never gets to
199+
# upgrade.
200+
oidc_gateway_pre_function_plugin(),
195201
],
196202
),
197203
)

src/ol_infrastructure/applications/mit_learn/__main__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
OLApisixRouteConfig,
5656
OLApisixSharedPlugins,
5757
OLApisixSharedPluginsConfig,
58-
oidc_error_callback_recovery_plugin,
58+
oidc_gateway_pre_function_plugin,
5959
stale_session_cookie_cleanup_plugin,
6060
)
6161
from ol_infrastructure.components.services.cert_manager import (
@@ -1355,8 +1355,11 @@
13551355
# openid-connect plugin serves each one a 500. Both route groups
13561356
# on this host need it, and the plugin derives its redirect target
13571357
# from the request URI, so the /login and /learn/login prefixes are
1358-
# handled from this one attachment.
1359-
oidc_error_callback_recovery_plugin(),
1358+
# handled from this one attachment. The same attachment also
1359+
# canonicalises the origin: `curl http://api.learn.mit.edu/login`
1360+
# currently sends Keycloak an http:// redirect_uri, and answers with
1361+
# an OIDC session cookie over cleartext.
1362+
oidc_gateway_pre_function_plugin(),
13601363
],
13611364
),
13621365
)

src/ol_infrastructure/applications/mitxonline/__main__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
OLApisixRouteConfig,
5252
OLApisixSharedPlugins,
5353
OLApisixSharedPluginsConfig,
54-
oidc_error_callback_recovery_plugin,
54+
oidc_gateway_pre_function_plugin,
5555
stale_session_cookie_cleanup_plugin,
5656
)
5757
from ol_infrastructure.components.services.cert_manager import (
@@ -815,8 +815,10 @@
815815
# cleanup below, this is safe to attach here rather than per route
816816
# group: it derives its redirect target from the request URI and
817817
# its guard cookie is host-only, so neither depends on which parent
818-
# domain a group's session cookie was scoped to.
819-
oidc_error_callback_recovery_plugin(),
818+
# domain a group's session cookie was scoped to. Ditto the
819+
# canonical-origin redirect it also carries, which is derived from
820+
# the request's own host.
821+
oidc_gateway_pre_function_plugin(),
820822
],
821823
),
822824
)

src/ol_infrastructure/components/services/apisix.py

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515
)
1616
from ol_infrastructure.lib.pulumi_helper import parse_stack
1717

18-
# Read once at import: the file is shipped verbatim as the serverless function
19-
# body, with configuration passed separately on the plugin config.
18+
# Read once at import: the files are shipped verbatim as serverless function
19+
# bodies, with configuration passed separately on the plugin config.
2020
OIDC_ERROR_RECOVERY_LUA = (
2121
Path(__file__)
2222
.parent.joinpath("files", "oidc_error_callback_recovery.lua")
2323
.read_text()
2424
)
25+
CANONICAL_HTTPS_REDIRECT_LUA = (
26+
Path(__file__).parent.joinpath("files", "canonical_https_redirect.lua").read_text()
27+
)
28+
29+
# The only statuses ngx.redirect accepts; anything else is a Lua error at
30+
# request time (ngx_http_lua_control.c:209-219).
31+
NGX_REDIRECT_STATUSES = (301, 302, 303, 307, 308)
2532

2633

2734
class OLApisixPluginConfig(BaseModel):
@@ -112,21 +119,60 @@ def stale_session_cookie_cleanup_plugin(
112119
)
113120

114121

115-
def oidc_error_callback_recovery_plugin(
122+
def oidc_gateway_pre_function_plugin(
116123
recoverable_errors: list[str] | None = None,
117124
guard_cookie_name: str = "apisix_oidc_recovery",
118125
guard_max_age: int = 60,
126+
*,
127+
canonical_https_redirect: bool = True,
128+
canonical_redirect_status: Literal[301, 302, 303, 307, 308] = 308,
119129
) -> OLApisixPluginConfig:
120-
"""Restart the login flow when the IdP redirects back with a recoverable error.
121-
122-
An authorization request whose Keycloak authentication session has expired
123-
-- the user left the login tab open, or followed a stale bookmark -- comes
124-
back to the callback with ``error=temporarily_unavailable`` and no ``code``.
130+
"""Everything that has to happen before openid-connect sees the request.
131+
132+
APISIX keys a plugin config by plugin name, so a route can carry exactly ONE
133+
``serverless-pre-function``. Both of the fixes below have to run ahead of
134+
openid-connect (priority 2599), and ``serverless-pre-function`` (priority
135+
10000) is the only hook that gets there, so they are necessarily one plugin
136+
rather than two. ``serverless/init.lua`` runs ``functions`` in array order
137+
and stops at the first one returning a code or body, which is exactly the
138+
sequencing wanted here: normalise the origin first, and only then look at
139+
whether this is a failed callback.
140+
141+
**Canonical origin** (``canonical_https_redirect.lua``). The shared-plugin
142+
defaults already include APISIX's ``redirect`` plugin with ``http_to_https``,
143+
but its priority is below openid-connect's, so on an OIDC route it is dead
144+
code -- openid-connect has already answered. The consequences are measured,
145+
not hypothetical. APISIX derives only a relative redirect_uri and
146+
lua-resty-openidc 1.8.0 (the version APISIX 3.17 pins) makes it absolute from
147+
``ngx.var.scheme`` and ``ngx.var.http_host``, so a plain-HTTP request sends
148+
Keycloak ``http://...`` and a request carrying ``Host: <host>:443`` sends
149+
``https://<host>:443/...``. Keycloak registers bare-host https URIs only and
150+
rejects both with ``error="invalid_redirect_uri"``; the login dies at the
151+
authorization endpoint, before any callback exists for the recovery function
152+
below to rescue. Worse than the failed logins: because the upgrade never
153+
runs, APISIX answers plain-HTTP requests with an OIDC session cookie over
154+
cleartext and without the ``Secure`` attribute.
155+
156+
Redirecting is what fixes this, not header-setting. lua-resty-openidc does
157+
prefer ``Forwarded`` / ``X-Forwarded-Proto`` / ``X-Forwarded-Host`` over those
158+
ngx vars, but on this deployment none of the three reaches it -- sending each
159+
against production leaves the redirect_uri unchanged -- so pinning them would
160+
be a no-op dressed up as a fix.
161+
162+
Note this leaves port 80 answering with a redirect rather than closing it.
163+
That is only safe because every ACME ClusterIssuer on the cluster solves via
164+
dns01/Route53; an issuer switched to http-01 would need its challenge path
165+
carved out of the redirect.
166+
167+
**Error-callback recovery** (``oidc_error_callback_recovery.lua``). An
168+
authorization request whose Keycloak authentication session has expired --
169+
the user left the login tab open, or followed a stale bookmark -- comes back
170+
to the callback with ``error=temporarily_unavailable`` and no ``code``.
125171
Keycloak's intent there is that the client start over; it even marks the
126-
event ``restart_after_timeout="true"``. ``lua-resty-openidc`` instead
127-
treats any ``error`` parameter as fatal and hands the openid-connect plugin
128-
a failure, which APISIX serves as a 21KB HTTP 500. The user sees a
129-
stack-trace page where they expected a login form, and nothing retries.
172+
event ``restart_after_timeout="true"``. ``lua-resty-openidc`` instead treats
173+
any ``error`` parameter as fatal and hands the openid-connect plugin a
174+
failure, which APISIX serves as a 21KB HTTP 500. The user sees a stack-trace
175+
page where they expected a login form, and nothing retries.
130176
131177
This is measured, not hypothetical: 614 such callbacks a day across
132178
api.learn.mit.edu, mitxonline.mit.edu and nb.learn.mit.edu, from 530
@@ -154,14 +200,15 @@ def oidc_error_callback_recovery_plugin(
154200
looping: a persistently broken IdP should surface as an error, not as an
155201
infinite redirect.
156202
157-
The Lua itself lives in ``files/oidc_error_callback_recovery.lua`` and is
158-
shipped verbatim -- nothing is interpolated into it. Tunables travel as an
159-
``oidc_error_recovery`` block on the plugin config, which the function reads
160-
off ``conf``: ``serverless/init.lua`` invokes each function as
203+
Both functions live in ``files/`` and are shipped verbatim -- nothing is
204+
interpolated into them. Tunables travel as ``oidc_error_recovery`` and
205+
``canonical_https_redirect`` blocks on the plugin config, which the functions
206+
read off ``conf``: ``serverless/init.lua`` invokes each as
161207
``func(conf, ctx)``, and its schema does not set ``additionalProperties``,
162-
so extra keys validate. Keeping it a real ``.lua`` file means it is
208+
so extra keys validate. Keeping them real ``.lua`` files means they are
163209
syntax-highlighted, reviewable, and testable under APISIX's own test-nginx
164-
harness (``t/oidc_error_callback_recovery.t``).
210+
harness (``t/oidc_error_callback_recovery.t``,
211+
``t/canonical_https_redirect.t``).
165212
166213
:param recoverable_errors: OAuth 2.0 ``error`` codes to restart the flow
167214
for. Defaults to ``temporarily_unavailable``, which is 100% of what
@@ -171,21 +218,51 @@ def oidc_error_callback_recovery_plugin(
171218
:param guard_cookie_name: Name of the loop-breaker cookie.
172219
:param guard_max_age: Seconds the guard cookie lives, bounding how often one
173220
browser can be sent back through login.
221+
:param canonical_https_redirect: Whether to send non-canonical origins to
222+
``https://<bare host>`` before openid-connect runs. ``False`` drops the
223+
function entirely, for a host that must keep answering on plain HTTP.
224+
:param canonical_redirect_status: Status for that redirect, uniform across
225+
methods. APISIX's own ``redirect`` plugin instead picks per method --
226+
301 for GET/HEAD, 308 for everything else (``redirect.lua`` 208-215) --
227+
so 308 here is a simplification rather than a behavioural fix: both
228+
preserve a POST. Restricted to the codes ``ngx.redirect`` accepts.
174229
175230
:returns: A ``serverless-pre-function`` plugin config to attach to routes.
176231
:rtype: OLApisixPluginConfig
177232
"""
233+
# Checked rather than left to the annotation: the `Literal` above documents
234+
# the contract but nothing enforces it at the call sites, since this repo's
235+
# mypy hook runs without the project installed and resolves a cross-module
236+
# import to Any. APISIX will not catch it either -- the block this travels
237+
# in is not part of serverless-pre-function's schema -- so an unchecked bad
238+
# value would first surface as a 500 on live traffic. Raising here moves
239+
# that to `pulumi preview`.
240+
if canonical_redirect_status not in NGX_REDIRECT_STATUSES:
241+
msg = (
242+
f"canonical_redirect_status must be one of {NGX_REDIRECT_STATUSES}, "
243+
f"got {canonical_redirect_status}: ngx.redirect rejects anything else."
244+
)
245+
raise ValueError(msg)
246+
247+
# Order matters and is load-bearing: serverless/init.lua stops at the first
248+
# function returning a code, so the origin has to be canonical before the
249+
# recovery function decides whether to redirect back into the login flow.
250+
functions = [OIDC_ERROR_RECOVERY_LUA]
251+
if canonical_https_redirect:
252+
functions.insert(0, CANONICAL_HTTPS_REDIRECT_LUA)
178253
return OLApisixPluginConfig(
179254
name="serverless-pre-function",
180255
secretRef=None,
181256
# rewrite rather than the plugin's default access phase: openid-connect
182257
# also runs in rewrite, and serverless-pre-function's priority (10000)
183-
# outranks it (2599), so this gets to inspect the callback and bail out
184-
# before the plugin turns the error parameter into a 500. In the access
185-
# phase it would run after openid-connect had already failed.
258+
# outranks it (2599), so this gets to normalise the origin and inspect
259+
# the callback before the plugin reads either. In the access phase it
260+
# would run after openid-connect had already built its redirect_uri and
261+
# failed.
186262
config={
187263
"phase": "rewrite",
188-
"functions": [OIDC_ERROR_RECOVERY_LUA],
264+
"functions": functions,
265+
"canonical_https_redirect": {"status": canonical_redirect_status},
189266
"oidc_error_recovery": {
190267
# `is None`, not `or`: an explicit empty list means "recover
191268
# nothing", and `or` would quietly turn that back into the
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
-- Send the request to the canonical https://<host> origin before the
2+
-- openid-connect plugin builds a redirect_uri out of it.
3+
--
4+
-- Attached as a serverless-pre-function in the `rewrite` phase, where this
5+
-- plugin's priority (10000) outranks openid-connect's (2599). That ordering is
6+
-- the whole point: the shared-plugin defaults already carry APISIX's `redirect`
7+
-- plugin with http_to_https, but `redirect` has a lower priority than
8+
-- openid-connect, so on an OIDC route it never gets to run.
9+
--
10+
-- APISIX only derives a *relative* redirect_uri (`<uri>/.apisix/redirect`), and
11+
-- lua-resty-openidc 1.8.0 -- the version pinned by APISIX 3.17 -- turns it
12+
-- absolute from `ngx.var.scheme` and `ngx.var.http_host`. Both are raw
13+
-- connection values, so a plain-HTTP request produces `http://...` and a
14+
-- request carrying `Host: example.org:443` produces `https://example.org:443/...`.
15+
-- Keycloak registers bare-host https URIs only, so it rejects both with
16+
-- error="invalid_redirect_uri" and the login dies at the authorization endpoint,
17+
-- before any callback exists to recover.
18+
--
19+
-- Normalising the request rather than the header is deliberate. lua-resty-openidc
20+
-- does consult `Forwarded` / `X-Forwarded-Proto` / `X-Forwarded-Host` ahead of
21+
-- those ngx vars, but on this deployment those headers demonstrably do not reach
22+
-- it: sending any of the three against production changes nothing about the
23+
-- redirect_uri. Setting them here would be a no-op that reads like a fix.
24+
--
25+
-- Configuration arrives on the plugin config under `canonical_https_redirect`,
26+
-- the same mechanism `oidc_error_callback_recovery.lua` uses.
27+
--
28+
-- canonical_https_redirect.status redirect status code
29+
--
30+
-- See `oidc_gateway_pre_function_plugin` in ../apisix.py for the deployment
31+
-- reasoning and t/canonical_https_redirect.t for the behavioural tests.
32+
return function(conf, ctx)
33+
-- $host is the Host header lowercased with any port stripped, falling back
34+
-- to server_name; $http_host is the header verbatim. Comparing them catches
35+
-- an explicit :443, an uppercased host, and anything else that would reach
36+
-- lua-resty-openidc as a non-canonical authority.
37+
local raw_host = ngx.var.http_host
38+
local host = ngx.var.host
39+
40+
-- No Host header at all (HTTP/1.0). There is nothing to build a canonical
41+
-- origin from -- $host would be the server_name -- so leave it alone and let
42+
-- openid-connect's own 400 handle it.
43+
if not raw_host or not host then
44+
return
45+
end
46+
47+
if ngx.var.scheme == "https" and raw_host == host then
48+
return
49+
end
50+
51+
local core = require("apisix.core")
52+
local opts = conf.canonical_https_redirect or {}
53+
54+
core.log.warn("non-canonical origin scheme=", ngx.var.scheme,
55+
" host=", raw_host, " redirecting to https://", host)
56+
57+
-- One status for every method. The shadowed `redirect` plugin picks per
58+
-- method instead -- 301 for GET/HEAD, 308 for the rest (redirect.lua
59+
-- 208-215) -- so both preserve a POST and this is a simplification, not a
60+
-- behavioural fix. `status` is constrained in ../apisix.py to the codes
61+
-- ngx.redirect accepts; anything else raises a Lua error here.
62+
return ngx.redirect("https://" .. host .. ngx.var.request_uri,
63+
opts.status or 308)
64+
end

src/ol_infrastructure/components/services/files/oidc_error_callback_recovery.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
-- oidc_error_recovery.guard_cookie_name loop-breaker cookie name
1616
-- oidc_error_recovery.guard_max_age guard cookie lifetime, seconds
1717
--
18-
-- See `oidc_error_callback_recovery_plugin` in ../apisix.py for why each branch
18+
-- See `oidc_gateway_pre_function_plugin` in ../apisix.py for why each branch
1919
-- is here, and t/oidc_error_callback_recovery.t for the behavioural tests.
2020
return function(conf, ctx)
2121
local uri = ngx.var.uri

tests/apisix_integration/conftest.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from collections.abc import Generator
2828

2929
from ol_infrastructure.components.services.apisix import (
30-
oidc_error_callback_recovery_plugin,
30+
oidc_gateway_pre_function_plugin,
3131
)
3232

3333
# Must track the APISIX shipped by the chart pinned in bridge.lib.versions
@@ -90,7 +90,14 @@ def apisix_routes() -> dict[str, Any]:
9090
what the gateway itself does before proxying, and a request that reaches the
9191
upstream is one the plugin correctly declined to intercept.
9292
"""
93-
recovery = oidc_error_callback_recovery_plugin()
93+
# APISIX listens on plain HTTP here, so the canonical-origin function would
94+
# upgrade every request before the recovery function ever ran -- which is
95+
# the production behaviour, and is asserted directly on the routes below.
96+
# The recovery routes therefore switch it off so they exercise the callback
97+
# handling in isolation, exactly as they did before the two were fused into
98+
# the single serverless-pre-function APISIX allows per plugin config.
99+
recovery = oidc_gateway_pre_function_plugin(canonical_https_redirect=False)
100+
full = oidc_gateway_pre_function_plugin()
94101
dead_upstream = {"type": "roundrobin", "nodes": {"127.0.0.1:1": 1}}
95102
return {
96103
"routes": [
@@ -106,6 +113,15 @@ def apisix_routes() -> dict[str, Any]:
106113
"upstream": dead_upstream,
107114
"plugins": {recovery.name: recovery.config},
108115
},
116+
# Not a catch-all: `_wait_until_ready` polls `/` and follows
117+
# redirects, so a `/*` route carrying this plugin would send the
118+
# readiness probe at an https origin that does not exist here.
119+
{
120+
"id": "canonical-origin",
121+
"uri": "/origin/*",
122+
"upstream": dead_upstream,
123+
"plugins": {full.name: full.config},
124+
},
109125
]
110126
}
111127

@@ -207,3 +223,27 @@ def _callback(
207223
return response.status, dict(response.headers)
208224

209225
return _callback
226+
227+
228+
@pytest.fixture
229+
def origin_request(apisix):
230+
"""Request a canonical-origin route and return (status, headers).
231+
232+
Sends an explicit Host so the port-stripping and scheme-upgrade branches can
233+
be driven independently of the container's own listen address.
234+
"""
235+
236+
def _origin_request(
237+
path: str = "/origin/",
238+
host: str = "nb.learn.mit.edu",
239+
) -> tuple[int, dict[str, str]]:
240+
response = urllib3.PoolManager(retries=False).request(
241+
"GET",
242+
f"{apisix}{path}",
243+
headers={"Host": host},
244+
redirect=False,
245+
timeout=10.0,
246+
)
247+
return response.status, dict(response.headers)
248+
249+
return _origin_request

0 commit comments

Comments
 (0)