Skip to content

Instantly share code, notes, and snippets.

@ralovely
Created March 5, 2014 14:03
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save ralovely/9367737 to your computer and use it in GitHub Desktop.
Save ralovely/9367737 to your computer and use it in GitHub Desktop.
Git pre-commit hook for Ansible Vault
#!/bin/sh
#
# Pre-commit hook that verifies if all files containing 'vault' in the name
# are encrypted.
# If not, commit will fail with an error message
#
# File should be .git/hooks/pre-commit and executable
FILES_PATTERN='.*vault.*\.yml$'
REQUIRED='ANSIBLE_VAULT'
EXIT_STATUS=0
wipe="\033[1m\033[0m"
yellow='\033[1;33m'
# carriage return hack. Leave it on 2 lines.
cr='
'
for f in $(git diff --cached --name-only | grep -E $FILES_PATTERN)
do
MATCH=`grep --invert-match --no-messages $REQUIRED $f | head -n1`
if [ -z $MATCH ] ; then
UNENCRYPTED_FILES="$f$cr$UNENCRYPTED_FILES"
EXIT_STATUS=1
fi
done
if [ $EXIT_STATUS = 0 ] ; then
exit 0
else
echo '# COMMIT REJECTED'
echo '# Looks like unencrypted ansible-vault files are part of the commit:'
echo '#'
while read -r line; do
if [ -n "$line" ]; then
echo "#\t${yellow}unencrypted: $line${wipe}"
fi
done <<< "$UNENCRYPTED_FILES"
echo '#'
echo "# Please encrypt them with 'ansible-vault encrypt <file>'"
echo "# (or force the commit with '--no-verify')."
exit $EXIT_STATUS
fi
@gio-salvador
Copy link

Light changes for a more general use case:

#!/usr/bin/env bash
#
# Called by "git commit" with no arguments.  The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.

# Unset variables produce errors
set -u

if git rev-parse --verify HEAD >/dev/null 2>&1
then
	against=HEAD
else
	# Initial commit: diff against an empty tree object
	against=0000000000000000000000000000000000000000
fi

# Redirect output to stderr.
exec 1>&2

EXIT_STATUS=0

# Check that all changed *.vault files are encrypted
# read: -r do not allow backslashes to escape characters; -d delimiter
while IFS= read -r -d $'\0' file; do
	[[ "$file" != *vault* ]] && continue
	# cut gets symbols 1-2
	file_status=$(git status --porcelain -- "$file" 2>&1 | cut -c1-2)
	file_status_index=${file_status:0:1}
	file_status_worktree=${file_status:1:1}
	[[ "$file_status_worktree" != ' ' ]] && {
		echo "ERROR: vault file is modified in worktree but not added to the index: $file"
		echo "Can not check if it is properly encrypted. Use git add or git stash to fix this."
		EXIT_STATUS=1
	}
	# check is neither required nor possible for deleted files
	[[ "$file_status_index" = 'D' ]] && continue
	head -1 "$file" | grep --quiet '^\$ANSIBLE_VAULT;' || {
		echo "ERROR: non-encrypted vault file: $file"
		EXIT_STATUS=1
	}
done < <(git diff --cached --name-only -z "$against")

exit $EXIT_STATUS

@jonathansloman
Copy link

In case anyone else finds this useful - there's an issue with this code, in that it tests the contents of the file on disk. It's possible to stage a file with unencrypted data, then encrypt it on disk, but not stage it. At that point the commit will be accepted, of the unencrypted file.

This can be fixed by replacing this:

MATCH=grep --invert-match --no-messages $REQUIRED $f | head -n1

with:

MATCH=`git show :$f | grep --invert-match --no-messages $REQUIRED | head -n1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment