diff --git a/README.rst b/README.rst index 13f7c02..9cc128e 100644 --- a/README.rst +++ b/README.rst @@ -182,6 +182,10 @@ Each of these options must appear first on the command line. optionally provide the path to an INI file. +``--update_hook`` + This option can be used as part of a server side update hook by adding + `git-secrets --update_hook -- "$@"` to an update script on a git server + Options for ``--install`` ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/git-secrets b/git-secrets index 11be153..03dfcb6 100755 --- a/git-secrets +++ b/git-secrets @@ -38,6 +38,7 @@ f,force --install overwrites hooks if the hook already exists l,literal --add and --add-allowed patterns are escaped so that they are literal a,allowed --add adds an allowed pattern instead of a prohibited pattern global Uses the --global git config +update_hook* update hook (internal only) commit_msg_hook* commit-msg hook (internal only) pre_commit_hook* pre-commit hook (internal only) prepare_commit_msg_hook* prepare-commit-msg hook (internal only)" @@ -61,11 +62,17 @@ load_patterns() { } load_allowed() { + local new_rev="$1" git config --get-all secrets.allowed local gitallowed="$(git rev-parse --show-toplevel)/.gitallowed" if [ -e "$gitallowed" ]; then cat $gitallowed | awk 'NF && $1!~/^#/' fi + if [ -n "${new_rev}" ]; then + git show ${new_rev}:.gitallowed 2>/dev/null | awk 'NF && $1!~/^#/' + # If there is a new commit being pushed to the server, read the .gitallowed from the new commit + # not the one that is already there + fi } # load patterns and combine them with | @@ -106,6 +113,38 @@ scan_history() { process_output $? "${output}" } + +# Scans commits that have been pushed to server through update hook +update_hook() { + local old_rev=$2 + local new_rev=$3 + + local new_branch=0 + local to_scan='' + + # Deal with weirdness on new branches. Thank you stack overflow! https://stackoverflow.com/a/19738143 + if [ "${old_rev}" = '0000000000000000000000000000000000000000' ]; then + new_branch=1 + to_scan=$(git rev-list $new_rev --not --branches=*) + fi + + local combined_patterns=$(load_combined_patterns) + + [ -z "${combined_patterns}" ] && return 0 + + # Looks for differences in commit range if not a new branch + if [ $new_branch -eq 0 ]; then + local to_scan=$(git log ${old_rev}..${new_rev} -G"${combined_patterns}" --pretty=%H) + fi + [ -z "${to_scan}" ] && return 0 + + # Scan through revisions with findings to normalize output + output=$(GREP_OPTIONS= LC_ALL=C git grep -nwHEI "${combined_patterns}" $to_scan) + process_output $? "${output}" "${new_rev}" +} + + + # Performs a git-grep, taking into account patterns and options. # Note: this function returns 1 on success, 0 on error. git_grep() { @@ -129,8 +168,8 @@ regular_grep() { # Takes into account allowed patterns, and if a bad match is found, # prints an error message and exits 1. process_output() { - local status="$1" output="$2" - local allowed=$(load_allowed) + local status="$1" output="$2" new_rev="$3" + local allowed=$(load_allowed $new_rev) case "$status" in 0) [ -z "${allowed}" ] && echo "${output}" >&2 && return 1 @@ -179,6 +218,7 @@ pre_commit_hook() { scan_with_fn_or_die "scan" "${files[@]}" } + # Determines if merging in a commit will introduce tainted history. prepare_commit_msg_hook() { case "$2,$3" in @@ -324,7 +364,7 @@ case "${COMMAND}" in --add-provider) add_config "secrets.providers" "$@" ;; --register-aws) register_aws ;; --aws-provider) aws_provider "$1" ;; - --commit_msg_hook|--pre_commit_hook|--prepare_commit_msg_hook) + --commit_msg_hook|--pre_commit_hook|--prepare_commit_msg_hook|--update_hook) ${COMMAND:2} "$@" ;; --add) diff --git a/test/update.bats b/test/update.bats new file mode 100644 index 0000000..c78045b --- /dev/null +++ b/test/update.bats @@ -0,0 +1,128 @@ +#!/usr/bin/env bats +load test_helper + +export TEST_REMOTE="$BATS_TMPDIR/test-remote.git" + +setup_remote() { + delete_remote + mkdir -p $TEST_REMOTE + cd $TEST_REMOTE + git init --bare + git config --local --add secrets.patterns '@todo' + git config --local --add secrets.patterns 'forbidden|me' + git config --local --add secrets.patterns '#hash' + cat <<-SCRIPT >> hooks/update +#!/usr/bin/env bash +$(cd $BATS_TEST_DIRNAME/..; pwd)/git-secrets --update_hook -- "\$@" +SCRIPT + chmod +x hooks/update + cd - +} + +delete_remote() { + [ -d $TEST_REMOTE ] && rm -rf $TEST_REMOTE || true +} + +alias_function() { + eval "${1}() $(declare -f ${2} | sed 1d)" +} + +alias_function _setup setup +setup() { + _setup + repo_run git config --unset-all secrets + setup_remote +} + +alias_function _teardown teardown +teardown() { + _teardown + delete_remote +} + +@test "Pushes branch contained allowed words" { + echo 'todo' > $TEST_REPO/test.txt + git add -A + git commit -m "Create test.txt" + run git push $TEST_REMOTE master + [ $status -eq 0 ] +} + +@test "fails to push branch contained secret words" { + hashes=() + + echo '@todo' > $TEST_REPO/test.txt + git add -A + git commit -m "Create test.txt" + hashes+=( $(git rev-parse HEAD) ) + + run git push $TEST_REMOTE master + [ $status -eq 1 ] + echo "$output" | grep -F "remote: ${hashes[0]}:test.txt:1:@todo" +} + +@test "fails to push branch when secret words got mixed in a commit" { + hashes=() + + cd $TEST_REPO + echo 'todo' > $TEST_REPO/test1.txt + echo '@todo' > $TEST_REPO/test2.txt + echo 'TODO' > $TEST_REPO/test3.txt + git add -A + git commit -m "Create files" + hashes+=( $(git rev-parse HEAD) ) + + run git push $TEST_REMOTE master + [ $status -eq 1 ] + echo "$output" | grep -F "remote: ${hashes[0]}:test2.txt:1:@todo" +} + +@test "fails to push branch even if secret words are fixed" { + hashes=() + + cd $TEST_REPO + echo 'todo' > $TEST_REPO/test.txt + git add -A + git commit -m "Create test.txt" + hashes+=( $(git rev-parse HEAD) ) + + echo '@todo' > $TEST_REPO/test.txt + git add -A + git commit -m "Update test.txt" + hashes+=( $(git rev-parse HEAD) ) + + echo 'todo' > $TEST_REPO/test.txt + git add -A + git commit -m "Update test.txt" + hashes+=( $(git rev-parse HEAD) ) + + run git push $TEST_REMOTE master + [ $status -eq 1 ] + echo "$output" | grep -F "remote: ${hashes[1]}:test.txt:1:@todo" +} + +@test "Pushes branch when secret words set as allowed patterns" { + hashes=() + + cd $TEST_REMOTE + git config --local --add secrets.allowed '@todo' + + cd $TEST_REPO + echo 'todo' > $TEST_REPO/test.txt + git add -A + git commit -m "Create test.txt" + hashes+=( $(git rev-parse HEAD) ) + + echo '@todo' > $TEST_REPO/test.txt + git add -A + git commit -m "Update test.txt" + hashes+=( $(git rev-parse HEAD) ) + + echo 'todo' > $TEST_REPO/test.txt + git add -A + git commit -m "Update test.txt" + hashes+=( $(git rev-parse HEAD) ) + + run git push $TEST_REMOTE master + [ $status -eq 0 ] +}