[openssh-commits] [openssh] 01/05: add -s flag: to install keys via SFTP

git+noreply at mindrot.org git+noreply at mindrot.org
Tue Oct 13 12:45:47 AEDT 2020


This is an automated email from the git hooks/post-receive script.

dtucker pushed a commit to branch master
in repository openssh.

commit a9c9e91a82bc1a2cf801b4e3ef27a941dbd27717
Author: Philip Hands <phil at hands.com>
Date:   Wed Sep 16 16:13:30 2020 +0200

    add -s flag: to install keys via SFTP
    
    This is prompted by:
    
     https://bugzilla.mindrot.org/show_bug.cgi?id=3201
    
    Thanks go to Matthias Blümel for the idea, and the helpful patch, from
    which this patch grew.
    
    SSH-Copy-ID-Upstream: f7c76dc64427cd20287a6868f672423b62057614
---
 contrib/ssh-copy-id   | 99 +++++++++++++++++++++++++++++++++++++--------------
 contrib/ssh-copy-id.1 |  9 ++++-
 2 files changed, 80 insertions(+), 28 deletions(-)

diff --git a/contrib/ssh-copy-id b/contrib/ssh-copy-id
index ee3f6374..6b8aca7d 100644
--- a/contrib/ssh-copy-id
+++ b/contrib/ssh-copy-id
@@ -1,6 +1,7 @@
 #!/bin/sh
 
 # Copyright (c) 1999-2020 Philip Hands <phil at hands.com>
+#               2020 Matthias Blümel <blaimi at blaimi.de>
 #               2017 Sebastien Boyron <seb at boyron.eu>
 #               2013 Martin Kletzander <mkletzan at redhat.com>
 #               2010 Adeodato =?iso-8859-1?Q?Sim=F3?= <asp16 at alu.ua.es>
@@ -61,11 +62,14 @@ fi
 
 # shellcheck disable=SC2010
 DEFAULT_PUB_ID_FILE=$(ls -t "${HOME}"/.ssh/id*.pub 2>/dev/null | grep -v -- '-cert.pub$' | head -n 1)
+SSH="ssh -a -x"
+umask 0177
 
 usage () {
-  printf 'Usage: %s [-h|-?|-f|-n] [-i [identity_file]] [-p port] [-F alternative ssh_config file] [[-o <ssh -o options>] ...] [user@]hostname\n' "$0" >&2
+  printf 'Usage: %s [-h|-?|-f|-n|-s] [-i [identity_file]] [-p port] [-F alternative ssh_config file] [[-o <ssh -o options>] ...] [user@]hostname\n' "$0" >&2
   printf '\t-f: force mode -- copy keys without trying to check if they are already installed\n' >&2
   printf '\t-n: dry run    -- no keys are actually copied\n' >&2
+  printf '\t-s: use sftp   -- use sftp instead of executing remote-commands. Can be useful if the remote only allows sftp\n' >&2
   printf '\t-h|-?: print this help\n' >&2
   exit 1
 }
@@ -108,9 +112,8 @@ if [ -n "$SSH_AUTH_SOCK" ] && ssh-add -L >/dev/null 2>&1 ; then
   GET_ID="ssh-add -L"
 fi
 
-while getopts "i:o:p:F:fnh?" OPT
+while getopts "i:o:p:F:fnsh?" OPT
 do
-
   case "$OPT" in
     i)
       [ "${SEEN_OPT_I}" ] && {
@@ -129,6 +132,9 @@ do
     n)
       DRY_RUN=1
       ;;
+    s)
+      SFTP=sftp
+      ;;
     h|\?)
       usage
       ;;
@@ -137,9 +143,6 @@ done
 #shift all args to keep only USER_HOST
 shift $((OPTIND-1))
 
