pyinfra-dev / pyinfra

pyinfra turns Python code into shell commands and runs them on your servers. Execute ad-hoc commands and write declarative operations. Target SSH servers, local machine and Docker containers. Fast and scales from one server to thousands.
https://pyinfra.com
MIT License
3.85k stars 374 forks source link

Connect to hosts over Tailscale/Wireguard #1021

Closed klvbdmh closed 10 months ago

klvbdmh commented 11 months ago

Is your feature request related to a problem? Please describe

I have a tailnet with a local computer and a remote server. I'd like to use pyinfra to deploy scripts over Tailscale. Right now, I can use the regular SSH connector using the original IP address of the remote server. But when I try to use the IP address assigned by Tailscale (100.x.x.x):

pyinfra --debug 100.x.x.x exec -- echo "hello world"

I get the following error:

[pyinfra.connectors.sshuserclient.client] Loading SSH config: None
[100.x.x.x] Authentication error () (Authentication failed.)

Connecting over SSH outside of pyinfra works as expected:

ssh 100.x.x.x

Describe the solution you'd like

From the documentation about Tailscale SSH:

Normally, to establish an SSH connection, the local SSH client you use will connect to the SSH server on the machine you’re trying to reach.

With Tailscale SSH, Tailscale will authenticate and encrypt the connection over WireGuard, using Tailscale node keys. The SSH client and server will still create an SSH connection, but during the SSH protocol’s authentication phase, the Tailscale SSH server already knows who the remote party is and takes over, not requiring the SSH client to provide further proof (using the SSH authentication type none).

It looks like authentication works differently when you do it over WireGuard compared to normal SSH.

Would writing a new connector be enough or some other changes are necessary?

macabrus commented 10 months ago

why not use standard SSH over tailscale0 interface? I am doing that and it works fine.

klvbdmh commented 10 months ago

why not use standard SSH over tailscale0 interface? I am doing that and it works fine.

Thanks for pointing me in the right direction, it works fine on my end too!

klvbdmh commented 3 months ago

I'm coming back to this issue in case others are struggling.

Pyinfra uses paramiko under the hood. It doesn't support connecting to ssh with no password and ssh key (paramiko/paramiko#2370), which you don't need if you connect through tailscale.

As a workaround (paramiko/paramiko#2370), generate a dummy key through ssh_paramiko_connect_kwargs in your inventory, like so:

import paramiko

dev = [
    # Your tailscale server IP goes here
    ("100.x.x.x",
     {
         "_name": "Server via Tailscale",
         # Your username goes here
         "ssh_user": "username",
         "ssh_paramiko_connect_kwargs": {"pkey": paramiko.ecdsakey.ECDSAKey.generate()},
     }
     )
]