Merge branch 'bw/transport-protocol-policy'

Finer-grained control of what protocols are allowed for transports
during clone/fetch/push have been enabled via a new configuration
mechanism.

* bw/transport-protocol-policy:
  http: respect protocol.*.allow=user for http-alternates
  transport: add from_user parameter to is_transport_allowed
  http: create function to get curl allowed protocols
  transport: add protocol policy config option
  http: always warn if libcurl version is too old
  lib-proto-disable: variable name fix
This commit is contained in:
Junio C Hamano 2016-12-27 00:11:41 -08:00
commit 9d540e9726
12 changed files with 361 additions and 84 deletions

View File

@ -2318,6 +2318,52 @@ pretty.<name>::
Note that an alias with the same name as a built-in format
will be silently ignored.
protocol.allow::
If set, provide a user defined default policy for all protocols which
don't explicitly have a policy (`protocol.<name>.allow`). By default,
if unset, known-safe protocols (http, https, git, ssh, file) have a
default policy of `always`, known-dangerous protocols (ext) have a
default policy of `never`, and all other protocols have a default
policy of `user`. Supported policies:
+
--
* `always` - protocol is always able to be used.
* `never` - protocol is never able to be used.
* `user` - protocol is only able to be used when `GIT_PROTOCOL_FROM_USER` is
either unset or has a value of 1. This policy should be used when you want a
protocol to be directly usable by the user but don't want it used by commands which
execute clone/fetch/push commands without user input, e.g. recursive
submodule initialization.
--
protocol.<name>.allow::
Set a policy to be used by protocol `<name>` with clone/fetch/push
commands. See `protocol.allow` above for the available policies.
+
The protocol names currently used by git are:
+
--
- `file`: any local file-based path (including `file://` URLs,
or local paths)
- `git`: the anonymous git protocol over a direct TCP
connection (or proxy, if configured)
- `ssh`: git over ssh (including `host:path` syntax,
`ssh://`, etc).
- `http`: git over http, both "smart http" and "dumb http".
Note that this does _not_ include `https`; if you want to configure
both, you must do so individually.
- any external helpers are named by their protocol (e.g., use
`hg` to allow the `git-remote-hg` helper)
--
pull.ff::
By default, Git does not create an extra merge commit when merging
a commit that is a descendant of the current commit. Instead, the

View File

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

View File

@ -21,14 +21,10 @@ require_work_tree
wt_prefix=$(git rev-parse --show-prefix)
cd_to_toplevel
# Restrict ourselves to a vanilla subset of protocols; the URLs
# we get are under control of a remote repository, and we do not
# want them kicking off arbitrary git-remote-* programs.
#
# If the user has already specified a set of allowed protocols,
# we assume they know what they're doing and use that instead.
: ${GIT_ALLOW_PROTOCOL=file:git:http:https:ssh}
export GIT_ALLOW_PROTOCOL
# Tell the rest of git that any URLs we get don't come
# directly from the user, so it can apply policy as appropriate.
GIT_PROTOCOL_FROM_USER=0
export GIT_PROTOCOL_FROM_USER
command=
branch=

View File

@ -3,6 +3,7 @@
#include "walker.h"
#include "http.h"
#include "list.h"
#include "transport.h"
struct alt_base {
char *base;
@ -160,6 +161,32 @@ static void prefetch(struct walker *walker, unsigned char *sha1)
#endif
}
static int is_alternate_allowed(const char *url)
{
const char *protocols[] = {
"http", "https", "ftp", "ftps"
};
int i;
for (i = 0; i < ARRAY_SIZE(protocols); i++) {
const char *end;
if (skip_prefix(url, protocols[i], &end) &&
starts_with(end, "://"))
break;
}
if (i >= ARRAY_SIZE(protocols)) {
warning("ignoring alternate with unknown protocol: %s", url);
return 0;
}
if (!is_transport_allowed(protocols[i], 0)) {
warning("ignoring alternate with restricted protocol: %s", url);
return 0;
}
return 1;
}
static void process_alternates_response(void *callback_data)
{
struct alternates_request *alt_req =
@ -274,17 +301,20 @@ static void process_alternates_response(void *callback_data)
struct strbuf target = STRBUF_INIT;
strbuf_add(&target, base, serverlen);
strbuf_add(&target, data + i, posn - i - 7);
warning("adding alternate object store: %s",
target.buf);
newalt = xmalloc(sizeof(*newalt));
newalt->next = NULL;
newalt->base = strbuf_detach(&target, NULL);
newalt->got_indices = 0;
newalt->packs = NULL;
while (tail->next != NULL)
tail = tail->next;
tail->next = newalt;
if (is_alternate_allowed(target.buf)) {
warning("adding alternate object store: %s",
target.buf);
newalt = xmalloc(sizeof(*newalt));
newalt->next = NULL;
newalt->base = strbuf_detach(&target, NULL);
newalt->got_indices = 0;
newalt->packs = NULL;
while (tail->next != NULL)
tail = tail->next;
tail->next = newalt;
}
}
}
i = posn + 1;

