Skip to content

Commit

Permalink
/vsicurl/: no longer forward Authorization header when doing redirect…
Browse files Browse the repository at this point in the history
…ion to other hosts. Can be configured with the CPL_VSIL_CURL_AUTHORIZATION_HEADER_ALLOWED_IF_REDIRECT=YES/NO/IF_SAME_HOST config option
  • Loading branch information
rouault committed Sep 22, 2024
1 parent 3ca305c commit b04b627
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 25 deletions.
130 changes: 129 additions & 1 deletion autotest/gcore/vsicurl.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,139 @@ def server():
webserver.server_stop(process, port)


###############################################################################
# Test regular redirection


@pytest.mark.parametrize(
"authorization_header_allowed", [None, "YES", "NO", "IF_SAME_HOST"]
)
def test_vsicurl_test_redirect(server, authorization_header_allowed):

gdal.VSICurlClearCache()

expected_headers = None
unexpected_headers = []
if authorization_header_allowed != "NO":
expected_headers = {"Authorization": "Bearer xxx"}
else:
unexpected_headers = ["Authorization"]

handler = webserver.SequentialHandler()
handler.add("GET", "/test_redirect/", 404)
handler.add(
"HEAD",
"/test_redirect/test.bin",
301,
{"Location": "http://localhost:%d/redirected/test.bin" % server.port},
expected_headers={"Authorization": "Bearer xxx"},
)

# Curl always forward Authorization if same server when handling itself
# the redirect, so this means that CPL_VSIL_CURL_AUTHORIZATION_HEADER_ALLOWED_IF_REDIRECT=NO
# is not honored for that particular request. To honour it, we would have
# to disable CURLOPT_FOLLOWLOCATION and implement it at hand
handler.add(
"HEAD",
"/redirected/test.bin",
200,
{"Content-Length": "3"},
expected_headers={"Authorization": "Bearer xxx"},
)

handler.add(
"GET",
"/redirected/test.bin",
200,
{"Content-Length": "3"},
b"xyz",
expected_headers=expected_headers,
unexpected_headers=unexpected_headers,
)

options = {"GDAL_HTTP_HEADERS": "Authorization: Bearer xxx"}
if authorization_header_allowed:
options[
"CPL_VSIL_CURL_AUTHORIZATION_HEADER_ALLOWED_IF_REDIRECT"
] = authorization_header_allowed
with webserver.install_http_handler(handler), gdal.config_options(options):
f = gdal.VSIFOpenL(
"/vsicurl/http://localhost:%d/test_redirect/test.bin" % server.port,
"rb",
)
assert f is not None
try:
assert gdal.VSIFReadL(1, 3, f) == b"xyz"
finally:
gdal.VSIFCloseL(f)


###############################################################################
# Test regular redirection


@pytest.mark.parametrize(
"authorization_header_allowed", [None, "YES", "NO", "IF_SAME_HOST"]
)
def test_vsicurl_test_redirect_different_server(server, authorization_header_allowed):

gdal.VSICurlClearCache()

expected_headers = None
unexpected_headers = []
if authorization_header_allowed == "YES":
expected_headers = {"Authorization": "Bearer xxx"}
else:
unexpected_headers = ["Authorization"]

handler = webserver.SequentialHandler()
handler.add("GET", "/test_redirect/", 404)
handler.add(
"HEAD",
"/test_redirect/test.bin",
301,
{"Location": "http://127.0.0.1:%d/redirected/test.bin" % server.port},
expected_headers={"Authorization": "Bearer xxx"},
)
handler.add(
"HEAD",
"/redirected/test.bin",
200,
{"Content-Length": "3"},
expected_headers=expected_headers,
unexpected_headers=unexpected_headers,
)
handler.add(
"GET",
"/redirected/test.bin",
200,
{"Content-Length": "3"},
b"xyz",
expected_headers=expected_headers,
unexpected_headers=unexpected_headers,
)

options = {"GDAL_HTTP_HEADERS": "Authorization: Bearer xxx"}
if authorization_header_allowed:
options[
"CPL_VSIL_CURL_AUTHORIZATION_HEADER_ALLOWED_IF_REDIRECT"
] = authorization_header_allowed
with webserver.install_http_handler(handler), gdal.config_options(options):
f = gdal.VSIFOpenL(
"/vsicurl/http://localhost:%d/test_redirect/test.bin" % server.port,
"rb",
)
try:
assert gdal.VSIFReadL(1, 3, f) == b"xyz"
finally:
gdal.VSIFCloseL(f)


###############################################################################
# Test redirection with Expires= type of signed URLs


def test_vsicurl_test_redirect(server):
def test_vsicurl_test_redirect_with_expires(server):

gdal.VSICurlClearCache()

Expand Down
8 changes: 8 additions & 0 deletions doc/source/user/virtual_file_systems.rst
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,14 @@ As an alternative, starting with GDAL 3.6, the
:config:`GDAL_HTTP_HEADERS` configuration option can also be
used to specify headers. :config:`CPL_CURL_VERBOSE=YES` allows one to see them and more, when combined with ``--debug``.

