Merge branch 'ds/credentials-in-url'

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
This commit is contained in:
Junio C Hamano 2022-06-13 15:53:42 -07:00
commit 11698e551c
4 changed files with 117 additions and 0 deletions

View file

@ -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.

View file

@ -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"
@ -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 *,
@ -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;
}

View file

@ -12,6 +12,7 @@ This test checks the following functionality:
* --porcelain output format
* hiderefs
* reflogs
* URL validation
'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
@ -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

View file

@ -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 &&