From 37a997cf7dea12c157a6e58e88443465609d80d2 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 26 Feb 2025 00:46:31 -0500 Subject: [PATCH] feat: validate the authorized_keys file when running the list subcommand --- README.md | 20 ++++++++++++++++++++ sshcommand | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/README.md b/README.md index 7f1a52e..ea6a6c2 100644 --- a/README.md +++ b/README.md @@ -82,3 +82,23 @@ no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding This can be useful for cases where the ssh server does not allow certain options or you wish to further constrain a user's environment. Please see `man sshd` for more information. + +Existing keys can be listed via the `list` subcommand: + +```shell +# in text format +sshcommand list cmd + +# filter by a particular name +sshcommand list cmd progrium + +# in json format +sshcommand list cmd "" json + +# with name filtering +sshcommand list cmd progrium json + +# ignore validation errors (though they will be printed to stderr) +export SSHCOMMAND_IGNORE_LIST_WARNINGS=true +sshcommand list cmd +``` diff --git a/sshcommand b/sshcommand index 72e5653..64d7b86 100755 --- a/sshcommand +++ b/sshcommand @@ -86,12 +86,49 @@ fn-adduser() { esac } +fn-verify-file() { + declare desc="Test that public key is valid" + declare file="$1" + local has_errors=false + + local key line=0 + local TMP_KEY_FILE + TMP_KEY_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") + trap "rm -rf '$TMP_KEY_FILE' >/dev/null" RETURN INT TERM EXIT + + SSHCOMMAND_IGNORE_LIST_WARNINGS="${SSHCOMMAND_IGNORE_LIST_WARNINGS:-false}" + while read -r key; do + line=$((line + 1)) + [[ -z "$key" ]] && continue + [[ "$key" =~ ^#.*$ ]] && continue + + echo "$key" >"$TMP_KEY_FILE" + if ! ssh-keygen -lf "$TMP_KEY_FILE" &>/dev/null; then + has_errors=true + if [[ "$SSHCOMMAND_IGNORE_LIST_WARNINGS" == "false" ]]; then + log-warn "${file} line $line failed ssh-keygen check." + else + log-warn "${file} line $line failed ssh-keygen check, ignoring." + fi + fi + done <"${file}" + + if [[ "$has_errors" == "true" ]]; then + return 1 + fi +} + log-fail() { declare desc="Log fail formatter" echo "$@" 1>&2 exit 1 } +log-warn() { + declare desc="Log warn formatter" + echo "$@" 1>&2 +} + log-verbose() { declare desc="Log verbose formatter" if [[ -n "$SSHCOMMAND_VERBOSE_OUTPUT" ]]; then @@ -209,6 +246,11 @@ sshcommand-list() { [[ -e "$userhome/.ssh/authorized_keys" ]] || log-fail "authorized_keys not found for $USER" [[ -s "$userhome/.ssh/authorized_keys" ]] || log-fail "authorized_keys is empty for $USER" + SSHCOMMAND_IGNORE_LIST_WARNINGS="${SSHCOMMAND_IGNORE_LIST_WARNINGS:-false}" + if ! fn-verify-file "$userhome/.ssh/authorized_keys" && [[ "$SSHCOMMAND_IGNORE_LIST_WARNINGS" == "false" ]]; then + return 1 + fi + if [[ -n "$OUTPUT_TYPE" ]] && [[ "$OUTPUT_TYPE" == "json" ]]; then data=$(sed --silent --regexp-extended \ 's/^command="FINGERPRINT=(\S+) NAME=(\\"|)(.*)\2 `.*",(\S+).*/{ "fingerprint": "\1", "name": "\3", "SSHCOMMAND_ALLOWED_KEYS": "\4" }/p' \