Keys, agent, and config

Install and restrict public keys

Add the correct public key to the correct account and understand authorized_keys restrictions.

The server decides who can log in by reading one file per account: ~/.ssh/authorized_keys. Each line is a public key. If your client can sign with the matching private key, you are in as that user.

Two things follow from that. A key installed for deploy gives you nothing as root. And the file must belong to the account it protects, with permissions sshd is willing to trust.

Install the public line

If password login is still available on the fresh server, the quickest way is ssh-copy-id:

ssh-copy-id -i ~/.ssh/notes_server_ed25519.pub [email protected]

It appends the .pub line to the remote file and sets the permissions. Notice the .pub. If you ever find yourself copying a file without that extension to a server, stop.

To do it by hand, log in to the server and run:

mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN7fQ2... flavio notes-server operator' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Open a second terminal and test before you close the first:

ssh -o IdentitiesOnly=yes -i ~/.ssh/notes_server_ed25519 [email protected]

The permissions failure

Set the modes wrong on purpose. On the server, run chmod 644 ~/.ssh/authorized_keys and try the key again. You get Permission denied (publickey) on the client, with no hint why. The server log tells the truth:

Authentication refused: bad ownership or modes for file /home/deploy/.ssh/authorized_keys

sshd refuses a file that other users could edit, because otherwise anyone on the machine could add their own key. Put the 600 back and it works again. This is the single most common cause of “my key is installed but it does not work”.

Restrict what a key can do

Each line can carry options before the key type. This is how you give an automation identity one job:

command="/usr/local/bin/deploy-notes",restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGx8... ci deploy key

command= replaces whatever the client asks for. restrict turns off port forwarding, agent forwarding, X11, and the pseudo-terminal in one word. You can also pin the source with from="198.51.100.7".

Create a second test key on your machine, install it with that line, and connect asking for a shell:

ssh -i ~/.ssh/ci_test_ed25519 [email protected]

The script runs and the connection closes. No prompt appears. Now ask for a tunnel, and in another terminal try to use it with psql -h 127.0.0.1 -p 5433:

ssh -N -L 5433:127.0.0.1:5432 -i ~/.ssh/ci_test_ed25519 [email protected]
channel 2: open failed: administratively prohibited: open failed

The local listener opens, but the moment something tries to go through it the server says no. That refusal is the evidence you want. The same account, two keys, two very different sets of permissions.

Your own interactive key needs a shell, so never put restrict on it. Use these options for deploy jobs, backup pulls, and anything else that runs without a person watching.

Lesson completed