Skip to content

Instantly share code, notes, and snippets.

@blurayne
Last active July 1, 2022 23:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save blurayne/c9a86abf694b580f35d6915b081ca135 to your computer and use it in GitHub Desktop.
Save blurayne/c9a86abf694b580f35d6915b081ca135 to your computer and use it in GitHub Desktop.
ssh-copy-key
#!/bin/bash
# vim:tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab
# vim:syntax=sh
##
# ssh-copy-key - a better ssh-copy-id
#
# - have a different identify file to copy to remote host (and just one!)
# - (infrastructure where you don't own private key but wanna role out your private key –like it should be done ;)
# - automatically generates public key if not yet given
# - ability to override label on authorized key
#
# LICENSE
#
# I dedicate any and all copyright interest in this software to the
# public domain. I make this dedication for the benefit of the public at
# large and to the detriment of my heirs and successors. I intend this
# dedication to be an overt act of relinquishment in perpetuity of all
# present and future rights to this software under copyright law.
#
# https://unlicense.org/
# Strict bash scripting
set -euo pipefail -o errtrace
UMASK="$(umask)"
OPT_LABEL=""
OPT_COPYKEY=""
OPT_CONNKEY=""
BASENAME="$(basename $0)"
OPT_DRYRUN=0
OPT_ARGS=()
OPT_FORCE=0
OPT_SHOW=0
OPT_CLIPBOARD=1
usage() {
cat <<-USAGE
Usage: ${BASENAME} [-h|-?|-f|-n] [-i [identity_file]] [-p port] [[-o <ssh -o options>] ...] [user@]hostname
-f: force mode -- copy keys without trying to check if they are already installed
-i: identity file for connecting
-k: identity file for copying
-c: copy to clipboard
-o: override authorized key if already present (setting new label)
-s: show and copy public key
-n: dry run -- no keys are actually copied
-l: label (use label for key)
-h|-?: print this help
USAGE
}
if (($# == 0)); then
usage
exit 0
fi
while getopts ":fsnhfi:k:l:" OPT; do
# echo "${OPT}=${OPTARG:-}"
case ${OPT} in
k) OPT_COPYKEY="${OPTARG}";;
i) OPT_CONNKEY="${OPTARG}";OPT_ARGS+=(-i "${OPTARG}");;
f) OPT_FORCE=1;;
s) OPT_SHOW=1;;
c) OPT_CLIPBOARD=1;;
l) OPT_LABEL="${OPTARG}";;
n) OPT_DRYRUN=1;;
h)
usage
exit 0
;;
: )
echo "Invalid option: ${OPTARG} requires an argument" 1>&2
exit 1
;;
\? )
echo "Invalid Option: -${OPTARG}" 1>&2
exit 1
;;
*)
OPT_ARGS+=("${OPT}")
if [[ -n "${OPTARG:-}" ]]; then
OPT_ARGS+=("${OPTARG}")
fi
;;
esac
done
shift $((OPTIND -1))
OPT_ARGS+=("$@")
if [[ -z "${OPT_COPYKEY}" ]]; then
OPT_COPYKEY="$(command ls -1 ~/.ssh/id*.pub | sort | head -n1 )"
fi
if [[ ! -r "${OPT_COPYKEY}" ]]; then
>&2 echo "${OPT_COPYKEY}: does neither exist nor is readable!"
exit 1
fi
if ! grep -Eq "^(ssh-rsa|ed25519|ecdsa|dsa)" "${OPT_COPYKEY}" 2>/dev/null; then
if grep -q "PRIVATE" "${OPT_COPYKEY}" 2>/dev/null; then
>&2 echo "${OPT_COPYKEY}: private key given, trying public key derivation"
_OPT_COPYKEY="$(umask 077 && mktemp)"
ssh-keygen -y -f "${OPT_COPYKEY}" > "${_OPT_COPYKEY}"
OPT_COPYKEY="${_OPT_COPYKEY}"
else
>&2 echo "${OPT_COPYKEY}: private or public key not found"
exit 1
fi
fi
if [[ "${OPT_LABEL}" ]]; then
PUB_LABEL="${OPT_LABEL}"
else
PUB_LABEL="$(cat "${OPT_COPYKEY}" | rg -o ' (.{4,32})$' --replace '$1' || echo "$(id -un)@$(hostname)")"
fi
if ! echo "$PUB_LABEL" | rg '^[A-Za-z0-9\._@]+$' 1>/dev/null 2>&1; then
echo >&2 "$PUB_LABEL: bad label – use only <user@hostname> [A-Za-z0-9._@]"
exit 1
fi
SEARCH_PUB="$(cat "${OPT_COPYKEY}" | rg ' .{4,32}$' --replace '' || cat "${OPT_COPYKEY}")"
PUB="${SEARCH_PUB} ${PUB_LABEL}"
if [[ "${OPT_SHOW}" -eq 1 ]]; then
echo "${PUB}"
echo "${PUB}" | cb || true
exit 0
fi
if [[ "${OPT_FORCE}" -eq 1 ]]; then
cmd="$(cat <<-EOF
set -euo pipefail -o errtrace;
if [[ ! -d "\${HOME}/.ssh" ]]; then mkdir -p -m 700 "\${HOME}/.ssh"; fi;
umask 177;
if [[ ! -e "\${HOME}/.ssh/authorized_keys" ]]; then touch \${HOME}/.ssh/authorized_keys; fi;
( cat <(cat "\${HOME}/.ssh/authorized_keys" | grep -v "${SEARCH_PUB}") <(echo -e "\n${PUB}") | awk '!visited[\$0]++' | sed '/^\$/d' ) > "\${HOME}/.ssh/.authorized_keys";
mv "\${HOME}/.ssh/.authorized_keys" "\${HOME}/.ssh/authorized_keys";
echo "\$(id -un)@\$(hostname): forced key for ${PUB_LABEL}";
EOF
)"
else
cmd="$(cat <<-EOF
set -euo pipefail -o errtrace;
if [[ ! -d "\${HOME}/.ssh" ]]; then mkdir -p -m 700 "\${HOME}/.ssh"; fi;
umask 177;
if [[ ! -e "\${HOME}/.ssh/authorized_keys" ]]; then touch \${HOME}/.ssh/authorized_keys; fi;
if ! grep -q "${SEARCH_PUB}" "\${HOME}/.ssh/authorized_keys"; then
( cat "\${HOME}/.ssh/authorized_keys" <(echo -e "\n${PUB}") | awk '!visited[\$0]++' | sed '/^\$/d' ) > "\${HOME}/.ssh/.authorized_keys";
mv "\${HOME}/.ssh/.authorized_keys" "\${HOME}/.ssh/authorized_keys";
echo "\$(id -un)@\$(hostname): added key for ${PUB_LABEL}";
else
echo "\$(id -un)@\$(hostname): key does already exist";
>&2 grep "${SEARCH_PUB}" "\${HOME}/.ssh/authorized_keys";
fi
EOF
)"
fi
if [[ ${OPT_DRYRUN} -eq 1 ]]; then
echo ssh ${OPT_ARGS[@]} $(printf "%q" "${cmd}")
echo eval "$(printf "%q" "${cmd}")" | cb
exit 0
fi
ssh ${OPT_ARGS[@]} "${cmd}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment