diff --git a/ssh-authorize/README.md b/ssh-authorize/README.md new file mode 100644 index 0000000..7f87a22 --- /dev/null +++ b/ssh-authorize/README.md @@ -0,0 +1,61 @@ +--- +title: SSH Authorize +homepage: https://webinstall.dev/ssh-authorize +tagline: | + Add to your SSH Authorized Keys from a string, file, or url. +--- + +## Cheat Sheet + +> Does the tedious work of making sure your `.ssh/authorized_keys` exists with +> the proper permissions, and that only valid keys from a given string, file, or +> URL go into it! + +Use `ssh-authorize` to add trusted public keys to allow others to login to your +servers / systems / local computers; + +```sh +# ssh-authorize [comment] +ssh-authorize https://github.com/jonny.keys 'My GitHub Keys' +``` + +```text +USAGE + + ssh-authorize [comment] + +EXAMPLES + + ssh-authorize https://github.com/you.keys 'My GH Keys' + + ssh-authorize ./id_rsa.you@example.co.pub + + ssh-authorize 'ssh-rsa AAAA...example.co' + +LOCAL IDENTIFY FILES + + /home/app/.ssh/id_rsa.pub +``` + +### How to Add Manually + +For the simplest case it seems almost silly to even have a utility for this: + +```sh +mkdir -p ~/.ssh/ +chmod 0700 ~/.ssh/ + +touch ~/.ssh/authorized_keys +chmod 0600 ~/.ssh/ + +curl https://github.com/me.keys >> ~/.ssh/authorized_keys +``` + +but... tedium, error checking... things are never as simple as they seem. + +### But really, why? + +- handles arbitrary files and URLs, failing bad key lines +- sets permissions correctly, even if they were incorrect +- works `curl` (macOS, Ubuntu) or `wget` (Docker, Alpine) +- enforces `https` diff --git a/ssh-authorize/install.sh b/ssh-authorize/install.sh new file mode 100644 index 0000000..79da3c7 --- /dev/null +++ b/ssh-authorize/install.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -e +set -u + +__install_ssh_authorize() { + my_cmd="ssh-authorize" + + rm -f "$HOME/.local/bin/${my_cmd}" + + webi_download \ + "$WEBI_HOST/packages/${my_cmd}/${my_cmd}" \ + "$HOME/.local/bin/${my_cmd}" + + chmod a+x "$HOME/.local/bin/${my_cmd}" +} + +__install_ssh_authorize diff --git a/ssh-authorize/ssh-authorize b/ssh-authorize/ssh-authorize new file mode 100644 index 0000000..1b3a5cc --- /dev/null +++ b/ssh-authorize/ssh-authorize @@ -0,0 +1,164 @@ +#!/bin/sh +set -e +set -u + +cmd_http_get="curl ${CURL_OPTS:-} --max-time 15 --proto =https --tlsv1.2 -fsS" +if ! command -v curl > /dev/null; then + cmd_http_get="wget ${WGET_OPTS:-} --secure-protocol=TLSv1_2 --secure-protocol=TLSv1_3 --timeout=15 -o /dev/null -O -" +fi + +fn_init_authorized_keys() { ( + if ! test -e ~/.ssh/; then + mkdir -p ~/.ssh/ + fi + chmod 0700 ~/.ssh/ + + if ! test -e ~/.ssh/authorized_keys; then + touch ~/.ssh/authorized_keys + fi + chmod 0600 ~/.ssh/authorized_keys + +); } + +fn_ssh_keys() { ( + my_key_uri="${1:-}" + + # Handle keys as a URL, a file, or string + my_keys="${my_key_uri}" + case "${my_key_uri}" in + http:*) + echo "please use 'https://' for ssh public key urls" + return 1 + ;; + https:*) + my_keys="$( + ${cmd_http_get} "${my_key_uri}" + )" + ;; + *) + if test -e "${my_key_uri}"; then + my_keys="$( + cat "${my_key_uri}" + )" + fi + ;; + esac + + # printf for proper whitespace handling + printf '%s' "${my_keys}" +); } + +main() { ( + my_keything="${1:-}" + my_comment="${2:-}" + + fn_init_authorized_keys + + if test -z "${my_keything:-}"; then + echo "" + echo "USAGE" + echo "" + echo " ssh-authorize [comment]" + echo "" + echo "EXAMPLES" + echo "" + echo " ssh-authorize https://github.com/you.keys 'My GH Keys'" + echo "" + echo " ssh-authorize ./id_rsa.you@example.co.pub" + echo "" + echo " ssh-authorize 'ssh-rsa AAAA...example.co'" + echo "" + echo "LOCAL IDENTIFY FILES" + echo "" + for my_file in ~/.ssh/*.pub; do + if test "${my_file}" = "$HOME/.ssh/*.pub"; then + echo " (no files match ~/.ssh/*.pub)" + break + fi + printf ' %s\n' "${my_file}" + done + echo "" + + return 1 + fi + + my_pubkeys="" + if test -n "${my_keything}"; then + my_pubkeys="$( + fn_ssh_keys "${my_keything}" + )" + else + # if ! command -v ssh-pubkey > /dev/null; then + # { + # echo "" + # echo "ERROR" + # echo " no key string, file, or url was given," + # echo " and 'ssh-pubkey' is not installed." + # echo "" + # echo "SOLUTION" + # echo " provide a valid key string, file or url," + # echo " or 'curl https://webi.sh/ssh-pubkey | sh'" + # echo " to install 'ssh-pubkey'" + # echo "" + # } >&2 + # + # return 1 + # fi + + # Get the default key (for authorizing self) + my_pubkeys="$( + ssh-pubkey 2> /dev/null + )" + fi + + # Clean up non-key lines, preserving comments and newlines + my_pubkeys_safe="$( + printf '%s' "${my_pubkeys}" | grep -E '^(#|\s*$|(ssh|ecdsa)-[a-zA-Z0-9-]+ AAA)' + )" + my_pubkeys_trimmed="$( + printf '%s' "${my_pubkeys}" | grep -E '^(ssh|ecdsa)-[a-zA-Z0-9-]+ AAA' + )" + my_pubkeys_excluded="$( + printf '%s' "${my_pubkeys}" | grep -v -E '^(#|\s*$|(ssh|ecdsa)-[a-zA-Z0-9-]+ AAA)' || true + )" + + if test -z "${my_pubkeys_trimmed}"; then + { + echo "" + echo "ERROR" + echo " not a valid key string, file, or url:" + echo " '${my_keything}'" + echo "" + echo "SOLUTION" + echo " inspect the file / url / string, double" + echo " check that you copied correctly, etc" + echo "" + } >&2 + + return 1 + fi + + { + if test -n "${my_comment}"; then + printf '# %s\n' "${my_comment}" + fi + printf '%s\n' "${my_pubkeys_safe}" + } | tee -a ~/.ssh/authorized_keys + + echo "" + echo "Successfully copied the above ssh keys to ~/.ssh/authorized_keys" + + if test -n "${my_pubkeys_excluded}"; then + { + echo "" + echo "WARNING: the following (invalid) lines were excluded:" + echo "" + echo "${my_pubkeys_excluded}" + echo "" + } >&2 + fi +); } + +if test -z "${SSH_AUTHORIZE_UNIT_TEST:-}"; then + main "${@:-}" +fi