Ssh – Remote access to internal machine (ssh port-forwarding)

iptablesnat;port-forwardingremote-accessssh

I have a server (serv05) at work with a public ip, hosting two KVM guests – vtest1 & vtest2 – in two different private network – 192.168.122.0 & 192.168.100.0 – respectively, this way:

[root@serv05 ~]# ip -o addr show | grep -w inet
1: lo    inet 127.0.0.1/8 scope host lo
2: eth0    inet xxx.xxx.xx.197/24 brd xxx.xxx.xx.255 scope global eth0
4: virbr1    inet 192.168.100.1/24 brd 192.168.100.255 scope global virbr1
6: virbr0    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
#
[root@serv05 ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.100.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr1
xxx.xxx.xx.0    0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 eth0
0.0.0.0         xxx.xxx.xx.62   0.0.0.0         UG    0      0        0 eth0

I've also setup IP FORWARDing and Masquerading this way:

iptables --table nat --append POSTROUTING --out-interface eth0 -j MASQUERADE
iptables --append FORWARD --in-interface virbr0 -j ACCEPT

All works up to this point. If I want to remote access vtest1 (or vtest2) first I ssh to serv05 and then from there ssh to vtest1. Is there a way to setup a port forwarding so that vtest1 can be accessed directly from the outside world? This is what I probably need to setup:

 external_ip (tcp port 4444) -> DNAT -> 192.168.122.50 (tcp port 22)

I know it's easily doable using a SOHO router but can't figure out how can I do that on a Linux box.


Update: 1

Now I've made ssh to listen to both of the ports:

[root@serv05 ssh]# netstat -tulpn | grep ssh
tcp        0      0 xxx.xxx.xx.197:22           0.0.0.0:*         LISTEN     5092/sshd
tcp        0      0 xxx.xxx.xx.197:4444         0.0.0.0:*         LISTEN     5092/sshd

and port 4444 is allowed in the iptables rules:

[root@serv05 sysconfig]# grep 4444 iptables
-A PREROUTING -i eth0 -p tcp -m tcp --dport 4444 -j DNAT --to-destination 192.168.122.50:22 
-A INPUT -p tcp -m state --state NEW -m tcp --dport 4444 -j ACCEPT 
-A FORWARD -i eth0 -p tcp -m tcp --dport 4444 -j ACCEPT 

But I'm getting connection refused:

maci:~ santa$ telnet serv05 4444
Trying xxx.xxx.xx.197...
telnet: connect to address xxx.xxx.xx.197: Connection refused
telnet: Unable to connect to remote host

Any idea what's I'm still missing?


Update: 2

I've removed the third interface, virbr1, from the iptables just make the output shorter.

[root@serv05 sysconfig]# iptables -vL -n
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     udp  --  virbr0 *       0.0.0.0/0            0.0.0.0/0           udp dpt:53
    0     0 ACCEPT     tcp  --  virbr0 *       0.0.0.0/0            0.0.0.0/0           tcp dpt:53
    0     0 ACCEPT     udp  --  virbr0 *       0.0.0.0/0            0.0.0.0/0           udp dpt:67
    0     0 ACCEPT     tcp  --  virbr0 *       0.0.0.0/0            0.0.0.0/0           tcp dpt:67
  108  8112 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0
  189 42273 ACCEPT     all  --  eth0   *       0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     tcp  --  *      *       xxx.xxx.xx.0/24      0.0.0.0/0           state NEW tcp dpt:21
    0     0 ACCEPT     tcp  --  *      *       192.168.122.0/24     0.0.0.0/0           state NEW tcp dpt:21
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:4444
    0     0 ACCEPT     tcp  --  *      *       xxx.xxx.xx.0/24      0.0.0.0/0           state NEW tcp dpt:80
    0     0 ACCEPT     tcp  --  *      *       192.168.122.0/24     0.0.0.0/0           state NEW tcp dpt:80
    2    64 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     all  --  *      virbr0  0.0.0.0/0            192.168.122.0/24    state RELATED,ESTABLISHED
    0     0 ACCEPT     all  --  virbr0 *       192.168.122.0/24     0.0.0.0/0
    0     0 ACCEPT     all  --  virbr0 virbr0  0.0.0.0/0            0.0.0.0/0
    0     0 REJECT     all  --  *      virbr0  0.0.0.0/0            0.0.0.0/0           reject-with icmp-port-unreachable
    0     0 REJECT     all  --  virbr0 *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-port-unreachable
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited
    0     0 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0           tcp dpt:4444

Chain OUTPUT (policy ACCEPT 57 packets, 11124 bytes)
 pkts bytes target     prot opt in     out     source               destination

The very same thing is here too, virbr1 is intentionally removed from the output.

[root@serv05 sysconfig]# iptables -t nat -vL -n
Chain PREROUTING (policy ACCEPT 611 packets, 105K bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DNAT       tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0           tcp dpt:4444 to:192.168.122.50:22 

Chain POSTROUTING (policy ACCEPT 4 packets, 344 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 MASQUERADE  tcp  --  *      *       192.168.122.0/24    !192.168.122.0/24    masq ports: 1024-65535 
    0     0 MASQUERADE  udp  --  *      *       192.168.122.0/24    !192.168.122.0/24    masq ports: 1024-65535 
    0     0 MASQUERADE  all  --  *      *       192.168.122.0/24    !192.168.122.0/24    
    0     0 MASQUERADE  tcp  --  *      *       192.168.100.0/24    !192.168.100.0/24    masq ports: 1024-65535 
    0     0 MASQUERADE  udp  --  *      *       192.168.100.0/24    !192.168.100.0/24    masq ports: 1024-65535 
    0     0 MASQUERADE  all  --  *      *       192.168.100.0/24    !192.168.100.0/24    

Chain OUTPUT (policy ACCEPT 4 packets, 344 bytes)
 pkts bytes target     prot opt in     out     source               destination  


Update: 3

SSH is not listening to port 4444 anymore:

[root@serv05 sysconfig]# netstat -tulpn | grep ssh
tcp        0      0 xxx.xxx.xx.197:22         0.0.0.0:*      LISTEN      15231/sshd

The FORWARD order is fixed:

[root@serv05 sysconfig]# iptables -vL -n
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
[ .... ]

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      virbr0  0.0.0.0/0            192.168.122.0/24    state RELATED,ESTABLISHED 
    0     0 ACCEPT     all  --  virbr0 *       192.168.122.0/24     0.0.0.0/0                     
    0     0 ACCEPT     all  --  virbr0 virbr0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0           tcp dpt:4444 
    1    64 REJECT     all  --  *      virbr0  0.0.0.0/0            0.0.0.0/0           reject-with icmp-port-unreachable 
    0     0 REJECT     all  --  virbr0 *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-port-unreachable 
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

Chain OUTPUT (policy ACCEPT 5 packets, 612 bytes)
 pkts bytes target     prot opt in     out     source               destination         

but still getting connection refused:

maci:~ santa$ telnet serv05 4444
Trying xxx.xxx.xx.197...
telnet: connect to address xxx.xxx.xx.197: Connection refused
telnet: Unable to connect to remote host

Is there any other gray-area to cover?

Best Answer

You need a NAT rule (to direct the traffic) and a regular firewall rule (to permit it).

The former will look something like

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 4444 -j DNAT --to-destination 192.168.122.50:22

The latter will look something like

iptables -A FORWARD -i eth0 -p tcp --dport 4444 -j ACCEPT

It's up to you to make sure those come at the right point in your existing PREROUTING and FORWARD chains, and in addition you may need a second firewall rule to permit the back-half of those connections out, if you don't already have a general ACCEPT for ESTABLISHED packets.

Edit: the order of your rules is extremely important. The right rule in the wrong place will do no good. Could you replace the grep output above with the result of iptables -L -n -v and iptables -t nat -L -n -v? And if you want port 4444 to be forwarded, don't run a local sshd also bound to that port.

Edit 2: and there's your problem. The ACCEPT you've added in the FORWARD chain is line 7, but line 4 has already explicitly denied all not-previously-permitted traffic from everywhere (*) to virbr0. You need to make arrangments for the line you've added to come before line 4, perhaps by adding the rule with

iptables -I FORWARD 4 -i eth0 -p tcp --dport 4444 -j ACCEPT

which will insert it at line 4, displacing the current line 4 to be line 5 (and so on).

Regarding the current sshd, I mean what I said: that you shouldn't have a daemon bound to port 4444 if you're trying to forward that port. I don't care what other ports it's bound to, only that 4444 is a bad idea.

Edit 3: the machine you're testing this from, this is completely outside the serv05 system, yes? And (after a very trying day putting Fedora 16 on several boxes) I fear you may be right, could you put a comparable ACCEPT rule for 4444 in the INPUT chain as well, being careful to get it before any REJECTs?