5

I have an ssh tunnel listening on a local port, say 8888, opened from a remote machine with ssh -R 8888:localhost:80 myuser@myhost. I need to write a script for "myuser" on "myhost" that will close this tunnel.

The script will be executed by "myuser" on "myhost". It won't be executed by the root nor it will be able to sudo.

One approach wound be to find PID of the process listening on 8888 using lsof or netstat and then kill the process. However, both lsof and netstat refuse to give me the PID without going sudo. With netstat I just get - instead of "PID/Program name" without sudo.

Is there a way to find out PID of the process listening on a given port without the root priviledges? The process listening on this port is running under the same user we are. Or is there any other way to close the ssh tunnel from outside of it?

Note that using the ssh escape sequence to reset the connection is not a solution as we are not in the tunnel. Our script needs to run separately of the tunnel on the same host by the same user.

Also note that just running killall sshd is not a solution as it will kill all other ssh connections not just this one.

This question is not a duplicate of many similar questions, because all of their accepted answers I have found requires root priviledges, or just kills all ssh connections.

The host "myhost" is running Ubuntu 12.04.5.


Edit: Discussion summary as requested by @Jakuje:

  • @Patrick suggested an approach to use setuid or to modify /etc/sudoers to allow the user to run the script with root priviledges without having full sudo. Although, for setuid we'd need to write a binary instead of the script. However, allowing the user to run the script with root priviledges could be a potential security risk. And still this solution does not cover the case the user has no root access at all, so he cannot modify /etc/sudoers. If @Patrick writes this as an answer I'll definitely upvote it, but I won't mark it accepted, yet.

  • @EricRenouf found out that sshd does chown the socket to the user so the user can't use /proc/*/fd to find the process that has the socket. Which is probably why neither lsof nor netstat show it.

More ideas:

As @EricRenouf found there is probably no way to get sshd PID by the opened socket port number nor inode. But I'm curious if there isn't any other trick how to find this sshd PID or how to tell it to close the connection. Maybe somehow directly with sshd.

For instance, if I enabled sshd debug mode sshd -d then it would log which sshd process opened a tunnel to which port in /var/log/auth.log which is readable by the user. So the script could parse the log and find the PID there. But running a debug mode sshd is not a good idea. There is actually a simple patch for sshd to log opened tunnels even without debug mode. But I'm not sure running a patched sshd would be a good idea either.

