From 73396c8a4d6814618c686bf95bb806bce66c9136 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Mon, 7 Jul 2014 20:30:16 -0400 Subject: [PATCH] Initial check in --- .gitignore | 1 - bin/blackbox_common.sh | 219 ++++++++++++++++++++++++++++++ bin/blackbox_edit_end.sh | 25 ++++ bin/blackbox_edit_start.sh | 29 ++++ bin/blackbox_postdeploy.sh | 26 ++++ bin/blackbox_register_new_file.sh | 53 ++++++++ bin/blackbox_update_all_files.sh | 60 ++++++++ bin/start | 21 +++ 8 files changed, 433 insertions(+), 1 deletion(-) create mode 100755 bin/blackbox_common.sh create mode 100755 bin/blackbox_edit_end.sh create mode 100755 bin/blackbox_edit_start.sh create mode 100755 bin/blackbox_postdeploy.sh create mode 100755 bin/blackbox_register_new_file.sh create mode 100755 bin/blackbox_update_all_files.sh create mode 100755 bin/start diff --git a/.gitignore b/.gitignore index 51cbe85..03b2136 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ __pycache__/ # Distribution / packaging .Python env/ -bin/ build/ develop-eggs/ dist/ diff --git a/bin/blackbox_common.sh b/bin/blackbox_common.sh new file mode 100755 index 0000000..daaeaf4 --- /dev/null +++ b/bin/blackbox_common.sh @@ -0,0 +1,219 @@ + +# +# Common constants and functions used by the blackbox_* utilities. +# + +KEYRINGDIR=keyrings/live +BB_ADMINS="${KEYRINGDIR}/blackbox-admins.txt" +BB_FILES="${KEYRINGDIR}/blackbox-files.txt" +SECRING="${KEYRINGDIR}/secring.gpg" +PUBRING="${KEYRINGDIR}/pubring.gpg" + +# Exit with error if the environment is not right. +function fail_if_bad_environment() { + # Current checked: + # Are we in the base directory. + + # Are we in the base directory. + if [[ ! $(pwd) =~ \/puppet$ ]]; then + echo 'ERROR: Please run this script from the base directory.' + echo 'Exiting...' + exit 1 + fi +} + +# Exit with error if a file exists. +function fail_if_exists() { + if [[ -f "$1" ]]; then + echo ERROR: "$1" exists. "$2" + echo Exiting... + exit 1 + fi +} + +# Exit with error if a file is missing. +function fail_if_not_exists() { + if [[ ! -f "$1" ]]; then + echo ERROR: "$1" not found. "$2" + echo Exiting... + exit 1 + fi +} + +# Exit with error if filename is not registered on blackbox list. +function fail_if_not_on_cryptlist() { + if ! grep -s -q "$name" "$BB_FILES" ; then + echo 'ERROR: Please run this script from the base directory.' + echo 'Exiting...' + exit 1 + fi +} + +# Exit with error if keychain contains secret keys. +function fail_if_keychain_has_secrets() { + if [[ -s ${SECRING} ]]; then + echo 'ERROR: The file' "$SECRING" 'should be empty.' + echo 'Did someone accidentally add this private key to the ring?' + echo 'Exiting...' + exit 1 + fi +} + +# Output the unencrypted filename. +function get_unencrypted_filename() { + echo $(dirname "$1")/$(basename "$1" .gpg) | sed -e 's#^\./##' +} + +# Output the encrypted filename. +function get_encrypted_filename() { + echo $(dirname "$1")/$(basename "$1" .gpg).gpg | sed -e 's#^\./##' +} + +# Prepare keychain for use. +function prepare_keychain() { + echo '========== Importing keychain: START' + gpg --import "${PUBRING}" 2>&1 | egrep -v 'not changed$' + echo '========== Importing keychain: DONE' +} + +# Add file to list of encrypted files. +function add_filename_to_cryptlist() { + # If the name is already on the list, this is a no-op. + # However no matter what the datestamp is updated. + local name + name="$1" + + if grep -s -q "$name" "$BB_FILES" ; then + echo ========== File is registered. No need to add to list. + else + echo ========== Adding file to list. + touch "$BB_FILES" + sort -u -o "$BB_FILES" <(echo "$name") "$BB_FILES" + fi +} + +# Print out who the current BB ADMINS are: +function disclose_admins() { + echo ========== blackbox administrators are: + cat "$BB_ADMINS" +} + +# Encrypt file, overwriting .gpg if it exists. +function encrypt_file() { + local unencrypted + local encrypted + unencrypted="$1" + encrypted="$2" + + echo "========== Encrypting: $unencrypted" + gpg --yes --trust-model=always --encrypt -o "$encrypted" $(awk '{ print "-r" $1 }' < "$BB_ADMINS") "$unencrypted" + echo '========== Encrypting: DONE' +} + +# Decrypt .gpg file, asking "yes/no" before overwriting unencrypted file. +function decrypt_file() { + local encrypted + local unencrypted + encrypted="$1" + unencrypted="$2" + + echo "========== EXTRACTING $unencrypted" + gpg -q --decrypt -o "$unencrypted" "$encrypted" +} + +# Decrypt .gpg file, overwriting unencrypted file if it exists. +function decrypt_file_overwrite() { + local encrypted + local unencrypted + local old_hash + local new_hash + encrypted="$1" + unencrypted="$2" + + if [[ -f "$unencrypted" ]]; then + old_hash=$(md5sum < "$unencrypted"| awk '{ print $1 }') + else + old_hash=unmatchable + fi + gpg --yes -q --decrypt -o "$unencrypted" "$encrypted" + new_hash=$(md5sum < "$unencrypted"| awk '{ print $1 }') + if [[ $old_hash != $new_hash ]]; then + echo "========== EXTRACTED $unencrypted" + fi +} + +# Shred a file. If shred binary does not exist, delete it. +function shred_file() { + local name + local CMD + local OPT + name="$1" + + if which shred >/dev/null ; then + CMD=shred + OPT=-u + else + CMD=rm + OPT=-f + fi + + $CMD $OPT "$name" +} + +# $1 is the name of a file that contains a list of files. +# For each filename, output the individual subdirectories +# leading up to that file. i.e. one one/two one/two/three +function enumerate_subdirs() { + local listfile + local dir + local filename + listfile="$1" + + while read filename; do + dir=$(dirname "$filename") + while [[ $dir != '.' && $dir != '/' ]]; do + echo $dir + dir=$(dirname $dir) + done + done <"$listfile" | sort -u +} + +# Are we in git, hg, or other repo? +function which_vcs() { + if [[ -d .git || git rev-parse --git-dir > /dev/null 2>&1 ]]; then + echo git + elif [[ -d .hg || hg status >/dev/null 2>&1 ]]; then + echo hg + else + echo other + fi +} + +# Is this file in the current repo? +function is_in_vcs() { + is_in_$(which_vcs) """$@""" +} + +# Is this file in mercurial? +function is_in_hg() { + local filename + filename="$1" + + if hg locate "$filename" ; then + echo true + else + echo false + fi +} + +# Is this file in git? +function is_in_git() { + local filename + filename="$1" + + if git ls-files --error-unmatch >/dev/null 2>&1 -- "$filename" ; then + echo true + else + echo false + fi +} diff --git a/bin/blackbox_edit_end.sh b/bin/blackbox_edit_end.sh new file mode 100755 index 0000000..42f4fe1 --- /dev/null +++ b/bin/blackbox_edit_end.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# +# blackbox_edit_end.sh -- Re-encrypt file after edits. +# + +source bin/blackbox_common.sh +set -e + +fail_if_bad_environment +unencrypted_file=$(get_unencrypted_filename "$1") +encrypted_file=$(get_encrypted_filename "$1") +echo ========== PLAINFILE "$unencrypted_file" +echo ========== ENCRYPTED "$encrypted_file" + +fail_if_not_on_cryptlist "$unencrypted_file" +fail_if_not_exists "$unencrypted_file" "No unencrypted version to encrypt!" +fail_if_keychain_has_secrets + +encrypt_file "$unencrypted_file" "$encrypted_file" +shred_file "$unencrypted_file" + +echo "========== UPDATED ${encrypted_file}" +echo "Likely next step:" +echo " git commit -m\"${encrypted_file} updated\" $encrypted_file" diff --git a/bin/blackbox_edit_start.sh b/bin/blackbox_edit_start.sh new file mode 100755 index 0000000..cd11065 --- /dev/null +++ b/bin/blackbox_edit_start.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# +# blackbox_edit_start.sh -- Decrypt a file for editing. +# + +source bin/blackbox_common.sh +set -e + +fail_if_bad_environment + +for param in """$@""" ; do + unencrypted_file=$(get_unencrypted_filename "$param") + encrypted_file=$(get_encrypted_filename "$param") + echo ========== PLAINFILE "$unencrypted_file" + + fail_if_not_on_cryptlist "$unencrypted_file" + fail_if_not_exists "$encrypted_file" "This should not happen." + if [[ ! -s "$unencrypted_file" ]]; then + rm -f "$unencrypted_file" + fi + if [[ -f "$unencrypted_file" ]]; then + echo SKIPPING: "$1" "Will not overwrite non-empty files." + continue + fi + + prepare_keychain + decrypt_file "$encrypted_file" "$unencrypted_file" +done diff --git a/bin/blackbox_postdeploy.sh b/bin/blackbox_postdeploy.sh new file mode 100755 index 0000000..6567a8a --- /dev/null +++ b/bin/blackbox_postdeploy.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# +# blackbox_postdeploy.sh -- Decrypt all blackbox files. +# + +: ${BASEDIR:=/etc/puppet} ; +: ${CHGRP:=chgrp} ; + +cd "$BASEDIR" +export PATH=/usr/bin:/bin:"$BASEDIR"/bin:"$PATH" + +source blackbox_common.sh +set -e + +prepare_keychain + +# Decrypt: +echo '========== Decrypting new/changed files: START' +while read unencrypted_file; do + encrypted_file=$(get_encrypted_filename "$unencrypted_file") + decrypt_file_overwrite "$encrypted_file" "$unencrypted_file" + chmod g+r,o-rwx "$unencrypted_file" + $CHGRP puppet "$unencrypted_file" +done <"$BB_FILES" +echo '========== Decrypting new/changed files: DONE' diff --git a/bin/blackbox_register_new_file.sh b/bin/blackbox_register_new_file.sh new file mode 100755 index 0000000..cf1d536 --- /dev/null +++ b/bin/blackbox_register_new_file.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# +# blackbox_register_new_file.sh -- Enroll a new file in the blackbox system. +# +# Takes a previously unencrypted file and enters it into the blackbox +# system. It will be kept in HG as an encrypted file. On deployment +# to the puppet masters, it will be decrypted. The puppet masters +# refer to the unencrypted filename. + +source bin/blackbox_common.sh +set -e + +fail_if_bad_environment +unencrypted_file=$(get_unencrypted_filename "$1") +encrypted_file=$(get_encrypted_filename "$1") + +if [[ $1 == $encrypted_file ]]; then + echo ERROR: Please only register unencrypted files. + exit 1 +fi + +echo ========== PLAINFILE "$unencrypted_file" +echo ========== ENCRYPTED "$encrypted_file" + +fail_if_not_exists "$unencrypted_file" "Please specify an existing file." +fail_if_exists "$encrypted_file" "Will not overwrite." + +prepare_keychain +encrypt_file "$unencrypted_file" "$encrypted_file" +add_filename_to_cryptlist "$unencrypted_file" + +# TODO(tlim): The code below should be rewritten to check +# for HG vs. GIT use and DTRT depending. + +# Is the unencrypted file already in HG? (ie. are we correcting a bad situation) +SECRETSEXPOSED=$(is_in_hg ${unencrypted_file}) +echo "========== CREATED: ${encrypted_file}" +echo "========== UPDATING HG:" +shred_file "$unencrypted_file" +if $SECRETSEXPOSED ; then + hg rm -A "$unencrypted_file" + hg add "$encrypted_file" + COMMIT_FILES="$BB_FILES $encrypted_file $unencrypted_file" +else + COMMIT_FILES="$BB_FILES $encrypted_file" +fi +echo 'NOTE: "already tracked!" messages are safe to ignore.' +hg add $BB_FILES $encrypted_file +hg commit -m"registered in blackbox: ${unencrypted_file}" $COMMIT_FILES +echo "========== UPDATING HG: DONE" +echo "Local repo updated. Please push when ready." +echo " hg push" diff --git a/bin/blackbox_update_all_files.sh b/bin/blackbox_update_all_files.sh new file mode 100755 index 0000000..1cd2277 --- /dev/null +++ b/bin/blackbox_update_all_files.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# +# blackbox_edit_end.sh -- Re-encrypt file after edits. +# + +source bin/blackbox_common.sh +set -e + +fail_if_bad_environment + +if [[ -z $GPG_AGENT_INFO ]]; then + echo 'WARNING: You probably want to run gpg-agent as' + echo 'you will be asked for your passphrase many times.' + echo 'Example: $ eval $(gpg-agent --daemon)' + read -p 'Press CTRL-C now to stop. ENTER to continue: ' +fi + +disclose_admins + +echo '========== ENCRYPTED FILES TO BE RE-ENCRYPTED:' +awk <"$BB_FILES" '{ print " " $1 ".gpg" }' + +echo '========== FILES IN THE WAY:' +need_warning=false +for i in $(<$BB_FILES) ; do + unencrypted_file=$(get_unencrypted_filename "$i") + encrypted_file=$(get_encrypted_filename "$i") + if [[ -f "$unencrypted_file" ]]; then + need_warning=true + echo " $unencrypted_file" + fi +done +if $need_warning ; then + echo + echo 'WARNING: This will overwrite any unencrypted files laying about.' + read -p 'Press CTRL-C now to stop. ENTER to continue: ' +else + echo 'All OK.' +fi + +echo '========== RE-ENCRYPTING FILES:' +for i in $(<$BB_FILES) ; do + unencrypted_file=$(get_unencrypted_filename "$i") + encrypted_file=$(get_encrypted_filename "$i") + echo ========== PROCESSING "$unencrypted_file" + fail_if_not_on_cryptlist "$unencrypted_file" + decrypt_file_overwrite "$encrypted_file" "$unencrypted_file" + encrypt_file "$unencrypted_file" "$encrypted_file" + shred_file "$unencrypted_file" +done + +fail_if_keychain_has_secrets + +echo '========== COMMITING TO HG:' +hg commit -m'Re-encrypted keys' $(awk <$BB_FILES '{ print $1 ".gpg" }' ) + +echo '========== DONE.' +echo 'Likely next step:' +echo ' hg push' diff --git a/bin/start b/bin/start new file mode 100755 index 0000000..7088a1c --- /dev/null +++ b/bin/start @@ -0,0 +1,21 @@ +#!/bin/bash + +# +# blackbox_edit_start.sh -- Decrypt a file for editing. +# + +source bin/blackbox_common.sh + +fail_if_bad_environment + +for param in """$@""" ; do + unencrypted_file=$(get_unencrypted_filename "$param") + encrypted_file=$(get_encrypted_filename "$param") + + fail_if_not_on_cryptlist "$unencrypted_file" + fail_if_not_exists "$encrypted_file" "This should not happen." + rm -f "$unencrypted_file" + + prepare_keychain + decrypt_file "$encrypted_file" "$unencrypted_file" +done