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:
DNAT incoming connections to port host:22
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
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'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. :-/
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.
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.
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.