From: Lars Schneider <larsxschneider@gmail.com>
To: Antoine Queru <antoine.queru@ensimag.grenoble-inp.fr>
Cc: git@vger.kernel.org, william.duclot@ensimag.grenoble-inp.fr,
simon.rabourg@ensimag.grenoble-inp.fr,
francois.beutin@ensimag.grenoble-inp.fr, rsbecker@nexbridge.com,
aaron@schrab.com, gitster@pobox.com,
Matthieu.Moy@grenoble-inp.fr, peff@peff.net
Subject: Re: [PATCH] push: deny policy to prevent pushes to unwanted remotes.
Date: Thu, 2 Jun 2016 11:30:33 -0400 [thread overview]
Message-ID: <C7CBA69F-64BC-4710-8FF7-5CDC2A59E1E5@gmail.com> (raw)
In-Reply-To: <20160530104501.4402-1-antoine.queru@ensimag.grenoble-inp.fr>
> On 30 May 2016, at 06:45, Antoine Queru <antoine.queru@ensimag.grenoble-inp.fr> wrote:
>
> Currently, a user wanting to prevent accidental pushes to the wrong remote
> has to create a pre-push hook.
> The feature offers a configuration to allow users to prevent accidental pushes
> to the wrong remote. The user may define a list of whitelisted remotes, a list
> of blacklisted remotes and a default policy ("allow" or "deny"). A push
> is denied if the remote is explicitely blacklisted or if it isn't
> whitelisted and the default policy is "deny".
>
> This feature is intended as a safety net more than a real security, the user
> will always be able to modify the config if he wants to. It is here for him to
> consciously restrict his push possibilities. For example, it may be useful
> for an unexperimented user fearing to push to the wrong remote, or for
> companies wanting to avoid unintentionnal leaking of private code on public
> repositories.
Thanks for working on this feature. Unfortunately I won't be able to test and review it before June 14. I am traveling without laptop and only very sporadic internet access :)
One thing that I noticed already: I think a custom warning/error message for rejected pushes would be important because, as you wrote above, this feature does not provide real security. That means if a push is rejected for someone in an organization then the user needs to understand what is going on. E.g. in my organization I would point the user to the open source contribution guidelines etc.
Thanks,
Lars
>
> By default the whitelist/blacklist feature is disabled since the default policy
> is "allow".
>
> Signed-off-by: Antoine Queru <antoine.queru@ensimag.grenoble-inp.fr>
> Signed-off-by: Francois Beutin <francois.beutin@ensimag.grenoble-inp.fr>
> Signed-off-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
> ---
> This is the first implementation of the feature proposed in SoCG 2016.
> The conversation about it can be found here:
> http://thread.gmane.org/gmane.comp.version-control.git/295166
> and http://thread.gmane.org/gmane.comp.version-control.git/289749.
> We have included in cc all the participants in the previous conversation.
> Documentation/config.txt | 17 +++++++
> Documentation/git-push.txt | 32 +++++++++++++
> builtin/push.c | 75 ++++++++++++++++++++++++++++---
> t/t5544-push-whitelist-blacklist.sh | 90 +++++++++++++++++++++++++++++++++++++
> 4 files changed, 209 insertions(+), 5 deletions(-)
> create mode 100755 t/t5544-push-whitelist-blacklist.sh
>
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 53f00db..1478ce3 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -2517,6 +2517,23 @@ remote.pushDefault::
> `branch.<name>.remote` for all branches, and is overridden by
> `branch.<name>.pushRemote` for specific branches.
>
> +remote.pushBlacklisted::
> + The list of remotes the user is forbidden to push to.
> + See linkgit:git-push[1]
> +
> +remote.pushWhitelisted::
> + The list of remotes the user is allowed to push to.
> + See linkgit:git-push[1]
> +
> +remote.pushDefaultPolicy::
> + Can be set to either 'allow' or 'deny', defines the policy to adopt
> + when the user tries to push to a remote not in the whitelist or
> + the blacklist. Defaults to 'allow'.
> +
> +remote.pushDenyMessage::
> + The message printed when pushing to a forbidden remote.
> + Defaults to "Pushing to this remote has been forbidden".
> +
> remote.<name>.url::
> The URL of a remote repository. See linkgit:git-fetch[1] or
> linkgit:git-push[1].
> diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
> index cf6ee4a..644bfde 100644
> --- a/Documentation/git-push.txt
> +++ b/Documentation/git-push.txt
> @@ -368,6 +368,38 @@ reason::
> refs, no explanation is needed. For a failed ref, the reason for
> failure is described.
>
> +
> +Note about denying pushes to the wrong remotes
> +----------------------------------------------
> +
> +If you fear accidental pushes to the wrong remotes, you can use the
> +blacklist/whitelist feature. The goal is to catch pushes to unwanted
> +remotes before they happen.
> +
> +The simplest way to forbid pushes to a remote is to add its url in the
> +config file under the 'remote.pushBlacklisted' variable.
> +A more restrictive way is to change 'remote.pushDefaultPolicy' to 'deny',
> +thus denying pushes to every remote not whitelisted. You can then add
> +the right remotes under 'remote.pushWhitelisted'.
> +
> +You can also configure more advanced acceptation/denial behavior following
> +this rule: the more the url in the config prefixes the asked url the more
> +priority it has.
> +
> +For example, if we set up the configuration variables like this:
> + git config --add remote.pushBlacklisted repository.com
> + git config --add remote.pushWhitelisted repository.com/Special_Folder
> +Any push of this form will be accepted:
> + git push repository.com/Special_Folder/foo
> +While those ones for example will be denied:
> + git push repository.com/Normal_Folder/bar
> +
> +Additionally, you can configure the message printed when a push is denied
> +with the 'remote.pushDenyMessage' configuration variable.
> +
> +An error will be raised if the url is blacklisted and whitelisted at the same.
> +
> +
> Note about fast-forwards
> ------------------------
>
> diff --git a/builtin/push.c b/builtin/push.c
> index 4e9e4db..0aa1f7d 100644
> --- a/builtin/push.c
> +++ b/builtin/push.c
> @@ -11,6 +11,8 @@
> #include "submodule.h"
> #include "submodule-config.h"
> #include "send-pack.h"
> +#include "urlmatch.h"
> +#include "url.h"
>
> static const char * const push_usage[] = {
> N_("git push [<options>] [<repository> [<refspec>...]]"),
> @@ -353,6 +355,68 @@ static int push_with_options(struct transport *transport, int flags)
> return 1;
> }
>
> +/*
> + * TODO: the shorter scp-like syntax for the SSH protocol isn't recognize
> + * with url_normalize
> + */
> +static const char *string_url_normalize(const char *repo_name)
> +{
> + if (starts_with(repo_name, "file://"))
> + return repo_name + 7;
> + if (is_url(repo_name)) {
> + struct url_info url_infos;
> + url_normalize(repo_name, &url_infos);
> + return url_infos.url + url_infos.host_off;
> + }
> + return repo_name;
> +}
> +
> +static int longest_prefix_size(const char *repo_to_push, const struct string_list *list)
> +{
> + struct string_list_item *item_curr;
> + int max_size = 0;
> + if (!list)
> + return 0;
> + for_each_string_list_item(item_curr, list) {
> + const char *repo_curr = string_url_normalize(item_curr->string);
> + if (starts_with(repo_to_push, repo_curr) &&
> + max_size < strlen(repo_curr)){
> + max_size = strlen(repo_curr);
> + if (strlen(repo_to_push) == max_size)
> + break;
> + }
> + }
> + return max_size;
> +}
> +
> +static void block_unauthorized_url(const char *repo)
> +{
> + const char *default_policy, *deny_message;
> + const struct string_list *whitelist, *blacklist;
> + int whitelist_match_size, blacklist_match_size;
> + const char* repo_normalize = string_url_normalize(repo);
> + if (git_config_get_value("remote.pushDefaultPolicy", &default_policy))
> + default_policy = "allow";
> + if (git_config_get_value("remote.pushDenyMessage", &deny_message))
> + deny_message = "Pushing to this remote is forbidden";
> + whitelist = git_config_get_value_multi("remote.pushWhitelisted");
> + blacklist = git_config_get_value_multi("remote.pushBlacklisted");
> +
> + whitelist_match_size = longest_prefix_size(repo_normalize, whitelist);
> + blacklist_match_size = longest_prefix_size(repo_normalize, blacklist);
> +
> + if (whitelist_match_size < blacklist_match_size)
> + die(_("%s"), deny_message);
> + if (whitelist_match_size == blacklist_match_size) {
> + if (whitelist_match_size)
> + die(_("%s cannot be whitelisted and blacklisted at the same time"), repo);
> + if (!strcmp(default_policy, "deny"))
> + die(_("%s"), deny_message);
> + if (strcmp(default_policy, "allow"))
> + die(_("Unrecognized value for remote.pushDefaultPolicy, should be 'allow' or 'deny'"));
> + }
> +}
> +
> static int do_push(const char *repo, int flags)
> {
> int i, errs;
> @@ -404,15 +468,16 @@ static int do_push(const char *repo, int flags)
> url_nr = push_url_of_remote(remote, &url);
> if (url_nr) {
> for (i = 0; i < url_nr; i++) {
> - struct transport *transport =
> - transport_get(remote, url[i]);
> + struct transport *transport;
> + block_unauthorized_url(url[i]);
> + transport = transport_get(remote, url[i]);
> if (push_with_options(transport, flags))
> errs++;
> }
> } else {
> - struct transport *transport =
> - transport_get(remote, NULL);
> -
> + struct transport *transport;
> + block_unauthorized_url(remote->url[0]);
> + transport = transport_get(remote, NULL);
> if (push_with_options(transport, flags))
> errs++;
> }
> diff --git a/t/t5544-push-whitelist-blacklist.sh b/t/t5544-push-whitelist-blacklist.sh
> new file mode 100755
> index 0000000..e0683d6
> --- /dev/null
> +++ b/t/t5544-push-whitelist-blacklist.sh
> @@ -0,0 +1,90 @@
> +#!/bin/sh
> +
> +test_description='push allowed/denied depending on config options'
> +
> +. ./test-lib.sh
> +
> +test_expect_success 'setup' '
> + test_commit commit &&
> + git init --bare wlisted1 &&
> + git init --bare wlisted1/folder &&
> + git init --bare wlisted1/folder/blistedfolder &&
> + git init --bare wlisted1/folder/blistedfolder/folder &&
> + git init --bare wlisted2 &&
> + git init --bare blisted1 &&
> + git init --bare blisted1/folder &&
> + git init --bare blisted2 &&
> + git init --bare untracked &&
> + git init --bare repo &&
> + git remote add wlisted wlisted1 &&
> + git remote add wlisted2 wlisted2 &&
> + git remote add blisted blisted1 &&
> + git remote add blisted2 blisted2 &&
> + git remote add repo repo &&
> + git config --add remote.pushWhitelisted wlisted1 &&
> + git config --add remote.pushWhitelisted wlisted2 &&
> + git config --add remote.pushWhitelisted repo &&
> + git config --add remote.pushBlacklisted blisted1 &&
> + git config --add remote.pushBlacklisted blisted2 &&
> + git config --add remote.pushBlacklisted repo &&
> + git config --add remote.pushBlacklisted wlisted1/folder/blistedfolder
> +'
> +
> +test_expect_success 'whitelist/blacklist with default pushDefaultPolicy' '
> + git push wlisted1 HEAD &&
> + git push wlisted2 HEAD &&
> + test_must_fail git push blisted1 HEAD &&
> + test_must_fail git push blisted2 HEAD &&
> + git push untracked HEAD
> +'
> +
> +test_expect_success 'whitelist/blacklist with allow pushDefaultPolicy' '
> + test_config remote.pushDefaultPolicy allow &&
> + git push wlisted1 HEAD &&
> + git push wlisted2 HEAD &&
> + test_must_fail git push blisted1 HEAD &&
> + test_must_fail git push blisted2 HEAD &&
> + git push untracked HEAD
> +'
> +
> +test_expect_success 'whitelist/blacklist with deny pushDefaultPolicy' '
> + test_config remote.pushDefaultPolicy deny &&
> + git push wlisted1 HEAD &&
> + git push wlisted2 HEAD &&
> + test_must_fail git push blisted1 HEAD &&
> + test_must_fail git push blisted2 HEAD &&
> + test_must_fail git push untracked HEAD
> +'
> +
> +test_expect_success 'remote is whitelisted and blacklisted at the same time exception' '
> + test_must_fail git push repo HEAD 2> result &&
> + echo "fatal: repo cannot be whitelisted and blacklisted at the same time" > expected &&
> + test_cmp result expected
> +'
> +
> +test_expect_success 'remote rejected default message' '
> + test_must_fail git push blisted1 HEAD 2> result &&
> + echo "fatal: Pushing to this remote is forbidden" > expected &&
> + test_cmp result expected
> +'
> +
> +test_expect_success 'remote rejected custom message' '
> + test_config remote.pushDenyMessage "denied" &&
> + test_must_fail git push blisted1 HEAD 2> result &&
> + echo "fatal: denied" > expected &&
> + test_cmp result expected
> +'
> +
> +test_expect_success 'push accepted/rejected depending on config precision' '
> + test_config remote.pushDefaultPolicy deny &&
> + git push wlisted1/folder HEAD &&
> + test_must_fail git push blisted1/folder HEAD &&
> + test_must_fail git push wlisted1/folder/blistedfolder/folder HEAD
> +'
> +
> +test_expect_success 'unsetup' '
> + test_unconfig remote.pushBlackListed &&
> + test_unconfig remote.pushWhiteListed
> +'
> +
> +test_done
> --
> 2.8.2.627.gd450e06.dirty
>
next prev parent reply other threads:[~2016-06-02 15:30 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-05-30 10:45 [PATCH] push: deny policy to prevent pushes to unwanted remotes Antoine Queru
2016-05-30 13:19 ` Matthieu Moy
2016-05-30 15:30 ` Francois Beutin
2016-06-02 15:30 ` Lars Schneider [this message]
2016-06-06 14:00 ` Antoine Queru
2016-06-15 19:31 ` Lars Schneider
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=C7CBA69F-64BC-4710-8FF7-5CDC2A59E1E5@gmail.com \
--to=larsxschneider@gmail.com \
--cc=Matthieu.Moy@grenoble-inp.fr \
--cc=aaron@schrab.com \
--cc=antoine.queru@ensimag.grenoble-inp.fr \
--cc=francois.beutin@ensimag.grenoble-inp.fr \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=peff@peff.net \
--cc=rsbecker@nexbridge.com \
--cc=simon.rabourg@ensimag.grenoble-inp.fr \
--cc=william.duclot@ensimag.grenoble-inp.fr \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).