Conversation
Allow the UI HTTP server to bind to a configurable host instead of hardcoded localhost. This enables binding to 0.0.0.0 for remote access. The setting follows the same pattern as ui_local_port: configurable via SQL (SET ui_local_host), environment variable (UI_LOCAL_HOST), or the default (localhost).
|
@jraymakers: requesting review! Thank you 🙏 |
|
Thanks for the PR! Sorry for the delay. I mean to review this (carefully), but I haven't found the time yet. |
jraymakers
left a comment
There was a problem hiding this comment.
Finally got a chance to review this and also test it out. I have a range of comments, from small nits to functional problems that will need to be fixed.
The most important parts are the problem with Set-Cookie and the origin checks. Both of these prevent this from working properly.
| #include "settings.hpp" | ||
| #include "state.hpp" | ||
| #include "utils/encoding.hpp" | ||
| #include "utils/token.hpp" |
| remote_url = _remote_url; | ||
| http_params = std::move(_http_params); | ||
| token_auth_enabled = _token_auth_enabled; | ||
| auth_token = GenerateToken(); |
There was a problem hiding this comment.
We shouldn't generate a token if token auth is disabled, both to avoid the unnecessary work, and also to avoid it showing up in the URL.
| #define UI_POLLING_INTERVAL_SETTING_NAME "ui_polling_interval" | ||
| #define UI_POLLING_INTERVAL_SETTING_DEFAULT 284 | ||
| #define UI_ENABLE_TOKEN_AUTH_SETTING_NAME "ui_enable_token_auth" | ||
| #define UI_ENABLE_TOKEN_AUTH_SETTING_DEFAULT true |
There was a problem hiding this comment.
I think we should:
- default to token auth disabled if the local host is "localhost"
- default to token auth enabled otherwise
- respect the value of "ui_enable_token_auth" if it is set explicitly.
| httplib::Response &res) { | ||
| if (!ValidateToken(req, res)) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
We don't need (and shouldn't) require the token for these forwarded GET requests. The purpose of the token auth is to protect access to the DuckDB instance, but this function just proxies requests to the Internet, so there's nothing local to protect.
|
|
||
| // Check URL parameter: ?token=<TOKEN> | ||
| if (req.has_param("token")) { | ||
| if (req.get_param_value("token") == auth_token) { |
There was a problem hiding this comment.
We should name this URL param a bit more uniquely, to prevent it from interacting with other potential URL params. How about "duckdb-ui-token" or "duckdb_ui_token", to match the cookie?
| if (origin != local_url) { | ||
| res.status = 401; | ||
| return; | ||
| } |
There was a problem hiding this comment.
Here and in the other methods below, we have an existing security mechanism to restrict calls by origin - we only allow calls from localhost. This will likely prevent calls from other hosts, even when the new local_host setting is set.
My preference would be to keep these origin checks when the local_host setting is "localhost", but disable them when the local_host setting is something else.
| // Referer includes the path, so only compare the start. | ||
| auto referer = req.get_header_value("Referer"); | ||
| if (referer.compare(0, local_url.size(), local_url) != 0) { | ||
| res.status = 401; |
There was a problem hiding this comment.
We should keep this check unless the local_host setting is something other than "localhost". (This is a variant of the other origin checks I mentioned.)
| const auto token = server.GetAuthToken(); | ||
| if (!token.empty()) { | ||
| url += "?token=" + token; | ||
| } |
There was a problem hiding this comment.
To avoid repeating this logic to append the token, we could just put it into HttpServer::LocalUrl.
|
|
||
| namespace duckdb { | ||
|
|
||
| std::string GenerateToken(); |
There was a problem hiding this comment.
General naming comment: There's an existing use of "token" in the extension - the "localToken" endpoint and the "GetMDToken" helper - for (optional) MotherDuck support. This change adds a second kind of token. To avoid confusion, we should qualify this new token wherever we refer to it, in function names, variables, etc. I recommend "DuckDBUIToken".
| } | ||
|
|
||
| // Repond with result of forwarded GET | ||
| res = result.value(); |
There was a problem hiding this comment.
I don't see the Set-Cookie header in the response to the initial request, and I suspect this line is why - we overwrite the res object with the result of the forwarded request. We need to add the cookie after this point. We should take care to add it and not clear any other cookies that might be present in the response. (There typically won't be any, even when using MotherDuck, but we want this mechanism to behave nicely with any potential future uses of cookies.)
Summary
ui_local_hostDuckDB extension setting (default:localhost) that controls the host/address the UI HTTP server binds to.0.0.0.0for remote/network access, e.g.SET ui_local_host = '0.0.0.0';orUI_LOCAL_HOST=0.0.0.0 duckdb -ui.ui_local_portsetting: configurable via SQL, environment variable, or default.Changes
src/include/settings.hpp— DefineUI_LOCAL_HOST_SETTING_NAME/UI_LOCAL_HOST_SETTING_DEFAULTmacros, declareGetLocalHost().src/settings.cpp— ImplementGetLocalHost().src/ui_extension.cpp— Registerui_local_hostas an extension option (VARCHAR, env-overridable).src/include/http_server.hpp— Addlocal_hostmember, updateDoStartsignature.src/http_server.cpp— Threadlocal_hostthroughStart→DoStart→Run, use it inserver.listen(),LocalUrl(), andlocal_urlconstruction.IsRunningOnMachine()still probeslocalhostsince that works regardless of bind address.test/sql/ui.test— Add SQLLogicTests verifying the setting exists, has the correct default, and can be changed.Implemented suggestion from #232.