Skip to content

Commit f1762d7

Browse files
bmwillgitster
authored andcommitted
transport: add protocol policy config option
Previously the `GIT_ALLOW_PROTOCOL` environment variable was used to specify a whitelist of protocols to be used in clone/fetch/push commands. This patch introduces new configuration options for more fine-grained control for allowing/disallowing protocols. This also has the added benefit of allowing easier construction of a protocol whitelist on systems where setting an environment variable is non-trivial. Now users can specify a policy to be used for each type of protocol via the 'protocol.<name>.allow' config option. A default policy for all unconfigured protocols can be set with the 'protocol.allow' config option. If no user configured default is made git will allow known-safe protocols (http, https, git, ssh, file), disallow known-dangerous protocols (ext), and have a default policy of `user` for all other protocols. The supported policies are `always`, `never`, and `user`. The `user` policy can be used to configure a protocol to be usable when explicitly used by a user, while disallowing it for commands which run clone/fetch/push commands without direct user intervention (e.g. recursive initialization of submodules). Commands which can potentially clone/fetch/push from untrusted repositories without user intervention can export `GIT_PROTOCOL_FROM_USER` with a value of '0' to prevent protocols configured to the `user` policy from being used. Fix remote-ext tests to use the new config to allow the ext protocol to be tested. Based on a patch by Jeff King <peff@peff.net> Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent f962ddf commit f1762d7

File tree

7 files changed

+264
-39
lines changed

7 files changed

+264
-39
lines changed

Documentation/config.txt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,6 +2260,52 @@ pretty.<name>::
22602260
Note that an alias with the same name as a built-in format
22612261
will be silently ignored.
22622262

2263+
protocol.allow::
2264+
If set, provide a user defined default policy for all protocols which
2265+
don't explicitly have a policy (`protocol.<name>.allow`). By default,
2266+
if unset, known-safe protocols (http, https, git, ssh, file) have a
2267+
default policy of `always`, known-dangerous protocols (ext) have a
2268+
default policy of `never`, and all other protocols have a default
2269+
policy of `user`. Supported policies:
2270+
+
2271+
--
2272+
2273+
* `always` - protocol is always able to be used.
2274+
2275+
* `never` - protocol is never able to be used.
2276+
2277+
* `user` - protocol is only able to be used when `GIT_PROTOCOL_FROM_USER` is
2278+
either unset or has a value of 1. This policy should be used when you want a
2279+
protocol to be directly usable by the user but don't want it used by commands which
2280+
execute clone/fetch/push commands without user input, e.g. recursive
2281+
submodule initialization.
2282+
2283+
--
2284+
2285+
protocol.<name>.allow::
2286+
Set a policy to be used by protocol `<name>` with clone/fetch/push
2287+
commands. See `protocol.allow` above for the available policies.
2288+
+
2289+
The protocol names currently used by git are:
2290+
+
2291+
--
2292+
- `file`: any local file-based path (including `file://` URLs,
2293+
or local paths)
2294+
2295+
- `git`: the anonymous git protocol over a direct TCP
2296+
connection (or proxy, if configured)
2297+
2298+
- `ssh`: git over ssh (including `host:path` syntax,
2299+
`ssh://`, etc).
2300+
2301+
- `http`: git over http, both "smart http" and "dumb http".
2302+
Note that this does _not_ include `https`; if you want to configure
2303+
both, you must do so individually.
2304+
2305+
- any external helpers are named by their protocol (e.g., use
2306+
`hg` to allow the `git-remote-hg` helper)
2307+
--
2308+
22632309
pull.ff::
22642310
By default, Git does not create an extra merge commit when merging
22652311
a commit that is a descendant of the current commit. Instead, the

Documentation/git.txt

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,30 +1129,20 @@ of clones and fetches.
11291129
cloning a repository to make a backup).
11301130