Is there any other trick?

  • Nice problem, I'm looking forward to the given solutions to this as I suffer the same issue sometimes. – djsmiley2kStaysInside May 24 '17 at 13:42
  • You could add the lsof/netstat, and kill commands to a script, then setuid on the script to let it escalate to root when executed. You will need to add the appropriate commands to a script, make sure the script is owned by root, and then use `chmod u+s /path/to/script` to make it SUID root. – Patrick May 24 '17 at 13:43
  • 1
    @Patrick Isn't SUID ignored on scripts? – martin.macko.47 May 24 '17 at 13:50
  • 2
    You're correct. I wasn't aware of that. Also see this thread: https://superuser.com/questions/440363/can-i-make-a-script-always-execute-as-root I think changing the sudoers file might be a better option. – Patrick May 24 '17 at 14:04
  • @Patrick Yes, this could work as a workaround. But I feel like opening a security hole by it. I would prefer not to use root priviledges at all. As the tunnel is run by the user, the user may kill it. So I don't see a reason why the user can't find which process to kill. I just can't find a way how to find it. – martin.macko.47 May 24 '17 at 14:31
  • 1
    It's requested by the user, but it's really being created by `sshd` which is almost certainly running as `root`. I think this is backed up by looking in `/proc/net/tcp` and finding the entry for `22B8` (hex for 8888) and seeing (for me at least) that the `uid` column has `0` – Eric Renouf May 24 '17 at 14:42
  • @EricRenouf for me the `uid` is `1000` the user's uid: `cat /proc/net/tcp | grep 22B8`: `10: 00000000:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 758580 1 0000000000000000 100 0 0 10 0`. Column order: `sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode` – martin.macko.47 May 24 '17 at 15:38
  • Uid is 0 in that output, isn't it? – Eric Renouf May 24 '17 at 15:48
  • @EricRenouf It's difficult to format it here, so see the [screenshot](https://imgur.com/a/ytRcq). If i'm not mistaken the uid is 1000. Isn't it? – martin.macko.47 May 24 '17 at 16:07
  • Yup, looks like uid 0 to me – Eric Renouf May 24 '17 at 16:33
  • Then I'm puzzled. Which column is uid? Isn't inode 758580 and uid the second column to the left from inode as written [in this answer](https://askubuntu.com/questions/243435/not-able-to-understand-and-map-output-of-proc-net-tcp)? – martin.macko.47 May 24 '17 at 16:41
  • 1
    @martin.macko.47 please edit the question with the additional information gathered in the comments. It is very hard to read through the poorly formated comments to understand what the actual problem is. – Jakuje May 24 '17 at 19:53
  • I agree with you about the UID now, I was looking at it wrongly, trying to count back from the end to get to the UID, but it doesn't seem to work that way, my bad! – Eric Renouf May 25 '17 at 11:39
  • Bad news on further investigation though, while sshd does chown the socket to your user, trying to find the PID that has it is still owned by root, which makes me think you're going to need to sudo for this. Using the inode from the /proc/net/tcp I looked through /proc/*/fd to find the process that had that socket, and it's sshd running as root, so you may well have a hard time tracking that down as an unprivileged user – Eric Renouf May 25 '17 at 11:46
  • Can you run a special copy of sshd, perhaps specifying a specific configuration file (which could specify a different port)? Then `pkill -f` with that config filename (or any other unique part of the command line, like TCP port number to listen on)? – TOOGAM May 26 '17 at 05:16

1 Answers1

2

Start by setting up a custom SSH key for connecting to "myhost". Then, edit your .ssh/authorized_keys file on "myhost" so that the shell's parent PID is written to a file:

command="echo $PPID > tunnel.pid; bash" ssh-rsa AAA...

You may want to tweak some other settings for security and/or you could write a custom script to just write the PID and hang. The principle is the same either way.

Once you have the PID of the process that is keeping the SSH session open, it's just a matter of scripting...

kill `cat tunnel.pid`

...to terminate the process, thereby closing the SSH session.

Note that you can use the -i flag to the ssh command to specify a private key to use for the connection.

Edit: Killing the shell process won't close the tunnel if there are active connections, so we kill the parent SSH process.

Edit: Although my first inclination with any automated SSH connection is to use SSH keys, the same process could be emulated with:

ssh -R 8888:localhost:80 myuser@myhost 'echo $PPID > tunnel.pid; for ((;;)); do sleep 1; done'

You can also substitute a script that checks for some other criteria to decide when to terminate itself. That's probably a cleaner way of handling this.

Google Fail
  • 174
  • 1
  • 9
  • Thanks. Almost what I wanted. But it works only if there are **no open connections** in the tunnel. Because `echo $$` gives us only the PID of the bash running inside sshd. And sshd won't end until all open connections close even if the command ends. If there are any connection inside the tunnel sshd will wait for them. Perhaps is there any ssh option to force close all connections when the command ends? – martin.macko.47 May 26 '17 at 11:04
  • Finally. Using `echo $PPID` instead of `echo $$` helped. Here `$PPID` is PID of bash parent process which is sshd. After you edit your answer to use `$PPID` I'll accept it. – martin.macko.47 May 26 '17 at 12:12
  • At the end I didn't use `tunnel.pid` file so I won't accidentally kill some other process with the same PID in case I'd have some ancient `tunnel.pid` there. But I used a `.killswich` file and waited for it to be touched: `ssh -R 8888:localhost:80 myuser@myhost 'inotifywait .killswich; kill $PPID'`. So after `touch .killswich` the sshd will kill itself. – martin.macko.47 May 26 '17 at 12:16
  • @martin.macko.47, I only tested with no active connections. Thanks for pointing that out. I have edited my answer. – Google Fail May 26 '17 at 14:09