config: add new way to pass config via --config-env

While it's already possible to pass runtime configuration via `git -c
<key>=<value>`, it may be undesirable to use when the value contains
sensitive information. E.g. if one wants to set `http.extraHeader` to
contain an authentication token, doing so via `-c` would trivially leak
those credentials via e.g. ps(1), which typically also shows command
arguments.

To enable this usecase without leaking credentials, this commit
introduces a new switch `--config-env=<key>=<envvar>`. Instead of
directly passing a value for the given key, it instead allows the user
to specify the name of an environment variable. The value of that
variable will then be used as value of the key.

Co-authored-by: Jeff King <peff@peff.net>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Patrick Steinhardt 2021-01-12 13:26:45 +01:00 committed by Junio C Hamano
parent b0812b6ac0
commit ce81b1da23
5 changed files with 100 additions and 2 deletions

View File

@ -13,7 +13,7 @@ SYNOPSIS
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
[--super-prefix=<path>]
[--super-prefix=<path>] [--config-env <name>=<envvar>]
<command> [<args>]
DESCRIPTION
@ -80,6 +80,28 @@ config file). Including the equals but with an empty value (like `git -c
foo.bar= ...`) sets `foo.bar` to the empty string which `git config
--type=bool` will convert to `false`.
--config-env=<name>=<envvar>::
Like `-c <name>=<value>`, give configuration variable
'<name>' a value, where <envvar> is the name of an
environment variable from which to retrieve the value. Unlike
`-c` there is no shortcut for directly setting the value to an
empty string, instead the environment variable itself must be
set to the empty string. It is an error if the `<envvar>` does not exist
in the environment. `<envvar>` may not contain an equals sign
to avoid ambiguity with `<name>`s which contain one.
+
This is useful for cases where you want to pass transitory
configuration options to git, but are doing so on OS's where
other processes might be able to read your cmdline
(e.g. `/proc/self/cmdline`), but not your environ
(e.g. `/proc/self/environ`). That behavior is the default on
Linux, but may not be on your system.
+
Note that this might add security for variables such as
`http.extraHeader` where the sensitive information is part of
the value, but not e.g. `url.<base>.insteadOf` where the
sensitive information can be part of the key.
--exec-path[=<path>]::
Path to wherever your core Git programs are installed.
This can also be controlled by setting the GIT_EXEC_PATH

View File

@ -345,6 +345,31 @@ void git_config_push_parameter(const char *text)
strbuf_release(&env);
}
void git_config_push_env(const char *spec)
{
struct strbuf buf = STRBUF_INIT;
const char *env_name;
const char *env_value;
env_name = strrchr(spec, '=');
if (!env_name)
die(_("invalid config format: %s"), spec);
env_name++;
if (!*env_name)
die(_("missing environment variable name for configuration '%.*s'"),
(int)(env_name - spec - 1), spec);
env_value = getenv(env_name);
if (!env_value)
die(_("missing environment variable '%s' for configuration '%.*s'"),
env_name, (int)(env_name - spec - 1), spec);
strbuf_add(&buf, spec, env_name - spec);
strbuf_addstr(&buf, env_value);
git_config_push_parameter(buf.buf);
strbuf_release(&buf);
}
static inline int iskeychar(int c)
{
return isalnum(c) || c == '-';

View File

@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,
int git_config_from_blob_oid(config_fn_t fn, const char *name,
const struct object_id *oid, void *data);
void git_config_push_parameter(const char *text);
void git_config_push_env(const char *spec);
int git_config_from_parameters(config_fn_t fn, void *data);
void read_early_config(config_fn_t cb, void *data);
void read_very_early_config(config_fn_t cb, void *data);

4
git.c
View File

@ -29,7 +29,7 @@ const char git_usage_string[] =
" [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
" [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\n"
" [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
" [--super-prefix=<path>]\n"
" [--super-prefix=<path>] [--config-env=<name>=<envvar>]\n"
" <command> [<args>]");
const char git_more_info_string[] =
@ -255,6 +255,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
git_config_push_parameter((*argv)[1]);
(*argv)++;
(*argc)--;
} else if (skip_prefix(cmd, "--config-env=", &cmd)) {
git_config_push_env(cmd);
} else if (!strcmp(cmd, "--literal-pathspecs")) {
setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "1", 1);
if (envchanged)

View File

@ -1316,6 +1316,54 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '
git config --get-regexp "env.*"
'
test_expect_success 'git --config-env=key=envvar support' '
cat >expect <<-\EOF &&
value
value
false
EOF
{
ENVVAR=value git --config-env=core.name=ENVVAR config core.name &&
ENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&
ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag
} >actual &&
test_cmp expect actual
'
test_expect_success 'git --config-env fails with invalid parameters' '
test_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&
test_i18ngrep "invalid config format: foo.flag" error &&
test_must_fail git --config-env=foo.flag= config --bool foo.flag 2>error &&
test_i18ngrep "missing environment variable name for configuration ${SQ}foo.flag${SQ}" error &&
sane_unset NONEXISTENT &&
test_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&
test_i18ngrep "missing environment variable ${SQ}NONEXISTENT${SQ} for configuration ${SQ}foo.flag${SQ}" error
'
test_expect_success 'git -c and --config-env work together' '
cat >expect <<-\EOF &&
bar.cmd cmd-value
bar.env env-value
EOF
ENVVAR=env-value git \
-c bar.cmd=cmd-value \
--config-env=bar.env=ENVVAR \
config --get-regexp "^bar.*" >actual &&
test_cmp expect actual
'
test_expect_success 'git -c and --config-env override each other' '
cat >expect <<-\EOF &&
env
cmd
EOF
{
ENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&
ENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar
} >actual &&
test_cmp expect actual
'
test_expect_success 'git config --edit works' '
git config -f tmp test.value no &&
echo test.value=yes >expect &&