-
Notifications
You must be signed in to change notification settings - Fork 285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Http proxy basic authentication on windows #1410
Conversation
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
@microsoft-github-policy-service agree company="RTE" |
I tried it and it worked. |
src/vcpkg/base/downloads.cpp
Outdated
|
||
ProxyUrlParts parse_proxy_url(const std::wstring& url) | ||
{ | ||
std::wregex scheme_pattern(L"^(.+://)(.*)"); // Matches scheme part |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Parsing URIs with regex is notoriously difficult and I'm not convinced that there wouldn't be ways to exploit this.
- We've tried really really hard to break the std::regex dependency entirely because all the std::regex implementations are awful.
Perhaps this means we should be looking at deploying something like trurl?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the feedback!
Actually here it should be pretty easy to use simple string search instead of regex. I can implement this.
For "deploying trurl", would you mean adding a dependency to libcurl, on which trurl seems to rely behind the scene?
But I'm not sure it's easy to use on windows. I guess in that case it would make sense to completely rely on libcurl for the download.
Or do you mean something else, like requiring the user to install trurl in addition to vcpkg.exe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the regex use in last commit, to use only basic string searches.
tls12-download needs to be very very tiny in order to do its job since it needs to be checked in. Customers in this condition can manually download a vcpkg.exe with any browser they want. |
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
ProxyUrlParts{L"my-hostname.com:8080", ProxyCredentials{L"Joe", L"secret"}}); | ||
REQUIRE(parse_proxy_url(L"https://my-hostname.com:8080") == ProxyUrlParts{L"https://my-hostname.com:8080", {}}); | ||
REQUIRE(parse_proxy_url(L"https://Joe:secret@my-hostname.com:8080") == | ||
ProxyUrlParts{L"https://my-hostname.com:8080", ProxyCredentials{L"Joe", L"secret"}}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this should at least contain all the forms in the RFC: https://www.rfc-editor.org/rfc/rfc3986#section-1.1.2
auto credentials_separator_pos = remaining_parts.find(L"@"); | ||
auto password_separator_pos = remaining_parts.find(L":"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems incorrect as the password separator, if present, must be inside the userinfo. Perhaps the STL algorithm form would be clearer since it makes chopping ranges into subranges like this easy:
auto user_info_end = std::find(remaining_parts.begin(), remaining_parts.end(), '@');
if (user_info_end != remaining_parts.end()) {
auto password_separator = std::find(remaining_parts.begin(), user_info_end, ':');
if (password_separator != user_info_end) {
auto password_start = password_separator + 1;
credentials = ProxyCredentials{std::wstring(remaining_parts.begin(), password_separator - remaining_parts.begin()),
std::wstring(password_start, user_info_end - password_start)};
}
}
Also, you want the character finds (L'@'
) not string finds (L"@"
).
std::wstring env_proxy_settings = Strings::to_utf16(*p_https_proxy); | ||
ProxyUrlParts parts = parse_proxy_url(env_proxy_settings); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be a good idea to make the parsing stuff etc. use chars rather than wchar_ts making that parsing code consistent with all other parsing code in the product.
constexpr auto npos = std::wstring::npos; | ||
auto scheme_separator_pos = url.find(L"://"); | ||
std::wstring scheme; | ||
std::wstring_view remaining_parts = url; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use StringView rather than std::wstring_view as we don't want to add a dependency on a new C++17 feature in order to have this. (Note that this would also require the char* rather than wchar_t* suggestion below)
explicit WinHttpContext(const T& value) | ||
{ | ||
T* value_ptr = new T(value); | ||
m_value_ptr_ptr = new T*(value_ptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This leaks a T if that new throws. Can you just use unique_ptr please?
// which needs to be provided as a "DWORD_PTR" to WinHttpSendRequest | ||
// (pointer to a pointer-sized variable). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DWORD_PTR
is a pointer-sized variable itself. It models uintptr_t
not uintptr_t*
. You should be able to pass T* directly.
WINHTTP_NO_REQUEST_DATA, | ||
0, | ||
0, | ||
ret.m_proxy_credentials.as_dword_ptr()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this data is tied to the lifetime of the request, is there any reason we don't just use an ordinary member variable and pass reinterpret_cast<DWORD_PTR>(this)
for that?
Then set_proxy_credentials_on_redirect
does reinterpret_cast<WinHttpRequest*>(pContext)->any data you want
.
ProxyCredentials& ctxt = **ctxt_ptr_ptr; | ||
WinHttpSetCredentials(hInternet, | ||
WINHTTP_AUTH_TARGET_PROXY, | ||
WINHTTP_AUTH_SCHEME_BASIC, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems very concerning to me to just arbitrarily staple basic auth creds when a redirect happens, in particular because basic auth is not safe to use without TLS. So if there is TLS for the first request, and that redirects to a not TLS place, stapling creds on again leaked the creds to the whole internet.
This kind of thing makes me extremely nervous to take this change. Perhaps the right behavior is that if we see proxy stuff that looks like it needs auth to always use curl.exe
instead...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your concerns, I am clearly not a security expert. For sure relying on a proven solution would be more comfortable.
For the case you describe, I don't think this can happen: from my company network, the redirected request will go through the proxy again (it cannot go to the internet without going through the proxy), and I assume the proxy removes the credentials from the transferred request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed curl.exe
seems to be widely present on windows at least in developer environments (comes with git), so it may be reasonable to rely on it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already rely on it all over the place :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so I understand that just changing that condition (= use winhttp only if there are no headers, else use curl), to use curl also when we have credentials in the URL, would be the best solution ?
vcpkg-tool/src/vcpkg/base/downloads.cpp
Line 764 in fb6c0b5
if (headers.size() == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See alternative proposition based on this idea in #1434 : we can close this PR if it is indeed a better way to go.
Closing this PR because #1434 was merged and fixes the problem. Thanks! |
This is a proposition to solve the issue reported in this discussion.
Instead of just providing the content of HTTPS_PROXY environment variable, we now parse it to extract proxy credentials, then provide it to WinHTTP API (which is not as simple as one could expect).
I've been able to validate the behaviour in a test environment with a squid proxy.
Notes