11311131
`GIT_ALLOW_PROTOCOL`::
1132-
If set, provide a colon-separated list of protocols which are
1133-
allowed to be used with fetch/push/clone. This is useful to
1134-
restrict recursive submodule initialization from an untrusted
1135-
repository. Any protocol not mentioned will be disallowed (i.e.,
1136-
this is a whitelist, not a blacklist). If the variable is not
1137-
set at all, all protocols are enabled. The protocol names
1138-
currently used by git are:
1139-
1140-
- `file`: any local file-based path (including `file://` URLs,
1141-
or local paths)
1142-
1143-
- `git`: the anonymous git protocol over a direct TCP
1144-
connection (or proxy, if configured)
1145-
1146-
- `ssh`: git over ssh (including `host:path` syntax,
1147-
`ssh://`, etc).
1148-
1149-
- `http`: git over http, both "smart http" and "dumb http".
1150-
Note that this does _not_ include `https`; if you want both,
1151-
you should specify both as `http:https`.
1152-
1153-
- any external helpers are named by their protocol (e.g., use
1154-
`hg` to allow the `git-remote-hg` helper)
1155-
1132+
If set to a colon-separated list of protocols, behave as if
1133+
`protocol.allow` is set to `never`, and each of the listed
1134+
protocols has `protocol.<name>.allow` set to `always`
1135+
(overriding any existing configuration). In other words, any
1136+
protocol not mentioned will be disallowed (i.e., this is a
1137+
whitelist, not a blacklist). See the description of
1138+
`protocol.allow` in linkgit:git-config[1] for more details.
1139+
1140+
`GIT_PROTOCOL_FROM_USER`::
1141+
Set to 0 to prevent protocols used by fetch/push/clone which are
1142+
configured to the `user` state. This is useful to restrict recursive
1143+
submodule initialization from an untrusted repository or for programs
1144+
which feed potentially-untrusted URLS to git commands. See
1145+
linkgit:git-config[1] for more details.
11561146

11571147
Discussion[[Discussion]]
11581148
------------------------

git-submodule.sh

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,10 @@ require_work_tree
2222
wt_prefix=$(git rev-parse --show-prefix)
2323
cd_to_toplevel
2424

25-
# Restrict ourselves to a vanilla subset of protocols; the URLs
26-
# we get are under control of a remote repository, and we do not
27-
# want them kicking off arbitrary git-remote-* programs.
28-
#
29-
# If the user has already specified a set of allowed protocols,
30-
# we assume they know what they're doing and use that instead.
31-
: ${GIT_ALLOW_PROTOCOL=file:git:http:https:ssh}
32-
export GIT_ALLOW_PROTOCOL
25+
# Tell the rest of git that any URLs we get don't come
26+
# directly from the user, so it can apply policy as appropriate.
27+
GIT_PROTOCOL_FROM_USER=0
28+
export GIT_PROTOCOL_FROM_USER
3329

3430
command=
3531
branch=

t/lib-proto-disable.sh

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
# Test routines for checking protocol disabling.
22

