Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Git-crypt remove user.
#!/usr/bin/env bash
#
# Script to remove GPG user (recipient) with git-crypt
#
# It will re-initialize git-crypt for the repository and re-add all keys except
# the one requested for removal.
#
# Note: You still need to change all your secrets to fully protect yourself.
# Removing a user will prevent them from reading future changes but they will
# still have a copy of the data up to the point of their removal.
#
# Usage: see 'git-crypt-rm-gpg-user.sh -h'
# git-crypt-rm-gpg-user.sh -l : list GPG keys configured within git-crypt for the current directory
# git-crypt-rm-gpg-user.sh -r <FULL-GPG-FINGERPRINT> : remove specified key from git-crypt configuration
# Ex: remove-gpg-user.sh -r 3BC18383F838C0B815B961480F8CAF5467D
#
# Typical workflow (assuming the script is placed in ~/bin/) :
#
# cd /path/to/protected/repo
# git checkout -b git-crypt-remove
# git-crypt-rm-gpg-user.sh -l # List configured GPG keys
# git-crypt-rm-gpg-user.sh -r <KEYID>
# git push origin git-crypt-remove # publish the branch
#
# Merge (sync with your collaborators for the appropriate timing):
# git checkout master # or devel or whatever main branch
# git-crypt lock
# git merge git-crypt-remove
# # release the repository
#
# Pulling for your collaborators
# cd /path/to/protected/repo
# git-crypt lock
# git pull origin
#
# You can check the full, 64-bit (8-byte) key ID for a key within your keyring with
# gpg -k [email | pattern]
# gpg -k --with-colons [email | pattern | id] | awk -F: '/^pub:/ { print $5 }'
# See https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob_plain;f=doc/DETAILS
#
# The script will create multiple commits to your repo. Feel free to squash them
# all down to one.
#
# Based on https://github.com/AGWA/git-crypt/issues/47#issuecomment-212734882
#
#
# GIST source https://gist.github.com/glogiotatidis/e0ab45ed5575a9d7973390dace0552b0
# amended to work on Mac OS and with cosmetic changes for a more convenient interface in
# https://gist.github.com/Falkor/7b29f16f5f79404fe41476be0d992783
# Script is now shellcheck compliant and safer: https://gist.github.com/thomsh/ed14fa82cf43a6b283c1eea9574fb76e
#################
set -eo pipefail
KEY_TO_REMOVE=
KEY_FINGERPRINT=
# CMD_PREFIX=
# HELPERS
print_error_and_exit () {
echo "*** ERROR *** $*"
echo "Usage: see '$(basename $0) -h'"
exit 1
}
really_continue() {
echo "/!\\ WARNING: $*"
echo "Are you sure you want to continue? [Y|n]"
read -r ans
case $ans in
n*|N*) exit 1;;
esac
}
print_usage () {
cat <<EOF
$(basename $0): Remove a given GPG key from a git-crypt enabled repository.
Options:
-l : list GPG keys configured within git-crypt
[-n] -r <FULL-GPG-FINGERPRINT> : remove key from git-crypt configuration
Typical workflow:
cd /path/to/protected/repo
git checkout -b git-crypt-remove
$(basename $0) -l # List configured GPG keys
$(basename $0) -r <KEYID>
You can check the full, 64-bit (8-byte) key ID for a key within your keyring with
gpg -k [email | pattern]
gpg -k --with-colons [email | pattern | id] | awk -F: '/^pub:/ { print \$5 }'
See https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob_plain;f=doc/DETAILS
EOF
}
###
# Print info for a given key from your keyring
##
print_key_info() {
[ -z "${1:-}" ] && print_error_and_exit "${FUNCNAME[0]} missing text argument"
local key
local gpgid
local fpr
key="$1"
gpgid="$(gpg --list-keys --with-colons "${key}" | awk -F: '/^pub:/ { print $5 }')"
fpr="$(gpg -k --with-colons "${key}" | awk -F: '/^fpr:/ { print $10; exit; }')" # Only first fpr
echo "==== Key Fingerprint: ${fpr} ==="
echo "==== Long GPG ID: ${gpgid}"
gpg --list-key "$key" || print_error_and_exit "Couldn't find info about $key"
export KEY_FINGERPRINT="$fpr"
}
###
# List GPG keys configured for protecting this repository
##
list_git_crypt_keys() {
for f in .git-crypt/keys/default/0/*.gpg; do
# key="$(basename "${f}" .gpg)"
print_key_info "$(basename "${f}" .gpg)"
# gpgid=$(gpg --list-keys --with-colons $key | awk -F: '/^pub:/ { print $5 }')
# echo "==== Key Fingerprint: ${key} ==="
# echo "==== Long GPG ID: ${gpgid}"
# gpg --list-key "$key" || print_error_and_exit "Couldn't find info about $key"
done
}
##### Let's go #####
# Check for options
while [ $# -ge 1 ]; do
case $1 in
-h | --help) print_usage; exit 0;;
-l | --list) list_git_crypt_keys; exit 0;;
-r | --remove) shift; KEY_TO_REMOVE=$1;;
# -n | --dry-run) CMD_PREFIX=echo ;;
esac
shift
done
if [ -z "${KEY_TO_REMOVE:-}" ];then
print_error_and_exit "No key to remove has been indicated"
fi
print_key_info "${KEY_TO_REMOVE}"
if [ -z "${KEY_FINGERPRINT:-}" ]; then
print_error_and_exit "Unable to retrieve key fingerprint"
fi
if [ -z "$(command -v rsync)" ]; then
print_error_and_exit "This script use rsync, sadly rsync is not found on your system"
fi
really_continue "About to remove the GPG Key ID ${KEY_TO_REMOVE} from Git-crypt configuration"
set -x # enable debug just in case we need to dig
set -euo pipefail # enforce mode with nounset (-u)
# Below code adapted from @glogiotatidis - https://gist.github.com/glogiotatidis/e0ab45ed5575a9d7973390dace0552b0
TMPDIR="$(mktemp -d)"
CURRENT_DIR="$(git rev-parse --show-toplevel)"
BASENAME="$(basename "$(pwd)")"
TMPFILE=list-encrypted-files.txt
cat <<EOF
TMPDIR=${TMPDIR}
CURRENT_DIR=${CURRENT_DIR}
EOF
# Unlock the directory, we need to copy encrypted versions of the files
git crypt unlock
# Work on copy.
cp -a -- "$(pwd)" "$TMPDIR"
pushd "$TMPDIR/$BASENAME"
git stash # stash potential typechange
# Remove encrypted files and git-crypt
git crypt status -e |sed -E 's/^\s+encrypted: //g' > "${TMPFILE}"
if [ -s "${TMPFILE}" ]; then
awk '{print $2}' ${TMPFILE} | xargs rm
#xargs -I {} -- rm -- {} < "${TMPFILE}"
git commit -a -m "Remove encrypted files"
fi
rm -rf -- .git-crypt
git commit -m "Remove git-crypt (in particular configuration related to key ${KEY_TO_REMOVE})" .git-crypt
rm -rf .git/git-crypt
# Re-initialize git crypt
git crypt init
# Add existing users, except the key to remove
find "${CURRENT_DIR}/.git-crypt/keys/default/0" -type f -iname '*gpg' -print|while read -r keyfilename
do
basename="$(basename "$keyfilename")"
key="${basename%.*}"
if [[ "$key" == "$KEY_FINGERPRINT" ]]; then
echo "ignoring key ${KEY_FINGERPRINT} to be removed"
continue
fi
# check if the key is expired - second field is 'e'
# See https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob_plain;f=doc/DETAILS
expired="$(gpg --with-colons --list-keys --with-fingerprint "$key" | awk -F: '/^pub:/ { print $2; }')"
if [[ "${expired}" == "e" ]]; then
echo "/!\\ WARNING: key $key expired thus not integrated in the git-crypt keyring"
echo "/!\\ WARNING: key details: "
print_key_info "$key"
continue
fi
git crypt add-gpg-user "$key"
done
cd "${CURRENT_DIR}"
for encrypted_file in $(awk '{print $2}' ${TMPDIR}/${BASENAME}/${TMPFILE}); do
rsync -rp -R "${encrypted_file}" "${TMPDIR}/${BASENAME}"
done
cd "${TMPDIR}/${BASENAME}"
for encrypted_file in $(awk '{print $2}' ${TMPFILE}); do
git add "${encrypted_file}"
done
git commit -m "New encrypted files" || true
popd
git crypt lock
git pull "${TMPDIR}/${BASENAME}"
rm -rf -- "${TMPDIR}"
@thomsh

This comment has been minimized.

Copy link

thomsh commented Mar 11, 2020

Thx, I confirm it work, (Few file in a repository, no space in file name etc)
I fork your gist to make a safer version & shellcheck compliant. This should handle all kind of file name etc
https://gist.github.com/thomsh/ed14fa82cf43a6b283c1eea9574fb76e

@Falkor

This comment has been minimized.

Copy link
Owner Author

Falkor commented Mar 11, 2020

Excellent, thanks for the huge improvement!
If that's OK for you, I'll merge your gist with mine.

@thomsh

This comment has been minimized.

Copy link

thomsh commented Mar 11, 2020

Sure do what ever you want with this :) !
git-crypt is awesome, don't want to stop using git-crypt for this lack of features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.