Skip to content

Commit bbfb4b1

Browse files
authored
Merge pull request #102 from github/block-unsigned-commits
Initial examples for pre-receive-hooks
2 parents 5788995 + 61f165b commit bbfb4b1

File tree

5 files changed

+174
-1
lines changed

5 files changed

+174
-1
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ But here it is, broken down:
1010

1111
* _api_: here's a bunch of sample code relating to the API. Subdirectories in this
1212
category are broken up by language. Do you have a language sample you'd like added?
13-
Make a pull request and we'll consider it.
13+
Make a pull request and we'll consider it.
14+
* _hooks_: wanna find out how to write a consumer for [our web hooks](https://developer.github.com/webhooks/)? The examples in this subdirectory show you how. We are open for more contributions via pull requests.
15+
* _pre-receive-hooks_: this one contains [pre-receive-hooks](https://help.github.com/enterprise/admin/guides/developer-workflow/about-pre-receive-hooks/) that can block commits on GitHub Enterprise that do not fit your requirements. Do you have more great examples? Create a pull request and we will check it out.

pre-receive-hooks/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
## Pre-receive hooks
2+
3+
### tl;dr
4+
5+
This directory contains examples for [pre-receive hooks ](https://help.github.com/enterprise/user/articles/working-with-pre-receive-hooks/) which are a [GitHub Enterprise feature](https://developer.github.com/v3/enterprise/pre_receive_hooks/) to block unwanted commits before they even reach your repository.
6+
7+
If you have a great example for a pre-receive hook you used with GitHub Enterprise that is not yet part of this directory, create a pull request and we will happily review it.
8+
9+
While blocking commits at push time using pre-receive-hooks seems like an awesome idea, there are many cases where other approaches work much better for your developers, check out the rest of this README for more info.
10+
11+
### Pre-receive hooks - The longer story
12+
13+
As of GitHub Enterprise 2.6 we [support pre-receive hooks](https://help.github.com/enterprise/user/articles/working-with-pre-receive-hooks/). [Pre-receive hooks](https://help.github.com/enterprise/user/articles/working-with-pre-receive-hooks/) run tests on code pushed to a repository to ensure contributions meet repository or organization policy. If the commits pass the tests, the push will be accepted into the repository. If the commits do not pass the tests, the push will not be accepted.
14+
15+
Your GitHub Enterprise site administrator can [create and remove pre-receive hooks](https://help.github.com/enterprise/admin/guides/developer-workflow/managing-pre-receive-hooks-on-the-github-enterprise-appliance/) for your organization or repository, and may allow organization or repository administrators to enable or disable pre-receive hooks. GitHub Enterprise allows you to [develop and test](https://help.github.com/enterprise/admin/guides/developer-workflow/creating-a-pre-receive-hook-script/) all scripts locally in a [pre-receive hook environment](https://help.github.com/enterprise/2.6/admin/guides/developer-workflow/creating-a-pre-receive-hook-environment/).
16+
17+
Examples of pre-receive hooks:
18+
* Require commit messages to follow a specific pattern or format, such as including a valid ticket number or being over a certain length.
19+
* Prevent sensitive data from being added to the repository by blocking keywords, patterns or filetypes.
20+
* Prevent a PR author from merging their own changes.
21+
* Prevent a developer from pushing commits of a different author or committer.
22+
* Prevent a developer from pushing unsigned commits.
23+
24+
You can find examples on how to write pre-receive hooks on the [Pro Git website](https://git-scm.com/book/en/v2/Customizing-Git-An-Example-Git-Enforced-Policy) and within this directory.
25+
26+
### Think twice before you deploy a pre-receive hook
27+
28+
GitHub recommends a cautious and thoughtful approach when applying mechanisms like pre-receive hooks that can block Git push operations. Blocking pushes right away typically prevents contribution and visibility into proposed changes. We think it's best that individuals collaborate with each other to identify and fix any problems after changes have been proposed. Even some of our largest customers have found that a subtle shift to [non-blocking web-hooks](https://help.github.com/enterprise/admin/guides/developer-workflow/using-webhooks-for-continuous-integration/) allowed more individuals to contribute and provided more opportunities for learning and collaboration. Combined with asynchronous collaboration workflows like [GitHubFlow](https://guides.github.com/introduction/flow/), non-blocking web-hooks typically resulted in higher-quality output.
29+
30+
That said, we understand there may be compliance or other organizational reasons to incorporate pre-receive hooks into a development workflow, e.g. ensuring that sensitive information is not included as part of pushed commits.
31+
32+
### Performance, stability and workflow implications of pre-receive hooks
33+
34+
Pre-receive hooks can have unintended effects on the performance of the GitHub Enterprise appliance and should be carefully [implemented and reviewed](https://help.github.com/enterprise/admin/guides/developer-workflow/creating-a-pre-receive-hook-script/). A misconfigured pre-receive hook may block all developers from contributing/pushing to a repository or consume all system resources on the appliance.
35+
36+
Running scripts will be automatically terminated after 5 seconds (blocking the push). Consequently, pre-receive hooks should not rely on the results of external systems that may not be always available or on any other potentially blocking resource. As any negative exit code of a pre-receive hook will reject the associated push attempt, your scripts should handle unforeseen standard input and environment variable values in a robust way.
37+
38+
When designing your scripts, also consider scenarios where many developers push at once (e.g. before lunch time). Parallel pushes will result in parallel runs of hook scripts. All parallel script runs have to compete for the same resources: CPU, memory, files, network, external systems. If any of the parallel runs needed more than 5 seconds to complete or triggered a programming error ([race condition](https://en.wikipedia.org/wiki/Race_condition#Software)), this may result in an unhappy developer whose push just got rejected for the wrong reasons.
39+
40+
**Any acceptable approach that can enforce your policy in an asynchronous fashion (see following paragraphs), will have less risk on the performance of your appliance and the effectiveness of your developer workflow.**
41+
42+
### Alternatives to pre-receive-hooks
43+
44+
Depending on your particular use case, you might be able to achieve your goals using [Protected Branches and Required Status checks](https://github.com/blog/2051-protected-branches-and-required-status-checks). Starting GitHub Enterprise 2.4, you can use Protected Branches to ensure that collaborators on your repository cannot make irrevocable changes to branches. If you [configure a branch as protected](https://help.github.com/articles/configuring-protected-branches/) it:
45+
46+
- Can't be force pushed
47+
- Can't be deleted
48+
49+
If you also enable [Required Status](https://help.github.com/articles/enabling-required-status-checks/) on a protected branch, all required checks must succeed before team members are able to merge a Pull Request. Using our [Status API](https://developer.github.com/v3/repos/statuses/) you are able to define which checks (required or optional) should be triggered upon a Pull Request submission.
50+
51+
Instead of preventing the code from being committed you can also prevent it from being deployed. To do this, you can configure your deployment process to be triggered by the Pull Request merge event. Using the information that [GitHub's webhooks](https://developer.github.com/webhooks/) provide, you'll be able to determine whether the Pull Request meets the review and CI requirements. If it does not, you can reject the deployment and post the failure information back to the Pull Request. You can learn more about delivering deployments at https://developer.github.com/guides/delivering-deployments/.
52+
53+
Instead of putting controls in place that technically enforce your policy, you can also socially enforce it. Let's say your policy prescribes that pull requests should not be merged by the author of the pull request. You can build a culture within the company which makes merging your own Pull Request unacceptable behavior. To do this, you will need to be notified when someone merges their own Pull Request so that you can revert it and educate them on why having independent review is important. You can write a simple script, and attach it to a [webhook](https://developer.github.com/webhooks/), that looks for Pull Requests that were merged by the author. When this happens you can either post a comment in the Pull Request pinging a compliance team or send an email to a mailing list reporting the transgression. Once a developer has had their Pull Request reverted, they will be unlikely to make the same mistake again. This model places trust in the developers but still allows a certain degree of control and audibility. The power of Git makes undoing any unreviewed changes easy.
54+
55+
Worth noting if you haven't already considered it, you can set up a similar mechanism on your team members' local machines using a pre-commit hook which could certainly be faster than a server-side implementation: http://git-scm.com/book/en/Customizing-Git-Git-Hooks#Client-Side-Hooks

pre-receive-hooks/always_reject.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
3+
#
4+
# Pre-receive hook that will reject all pushes
5+
# Useful for locking a repository
6+
#
7+
# More details on pre-receive hooks and how to apply them can be found on
8+
# https://help.github.com/enterprise/admin/guides/developer-workflow/managing-pre-receive-hooks-on-the-github-enterprise-appliance/
9+
#
10+
11+
echo "error: rejecting all pushes"
12+
exit 1
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env bash
2+
3+
#
4+
# Pre-receive hook that will block any new commits that contain files ending
5+
# with .gz, .zip or .tgz
6+
#
7+
# More details on pre-receive hooks and how to apply them can be found on
8+
# https://help.github.com/enterprise/admin/guides/developer-workflow/managing-pre-receive-hooks-on-the-github-enterprise-appliance/
9+
#
10+
11+
zero_commit="0000000000000000000000000000000000000000"
12+
13+
# Do not traverse over commits that are already in the repository
14+
# (e.g. in a different branch)
15+
# This prevents funny errors if pre-receive hooks got enabled after some
16+
# commits got already in and then somebody tries to create a new branch
17+
# If this is unwanted behavior, just set the variable to empty
18+
excludeExisting="--not --all"
19+
20+
while read oldrev newrev refname; do
21+
# echo "payload"
22+
echo $refname $oldrev $newrev
23+
24+
# branch or tag get deleted
25+
if [ "$newrev" = "$zero_commit" ]; then
26+
continue
27+
fi
28+
29+
# Check for new branch or tag
30+
if [ "$oldrev" = "$zero_commit" ]; then
31+
span=`git rev-list $newrev $excludeExisting`
32+
else
33+
span=`git rev-list $oldrev..$newrev $excludeExisting`
34+
fi
35+
36+
for COMMIT in $span;
37+
do
38+
for FILE in `git log -1 --name-only --pretty=format:'' $COMMIT`;
39+
do
40+
case $FILE in
41+
*.zip|*.gz|*.tgz )
42+
echo "Hello there! We have restricted committing that filetype. Please see Dave in IT to discuss alternatives."
43+
exit 1
44+
;;
45+
esac
46+
done
47+
done
48+
done
49+
exit 0
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env bash
2+
3+
#
4+
# Pre-receive hook that will block any unsigned commits and tagswhen pushed to a GitHub Enterprise repository
5+
# The script will not actually validate the GPG signature (would need access to PKI)
6+
# but just checks whether all new commits and tags have been signed
7+
#
8+
# More details on pre-receive hooks and how to apply them can be found on
9+
# https://help.github.com/enterprise/admin/guides/developer-workflow/managing-pre-receive-hooks-on-the-github-enterprise-appliance/
10+
#
11+
# More details on GPG commit and tag signing can be found on
12+
# https://help.github.com/articles/signing-commits-using-gpg/
13+
#
14+
15+
zero_commit="0000000000000000000000000000000000000000"
16+
17+
# we have to change the home directory of GPG
18+
# as in the default environment, /root/.gnupg is not writeable
19+
export GNUPGHOME=/tmp/
20+
21+
# Do not traverse over commits that are already in the repository
22+
# (e.g. in a different branch)
23+
# This prevents funny errors if pre-receive hooks got enabled after some
24+
# commits got already in and then somebody tries to create a new branch
25+
# If this is unwanted behavior, just set the variable to empty
26+
excludeExisting="--not --all"
27+
28+
while read oldrev newrev refname; do
29+
# echo "payload"
30+
echo $refname $oldrev $newrev
31+
32+
# branch or tag get deleted
33+
if [ "$newrev" = "$zero_commit" ]; then
34+
continue
35+
fi
36+
37+
# Check for new branch or tag
38+
if [ "$oldrev" = "$zero_commit" ]; then
39+
span=`git rev-list $newrev $excludeExisting`
40+
else
41+
span=`git rev-list $oldrev..$newrev $excludeExisting`
42+
fi
43+
44+
for COMMIT in $span;
45+
do
46+
signed=$(git verify-commit $COMMIT 2>&1 | grep "gpg: Signature made")
47+
if test -n "$signed"; then
48+
echo Commit $COMMIT was signed by a GPG key: $signed
49+
else
50+
echo Commit $COMMIT was not signed by a GPG key, rejecting push
51+
exit 1
52+
fi
53+
done
54+
done
55+
exit 0

0 commit comments

Comments
 (0)