35
http.c
View File

@ -636,11 +636,25 @@ void setup_curl_trace(CURL *handle)
curl_easy_setopt(handle, CURLOPT_DEBUGDATA, NULL);
}
static long get_curl_allowed_protocols(int from_user)
{
long allowed_protocols = 0;
if (is_transport_allowed("http", from_user))
allowed_protocols |= CURLPROTO_HTTP;
if (is_transport_allowed("https", from_user))
allowed_protocols |= CURLPROTO_HTTPS;
if (is_transport_allowed("ftp", from_user))
allowed_protocols |= CURLPROTO_FTP;
if (is_transport_allowed("ftps", from_user))
allowed_protocols |= CURLPROTO_FTPS;
return allowed_protocols;
}
static CURL *get_curl_handle(void)
{
CURL *result = curl_easy_init();
long allowed_protocols = 0;
if (!result)
die("curl_easy_init failed");
@ -736,20 +750,13 @@ static CURL *get_curl_handle(void)
curl_easy_setopt(result, CURLOPT_POST301, 1);
#endif
#if LIBCURL_VERSION_NUM >= 0x071304
if (is_transport_allowed("http"))
allowed_protocols |= CURLPROTO_HTTP;
if (is_transport_allowed("https"))
allowed_protocols |= CURLPROTO_HTTPS;
if (is_transport_allowed("ftp"))
allowed_protocols |= CURLPROTO_FTP;
if (is_transport_allowed("ftps"))
allowed_protocols |= CURLPROTO_FTPS;
curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols);
curl_easy_setopt(result, CURLOPT_PROTOCOLS, allowed_protocols);
curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS,
get_curl_allowed_protocols(0));
curl_easy_setopt(result, CURLOPT_PROTOCOLS,
get_curl_allowed_protocols(-1));
#else
if (transport_restrict_protocols())
warning("protocol restrictions not applied to curl redirects because\n"
"your curl version is too old (>= 7.19.4)");
warning("protocol restrictions not applied to curl redirects because\n"
"your curl version is too old (>= 7.19.4)");
#endif
if (getenv("GIT_CURL_VERBOSE"))
curl_easy_setopt(result, CURLOPT_VERBOSE, 1L);

View File

