Whitelisting Source IPs while FORWARDing SSH

All,

Cloudflare recently added Spectrum protection for SSH for Pro account holders. I decided to take them up on it for my git+ssh server. My current WAN gateway is a Ubiquiti EdgeRouter-X with OpenWRT running on it. As well as WireGuard.

My existing git server is behind the NAT, so I need to:

  1. DNAT incoming connections to port host:22
  2. permit only source IPs from Cloudflare IPv4 address space for port 22.

However, no matter what I do, via LuCi, or via /etc/firewall.user, as soon as I enable one DNAT rule for port 22, any traffic, regardless of source IP address restriction, hits my server. I've even tried inserting deliberate DROPs for --dport 22 afterwards, and it still lets everything through. I suspect the problem is with this rule:

Chain zone_wan_forward (References: 1)
...
0	0.00 B	ACCEPT	all	*	*	0.0.0.0/0	0.0.0.0/0	ctstate DNAT /* !fw3: Accept port forwards */

I'm pretty familiar with iptables, but this is the first time I've tried to do firewall/nat in OpenWRT where I needed to step out of the LuCi interface.

Here's my attempted /etc/firewall.user to set this up, as using the LuCi interface also failed.

WHITELIST=$(sed 's/#.*//' /etc/firewall.d/cloudflare_ipv4.txt)

# do the DNAT
iptables -t nat -A PREROUTING -p tcp --dport 22 -j DNAT --to 10.16.5.10:22

iptables -I zone_wan_forward 1 -p tcp --dport 22 -j DROP

# do the whitelist
for IP in ${WHITELIST}; do
    iptables -I zone_wan_forward 1 -p tcp -s $IP --dport 22 -d 10.16.5.10 -j zone_lan_dest_ACCEPT
done

I saw the example here: https://openwrt.org/docs/guide-user/firewall/firewall_configuration#includes ,but that's for INPUT. Can anyone provide some assistance on how to do this the OpenWRT way so that it's maintainable (meaning I forget about it till the next upgrade :wink: )?

Thanks!

If the allowed IPs from Cloudflare are a handful you can create a few DNAT rules with source IPs to match.
If not make an ipset with the list of IPs and use that in the DNAT.

I'm not sure what counts as a "handful", they're currently:

173.245.48.0/20
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
141.101.64.0/18
108.162.192.0/18
190.93.240.0/20
188.114.96.0/20
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
104.16.0.0/12
172.64.0.0/13
131.0.72.0/22

You can get the current list from http://www.cloudflare.com/ips-v4, which is what the user guide uses.

I've already created the DNAT rules. I've tried via LuCi, and via /etc/firewall.user, in both cases, it works fine except for the fact that it doesn't whitelist, all the random SSH probes still get forwarded (non-CF source IPs) to the git+ssh server. :-/

Post here the whole firewall to verify uci export firewall; cat /etc/firewall.user

package firewall

config defaults
        option syn_flood '1'
        option input 'ACCEPT'
        option output 'ACCEPT'
        option forward 'REJECT'

config zone 'lan'
        option name 'lan'
        option input 'ACCEPT'
        option output 'ACCEPT'
        option forward 'ACCEPT'
        option network 'lan'

config zone 'wan'
        option name 'wan'
        option input 'REJECT'
        option output 'ACCEPT'
        option mtu_fix '1'
        option network 'wan'
        option forward 'DROP'
        option masq '1'

config forwarding 'lan_wan'
        option src 'lan'
        option dest 'wan'

config rule
        option name 'Allow-DHCP-Renew'
        option src 'wan'
        option proto 'udp'
        option dest_port '68'
        option target 'ACCEPT'
        option family 'ipv4'

config rule
        option name 'Allow-Ping'
        option src 'wan'
        option proto 'icmp'
        option icmp_type 'echo-request'
        option family 'ipv4'
        option target 'ACCEPT'

