Message ID | YUuqKeXRYuXjXy1+@coredump.intra.peff.net (mailing list archive) |
---|---|
State | Accepted |
Commit | b66c77a64e696eb5e5994a58c0d50073f8e93bf1 |
Headers | show |
Series | [v2] http: match headers case-insensitively when redacting | expand |
On Wed, Sep 22, 2021 at 06:11:54PM -0400, Jeff King wrote: > Well, I did it anyway. :) Here's the updated patch. I think it explains > things more clearly by showing the example output from our discussion > (and reframes the text around it to explain it more). I'll send a > range-diff in a moment. Here's the range-diff. I split it out because the commit message is already so long and full of sample diffs and output that I thought it would get hard to tell what was range-diff and what was actual diff. :) 1: faa6e6d28e ! 1: ea064beb32 http: match headers case-insensitively when redacting @@ Commit message Authorization: Basic ... After breaking it into lines, we match each header using skip_prefix(). - This is case-insensitive, even though HTTP headers are case-insensitive. + This is case-sensitive, even though HTTP headers are case-insensitive. This has worked reliably in the past because these headers are generated by curl itself, which is predictable in what it sends. @@ Commit message (the overall operation works fine; we just don't see the header in the trace). - On top of that, we also need the test change that this patch _does_ do: - grepping the trace file case-insensitively. Otherwise the test continues - to pass even over HTTP/2, because it sees _both_ forms of the header - (redacted and unredacted), as we upgrade from HTTP/1.1 to HTTP/2. So our - double grep: + Furthermore, even with the changes above, this test still does not + detect the current failure, because we see _both_ HTTP/1.1 and HTTP/2 + requests, which confuse it. Quoting only the interesting bits from the + resulting trace file, we first see: + + => Send header: GET /auth/smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 + => Send header: Connection: Upgrade, HTTP2-Settings + => Send header: Upgrade: h2c + => Send header: HTTP2-Settings: AAMAAABkAAQCAAAAAAIAAAAA + + <= Recv header: HTTP/1.1 401 Unauthorized + <= Recv header: Date: Wed, 22 Sep 2021 20:03:32 GMT + <= Recv header: Server: Apache/2.4.49 (Debian) + <= Recv header: WWW-Authenticate: Basic realm="git-auth" + + So the client asks for HTTP/2, but Apache does not do the upgrade for + the 401 response. Then the client repeats with credentials: + + => Send header: GET /auth/smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 + => Send header: Authorization: Basic <redacted> + => Send header: Connection: Upgrade, HTTP2-Settings + => Send header: Upgrade: h2c + => Send header: HTTP2-Settings: AAMAAABkAAQCAAAAAAIAAAAA + + <= Recv header: HTTP/1.1 101 Switching Protocols + <= Recv header: Upgrade: h2c + <= Recv header: Connection: Upgrade + <= Recv header: HTTP/2 200 + <= Recv header: content-type: application/x-git-upload-pack-advertisement + + So the client does properly redact there, because we're speaking + HTTP/1.1, and the server indicates it can do the upgrade. And then the + client will make further requests using HTTP/2: + + => Send header: POST /auth/smart/repo.git/git-upload-pack HTTP/2 + => Send header: authorization: Basic dXNlckBob3N0OnBhc3NAaG9zdA== + => Send header: content-type: application/x-git-upload-pack-request + + And there we can see that the credential is _not_ redacted. This part of + the test is what gets confused: # Ensure that there is no "Basic" followed by a base64 string, but that # the auth details are redacted ! grep "Authorization: Basic [0-9a-zA-Z+/]" trace && grep "Authorization: Basic <redacted>" trace - gets confused. It sees the "<redacted>" one from the pre-upgrade - HTTP/1.1 request, but fails to see the unredacted HTTP/2 one, because it - does not match the lower-case "authorization". Even without the rest of - the test changes, we can still make this test more robust by matching - case-insensitively. That will future-proof the test for a day when - HTTP/2 is finally enabled by default, and doesn't hurt in the meantime. + The first grep does not match the un-redacted HTTP/2 header, because + it insists on an uppercase "A". And the second one does find the + HTTP/1.1 header. So as far as the test is concerned, everything is OK, + but it failed to notice the un-redacted lines. + + We can make this test (and the other related ones) more robust by adding + "-i" to grep case-insensitively. This isn't really doing anything for + now, since we're not actually speaking HTTP/2, but it future-proofs the + tests for a day when we do (either we add explicit HTTP/2 test support, + or it's eventually enabled by default by our Apache+curl test setup). + And it doesn't hurt in the meantime for the tests to be more careful. + + The change to use "grep -i", coupled with the changes to use HTTP/2 + shown above, causes the test to fail with the current code, and pass + after this patch is applied. And finally, there's one other way to demonstrate the issue (and how I actually found it originally). Looking at GIT_TRACE_CURL output against
diff --git a/http.c b/http.c index a0f169d2fe..4f6a32165f 100644 --- a/http.c +++ b/http.c @@ -550,8 +550,8 @@ static void redact_sensitive_header(struct strbuf *header) const char *sensitive_header; if (trace_curl_redact && - (skip_prefix(header->buf, "Authorization:", &sensitive_header) || - skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header))) { + (skip_iprefix(header->buf, "Authorization:", &sensitive_header) || + skip_iprefix(header->buf, "Proxy-Authorization:", &sensitive_header))) { /* The first token is the type, which is OK to log */ while (isspace(*sensitive_header)) sensitive_header++; @@ -561,7 +561,7 @@ static void redact_sensitive_header(struct strbuf *header) strbuf_setlen(header, sensitive_header - header->buf); strbuf_addstr(header, " <redacted>"); } else if (trace_curl_redact && - skip_prefix(header->buf, "Cookie:", &sensitive_header)) { + skip_iprefix(header->buf, "Cookie:", &sensitive_header)) { struct strbuf redacted_header = STRBUF_INIT; const char *cookie; diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 4f87d90c5b..4e54226162 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -196,8 +196,8 @@ test_expect_success 'GIT_TRACE_CURL redacts auth details' ' # Ensure that there is no "Basic" followed by a base64 string, but that # the auth details are redacted - ! grep "Authorization: Basic [0-9a-zA-Z+/]" trace && - grep "Authorization: Basic <redacted>" trace + ! grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace && + grep -i "Authorization: Basic <redacted>" trace ' test_expect_success 'GIT_CURL_VERBOSE redacts auth details' ' @@ -208,8 +208,8 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' ' # Ensure that there is no "Basic" followed by a base64 string, but that # the auth details are redacted - ! grep "Authorization: Basic [0-9a-zA-Z+/]" trace && - grep "Authorization: Basic <redacted>" trace + ! grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace && + grep -i "Authorization: Basic <redacted>" trace ' test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_REDACT=0' ' @@ -219,7 +219,7 @@ test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_RE git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth && expect_askpass both user@host && - grep "Authorization: Basic [0-9a-zA-Z+/]" trace + grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace ' test_expect_success 'disable dumb http on server' ' @@ -474,10 +474,10 @@ test_expect_success 'cookies are redacted by default' ' GIT_TRACE_CURL=true \ git -c "http.cookieFile=$(pwd)/cookies" clone \ $HTTPD_URL/smart/repo.git clone 2>err && - grep "Cookie:.*Foo=<redacted>" err && - grep "Cookie:.*Bar=<redacted>" err && - ! grep "Cookie:.*Foo=1" err && - ! grep "Cookie:.*Bar=2" err + grep -i "Cookie:.*Foo=<redacted>" err && + grep -i "Cookie:.*Bar=<redacted>" err && + ! grep -i "Cookie:.*Foo=1" err && + ! grep -i "Cookie:.*Bar=2" err ' test_expect_success 'empty values of cookies are also redacted' ' @@ -486,7 +486,7 @@ test_expect_success 'empty values of cookies are also redacted' ' GIT_TRACE_CURL=true \ git -c "http.cookieFile=$(pwd)/cookies" clone \ $HTTPD_URL/smart/repo.git clone 2>err && - grep "Cookie:.*Foo=<redacted>" err + grep -i "Cookie:.*Foo=<redacted>" err ' test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' ' @@ -496,8 +496,8 @@ test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' ' GIT_TRACE_REDACT=0 GIT_TRACE_CURL=true \ git -c "http.cookieFile=$(pwd)/cookies" clone \ $HTTPD_URL/smart/repo.git clone 2>err && - grep "Cookie:.*Foo=1" err && - grep "Cookie:.*Bar=2" err + grep -i "Cookie:.*Foo=1" err && + grep -i "Cookie:.*Bar=2" err ' test_expect_success 'GIT_TRACE_CURL_NO_DATA prevents data from being traced' '