@ -1,15 +1,12 @@
# Test routines for checking protocol disabling.
# test cloning a particular protocol
# $1 - description of the protocol
# $2 - machine-readable name of the protocol
# $3 - the URL to try cloning
test_proto () {
# Test clone/fetch/push with GIT_ALLOW_PROTOCOL whitelist
test_whitelist () {
desc=$1
proto=$2
url=$3
test_expect_success "clone $1 (enabled)" '
test_expect_success "clone $desc (enabled)" '
rm -rf tmp.git &&
(
GIT_ALLOW_PROTOCOL=$proto &&
@ -18,7 +15,7 @@ test_proto () {
)
'
test_expect_success "fetch $1 (enabled)" '
test_expect_success "fetch $desc (enabled)" '
(
cd tmp.git &&
GIT_ALLOW_PROTOCOL=$proto &&
@ -27,7 +24,7 @@ test_proto () {
)
'
test_expect_success "push $1 (enabled)" '
test_expect_success "push $desc (enabled)" '
(
cd tmp.git &&
GIT_ALLOW_PROTOCOL=$proto &&
@ -36,7 +33,7 @@ test_proto () {
)
'
test_expect_success "push $1 (disabled)" '
test_expect_success "push $desc (disabled)" '
(
cd tmp.git &&
GIT_ALLOW_PROTOCOL=none &&
@ -45,7 +42,7 @@ test_proto () {
)
'
test_expect_success "fetch $1 (disabled)" '
test_expect_success "fetch $desc (disabled)" '
(
cd tmp.git &&
GIT_ALLOW_PROTOCOL=none &&
@ -54,7 +51,7 @@ test_proto () {
)
'
test_expect_success "clone $1 (disabled)" '
test_expect_success "clone $desc (disabled)" '
rm -rf tmp.git &&
(
GIT_ALLOW_PROTOCOL=none &&
@ -62,6 +59,129 @@ test_proto () {
test_must_fail git clone --bare "$url" tmp.git
)
'
test_expect_success "clone $desc (env var has precedence)" '
rm -rf tmp.git &&
(
GIT_ALLOW_PROTOCOL=none &&
export GIT_ALLOW_PROTOCOL &&
test_must_fail git -c protocol.allow=always clone --bare "$url" tmp.git &&
test_must_fail git -c protocol.$proto.allow=always clone --bare "$url" tmp.git
)
'
}
test_config () {
desc=$1
proto=$2
url=$3
# Test clone/fetch/push with protocol.<type>.allow config
test_expect_success "clone $desc (enabled with config)" '
rm -rf tmp.git &&
git -c protocol.$proto.allow=always clone --bare "$url" tmp.git
'
test_expect_success "fetch $desc (enabled)" '
git -C tmp.git -c protocol.$proto.allow=always fetch
'
test_expect_success "push $desc (enabled)" '
git -C tmp.git -c protocol.$proto.allow=always push origin HEAD:pushed
'
test_expect_success "push $desc (disabled)" '
test_must_fail git -C tmp.git -c protocol.$proto.allow=never push origin HEAD:pushed
'
test_expect_success "fetch $desc (disabled)" '
test_must_fail git -C tmp.git -c protocol.$proto.allow=never fetch
'
test_expect_success "clone $desc (disabled)" '
rm -rf tmp.git &&
test_must_fail git -c protocol.$proto.allow=never clone --bare "$url" tmp.git
'
# Test clone/fetch/push with protocol.user.allow and its env var
test_expect_success "clone $desc (enabled)" '
rm -rf tmp.git &&
git -c protocol.$proto.allow=user clone --bare "$url" tmp.git
'
test_expect_success "fetch $desc (enabled)" '
git -C tmp.git -c protocol.$proto.allow=user fetch
'
test_expect_success "push $desc (enabled)" '
git -C tmp.git -c protocol.$proto.allow=user push origin HEAD:pushed
'
test_expect_success "push $desc (disabled)" '
(
cd tmp.git &&
GIT_PROTOCOL_FROM_USER=0 &&
export GIT_PROTOCOL_FROM_USER &&
test_must_fail git -c protocol.$proto.allow=user push origin HEAD:pushed
)
'
test_expect_success "fetch $desc (disabled)" '
(
cd tmp.git &&
GIT_PROTOCOL_FROM_USER=0 &&
export GIT_PROTOCOL_FROM_USER &&
test_must_fail git -c protocol.$proto.allow=user fetch
)
'
test_expect_success "clone $desc (disabled)" '
rm -rf tmp.git &&
(
GIT_PROTOCOL_FROM_USER=0 &&
export GIT_PROTOCOL_FROM_USER &&
test_must_fail git -c protocol.$proto.allow=user clone --bare "$url" tmp.git
)
'
# Test clone/fetch/push with protocol.allow user defined default
test_expect_success "clone $desc (enabled)" '
rm -rf tmp.git &&
git config --global protocol.allow always &&
git clone --bare "$url" tmp.git
'
test_expect_success "fetch $desc (enabled)" '
git -C tmp.git fetch
'
test_expect_success "push $desc (enabled)" '
git -C tmp.git push origin HEAD:pushed
'
test_expect_success "push $desc (disabled)" '
git config --global protocol.allow never &&
test_must_fail git -C tmp.git push origin HEAD:pushed
'
test_expect_success "fetch $desc (disabled)" '
test_must_fail git -C tmp.git fetch
'
test_expect_success "clone $desc (disabled)" '
rm -rf tmp.git &&
test_must_fail git clone --bare "$url" tmp.git
'
}
# test cloning a particular protocol
# $1 - description of the protocol
# $2 - machine-readable name of the protocol
# $3 - the URL to try cloning
test_proto () {
test_whitelist "$@"
test_config "$@"
}
# set up an ssh wrapper that will access $host/$repo in the

View File

@ -4,6 +4,7 @@ test_description='fetch/push involving ref namespaces'
. ./test-lib.sh
test_expect_success setup '
git config --global protocol.ext.allow user &&
test_tick &&
git init original &&
(

View File

@ -368,5 +368,15 @@ test_expect_success 'http-alternates cannot point at funny protocols' '
clone "$HTTPD_URL/dumb/evil.git" evil-file
'
test_expect_success 'http-alternates triggers not-from-user protocol check' '
echo "$HTTPD_URL/dumb/victim.git/objects" \
>"$evil/objects/info/http-alternates" &&
test_config_global http.followRedirects true &&
test_must_fail git -c protocol.http.allow=user \
clone $HTTPD_URL/dumb/evil.git evil-user &&
git -c protocol.http.allow=always \
clone $HTTPD_URL/dumb/evil.git evil-user
'
stop_httpd
test_done

View File

@ -4,6 +4,7 @@ test_description='ext::cmd remote "connect" helper'
. ./test-lib.sh
test_expect_success setup '
git config --global protocol.ext.allow user &&
test_tick &&
git commit --allow-empty -m initial &&
test_tick &&

View File

@ -30,5 +30,12 @@ test_expect_success 'curl limits redirects' '
test_must_fail git clone "$HTTPD_URL/loop-redir/smart/repo.git"
'
test_expect_success 'http can be limited to from-user' '
git -c protocol.http.allow=user \
clone "$HTTPD_URL/smart/repo.git" plain.git &&
test_must_fail git -c protocol.http.allow=user \
clone "$HTTPD_URL/smart-redir-perm/repo.git" redir.git
'
stop_httpd
test_done

View File

@ -664,23 +664,91 @@ static const struct string_list *protocol_whitelist(void)
return enabled ? &allowed : NULL;
}
int is_transport_allowed(const char *type)
enum protocol_allow_config {
PROTOCOL_ALLOW_NEVER = 0,
PROTOCOL_ALLOW_USER_ONLY,
PROTOCOL_ALLOW_ALWAYS
};
static enum protocol_allow_config parse_protocol_config(const char *key,
const char *value)
{
const struct string_list *allowed = protocol_whitelist();
return !allowed || string_list_has_string(allowed, type);
if (!strcasecmp(value, "always"))
return PROTOCOL_ALLOW_ALWAYS;
else if (!strcasecmp(value, "never"))
return PROTOCOL_ALLOW_NEVER;
else if (!strcasecmp(value, "user"))
return PROTOCOL_ALLOW_USER_ONLY;
die("unknown value for config '%s': %s", key, value);
}
static enum protocol_allow_config get_protocol_config(const char *type)
{
char *key = xstrfmt("protocol.%s.allow", type);
char *value;
/* first check the per-protocol config */
if (!git_config_get_string(key, &value)) {
enum protocol_allow_config ret =
parse_protocol_config(key, value);
free(key);
free(value);
return ret;
}
free(key);
/* if defined, fallback to user-defined default for unknown protocols */
if (!git_config_get_string("protocol.allow", &value)) {
enum protocol_allow_config ret =
parse_protocol_config("protocol.allow", value);
free(value);
return ret;
}
/* fallback to built-in defaults */
/* known safe */
if (!strcmp(type, "http") ||
!strcmp(type, "https") ||
!strcmp(type, "git") ||
!strcmp(type, "ssh") ||
!strcmp(type, "file"))
return PROTOCOL_ALLOW_ALWAYS;
/* known scary; err on the side of caution */
if (!strcmp(type, "ext"))
return PROTOCOL_ALLOW_NEVER;
/* unknown; by default let them be used only directly by the user */
return PROTOCOL_ALLOW_USER_ONLY;
}
int is_transport_allowed(const char *type, int from_user)
{
const struct string_list *whitelist = protocol_whitelist();
if (whitelist)
return string_list_has_string(whitelist, type);
switch (get_protocol_config(type)) {
case PROTOCOL_ALLOW_ALWAYS:
return 1;
case PROTOCOL_ALLOW_NEVER:
return 0;
case PROTOCOL_ALLOW_USER_ONLY:
if (from_user < 0)
from_user = git_env_bool("GIT_PROTOCOL_FROM_USER", 1);
return from_user;
}
die("BUG: invalid protocol_allow_config type");
}
void transport_check_allowed(const char *type)
{
if (!is_transport_allowed(type))
if (!is_transport_allowed(type, -1))
die("transport '%s' not allowed", type);
}
int transport_restrict_protocols(void)
{
return !!protocol_whitelist();
}
struct transport *transport_get(struct remote *remote, const char *url)
{
const char *helper;

View File

@ -153,10 +153,17 @@ extern int transport_summary_width(const struct ref *refs);
struct transport *transport_get(struct remote *, const char *);
/*
* Check whether a transport is allowed by the environment. Type should
* generally be the URL scheme, as described in Documentation/git.txt
* Check whether a transport is allowed by the environment.
*
* Type should generally be the URL scheme, as described in
* Documentation/git.txt
*
* from_user specifies if the transport was given by the user. If unknown pass
* a -1 to read from the environment to determine if the transport was given by
* the user.
*
*/
int is_transport_allowed(const char *type);
int is_transport_allowed(const char *type, int from_user);
/*
* Check whether a transport is allowed by the environment,
@ -164,12 +171,6 @@ int is_transport_allowed(const char *type);
*/
void transport_check_allowed(const char *type);
/*
* Returns true if the user has attempted to turn on protocol
* restrictions at all.
*/
int transport_restrict_protocols(void);
/* Transport options which apply to git:// and scp-style URLs */
/* The program to use on the remote side to send a pack */