config rule
        option name 'Allow-IGMP'
        option src 'wan'
        option proto 'igmp'
        option family 'ipv4'
        option target 'ACCEPT'

config rule
        option name 'Allow-DHCPv6'
        option src 'wan'
        option proto 'udp'
        option src_ip 'fc00::/6'
        option dest_ip 'fc00::/6'
        option dest_port '546'
        option family 'ipv6'
        option target 'ACCEPT'

config rule
        option name 'Allow-MLD'
        option src 'wan'
        option proto 'icmp'
        option src_ip 'fe80::/10'
        list icmp_type '130/0'
        list icmp_type '131/0'
        list icmp_type '132/0'
        list icmp_type '143/0'
        option family 'ipv6'
        option target 'ACCEPT'

config rule
        option name 'Allow-ICMPv6-Input'
        option src 'wan'
        option proto 'icmp'
        list icmp_type 'echo-request'
        list icmp_type 'echo-reply'
        list icmp_type 'destination-unreachable'
        list icmp_type 'packet-too-big'
        list icmp_type 'time-exceeded'
        list icmp_type 'bad-header'
        list icmp_type 'unknown-header-type'
        list icmp_type 'router-solicitation'
        list icmp_type 'neighbour-solicitation'
        list icmp_type 'router-advertisement'
        list icmp_type 'neighbour-advertisement'
        option limit '1000/sec'
        option family 'ipv6'
        option target 'ACCEPT'

config rule
        option name 'Allow-ICMPv6-Forward'
        option src 'wan'
        option dest '*'
        option proto 'icmp'
        list icmp_type 'echo-request'
        list icmp_type 'echo-reply'
        list icmp_type 'destination-unreachable'
        list icmp_type 'packet-too-big'
        list icmp_type 'time-exceeded'
        list icmp_type 'bad-header'
        list icmp_type 'unknown-header-type'
        option limit '1000/sec'
        option family 'ipv6'
        option target 'ACCEPT'

config rule
        option name 'Allow-IPSec-ESP'
        option src 'wan'
        option dest 'lan'
        option proto 'esp'
        option target 'ACCEPT'

config rule
        option name 'Allow-ISAKMP'
        option src 'wan'
        option dest 'lan'
        option dest_port '500'
        option proto 'udp'
        option target 'ACCEPT'

config include
        option path '/etc/firewall.user'

config rule
        option target 'ACCEPT'
        option src 'wan'
        option proto 'tcp'
        option dest_port '7772'
        option name 'Allow-SSH'

config rule 'wg'
        option name 'Allow-WireGuard'
        option src 'wan'
        option dest_port '51820'
        option proto 'udp'
        option target 'ACCEPT'

config zone
        option name 'wg'
        option input 'ACCEPT'
        option output 'ACCEPT'
        option network 'wg0'
        option forward 'ACCEPT'

config rule
        option target 'ACCEPT'
        option src 'wg'
        option name 'Allow-WG-traffic'
        option dest '*'

config forwarding
        option dest 'wg'
        option src 'lan'

config forwarding
        option dest 'lan'
        option src 'wg'

config forwarding
        option dest 'wan'
        option src 'wg'

config redirect
        option target 'DNAT'
        option src 'wan'
        option dest 'lan'
        option proto 'tcp udp'
        option src_dport '2525'
        option dest_ip '10.16.5.17'
        option dest_port '25'
        option name 'SMTP-ALT'

config rule
        option target 'ACCEPT'
        option src 'wan'
        option name 'SMTP'
        option proto 'tcp'
        option dest 'lan'
        option dest_ip '10.16.5.17'
        option dest_port '25'

config redirect
        option target 'DNAT'
        option src 'wan'
        option dest 'lan'
        option proto 'tcp udp'
        option src_dport '53'
        option dest_ip '10.16.5.9'
        option dest_port '51820'
        option name 'WG-DNS'

config redirect
        option target 'DNAT'
        option src 'wan'
        option dest 'lan'
        option src_dport '443'
        option dest_ip '10.16.5.9'
        option dest_port '51820'
        option proto 'udp'
        option name 'WG-HTTPS'

