neutrinolabs / xrdp

xrdp: an open source RDP server
http://www.xrdp.org/
Apache License 2.0
5.71k stars 1.73k forks source link

User authentication using SSH keys #773

Open ben-cohen opened 7 years ago

ben-cohen commented 7 years ago

We would like to use SSH keys for authentication to xrdp. Has anybody done this? I suspect not because I haven't found anything on Google.

It would need non-Microsoft/standard extensions to the protocol, perhaps using one of the following ideas. (So this would need changes to xrdp, FreeRDP and/or rdesktop and probably won't work using the Microsoft client or server!)

  1. Use a custom RDP virtual channel for SSH authentication, similar smart card authentication.
  2. Use the existing smart card authentication with a X.509 certificate provided by to the client locally, e.g. from a file or by ssh-agent, rather than a real smart card.
  3. Add new security protocols to RDP_NEG_REQ (2.2.1.1.1) and RDP_NEG_RSP (2.2.1.2.1), similar to CredSSP/PROTOCOL_HYBRID in the MS-RDPBCGR spec.

An alternative might be to connect using SSH, at which point the user is authenticated, so we can forward an xorg/xorgxrdp session running as that user without the usual login dialog. (Would that work? Would reconnecting sessions be possible?)

speidy commented 7 years ago

Hi Ben, A quick and dirty way to do it is probably modify xrdp and an open-source client such as FreeRDP, and add some custom pre connection messages that makes this SSH authentication phase first. This might limit the usage of xrdp to a custom client. (Or you can try to negotiate it through the RDP_NEG_REQ / RSP as you mentioned)

The cleaner way is to add NLA support in xrdp (not implemented yet) and then implement the custom authentication mechanism on top of it (for example, use GSSAPI module for RSA keys authentication if exists.) That will also require to add a client-side support. That way client will be able to negotiate supported auth methods.

I don't see how you can easily leverage SC channel for this purpose.

HTH, Idan Freiberg

PGP FP: 8108 7EC9 806E 4980 75F2 72B3 8AD3 2D04 337B 1F18

ben-cohen commented 7 years ago

Hi Idan,

Thanks for your reply. I am going to try to implement this using RDP over SSH as described below, because this should be a relatively simple change. I'll let you know how I get on...

RDP over SSH

I want to use SSH keys to authenticate to a computer, so why not use SSH to log in to it? :-) Then we just need xrdp to allow a user to connect as themselves to a local session and forward RDP over the SSH connection.

SSH can forward a connection to a Unix domain socket (AF_UNIX) on the server. This allows authentication [1, 2] locally within a Unix system which is sufficient to connect to xrdp without requiring a password (as the SSH authenticated user only). (Credit due to my colleague Andy for suggesting this: I was thinking of changing xrdp to allow a normal user to create a session, which would be more effort for a worse solution.)

Advantages

  1. Minimal changes required.
  2. No changes to RDP protocol so any client can connect without modification.
  3. Don't need to expose xrdp externally.
  4. ssh-agent is forwarded "for free" (if you specify "ssh -A").
  5. SSH authentication and policy all done by ssh itself.