3-
# test cloning a particular protocol
4-
# $1 - description of the protocol
5-
# $2 - machine-readable name of the protocol
6-
# $3 - the URL to try cloning
7-
test_proto () {
3+
# Test clone/fetch/push with GIT_ALLOW_PROTOCOL whitelist
4+
test_whitelist () {
85
desc=$1
96
proto=$2
107
url=$3
@@ -62,6 +59,129 @@ test_proto () {
6259
test_must_fail git clone --bare "$url" tmp.git
6360
)
6461
'
62+
63+
test_expect_success "clone $desc (env var has precedence)" '
64+
rm -rf tmp.git &&
65+
(
66+
GIT_ALLOW_PROTOCOL=none &&
67+
export GIT_ALLOW_PROTOCOL &&
68+
test_must_fail git -c protocol.allow=always clone --bare "$url" tmp.git &&
69+
test_must_fail git -c protocol.$proto.allow=always clone --bare "$url" tmp.git
70+
)
71+
'
72+
}
73+
74+
test_config () {
75+
desc=$1
76+
proto=$2
77+
url=$3
78+
79+
# Test clone/fetch/push with protocol.<type>.allow config
80+
test_expect_success "clone $desc (enabled with config)" '
81+
rm -rf tmp.git &&
82+
git -c protocol.$proto.allow=always clone --bare "$url" tmp.git
83+
'
84+
85+
test_expect_success "fetch $desc (enabled)" '
86+
git -C tmp.git -c protocol.$proto.allow=always fetch
87+
'
88+
89+
test_expect_success "push $desc (enabled)" '
90+
git -C tmp.git -c protocol.$proto.allow=always push origin HEAD:pushed
91+
'
92+
93+
test_expect_success "push $desc (disabled)" '
94+
test_must_fail git -C tmp.git -c protocol.$proto.allow=never push origin HEAD:pushed
95+
'
96+
97+
test_expect_success "fetch $desc (disabled)" '
98+
test_must_fail git -C tmp.git -c protocol.$proto.allow=never fetch
99+
'
100+
101+
test_expect_success "clone $desc (disabled)" '
102+
rm -rf tmp.git &&
103+
test_must_fail git -c protocol.$proto.allow=never clone --bare "$url" tmp.git
104+
'
105+
106+
# Test clone/fetch/push with protocol.user.allow and its env var
107+
test_expect_success "clone $desc (enabled)" '
108+
rm -rf tmp.git &&
109+
git -c protocol.$proto.allow=user clone --bare "$url" tmp.git
110+
'
111+
112+
test_expect_success "fetch $desc (enabled)" '
113+
git -C tmp.git -c protocol.$proto.allow=user fetch
114+
'
115+
116+
test_expect_success "push $desc (enabled)" '
117+
git -C tmp.git -c protocol.$proto.allow=user push origin HEAD:pushed
118+
'
119+
120+
test_expect_success "push $desc (disabled)" '
121+
(
122+
cd tmp.git &&
123+
GIT_PROTOCOL_FROM_USER=0 &&
124+
export GIT_PROTOCOL_FROM_USER &&
125+
test_must_fail git -c protocol.$proto.allow=user push origin HEAD:pushed
126+
)
127+
'
128+
129+
test_expect_success "fetch $desc (disabled)" '
130+
(
131+
cd tmp.git &&
132+
GIT_PROTOCOL_FROM_USER=0 &&
133+
export GIT_PROTOCOL_FROM_USER &&
134+
test_must_fail git -c protocol.$proto.allow=user fetch
135+
)
136+
'
137+
138+
test_expect_success "clone $desc (disabled)" '
139+
rm -rf tmp.git &&
140+
(
141+
GIT_PROTOCOL_FROM_USER=0 &&
142+
export GIT_PROTOCOL_FROM_USER &&
143+
test_must_fail git -c protocol.$proto.allow=user clone --bare "$url" tmp.git
144+
)
145+
'
146+
147+
# Test clone/fetch/push with protocol.allow user defined default
148+
test_expect_success "clone $desc (enabled)" '
149+
rm -rf tmp.git &&
150+
git config --global protocol.allow always &&
151+
git clone --bare "$url" tmp.git
152+
'
153+
154+
test_expect_success "fetch $desc (enabled)" '
155+
git -C tmp.git fetch
156+
'
157+
158+
test_expect_success "push $desc (enabled)" '
159+
git -C tmp.git push origin HEAD:pushed
160+
'
161+
162+
test_expect_success "push $desc (disabled)" '
163+
git config --global protocol.allow never &&
164+
test_must_fail git -C tmp.git push origin HEAD:pushed
165+
'
166+
167+
test_expect_success "fetch $desc (disabled)" '
168+
test_must_fail git -C tmp.git fetch
169+
'
170+
171+
test_expect_success "clone $desc (disabled)" '
172+
rm -rf tmp.git &&
173+
test_must_fail git clone --bare "$url" tmp.git
174+
'
175+
}
176+
177+
# test cloning a particular protocol
178+
# $1 - description of the protocol
179+
# $2 - machine-readable name of the protocol
180+
# $3 - the URL to try cloning
181+
test_proto () {
182+
test_whitelist "$@"
183+
184+
test_config "$@"
65185
}
66186

67187
# set up an ssh wrapper that will access $host/$repo in the

t/t5509-fetch-push-namespaces.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ test_description='fetch/push involving ref namespaces'
44
. ./test-lib.sh
55

66
test_expect_success setup '
7+
git config --global protocol.ext.allow user &&
78
test_tick &&
89
git init original &&
910
(

t/t5802-connect-helper.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ test_description='ext::cmd remote "connect" helper'
44
. ./test-lib.sh
55

66
test_expect_success setup '
7+
git config --global protocol.ext.allow user &&
78
test_tick &&
89
git commit --allow-empty -m initial &&
910
test_tick &&

transport.c

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -617,10 +617,81 @@ static const struct string_list *protocol_whitelist(void)
617617
return enabled ? &allowed : NULL;
618618
}
619619

620+
enum protocol_allow_config {
621+
PROTOCOL_ALLOW_NEVER = 0,
622+
PROTOCOL_ALLOW_USER_ONLY,
623+
PROTOCOL_ALLOW_ALWAYS
624+
};
625+
626+
static enum protocol_allow_config parse_protocol_config(const char *key,
627+
const char *value)
628+
{
629+
if (!strcasecmp(value, "always"))
630+
return PROTOCOL_ALLOW_ALWAYS;
631+
else if (!strcasecmp(value, "never"))
632+
return PROTOCOL_ALLOW_NEVER;
633+
else if (!strcasecmp(value, "user"))
634+
return PROTOCOL_ALLOW_USER_ONLY;
635+
636+
die("unknown value for config '%s': %s", key, value);
637+
}
638+
639+
static enum protocol_allow_config get_protocol_config(const char *type)
640+
{
641+
char *key = xstrfmt("protocol.%s.allow", type);
642+
char *value;
643+
644+
/* first check the per-protocol config */
645+
if (!git_config_get_string(key, &value)) {
646+
enum protocol_allow_config ret =
647+
parse_protocol_config(key, value);
648+
free(key);
649+
free(value);
650+
return ret;
651+
}
652+
free(key);
653+
654+
/* if defined, fallback to user-defined default for unknown protocols */
655+
if (!git_config_get_string("protocol.allow", &value)) {
656+
enum protocol_allow_config ret =
657+
parse_protocol_config("protocol.allow", value);
658+
free(value);
659+
return ret;
660+
}
661+
662+
/* fallback to built-in defaults */
663+
/* known safe */
664+
if (!strcmp(type, "http") ||
665+
!strcmp(type, "https") ||
666+
!strcmp(type, "git") ||
667+
!strcmp(type, "ssh") ||
668+
!strcmp(type, "file"))
669+
return PROTOCOL_ALLOW_ALWAYS;
670+
671+
/* known scary; err on the side of caution */
672+
if (!strcmp(type, "ext"))
673+
return PROTOCOL_ALLOW_NEVER;
674+
675+
/* unknown; by default let them be used only directly by the user */
676+
return PROTOCOL_ALLOW_USER_ONLY;
677+
}
678+
620679
int is_transport_allowed(const char *type)
621680
{
622-
const struct string_list *allowed = protocol_whitelist();
623-
return !allowed || string_list_has_string(allowed, type);
681+
const struct string_list *whitelist = protocol_whitelist();
682+
if (whitelist)
683+
return string_list_has_string(whitelist, type);
684+
685+
switch (get_protocol_config(type)) {
686+
case PROTOCOL_ALLOW_ALWAYS:
687+
return 1;
688+
case PROTOCOL_ALLOW_NEVER:
689+
return 0;
690+
case PROTOCOL_ALLOW_USER_ONLY:
691+
return git_env_bool("GIT_PROTOCOL_FROM_USER", 1);
692+
}
693+
694+
die("BUG: invalid protocol_allow_config type");
624695
}
625696

626697
void transport_check_allowed(const char *type)

0 commit comments

Comments
 (0)