http: support cookie redaction when tracing

When using GIT_TRACE_CURL, Git already redacts the "Authorization:" and "Proxy-Authorization:" HTTP headers. Extend this redaction to a user-specified list of cookies, specified through the "GIT_REDACT_COOKIES" environment variable. Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jonathan Tan committed Jan 18, 2018 at 16:28 UTC 83411783c3ebd0ffb55a05e8ad63b13b8530747f
3 files changed +82
Documentation/git.txt
+6
@@ -646,6 +646,12 @@ of clones and fetches.
646 variable.
647 See `GIT_TRACE` for available trace output options.
648
649 +`GIT_REDACT_COOKIES`::
650 + This can be set to a comma-separated list of strings. When a curl trace
651 + is enabled (see `GIT_TRACE_CURL` above), whenever a "Cookies:" header
652 + sent by the client is dumped, values of cookies whose key is in that
653 + list (case-sensitive) are redacted.
654 +
655 `GIT_LITERAL_PATHSPECS`::
656 Setting this variable to `1` will cause Git to treat all
657 pathspecs literally, rather than as glob patterns. For example,
http.c
+55
@@ -13,8 +13,10 @@
13 #include "transport.h"
14 #include "packfile.h"
15 #include "protocol.h"
16 +#include "string-list.h"
17
18 static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
19 +static struct string_list cookies_to_redact = STRING_LIST_INIT_DUP;
20 #if LIBCURL_VERSION_NUM >= 0x070a08
21 long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER;
22 #else
@@ -575,6 +577,54 @@ static void redact_sensitive_header(struct strbuf *header)
577 /* Everything else is opaque and possibly sensitive */
578 strbuf_setlen(header, sensitive_header - header->buf);
579 strbuf_addstr(header, " <redacted>");
580 + } else if (cookies_to_redact.nr &&
581 + skip_prefix(header->buf, "Cookie:", &sensitive_header)) {
582 + struct strbuf redacted_header = STRBUF_INIT;
583 + char *cookie;
584 +
585 + while (isspace(*sensitive_header))
586 + sensitive_header++;
587 +
588 + /*
589 + * The contents of header starting from sensitive_header will
590 + * subsequently be overridden, so it is fine to mutate this
591 + * string (hence the assignment to "char *").
592 + */
593 + cookie = (char *) sensitive_header;
594 +
595 + while (cookie) {
596 + char *equals;
597 + char *semicolon = strstr(cookie, "; ");
598 + if (semicolon)
599 + *semicolon = 0;
600 + equals = strchrnul(cookie, '=');
601 + if (!equals) {
602 + /* invalid cookie, just append and continue */
603 + strbuf_addstr(&redacted_header, cookie);
604 + continue;
605 + }
606 + *equals = 0; /* temporarily set to NUL for lookup */
607 + if (string_list_lookup(&cookies_to_redact, cookie)) {
608 + strbuf_addstr(&redacted_header, cookie);
609 + strbuf_addstr(&redacted_header, "=<redacted>");
610 + } else {
611 + *equals = '=';
612 + strbuf_addstr(&redacted_header, cookie);
613 + }
614 + if (semicolon) {
615 + /*
616 + * There are more cookies. (Or, for some
617 + * reason, the input string ends in "; ".)
618 + */
619 + strbuf_addstr(&redacted_header, "; ");
620 + cookie = semicolon + strlen("; ");
621 + } else {
622 + cookie = NULL;
623 + }
624 + }
625 +
626 + strbuf_setlen(header, sensitive_header - header->buf);
627 + strbuf_addbuf(header, &redacted_header);
628 }
629 }
630
@@ -807,6 +857,11 @@ static CURL *get_curl_handle(void)
857 if (getenv("GIT_CURL_VERBOSE"))
858 curl_easy_setopt(result, CURLOPT_VERBOSE, 1L);
859 setup_curl_trace(result);
860 + if (getenv("GIT_REDACT_COOKIES")) {
861 + string_list_split(&cookies_to_redact,
862 + getenv("GIT_REDACT_COOKIES"), ',', -1);
863 + string_list_sort(&cookies_to_redact);
864 + }
865
866 curl_easy_setopt(result, CURLOPT_USERAGENT,
867 user_agent ? user_agent : git_user_agent());
t/t5551-http-fetch-smart.sh
+21
@@ -364,5 +364,26 @@ test_expect_success 'custom http headers' '
364 submodule update sub
365 '
366
367 +test_expect_success 'GIT_REDACT_COOKIES redacts cookies' '
368 + rm -rf clone &&
369 + echo "Set-Cookie: Foo=1" >cookies &&
370 + echo "Set-Cookie: Bar=2" >>cookies &&
371 + GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Bar,Baz \
372 + git -c "http.cookieFile=$(pwd)/cookies" clone \
373 + $HTTPD_URL/smart/repo.git clone 2>err &&
374 + grep "Cookie:.*Foo=1" err &&
375 + grep "Cookie:.*Bar=<redacted>" err &&
376 + ! grep "Cookie:.*Bar=2" err
377 +'
378 +
379 +test_expect_success 'GIT_REDACT_COOKIES handles empty values' '
380 + rm -rf clone &&
381 + echo "Set-Cookie: Foo=" >cookies &&
382 + GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Foo \
383 + git -c "http.cookieFile=$(pwd)/cookies" clone \
384 + $HTTPD_URL/smart/repo.git clone 2>err &&
385 + grep "Cookie:.*Foo=<redacted>" err
386 +'
387 +
388 stop_httpd
389 test_done