DEV Community

Kingsley Kanu
Kingsley Kanu

Posted on Originally published at iamkay.eu

The one SSH command Windows doesn't ship

Every time a fresh machine comes up on my network, the first thing I want is my laptop's key in its authorized_keys file. On Linux or macOS that is one command you have known for years:

ssh-copy-id root@192.0.2.20
Enter fullscreen mode Exit fullscreen mode

On Windows, from PowerShell, that command does not exist. Not "is not on your PATH." Does not exist.

ssh-copy-id : The term 'ssh-copy-id' is not recognized as the name of a cmdlet,
function, script file, or operable program.
Enter fullscreen mode Exit fullscreen mode

Windows has shipped a real OpenSSH client since Windows 10, and it is genuinely good. Here is everything in it:

scp.exe   sftp.exe   ssh.exe   ssh-add.exe   ssh-agent.exe
ssh-keygen.exe   ssh-keyscan.exe   ssh-pkcs11-helper.exe   ssh-sk-helper.exe
Enter fullscreen mode Exit fullscreen mode

Nine binaries. No ssh-copy-id. The reason is that ssh-copy-id was never a binary. It is a POSIX shell script that ships alongside OpenSSH, and Microsoft's port compiles the C programs rather than carrying the shell scripts. So the omission is structural rather than an oversight, which also means it is not going to appear in a future update.

Do the same job in one line

The script does something simple: read your public key, connect, create the directory, append the key, set the permissions. You can do all of it inline.

Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub" | ssh root@192.0.2.20 `
  "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Enter fullscreen mode Exit fullscreen mode

You type the password once, in your own terminal, and never again for that host.

Two Windows-specific things that trip people up in that line. The path is $env:USERPROFILE\.ssh\, because ~/.ssh is not a thing PowerShell expands the way a POSIX shell does. And the backtick is PowerShell's line continuation character, not a backslash.

If you would rather not pipe text between a Windows program and a remote shell at all, this form avoids the whole class of line-ending and encoding questions by passing the key as an argument:

$key = (Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub" -Raw).Trim()
ssh root@192.0.2.20 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && printf '%s\n' '$key' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Enter fullscreen mode Exit fullscreen mode

This is the one I use, because it is explicit about the trailing newline instead of hoping the pipeline gets it right.

Or use the copy you already have

If you have Git for Windows installed, you already have the real script. Git Bash bundles the MSYS2 build of OpenSSH, which includes the shell scripts the Windows port leaves out:

$ command -v ssh-copy-id
/usr/bin/ssh-copy-id
Enter fullscreen mode Exit fullscreen mode

So open Git Bash instead of PowerShell and ssh-copy-id root@192.0.2.20 works exactly as it does on Linux. Two different SSH clients live on the same machine, one of which has the command and one of which does not, which is a good thing to know before you spend twenty minutes deciding your installation is broken.

When the key lands and login still asks for a password

This is the failure that actually costs time, because everything looks correct. The key is in the file, the file is on the server, and the server still wants a password.

Almost always it is permissions. sshd runs with StrictModes on by default, and it will refuse a key file that anyone other than the owner can write to. That includes the home directory itself, not just .ssh.

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod go-w ~            # the one people miss
Enter fullscreen mode Exit fullscreen mode

sshd will not tell you this on the client side. It just falls through to the next authentication method, which looks identical to the key not being there. The server knows exactly what happened:

tail /var/log/auth.log
Authentication refused: bad ownership or modes for directory /root
Enter fullscreen mode Exit fullscreen mode

Run ssh -v on the client and you will see it offer the key and get nothing back, which at least tells you the problem is on the far end.

The other common cause is having several keys. The client offers them in order, and if the server's MaxAuthTries limit is reached before it gets to the right one, you are refused for having too many keys rather than the wrong one. Pin it explicitly:

Host newnode
    HostName 192.0.2.20
    User root
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
Enter fullscreen mode Exit fullscreen mode

IdentitiesOnly yes is the important line. Without it, the client will still offer everything in your agent regardless of what you specified.

Why I bother with any of this

The point is not saving keystrokes. It is that the password gets typed exactly once, locally, by me, on the machine it belongs to. It never goes into a note, a chat window, a script or a config file. After that first connection the credential for that host is a key on my laptop protected by the operating system, and adding a new machine to my network is a thirty second job rather than a decision about where to write a password down.

That is a small habit that compounds. Every host I have built in the last year got exactly this treatment on its first boot, and I have never had to go looking for one of their passwords, because for most of them I do not remember what it was.


A note on the addresses

Addresses in this post use the range reserved for documentation under RFC 5737.


Resources

Top comments (0)