Skip to content

Commit

Permalink
Merge branch 'ds/credentials-in-url' into jch
Browse files Browse the repository at this point in the history
The "fetch.credentialsInUrl" configuration variable controls what
happens when a URL with embedded login credential is used.

* ds/credentials-in-url:
  remote: create fetch.credentialsInUrl config
  • Loading branch information
gitster committed Jun 13, 2022
2 parents 8f14f93 + 6dcbdc0 commit c1212d7
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Documentation/config/fetch.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,17 @@ fetch.writeCommitGraph::
merge and the write may take longer. Having an updated commit-graph
file helps performance of many Git commands, including `git merge-base`,
`git push -f`, and `git log --graph`. Defaults to false.

fetch.credentialsInUrl::
A URL can contain plaintext credentials in the form
`<protocol>://<user>:<password>@<domain>/<path>`. Using such URLs
is not recommended as it exposes the password in multiple ways,
including Git storing the URL as plaintext in the repository config.
The `fetch.credentialsInUrl` option provides instruction for how Git
should react to seeing such a URL, with these values:
+
* `allow` (default): Git will proceed with its activity without warning.
* `warn`: Git will write a warning message to `stderr` when parsing a URL
with a plaintext credential.
* `die`: Git will write a failure message to `stderr` when parsing a URL
with a plaintext credential.
48 changes: 48 additions & 0 deletions remote.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "cache.h"
#include "config.h"
#include "remote.h"
#include "urlmatch.h"
#include "refs.h"
#include "refspec.h"
#include "object-store.h"
Expand Down Expand Up @@ -617,6 +618,50 @@ const char *remote_ref_for_branch(struct branch *branch, int for_push)
return NULL;
}

static void validate_remote_url(struct remote *remote)
{
int i;
const char *value;
struct strbuf redacted = STRBUF_INIT;
int warn_not_die;

if (git_config_get_string_tmp("fetch.credentialsinurl", &value))
return;

if (!strcmp("warn", value))
warn_not_die = 1;
else if (!strcmp("die", value))
warn_not_die = 0;
else if (!strcmp("allow", value))
return;
else
die(_("unrecognized value fetch.credentialsInURL: '%s'"), value);

for (i = 0; i < remote->url_nr; i++) {
struct url_info url_info = { 0 };

if (!url_normalize(remote->url[i], &url_info) ||
!url_info.passwd_off)
goto loop_cleanup;

strbuf_reset(&redacted);
strbuf_add(&redacted, url_info.url, url_info.passwd_off);
strbuf_addstr(&redacted, "<redacted>");
strbuf_addstr(&redacted,
url_info.url + url_info.passwd_off + url_info.passwd_len);

if (warn_not_die)
warning(_("URL '%s' uses plaintext credentials"), redacted.buf);
else
die(_("URL '%s' uses plaintext credentials"), redacted.buf);

loop_cleanup:
free(url_info.url);
}

strbuf_release(&redacted);
}

static struct remote *
remotes_remote_get_1(struct remote_state *remote_state, const char *name,
const char *(*get_default)(struct remote_state *,
Expand All @@ -642,6 +687,9 @@ remotes_remote_get_1(struct remote_state *remote_state, const char *name,
add_url_alias(remote_state, ret, name);
if (!valid_remote(ret))
return NULL;

validate_remote_url(ret);

return ret;
}

Expand Down
32 changes: 32 additions & 0 deletions t/t5516-fetch-push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This test checks the following functionality:
* --porcelain output format
* hiderefs
* reflogs
* URL validation
'

GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
Expand Down Expand Up @@ -1833,4 +1834,35 @@ test_expect_success 'refuse to push a hidden ref, and make sure do not pollute t
test_dir_is_empty testrepo/.git/objects/pack
'

test_expect_success 'fetch warns or fails when using username:password' '
message="URL '\''https://username:<redacted>@localhost/'\'' uses plaintext credentials" &&
test_must_fail git -c fetch.credentialsInUrl=allow fetch https://username:password@localhost 2>err &&
! grep "$message" err &&
test_must_fail git -c fetch.credentialsInUrl=warn fetch https://username:password@localhost 2>err &&
grep "warning: $message" err >warnings &&
test_line_count = 3 warnings &&
test_must_fail git -c fetch.credentialsInUrl=die fetch https://username:password@localhost 2>err &&
grep "fatal: $message" err >warnings &&
test_line_count = 1 warnings &&
test_must_fail git -c fetch.credentialsInUrl=die fetch https://username:@localhost 2>err &&
grep "fatal: $message" err >warnings &&
test_line_count = 1 warnings
'


test_expect_success 'push warns or fails when using username:password' '
message="URL '\''https://username:<redacted>@localhost/'\'' uses plaintext credentials" &&
test_must_fail git -c fetch.credentialsInUrl=allow push https://username:password@localhost 2>err &&
! grep "$message" err &&
test_must_fail git -c fetch.credentialsInUrl=warn push https://username:password@localhost 2>err &&
grep "warning: $message" err >warnings &&
test_must_fail git -c fetch.credentialsInUrl=die push https://username:password@localhost 2>err &&
grep "fatal: $message" err >warnings &&
test_line_count = 1 warnings
'

test_done
23 changes: 23 additions & 0 deletions t/t5601-clone.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,29 @@ test_expect_success 'clone respects GIT_WORK_TREE' '
'

test_expect_success 'clone warns or fails when using username:password' '
message="URL '\''https://username:<redacted>@localhost/'\'' uses plaintext credentials" &&
test_must_fail git -c fetch.credentialsInUrl=allow clone https://username:password@localhost attempt1 2>err &&
! grep "$message" err &&
test_must_fail git -c fetch.credentialsInUrl=warn clone https://username:password@localhost attempt2 2>err &&
grep "warning: $message" err >warnings &&
test_line_count = 2 warnings &&
test_must_fail git -c fetch.credentialsInUrl=die clone https://username:password@localhost attempt3 2>err &&
grep "fatal: $message" err >warnings &&
test_line_count = 1 warnings &&
test_must_fail git -c fetch.credentialsInUrl=die clone https://username:@localhost attempt3 2>err &&
grep "fatal: $message" err >warnings &&
test_line_count = 1 warnings
'

test_expect_success 'clone does not detect username:password when it is https://username@domain:port/' '
test_must_fail git -c fetch.credentialsInUrl=warn clone https://username@localhost:8080 attempt3 2>err &&
! grep "uses plaintext credentials" err
'

test_expect_success 'clone from hooks' '
test_create_repo r0 &&
Expand Down

0 comments on commit c1212d7

Please sign in to comment.