Reproduced this on a fresh 24.04 LTS box (openssh-client 1:9.6p1) and
verified against 26.04.

Here is what happens:
1. Old key is authorized on the server, new key is not.
2. Run:
   $ ssh-copy-id -o StrictHostKeyChecking=no -i /tmp/new_key.pub -o 
IdentityFile=/tmp/old_key user@target
3. Output:
   /usr/bin/ssh-copy-id: WARNING: All keys were skipped because they already 
exist on the remote system.
   (if you think this is a mistake, you may want to use -f option)

Checking authorized_keys confirms the new key was never copied.

Why this happens:
In filter_ids(), ssh-copy-id runs:
ssh -i "${PRIV_ID_FILE:-$L_TMP_ID_FILE}" -o PreferredAuthentications=publickey 
-o IdentitiesOnly=yes "$@" exit

The script assumes IdentitiesOnly=yes tests only the new key from -i.
But per ssh_config(5), IdentitiesOnly still includes any IdentityFile
passed in "$@". So ssh happily logs in using the old key, exits 0, and
ssh-copy-id pats itself on the back thinking the new key is already
there.

Review of the Codex patch:
The logic of splitting option lists is actually spot on. However, typical AI 
moment: it dropped the LOGIN_ID_OPT logic straight into the cat <<-EOF block, 
so on execution it literally prints shell code to the user instead of running 
it. Also, on 26.04 when using -f, PRIV_ID_FILE is unset, which left a dangling 
"-i " flag in the suggested login command.

Here is the cleaned-up diff that fixes both the check and the login
hint:

--- /usr/bin/ssh-copy-id
+++ /usr/bin/ssh-copy-id
@@ -82,6 +82,18 @@
   printf '%s\n' "$1" | sed -e "s/'/'\\\\''/g"
 }
 
+identity_file_option() {
+  L_OPT_KEY=$(printf '%s\n' "$1" | sed 's/[[:space:]=].*//')
+
+  case "$L_OPT_KEY" in
+    [Ii][Dd][Ee][Nn][Tt][Ii][Tt][Yy][Ff][Ii][Ll][Ee])
+      return 0
+      ;;
+  esac
+
+  return 1
+}
+
 use_id_file() {
   L_ID_FILE="$1"
 
@@ -96,7 +108,8 @@
     PUB_ID_FILE="$L_ID_FILE.pub"
   fi
 
-  [ "$FORCED" ] || PRIV_ID_FILE=$(dirname "$PUB_ID_FILE")/$(basename 
"$PUB_ID_FILE" .pub)
+  SUGGEST_PRIV_ID_FILE=$(dirname "$PUB_ID_FILE")/$(basename "$PUB_ID_FILE" 
.pub)
+  [ "$FORCED" ] || PRIV_ID_FILE="$SUGGEST_PRIV_ID_FILE"
 
   # check that the files are readable
   for f in "$PUB_ID_FILE" ${PRIV_ID_FILE:+"$PRIV_ID_FILE"} ; do
@@ -127,7 +140,20 @@
       use_id_file "${OPTARG:-$DEFAULT_PUB_ID_FILE}"
       ;;
     o|F)
-      OPTS_oF="${OPTS_oF:+$OPTS_oF }-$OPT '$(quote "${OPTARG}")'"
+      Q_OPTARG=$(quote "${OPTARG}")
+      OPTS_oF="${OPTS_oF:+$OPTS_oF }-$OPT '$Q_OPTARG'"
+      case "$OPT" in
+        o)
+          if ! identity_file_option "$OPTARG" ; then
+            OPTS_FILTER_oF="${OPTS_FILTER_oF:+$OPTS_FILTER_oF }-$OPT 
'$Q_OPTARG'"
+            OPTS_LOGIN_oF="${OPTS_LOGIN_oF:+$OPTS_LOGIN_oF }-$OPT '$Q_OPTARG'"
+          fi
+          ;;
+        F)
+          OPTS_FILTER_oF="${OPTS_FILTER_oF:+$OPTS_FILTER_oF }-$OPT '$Q_OPTARG'"
+          OPTS_LOGIN_oF="${OPTS_LOGIN_oF:+$OPTS_LOGIN_oF }-$OPT '$Q_OPTARG'"
+          ;;
+      esac
       ;;
     f)
       FORCED=1
@@ -167,6 +193,9 @@
 USER_HOST="$*"
 # tack the hostname onto SSH_OPTS
 OPTS_USER_HOST="${OPTS_oF:+$OPTS_oF }'$(quote "$USER_HOST")'"
+OPTS_FILTER_USER_HOST="${OPTS_FILTER_oF:+$OPTS_FILTER_oF }'$(quote 
"$USER_HOST")'"
+OPTS_LOGIN_USER_HOST="${OPTS_LOGIN_oF:+$OPTS_LOGIN_oF }'$(quote "$USER_HOST")'"
+SSH_FILTER_OPTS="${SSH_PORT:+-p $SSH_PORT }$OPTS_FILTER_USER_HOST"
 SSH_OPTS="${SSH_PORT:+-p $SSH_PORT }$OPTS_USER_HOST"
 # and populate "$@" for later use (only way to get proper quoting of options)
 eval set -- "$SSH_OPTS"
@@ -190,7 +219,7 @@
   L_OUTPUT_FILE="$SCRATCH_DIR"/popids_output
 
   # repopulate "$@" inside this function
-  eval set -- "$SSH_OPTS"
+  eval set -- "$SSH_FILTER_OPTS"
 
   while read -r ID || [ "$ID" ] ; do
     printf '%s\n' "$ID" > "$L_TMP_ID_FILE"
@@ -375,11 +404,15 @@
        EOF
 else
   [ -z "$SFTP" ] || PORT_OPT=P
+  LOGIN_ID_OPT=
+  if [ "$SEEN_OPT_I" ] && [ "$SUGGEST_PRIV_ID_FILE" ] && [ -r 
"$SUGGEST_PRIV_ID_FILE" ] ; then
+    LOGIN_ID_OPT="-i '$(quote "$SUGGEST_PRIV_ID_FILE")' "
+  fi
   cat <<-EOF
 
        Number of key(s) added: $ADDED
 
-       Now try logging into the machine, with:   "${SFTP:-ssh}${SSH_PORT:+ 
-${PORT_OPT:-p} $SSH_PORT} ${OPTS_USER_HOST}"
+       Now try logging into the machine, with:   "${SFTP:-ssh} 
${LOGIN_ID_OPT}${SSH_PORT:+-${PORT_OPT:-p} $SSH_PORT }${OPTS_LOGIN_USER_HOST}"
        and check to make sure that only the key(s) you wanted were added.
 
        EOF

Tested on the stand:
- Normal install with -o IdentityFile: installs properly.
- Space syntax (-o "IdentityFile ..."): works.
- Idempotency: second run detects existing key and skips as expected.
- Final login hint: outputs clean command without trailing empty flags.

Moving status to Confirmed.

-- 
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2161129

Title:
  ssh-copy-id mix up identities with -i and -o

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/2161129/+subscriptions


-- 
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs

Reply via email to