Starting with GDAL 3.10, the ``Authorization`` header is no longer automatically forwarded when redirections are followed.
That behavior can be configured by setting the :config:`CPL_VSIL_CURL_AUTHORIZATION_HEADER_ALLOWED_IF_REDIRECT``
configuration option to:

- ``NO`` to always disable forwarding of Authorization header
- ``YES`` to always enable forwarding of Authorization header
- ``IF_SAME_HOST`` to enable forwarding of Authorization header only if the redirection is to the same host.

Starting with GDAL 2.3, the :config:`GDAL_HTTP_MAX_RETRY` (number of attempts) and :config:`GDAL_HTTP_RETRY_DELAY` (in seconds) configuration option can be set, so that request retries are done in case of HTTP errors 429, 502, 503 or 504.

Starting with GDAL 3.6, the following configuration options control the TCP keep-alive functionality (cf https://daniel.haxx.se/blog/2020/02/10/curl-ootw-keepalive-time/ for a detailed explanation):
Expand Down
44 changes: 35 additions & 9 deletions port/cpl_http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2237,14 +2237,22 @@ void *CPLHTTPSetOptions(void *pcurl, const char *pszURL,
CURLAUTH_ANYSAFE);
else if (EQUAL(pszHttpAuth, "BEARER"))
{
const char *pszBearer = CSLFetchNameValue(papszOptions, "HTTP_BEARER");
if (pszBearer == nullptr)
pszBearer = CPLGetConfigOption("GDAL_HTTP_BEARER", nullptr);
if (pszBearer != nullptr)
unchecked_curl_easy_setopt(http_handle, CURLOPT_XOAUTH2_BEARER,
pszBearer);
unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH,
CURLAUTH_BEARER);
const char *pszAuthorizationHeaderAllowed = CSLFetchNameValueDef(
papszOptions, "AUTHORIZATION_HEADER_ALLOWED", "YES");
const bool bAuthorizationHeaderAllowed =
CPLTestBool(pszAuthorizationHeaderAllowed);
if (bAuthorizationHeaderAllowed)
{
const char *pszBearer =
CSLFetchNameValue(papszOptions, "HTTP_BEARER");
if (pszBearer == nullptr)
pszBearer = CPLGetConfigOption("GDAL_HTTP_BEARER", nullptr);
if (pszBearer != nullptr)
unchecked_curl_easy_setopt(http_handle, CURLOPT_XOAUTH2_BEARER,
pszBearer);
unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH,
CURLAUTH_BEARER);
}
}
else if (EQUAL(pszHttpAuth, "NEGOTIATE"))
unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH,
Expand Down Expand Up @@ -2365,6 +2373,15 @@ void *CPLHTTPSetOptions(void *pcurl, const char *pszURL,
1L);

unchecked_curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1);
const char *pszUnrestrictedAuth = CPLGetConfigOption(
"CPL_VSIL_CURL_AUTHORIZATION_HEADER_ALLOWED_IF_REDIRECT",
"IF_SAME_HOST");
if (!EQUAL(pszUnrestrictedAuth, "IF_SAME_HOST") &&
CPLTestBool(pszUnrestrictedAuth))
{
unchecked_curl_easy_setopt(http_handle, CURLOPT_UNRESTRICTED_AUTH, 1);
}

unchecked_curl_easy_setopt(http_handle, CURLOPT_MAXREDIRS, 10);
unchecked_curl_easy_setopt(http_handle, CURLOPT_POSTREDIR,
CURL_REDIR_POST_ALL);
Expand Down Expand Up @@ -2664,6 +2681,11 @@ void *CPLHTTPSetOptions(void *pcurl, const char *pszURL,
}
if (!bHeadersDone)
{
const char *pszAuthorizationHeaderAllowed = CSLFetchNameValueDef(
papszOptions, "AUTHORIZATION_HEADER_ALLOWED", "YES");
const bool bAuthorizationHeaderAllowed =
CPLTestBool(pszAuthorizationHeaderAllowed);

// We accept both raw headers with \r\n as a separator, or as
// a comma separated list of foo: bar values.
const CPLStringList aosTokens(
Expand All @@ -2672,7 +2694,11 @@ void *CPLHTTPSetOptions(void *pcurl, const char *pszURL,
: CSLTokenizeString2(pszHeaders, ",", CSLT_HONOURSTRINGS));
for (int i = 0; i < aosTokens.size(); ++i)
{
headers = curl_slist_append(headers, aosTokens[i]);
if (bAuthorizationHeaderAllowed ||
!STARTS_WITH_CI(aosTokens[i], "Authorization:"))
{
headers = curl_slist_append(headers, aosTokens[i]);
}
}
}
}
Expand Down
Loading

0 comments on commit b04b627

Please sign in to comment.