config forwarding
        option dest 'lan'
        option src 'wan'

config forwarding
        option dest 'wg'
        option src 'wan'
# This file is interpreted as shell script.
# Put your custom iptables rules here, they will
# be executed with each firewall (re-)start.

# Internal uci firewall chains are flushed and recreated on reload, so
# put custom rules into the root chains e.g. INPUT or FORWARD or into the
# special user chains, e.g. input_wan_rule or postrouting_lan_rule.

WHITELIST=$(sed 's/#.*//' /etc/firewall.d/cloudflare_ipv4.txt)

# do the DNAT
iptables -t nat -A PREROUTING -p tcp --dport 22 -j DNAT --to 10.16.5.10:22

# attempt to have a fall-through DROP rule
iptables -I zone_wan_forward 1 -p tcp --dport 22 -j DROP

# do the whitelist
for IP in ${WHITELIST}; do
    iptables -I zone_wan_forward 1 -p tcp -s $IP --dport 22 -d 10.16.5.10 -j zone_lan_dest_ACCEPT
done

FTR, here's the example from the OpenWRT user guide:

# Replace the ips-v4 with v6 if needed
for ip in `wget -qO- http://www.cloudflare.com/ips-v4`; do
  iptables -I INPUT -p tcp -m multiport --dports http,https -s $ip -j ACCEPT
done

Which I presume works for when you are attempting to protect an http/https server on the OpenWRT box.

On non-OpenWRT systems, using just raw iptables, I'll usually do a blanket DNAT in the PREROUTING table, and then do my strict permit in my FORWARD table with a default policy of DROP. Once I moved away from LuCi for this task, that's one of the first setups I tried. But a lot of the OpenWRT firewall structure is obscuring my ability to comprehend where / how I should do that.

Delete this, you have forwardings already.

config rule
        option target 'ACCEPT'
        option src 'wg'
        option name 'Allow-WG-traffic'
        option dest '*'

Why do you open the lan and wg from the wan?

config forwarding
        option dest 'lan'
        option src 'wan'

config forwarding
        option dest 'wg'
        option src 'wan'

Finally this rule alone will do, after you fix the previous holes.

config redirect
        option dest_port '22'
        option src 'wan'
        option src_dport '22'
        option dest 'lan'
        option target 'DNAT'
        list proto 'tcp'
        option dest_ip '10.16.5.10'
        option name 'test'
        option src_ip '104.16.0.0/12'

Done, thanks.

Also done. Both were due to my trying to use the LuCi interface while still learning it. Thanks for the tips.

After doing that and the following /etc/firewall.user:

#!/bin/sh

WHITELIST=$(sed 's/#.*//' /etc/firewall.d/cloudflare_ipv4.txt)

# do the DNAT
iptables -t nat -A zone_wan_prerouting -p tcp --dport 22 -j DNAT --to 10.16.5.10:22

iptables -I zone_wan_forward 2 -p tcp --dport 22 -j DROP

# do the whitelist
for IP in ${WHITELIST}; do
    iptables -I zone_wan_forward 2 -p tcp -s $IP --dport 22 -d 10.16.5.10 -j zone_lan_dest_ACCEPT
done

The OpenWRT side seems to be working properly. However, I'm still getting hit with SSH scans. So, I started looking more closely at the source IPs. They actually match one of the IP ranges provided by CF as being theirs (?!).

I've submitted a support ticket to Cloudflare to look into the problem IP range, as once I disabled that IP range, all scanning ceased.

Thanks for all the help! Much appreciated

If your problem is solved, please consider marking this topic as [Solved]. See How to mark a topic as [Solved] for a short how-to.

Done. And I've now had to drop two more IP ranges from the list. WTF, CF? unfortunately, one of them has legit hosts in it as I can no longer SSH in via Spectrum. What fun.

You can run banIP to blacklist the malicious attempts.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.