diff --git a/bump-tag b/bump-tag index d14dcaa..e4af126 100755 --- a/bump-tag +++ b/bump-tag @@ -1,66 +1,268 @@ #!/bin/bash -set -e - -echo "Fetching any updates from server:" -git pull +echo "Fetching updates from $(git remote get-url origin) ..." echo "" +git pull --verify-signatures + +if [[ ${?} -ne 0 ]]; then + echo "WARNING: git pull did not exit successfully." + echo "" + echo "EXITING the script. In order to tag your changes," + echo "investigate and then run bump-tag again." + exit 1 +fi if [[ -f ./cosmos.conf ]]; then - . ./cosmos.conf + # shellcheck disable=SC1091 + source ./cosmos.conf fi -if [[ -z ${1} ]]; then - deftag=$(basename "${PWD}") + +# A tab will be used in multiple commands for git +t=$'\t' + +# Set the default tag according to the repo +# or by entering a name as the first argument. +if [[ -z "${1}" ]]; then + deftag="$(basename "${PWD}")" else - deftag="${1}" + deftag="${1}" fi -tagpfx=${tag:="${deftag}"} -last_tag=$(git tag -l "${tagpfx}-*" | sort | tail -1) -if [[ -n ${last_tag} ]]; then - echo "Verifying last tag ${last_tag}:" - (git tag -v "${last_tag}" | grep ^gpg:) || true - # again to not mask exit status of git with grep - git tag -v "${last_tag}" >/dev/null 2>&1 - echo "" +# Set the tag prefix according to: +# 1. $tag, if specified in cosmos.conf, +# 2. or $deftag, as specified above. +# shellcheck disable=SC2154 +if [[ ! -z "${tag}" ]]; then + tagpfx="${tag}" +else + tagpfx="${deftag}" +fi - echo "Differences between tag ${last_tag} and what you are about to sign:" - this_branch=$(git rev-parse --abbrev-ref HEAD) - env PAGER=cat git diff --color "${last_tag}..${this_branch}" +# Check why the tag couldn't be verified +# First argument: the tag to investigate +check_tag_sig_failure() +{ + local __tag_to_check="${1}" - iter=1 - ok= - while test -z "$ok"; do - this_tag=$(date "+${tagpfx}-%Y-%m-%d-v$(printf "%02d" ${iter})") - iter=$(( iter + 1)) - case $( ( - echo "${this_tag}" - echo "${last_tag}" - ) | sort | tail -1) in - "${last_tag}") ;; + # shellcheck disable=SC2155 + local __verify_tag_output="$(git verify-tag --raw "${__tag_to_check}" 2>&1)" - "${this_tag}") - ok=yes - ;; + if echo "${__verify_tag_output}" | grep -q "VALIDSIG"; then + + if echo "${__verify_tag_output}" | grep -q "EXPKEYSIG"; then + + echo "" + echo "WARNING: The tag was correctly signed, but the copy of" + echo "the key that you have stored on your computer has expired." + echo "Check for an updated key in:" + echo "global/overlay/etc/cosmos/keys/" + echo "" + echo "EXITING the script. In order to tag your changes," + echo "investigate and then run bump-tag again." + exit 1 + + else + + echo "" + echo "WARNING: The tag was probably correctly signed," + echo "but it still didn't pass the verification check." + echo "" + echo "EXITING the script. In order to tag your changes," + echo "investigate and then run bump-tag again." + exit 1 + + fi + + else + + echo "" + echo "WARNING: The signature of the tag could not be verified." + echo "Please make sure that you have imported the key and that" + echo "the key is signed by a trusted party." + echo "Keys used for signing in a Cosmos repo can be found at:" + echo "global/overlay/etc/cosmos/keys/" + echo "" + echo "EXITING the script. In order to tag your changes," + echo "investigate and then run bump-tag again." + exit 1 + + fi +} + +check_commit_sig_failure() +{ + local __commit_to_check="${1}" + local __file_related_to_commit="${2}" + + # shellcheck disable=SC2155 + local __verify_commit_output="$(git verify-commit --raw "${__commit_to_check}" 2>&1)" + + if echo "${__verify_commit_output}" | grep -q "VALIDSIG"; then + + if echo "${__verify_commit_output}" | grep -q "EXPKEYSIG"; then + + echo "WARNING: The commit to ${__file_related_to_commit}" + echo "was correctly signed, but the copy of the key that" + echo "you have stored on your computer has expired." + echo "Check for an updated key in:" + echo "global/overlay/etc/cosmos/keys/" + echo "" + echo "EXITING the script. In order to tag your changes," + echo "investigate and then run bump-tag again." + exit 1 + + else + + echo "WARNING: The commit to ${__file_related_to_commit}" + echo "was probably correctly signed, but it still didn't" + echo "pass the verification check." + echo "" + echo "EXITING the script. In order to tag your changes," + echo "investigate and then run bump-tag again." + exit 1 + + fi + + else + + echo "WARNING: The commit to ${__file_related_to_commit}" + echo "could not be verified. Please make sure that you have" + echo "imported the key and that the key is signed by a trusted party." + echo "" + echo "EXITING the script. In order to tag your changes," + echo "investigate and then run bump-tag again." + exit 1 + + fi +} + +# Verify the last commit of a file +# First argument: the file to verify +verify_last_commit() +{ + local __file_to_verify="${1}" + + if [[ ! -f "${__file_to_verify}" ]]; then + return 1 + fi + + if [[ ! -z "$(git status --porcelain "${__file_to_verify}")" ]]; then + echo "" + echo "INFO: local changes detected in ${__file_to_verify}," + echo "Not checking the signature of the last commit to ${__file_to_verify}." + echo "" + return 1 + fi + + # shellcheck disable=SC2155 + local __last_commit="$(git log -n 1 --pretty=format:%H -- "${__file_to_verify}")" + + if ! git verify-commit "${__last_commit}" 2> /dev/null; then + echo "" + echo "WARNING: Untrusted modification to ${__file_to_verify}:" + echo "----------------------------" + git verify-commit "$(git log -n 1 --pretty=format:%H -- "${__file_to_verify}")" + echo "----------------------------" + + check_commit_sig_failure "${__last_commit}" "${__file_to_verify}" + fi +} + +tag_list="$(git tag -l "${tagpfx}-*")" +if [[ ${?} -ne 0 ]] || [[ -z "${tag_list}" ]]; then + + echo "No tags found, verifying all commits instead." + # %H = commit hash + # %G? = show "G" for a good (valid) signature + git_log="$(git log --pretty="format:%H${t}%G?" \ + --first-parent \ + | grep -v "${t}G$")" + +else + + last_tag="$(echo "${tag_list}" | sort | tail -1)" + echo "Verifying last tag: ${last_tag} and the commits after that" + + git verify-tag "${last_tag}" + if [[ ${?} -ne 0 ]]; then + check_tag_sig_failure "${last_tag}" + fi + + tag_object="$(git verify-tag -v "${last_tag}" 2>&1 | grep ^object | cut -d' ' -f2)" + + # The commits after the last valid signed git tag that we need to check + revision_range="${tag_object}..HEAD" + + # Filter out the commits that are unsigned or untrusted + # %H = commit hash + # %G? = show "G" for a good (valid) signature + git_log="$(git log --pretty="format:%H${t}%G?" "${revision_range}" \ + --first-parent \ + | grep -v "${t}G$")" + +fi + +if [[ ! -z "${git_log}" ]]; then + echo "" + echo -e "------WARNING: unsigned or untrusted commits after the last tag------" + echo "${git_log}" + echo -e "---------------------------------------------------------------------" + echo "Quick referens on how to configure signing of commits in ~/.gitconfig:" + echo "[user]" + echo " signingkey = your-prefered-key-id" + echo "[commit]" + echo " gpgsign = true" + echo "" + echo "EXITING the script. In order to tag your changes," + echo "please make sure that you have configured signing of" + echo "your own commits and that the listed unsigned commits" + echo "have been made by a trusted party and are not malicous." + exit 1 +fi + +# Always check that the last commit of certain +# sensitive files is trusted, without taking into +# account whether the last tag was trusted or not. +verify_last_commit "./scripts/jsonyaml-no-output.py" +verify_last_commit "./bump-tag" + +# Test the syntax of each YAML-file to be tagged. +for file in $(git diff --name-only "${last_tag}..${this_branch}" | egrep "^.*\.(yaml|yml)$"); do + if [[ -f "${file}" ]]; then + ./scripts/jsonyaml-no-output.py yaml "${file}" + fi +done + +echo "Differences between tag ${last_tag} and what you are about to sign:" +# With PAGER=cat, git diff will simply dump the output to the screen. +# shellcheck disable=SC2037 +PAGER=cat git diff --color "${last_tag}..${this_branch}" + +# Iterate over the $last_tag until $this_tag is set to a later version +iter=1 +ok= +while [[ -z "${ok}" ]]; do + this_tag="$(date +"${tagpfx}-%Y-%m-%d-v$(printf "%02d" "${iter}")")" + iter="$(( iter + 1))" + + case "$( (echo "${this_tag}"; echo "${last_tag}") | sort | tail -1 )" in + "${last_tag}") + ;; + "${this_tag}") + ok=yes + ;; esac - done +done - if [ "${deftag}" != "${tagpfx}" ]; then +if [ "${deftag}" != "${tagpfx}" ]; then echo -e "Using new tag \e[94m${this_tag}\e[0m according to pattern in cosmos.conf" - else - echo -e "Using new tag \e[94m${this_tag}\e[0m" - fi - - echo -e "\e[1mONLY SIGN IF YOU APPROVE OF VERIFICATION AND DIFF ABOVE\e[0m" else - echo -e "\e[1mCOULD NOT FIND LAST TAG, ASSUMING NEW TAG\e[0m" - iter=1 - this_tag=$(date "+${tagpfx}-%Y-%m-%d-v$(printf "%02d" ${iter})") + echo -e "Using new tag \e[94m${this_tag}\e[0m" fi -# GITTAGEXTRA is for putting things like "-u 2117364A" -# shellcheck disable=SC2086 -git tag ${GITTAGEXTRA} -s "${this_tag}" -m bump. +echo -e "\e[1mONLY SIGN IF YOU APPROVE OF VERIFICATION AND DIFF ABOVE\e[0m" + +git tag $GITTAGEXTRA -m bump. -s "${this_tag}" git push git push --tags