From 22131868389fc5dc6eabea6ed0e7bc5dce6beca5 Mon Sep 17 00:00:00 2001 From: Simon Marsh Date: Sun, 2 May 2021 13:17:30 +0100 Subject: [PATCH] Add script to sign commits to allow for future automation --- sign-my-commit | 404 ++++++++++++++++++++++++++++++++++++++++++++++ squash-my-commits | 61 ++++--- 2 files changed, 446 insertions(+), 19 deletions(-) create mode 100755 sign-my-commit diff --git a/sign-my-commit b/sign-my-commit new file mode 100755 index 000000000..ecca7647d --- /dev/null +++ b/sign-my-commit @@ -0,0 +1,404 @@ +#!/bin/sh +########################################################################## +# +# This script will attempt to sign your commit using one of the +# authentication methods in your mntner record. +# +# If the script fails to work, PRs for fixes are always welcome +# and you can always sign your commit manually as detailed in the +# DN42 wiki: https://dn42.dev/howto/Registry-Authentication +# +# do './sign-my-commit --help' to get usage information +# +########################################################################## + +usage() +{ + echo "Usage: $0 [options] MNTNER" + echo 'Generic options:' + echo ' --pgp, sign using your PGP key' + echo ' --ssh, sign using your ssh key' + echo ' --push, force push result' + echo ' --verify, check existing signature is correct' + echo ' --help, display this message' + echo 'SSH specific options:' + echo ' --key, (required for signing) specify SSH private key file to use' +} + +########################################################################## +# defaults + +DO_PUSH=0 +AUTH_METHOD='' +MNTNER='' +SSH_KEYFILE='' +VERIFY_ONLY=0 + +########################################################################## +# parse arguments + +while [ -n "$1" ] +do + case "$1" in + + --pgp) + AUTH_METHOD='pgp' + ;; + --ssh) + AUTH_METHOD='ssh' + ;; + --force) + DO_PUSH=1 + ;; + --key) + shift + SSH_KEYFILE="$1" + ;; + --verify) + VERIFY_ONLY=1 + ;; + --help) + usage + exit 0 + ;; + *) + if [ -z "$MNTNER" ] + then + MNTNER=$1 + else + echo "ERROR: Unknown option: $1" + usage + exit 1 + fi + ;; + + esac + + shift +done + +########################################################################## +# initial sanity checks + +# if verifying only, try to guess some info from the existing sig +if [ "$VERIFY_ONLY" -eq 1 ] +then + if [ -z "$MNTNER" ] + then + MNTNER=$(git log -n 1 --format=format:%B | \ + grep '^### mntner:' | \ + cut -d':' -f2 | tr -d ' ') + if [ -n "$MNTNER" ] + then + echo "Found mntner $MNTNER from signature" + fi + fi + + if [ -z "$AUTH_METHOD" ] + then + AUTH_METHOD=$(git log -n 1 --format=format:%B | \ + grep '^### method:' | \ + cut -d':' -f2 | tr -d ' ') + if [ -n "$AUTH_METHOD" ] + then + echo "Using $AUTH_METHOD auth method from signature" + fi + fi +fi + +# check that a mntner has been specified and exists +if [ -z "${MNTNER}" ] +then + usage + exit 1 +fi + +if [ ! -f "data/mntner/${MNTNER}" ] +then + echo "ERROR: mntner '${MNTNER}' not found" + exit 1 +fi + +if [ "$VERIFY_ONLY" -ne 1 ] +then + + # check for untracked or uncommitted changes + if [ -n "$(git status --porcelain)" ] + then + echo "ERROR: git worktree has unstaged or uncommitted changes" + echo "This script can only be run once your commit is completed" + echo "---" + git status + exit 1 + fi + + # check that the commit has been squashed + ./squash-my-commits --verify + if [ $? -ne 0 ] + then + echo "ERROR: Ensure your commits are squashed before signing" + echo "Run the included script: ./squash-my-commits" + exit 1 + fi + + # check for an existing signature + git log -n 1 --format=format:%B 2>&1 | grep '^### DN42 Signature' > /dev/null + if [ $? -eq 0 ] + then + echo "ERROR: The last commit appears to already be signed" + echo "---" + git log -n 1 --show-signature + exit 1 + fi + +fi + +########################################################################## +# helper functions + +guess_auth_method() +{ + # look for the first auth method in the mntner object + method=$(grep '^auth' "data/mntner/${MNTNER}" | \ + head -n 1 | tr -s ' ' | cut -d' ' -f2 | cut -d'-' -f1) + # didn't find anything ? + if [ -z "$method" ] + then + echo "Unable to find mntner auth method for ${MNTNER}" + echo "Try specifying the method manually" + usage + exit 1 + fi + + case "$method" in + pgp) + AUTH_METHOD='pgp' + ;; + PGPKEY) + AUTH_METHOD='pgp' + ;; + ssh) + AUTH_METHOD='ssh' + ;; + *) + echo "ERROR: Auth method is unknown or unimplemented" + exit 1 + esac +} + + +########################################################################## +# PGP signing function + +sign_pgp() +{ + # check first if there is already a signature + git log -n 1 --show-signature | grep "^gpg" > /dev/null 2>&1 + if [ $? -eq 0 ] + then + echo "ERROR: The last commit appears to already be signed." + echo "---" + git log -n 1 --show-signature + exit 1 + fi + + # create a new comment with some additional metadata + comment="$(git log -n 1 --format=format:%B) + +### DN42 Signature +### method: pgp +### mntner: $MNTNER +" + + # PGP signing is straightforward + git commit --amend --no-edit -S -m "$comment" + + # assuming it's actually configured properly .... + if [ $? -ne 0 ] + then + echo "ERROR: failed to sign commit" + echo "Have you configured git with your PGP key ?" + echo "For example, to configure your key globally:" + echo " - Find your key using: gpg --list-keys" + echo " - Then add it to git: " \ + "git config --global user.signingkey " + exit 1 + fi +} + +########################################################################## +# verify PGP signature + +verify_pgp() +{ + echo "Verifying PGP signature" + + # find the current commit hash + hash=$(git log -n 1 --format=format:%H) + + # requires git 2.5 + git verify-commit "$hash" + if [ $? -ne 0 ] + then + echo "ERROR: failed to verify PGP signature" + exit 1 + fi + echo " - PGP signature verified ok" + + # extract the fingerprint of the key that was successful + prints=$(git verify-commit --raw "$hash" 2>&1 | \ + grep "VALIDSIG" | cut -f3,12 -d' ') + for print in $prints + do + grep "^auth" data/mntner/${MNTNER} | grep -i $print > /dev/null 2>&1 + if [ $? -eq 0 ] + then + echo " - matched with auth attribute for $MNTNER" + echo "Successfully verified PGP signature" + return + fi + done + + echo "ERROR: unable to match key fingerprint with mntner: $MNTNER" + exit 1 +} + +########################################################################## +# SSH signing function + +sign_ssh() +{ + # check for ssh-keygen signing capability + ssh-keygen -Y sign 2>&1 | grep 'missing namespace' > /dev/null + if [ $? -ne 0 ] + then + echo "ERROR: This script requires the key signing capability " \ + "from OpenSSH ssh-keygen > version 8" + echo "---" + ssh -V + exit 1 + fi + + if [ -z "$SSH_KEYFILE" ] + then + echo "ERROR: You must specify your SSH private key " \ + "using --key" + exit 1 + fi + + # find the current commit hash + hash=$(git log -n 1 --format=format:%H) + + # create the signature + sig=$(echo "$hash" | ssh-keygen -Y sign -n dn42 -f "$SSH_KEYFILE") + + comment="$(git log -n 1 --format=format:%B) + +### DN42 Signature +### method: ssh +### mntner: $MNTNER +### text: $hash +$sig +" + + # update the commit with the sig + git commit --amend --no-edit -m "$comment" +} + +########################################################################## +# verify SSH signature + +verify_ssh() +{ + echo "Verifying SSH signature" + + # create a temporary files for the 'allowed' keys file and signature + afile=$(mktemp) + sfile=$(mktemp) + + # extract and reformat the temp keys in to the allowed file + grep '^auth' "data/mntner/${MNTNER}" | tr -s ' ' | cut -d' ' -f2- | \ + grep '^ssh-' | sed "s/^/${MNTNER} /" > "$afile" + + # extract the signed text from the git comment + text=$(git log -n 1 --format=format:%B | grep '^### text:' | + cut -d':' -f2 | tr -d ' ') + + # extract the SSH signature from the comment + begin="-----BEGIN SSH SIGNATURE-----" + end="-----END SSH SIGNATURE-----" + git log -n 1 --format=format:%B | \ + sed "/^$begin\$/,/^$end\$/!d" > "$sfile" + + # and finally verify + echo "$text" | ssh-keygen -Y verify -f "$afile" \ + -n dn42 -I $MNTNER -s "$sfile" + # grab the result and then clean up + result=$? + rm -f "$afile" "$sfile" + + # did it work ? + if [ $result -eq 0 ] + then + echo "Successfully verified SSH sigature" + else + echo "ERROR: signature verification failed" + exit 1 + fi +} + + +########################################################################## +# body of the script starts here + +if [ -z "$AUTH_METHOD" ] +then + echo "Attempting to guess auth method from the mntner object" + guess_auth_method +fi + +# decide what to do +case "$AUTH_METHOD" in + pgp) + if [ "$VERIFY_ONLY" -ne 1 ] + then + echo "Signing using PGP key" + sign_pgp + fi + verify_pgp + ;; + ssh) + if [ "$VERIFY_ONLY" -ne 1 ] + then + echo "Signing using SSH key" + sign_ssh + fi + verify_ssh + ;; + *) + echo "ERROR: Unknown or unimplemented auth method: $AUTH_METHOD" + exit 1 + ;; +esac + +########################################################################## +# all done, tidy up + +if [ "$VERIFY_ONLY" -eq 1 ] +then + exit 0 +fi + +# push changes if requested +if [ "$DO_PUSH" -eq 1 ] +then + echo 'Force pushing changes' + git push --force +else + echo 'Remember to push your changes using: git push --force' +fi + +exit 0 + +########################################################################## +# end of file diff --git a/squash-my-commits b/squash-my-commits index 79f91d865..038b73e84 100755 --- a/squash-my-commits +++ b/squash-my-commits @@ -5,6 +5,9 @@ # with any changes in the main registry repository and will then squash # the local changes together in to a single commit # +# If the script fails to work, PRs for fixes are always welcome +# and you can always squash your commits manually +# # use './squash-my-commits -S' to sign the result with your pgp key # ########################################################################## @@ -13,10 +16,11 @@ usage() { echo "Usage: $0 [options]" echo 'Options:' - echo ' -S, sign the result with your pgp key' - echo ' --push, force push result' - echo ' --ssh, use ssh to fetch from the registry' - echo ' --https, use https to fetch from the registry' + echo ' -S, sign the result with your pgp key' + echo ' --push, force push result' + echo ' --ssh, use ssh to fetch from the registry' + echo ' --https, use https to fetch from the registry' + echo ' --verify, check only' echo 'Environment variables:' echo ' DN42_REG_URL, set the registry URL to use' } @@ -24,6 +28,9 @@ usage() ########################################################################## # parse arguments +do_push=0 +verify_only=0 + for arg do case "$arg" in @@ -42,6 +49,9 @@ do echo 'Forcing use of HTTPS to fetch from registry' reg_proto="https" ;; + --verify) + verify_only=1 + ;; --help) usage exit 0 @@ -100,23 +110,29 @@ fi ########################################################################## -# ensure the local branch is up to date -echo 'Rebasing local changes against the registry master' -git fetch dn42registry master -if [ $? -ne 0 ] +if [ "$verify_only" -ne 1 ] then - echo 'ERROR: Failed to fetch registry master branch' - echo 'Hint: you can use --ssh/--https to force use of ssh or https' - echo 'If all else fails, you can also set the DN42_REG_URL' - echo 'environment variable to directly specify the URL to fetch' - exit 1 + + # ensure the local branch is up to date + echo 'Rebasing local changes against the registry master' + git fetch dn42registry master + if [ $? -ne 0 ] + then + echo 'ERROR: Failed to fetch registry master branch' + echo 'Hint: you can use --ssh/--https to force use of ssh or https' + echo 'If all else fails, you can also set the DN42_REG_URL' + echo 'environment variable to directly specify the URL to fetch' + exit 1 + fi + + # fail on errors from here onwards + set -e + + git rebase dn42registry/master +else + set -e fi -# fail on errors from here onwards -set -e - -git rebase dn42registry/master - ########################################################################## # find number of local commits @@ -129,6 +145,12 @@ then exit 0 fi +if [ "$verify_only" -eq 1 ] +then + echo "$count local commits found" + exit 1 +fi + echo 'Squashing $count commits ...' # construct a new comment based on previous commits @@ -153,7 +175,8 @@ then echo 'Force pushing changes' git push --force else - echo 'Remember to push your changes using: git push --force' + echo 'Remember to sign your commit: ./sign-my-commit FOO-MNT' + echo 'and then push your changes using: git push --force' fi ##########################################################################