-
-
-
 if [ $# = 0 ] ; then
   usage
 fi
@@ -170,6 +173,8 @@ fi
 # and has the side effect of setting $NEW_IDS
 populate_new_ids() {
   L_SUCCESS="$1"
+  L_TMP_ID_FILE="$SCRATCH_DIR"/popids_tmp_id
+  L_OUTPUT_FILE="$SCRATCH_DIR"/popids_output
 
   # shellcheck disable=SC2086
   if [ "$FORCED" ] ; then
@@ -180,15 +185,6 @@ populate_new_ids() {
   # repopulate "$@" inside this function 
   eval set -- "$SSH_OPTS"
 
-  umask 0177
-  L_TMP_ID_FILE=$(mktemp ~/.ssh/ssh-copy-id_id.XXXXXXXXXX)
-  if test $? -ne 0 || test "x$L_TMP_ID_FILE" = "x" ; then
-    printf '%s: ERROR: mktemp failed\n' "$0" >&2
-    exit 1
-  fi
-  L_CLEANUP="rm -f \"$L_TMP_ID_FILE\" \"${L_TMP_ID_FILE}.stderr\""
-  # shellcheck disable=SC2064
-  trap "$L_CLEANUP" EXIT TERM INT QUIT
   printf '%s: INFO: attempting to log in with the new key(s), to filter out any that are already installed\n' "$0" >&2
   # shellcheck disable=SC2086
   NEW_IDS=$(
@@ -200,16 +196,19 @@ populate_new_ids() {
         # assumption will break if we implement the possibility of multiple -i options.
         # The point being that if file based, ssh needs the private key, which it cannot
         # find if only given the contents of the .pub file in an unrelated tmpfile
-        ssh -i "${PRIV_ID_FILE:-$L_TMP_ID_FILE}" \
+        $SSH -i "${PRIV_ID_FILE:-$L_TMP_ID_FILE}" \
             -o ControlPath=none \
             -o LogLevel=INFO \
             -o PreferredAuthentications=publickey \
-            -o IdentitiesOnly=yes "$@" exit 2>"$L_TMP_ID_FILE.stderr" </dev/null
+            -o IdentitiesOnly=yes "$@" exit >"$L_OUTPUT_FILE" 2>&1 </dev/null
         if [ "$?" = "$L_SUCCESS" ] ; then
           : > "$L_TMP_ID_FILE"
+        elif [ "$SFTP" ] && grep 'allows sftp connections only' "$L_OUTPUT_FILE" >/dev/null ; then
+          # this error counts as a success when we're setting up an sftp connection
+          : > "$L_TMP_ID_FILE"
         else
-          grep 'Permission denied' "$L_TMP_ID_FILE.stderr" >/dev/null || {
-            sed -e 's/^/ERROR: /' <"$L_TMP_ID_FILE.stderr" >"$L_TMP_ID_FILE"
+          grep 'Permission denied' "$L_OUTPUT_FILE" >/dev/null || {
+            sed -e 's/^/ERROR: /' <"$L_OUTPUT_FILE" >"$L_TMP_ID_FILE"
             cat >/dev/null #consume the other keys, causing loop to end
           }
         fi
@@ -218,7 +217,6 @@ populate_new_ids() {
       done
     }
   )
-  eval "$L_CLEANUP" && trap - EXIT TERM INT QUIT
 
   if expr "$NEW_IDS" : "^ERROR: " >/dev/null ; then
     printf '\n%s: %s\n\n' "$0" "$NEW_IDS" >&2
@@ -264,7 +262,49 @@ EOF
   printf "exec sh -c '%s'" "${INSTALLKEYS_SH}"
 }
 
-REMOTE_VERSION=$(ssh -v -o PreferredAuthentications=',' -o ControlPath=none "$@" 2>&1 |
+installkeys_via_sftp() {
+
+  # repopulate "$@" inside this function
+  eval set -- "$SSH_OPTS"
+
+  L_KEYS=$SCRATCH_DIR/authorized_keys
+  L_SHARED_CON=$SCRATCH_DIR/master-conn
+  $SSH -f -N -M -S $L_SHARED_CON "$@"
+  L_CLEANUP="$SSH -S $L_SHARED_CON -O exit 'ignored' >/dev/null 2>&1 ; $SCRATCH_CLEANUP"
+  trap "$L_CLEANUP" EXIT TERM INT QUIT
+  sftp -b - -o "ControlPath=$L_SHARED_CON" "ignored" <<-EOF || return 1
+	-get .ssh/authorized_keys $L_KEYS
+	EOF
+  # add a newline or create file if it's missing, same like above
+  [ -z "$(tail -1c $L_KEYS 2>/dev/null)" ] || echo >> $L_KEYS
+  # append the keys being piped in here
+  cat >> $L_KEYS
+  sftp -b - -o "ControlPath=$L_SHARED_CON" "ignored" <<-EOF || return 1
+	-mkdir .ssh
+	chmod 700 .ssh
+	put $L_KEYS .ssh/authorized_keys
+	chmod 600 .ssh/authorized_keys
+	EOF
+  eval "$L_CLEANUP" && trap "$SCRATCH_CLEANUP" EXIT TERM INT QUIT
+}
+
+
+# create a scratch dir for any temporary files needed
+SCRATCH_DIR=$(mktemp -d ~/.ssh/ssh-copy-id.XXXXXXXXXX)
+if test $? -ne 0 || test "x$SCRATCH_DIR" = "x" ; then
+  printf '%s: ERROR: mktemp failed\n' "$0" >&2
+  exit 1
+fi
+chmod 0700 $SCRATCH_DIR
+if [ -d "$SCRATCH_DIR" ] ; then
+  SCRATCH_CLEANUP="rm -rf \"$SCRATCH_DIR\""
+  trap "$SCRATCH_CLEANUP" EXIT TERM INT QUIT
+else
+  printf '%s: ERROR: Required scratch directory (%s) was not created\n' "$0" "$SCRATCH_DIR" >&2
+  exit 1
+fi
+
+REMOTE_VERSION=$($SSH -v -o PreferredAuthentications=',' -o ControlPath=none "$@" 2>&1 |
                  sed -ne 's/.*remote software version //p')
 
 # shellcheck disable=SC2029
@@ -277,7 +317,7 @@ case "$REMOTE_VERSION" in
          printf '%s: WARNING: Non-dsa key (#%d) skipped (NetScreen only supports DSA keys)\n' "$0" "$KEY_NO" >&2
          continue
       }
-      [ "$DRY_RUN" ] || printf 'set ssh pka-dsa key %s\nsave\nexit\n' "$KEY" | ssh -T "$@" >/dev/null 2>&1
+      [ "$DRY_RUN" ] || printf 'set ssh pka-dsa key %s\nsave\nexit\n' "$KEY" | $SSH -T "$@" >/dev/null 2>&1
       if [ $? = 255 ] ; then
         printf '%s: ERROR: installation of key #%d failed (please report a bug describing what caused this, so that we can make this message useful)\n' "$0" "$KEY_NO" >&2
       else
@@ -291,16 +331,21 @@ case "$REMOTE_VERSION" in
   dropbear*)
     populate_new_ids 0
     [ "$DRY_RUN" ] || printf '%s\n' "$NEW_IDS" | \
-      ssh "$@" "$(installkeys_sh /etc/dropbear/authorized_keys)" \
+      $SSH "$@" "$(installkeys_sh /etc/dropbear/authorized_keys)" \
       || exit 1
     ADDED=$(printf '%s\n' "$NEW_IDS" | wc -l)
     ;;
   *)
     # Assuming that the remote host treats ~/.ssh/authorized_keys as one might expect
     populate_new_ids 0
-    [ "$DRY_RUN" ] || printf '%s\n' "$NEW_IDS" | \
-      ssh "$@" "$(installkeys_sh)" \
-      || exit 1
+    if ! [ "$DRY_RUN" ] ; then
+      printf '%s\n' "$NEW_IDS" | \
+        if [ "$SFTP" ] ; then
+          installkeys_via_sftp
+        else
+          $SSH "$@" "$(installkeys_sh)"
+        fi || exit 1
+    fi
     ADDED=$(printf '%s\n' "$NEW_IDS" | wc -l)
     ;;
 esac
@@ -318,7 +363,7 @@ else
 
 	Number of key(s) added: $ADDED
 
-	Now try logging into the machine, with:   "ssh $SSH_OPTS"
+	Now try logging into the machine, with:   "${SFTP:-ssh} $SSH_OPTS"
 	and check to make sure that only the key(s) you wanted were added.
 
 	EOF
diff --git a/contrib/ssh-copy-id.1 b/contrib/ssh-copy-id.1
index b75a8836..c141a296 100644
--- a/contrib/ssh-copy-id.1
+++ b/contrib/ssh-copy-id.1
@@ -1,5 +1,5 @@
 .ig \"  -*- nroff -*-
-Copyright (c) 1999-2016 hands.com Ltd. <http://hands.com/>
+Copyright (c) 1999-2020 hands.com Ltd. <http://hands.com/>
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
@@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .Nm
 .Op Fl f
 .Op Fl n
+.Op Fl s
 .Op Fl i Op Ar identity_file
 .Op Fl p Ar port
 .Op Fl o Ar ssh_option
@@ -84,6 +85,12 @@ in more than one copy of the key being installed on the remote system.
 .It Fl n
 do a dry-run.  Instead of installing keys on the remote system simply
 prints the key(s) that would have been installed.
+.It Fl s
+SFTP mode: usually the public keys are installed by executing commands on the remote side.
+With this option the user's
+.Pa ~/.ssh/authorized_keys
+file will be downloaded, modified locally and uploaded with sftp.
+This option is useful if the server has restrictions on commands which can be used on the remote side.
 .It Fl h , Fl ?
 Print Usage summary
 .It Fl p Ar port , Fl o Ar ssh_option

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list