ssh install refuses stray arguments before -- and a symlinked authorized_keys (closes #61)
check / check (push) Failing after 3s
check / check (push) Failing after 3s
ssh install now takes the host alone before --: any other word there, or a second argument without --, is refused before the mnemonic is read or sftp runs, so keyfunc ssh install alice@host frank@host no longer installs the key for frank@host. The first listing of ~/.ssh is now ls -n, which shows the file type, so a symlinked authorized_keys is refused before any upload instead of being replaced by a regular file; the README says so. Judgement calls: install -- host is refused; a symlinked authorized_keys is refused even when its target already holds the key. Model: opus-5-5 Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
This commit was merged in pull request #62.
This commit is contained in:
@@ -99,6 +99,8 @@ const marker = "KEYFUNC_TEST_MARKER"
|
||||
//
|
||||
// The listing and the two ways a get can fail are worded as the
|
||||
// OpenSSH client words them, each naming the path the server expanded.
|
||||
// A long listing (-n) writes each entry as the client does, its type
|
||||
// first, so that a symlink shows as one.
|
||||
// A listing fails one way when .ssh is not there and another when it is
|
||||
// there but shut to the user; the first is the only failure read as a
|
||||
// host with no file. A get fails one way for a file that is not there,
|
||||
@@ -137,8 +139,7 @@ while IFS= read -r line; do
|
||||
worked=yes
|
||||
case "$1" in
|
||||
ls)
|
||||
dir=$2
|
||||
[ "$dir" = -1 ] && dir=$3
|
||||
dir=$3
|
||||
if [ ! -e "$home/$dir" ]; then
|
||||
worked=no
|
||||
printf 'Can'\''t ls: "%s" not found\n' "$home/$dir" >&2
|
||||
@@ -149,7 +150,13 @@ while IFS= read -r line; do
|
||||
else
|
||||
for entry in "$home/$dir"/*; do
|
||||
[ -e "$entry" ] || continue
|
||||
printf '%s/%s\n' "$dir" "$(basename "$entry")"
|
||||
name="$dir/$(basename "$entry")"
|
||||
if [ "$2" = -n ]; then
|
||||
printf '%s ? someone users 0 Oct 4 15:44 %s\n' \
|
||||
"$(stat -c '%A' "$entry")" "$name"
|
||||
else
|
||||
printf '%s\n' "$name"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
;;
|
||||
@@ -306,7 +313,7 @@ func TestTheFileIsUploadedBesideTheOldOneAndThenRenamedOverIt(t *testing.T) {
|
||||
|
||||
// The listing fails on a host with no .ssh, so the get never runs;
|
||||
// the write session then makes the directory and puts the file.
|
||||
require.Equal(t, "ls -1 .ssh", sent[0])
|
||||
require.Equal(t, "ls -n .ssh", sent[0])
|
||||
require.Equal(t, "-mkdir .ssh", sent[1])
|
||||
require.Equal(t, "chmod 700 .ssh", sent[2])
|
||||
require.Equal(t, "put", strings.Fields(sent[3])[0])
|
||||
@@ -372,6 +379,59 @@ func TestADirectoryThatCannotBeEnteredIsRefusedBeforeAnyUpload(t *testing.T) {
|
||||
require.Equal(t, notADirectory, read(t, inTheWay))
|
||||
}
|
||||
|
||||
func TestASymlinkedFileIsRefusedBeforeAnyUpload(t *testing.T) {
|
||||
t.Setenv(mnemonic.Variable, example())
|
||||
|
||||
pretend := pretendHost(t)
|
||||
|
||||
// The file the link points at, which the key would never reach.
|
||||
target := filepath.Join(pretend.home, "keys")
|
||||
require.NoError(t,
|
||||
os.WriteFile(target, []byte("somebody else\n"), fileMode),
|
||||
)
|
||||
|
||||
directory := filepath.Join(pretend.home, keptUnder)
|
||||
require.NoError(t, os.Mkdir(directory, directoryMode))
|
||||
|
||||
link := filepath.Join(directory, keptIn)
|
||||
require.NoError(t, os.Symlink(target, link))
|
||||
|
||||
printed, _, err := attempt(t, host)
|
||||
require.ErrorIs(t, err, ssh.ErrSymlink)
|
||||
require.Empty(t, printed)
|
||||
|
||||
// The read and nothing after it: no upload was tried, the link
|
||||
// still points where it did, and what it points at is unchanged.
|
||||
require.Equal(t, 1, connections(t, pretend))
|
||||
|
||||
pointsAt, err := os.Readlink(link)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, target, pointsAt)
|
||||
require.Equal(t, "somebody else\n", read(t, target))
|
||||
}
|
||||
|
||||
func TestAnArgumentBesideTheHostIsRefusedBeforeAnyConnection(t *testing.T) {
|
||||
t.Setenv(mnemonic.Variable, example())
|
||||
|
||||
pretend := pretendHost(t)
|
||||
|
||||
runs := [][]string{
|
||||
{host, "frank@example.com"},
|
||||
{host, "2222"},
|
||||
{host, "frank@example.com", "--", "-P", "2222"},
|
||||
{"--", host},
|
||||
}
|
||||
|
||||
for _, args := range runs {
|
||||
printed, _, err := attempt(t, args...)
|
||||
require.ErrorIs(t, err, ssh.ErrStrayArgument)
|
||||
require.Empty(t, printed)
|
||||
}
|
||||
|
||||
// sftp was never started, so nothing was uploaded.
|
||||
require.NoFileExists(t, pretend.arguments)
|
||||
}
|
||||
|
||||
func TestAnExistingDirectoryKeepsItsModeAndIsNotRemade(t *testing.T) {
|
||||
t.Setenv(mnemonic.Variable, example())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user