Disadvantages

  1. RDP over SSH might be slower or laggier.
  2. Requires SSH 6.7 or later to forward Unix domain sockets. (Workaround: socat)
  3. Requires Unix domain socket on xrdp server side. (I.e. if you port xrdp to Windows, you'll have to do it another way.)

Implementation

  1. Modify xrdp to listen on a Unix domain socket, e.g. /var/run/xrdp-ssh.socket, as well as (or instead of) a TCP socket. EDIT: xrdp already does this if you set "port" to an absolute path in /etc/xrdp/xrdp.ini!

  2. If a connection tries to log in using the socket then find the other end's user using "getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len);" (on Linux). If this is the same as the logon username then allow it: it can bypass the logon box.

  3. There should be an xrdp.ini config option e.g. af_unix_auth (and perhaps also af_unix_listen) to enable or disable this.

  4. Forward environment variables SSH_AUTH_SOCK and SSH_AGENT_PID to the session for the ssh-agent.

  5. Write a wrapper script to set up the SSH connection and launch the RDP client, e.g. roughly this.

    ssh <user>@<host> -fxNT [-A|-a] -L localhost:<port>:/var/run/xrdp-ssh.socket sleep 10
    rdesktop|xfreerdp -u <user> localhost:<port>   # or
    xfreerdp /v:localhost:<port>

Unsolved problems and thoughts

  1. If you disconnect and reconnect, the agent will have different credentials so will no longer work within the session. (This might be solved by ssh-agent-proxy.)

  2. If the server closes the RDP connection will ssh and the rdp client close cleanly? (An auto closing ssh tunnel will help.)

  3. How to prevent random other clients from connecting to the ssh port? (Answer: could have the SSH command write a secret to the server and have the client use the same secret as the password. Note we can use e.g. 127.0.NNN.1 as a private ip!)

  4. How to choose a free port?

  5. What does SO_PEERCRED actually report for an SSH forwarded unix socket?

Alternative solutions

My alternative choice would be to add an RDP channel to forward ssh-agent requests from the server to the client. Then the client sets a custom flag in the protocol, e.g. PROTOCOL_SSH_AUTH=0x00001000 in requestedProtocols in RDP_NEG_REQ. The server will attempt authentication over the ssh-agent channel. That is reasonably straightforward but sensitive to changes in the official protocol and the SSH bit might end up being a lot of work. (The ssh-agent channel would actually be useful generally during sessions, although it is up to the client whether to allow that.)

SSH can forward X.509 certificates but I think that is non-standard, and xrdp doesn't seem to support smartcard logon so I haven't pursued that route. And NLA looks quite complicated!

ben-cohen commented 7 years ago

That won't work because the RDP client connects to xrdp but the authentication is done by sesman, which can't see the RDP client's socket.

Instead add a command to sesman to generate a token to can be used as credentials by an RDP client. And allow sesman to authenticate using the user at the other end of a local socket.

So now the wrapper script will look like this:

ssh <user>@<host> -x \
                  [-A|-a] \
                  -L localhost:<port>:localhost:3389 \
                  'xrdp-sesadmin -u=`whoami` -c=token ; sleep 10' \
| (read token ; \
   rdesktop -u <user> -p $token localhost:<port>)

This has the advantage that ssh doesn't need to forward Unix domain sockets (solving some of the problems mentioned above).

ben-cohen commented 7 years ago

I've just written a PAM module that allows this, at least on Linux, by generating a temporary token. It needs no changes to xrdp or RDP clients at all, except for editing /etc/pam.d/xrdp-sesman. See https://github.com/ben-cohen/transient_token. The wrapper commands are:

# edit as appropriate
SERVER=server
USER=`whoami`
REMPORT=3389

# direct rdp connection
ssh $SERVER \
    -x \
    get_transient_token
| (read TOKEN ; \
   xfreerdp /v:$SERVER:$REMPORT /u:$USER /p:"$TOKEN")

# rdp via ssh tunnel
ssh $SERVER \
    -x \
    -L localhost:12345:localhost:$REMPORT \
    'get_transient_token ; sleep 10'
| (read TOKEN ; \
   xfreerdp /v:localhost:12345 /u:$USER /p:"$TOKEN")

(This doesn't help for systems without PAM. For such systems you could implement this by checking the token in auth_userpass() in verify_user.c.)

Unfortunately the client's ssh-agent is not made available to the remote session. For the SSH tunnel, which can forward the key, ssh-agent-proxy might work. For connections not made over SSH, I think an RDP channel could solve this.

ben-cohen commented 7 years ago

Hi @volth, no I don't believe so.

It might be possible by writing a custom Credential Provider and an virtual channel, but that's beyond my knowledge of Windows! But it might not be possible.