2023-01-25 11:48:44 +00:00
{ lib , pkgs , config , . . . }: {
# using https://github.com/greaka/ops/blob/818be4c4dea9129abe0f086d738df4cb0bb38288/apps/restic/options.nix as a base
options = {
skynet_firewall = {
enable = lib . mkEnableOption {
default = false ;
example = true ;
description = " S k y n e t F i r e w a l l " ;
type = lib . types . bool ;
} ;
forward = lib . mkOption {
default = [ ] ;
type = lib . types . listOf lib . types . str ;
description = ''
A list of routes to forward
'' ;
} ;
own = {
ip = lib . mkOption {
default = " 1 2 7 . 0 . 0 . 1 " ;
type = lib . types . str ;
description = ''
IP of the firewall
'' ;
} ;
ports = {
tcp = lib . mkOption {
default = [ ] ;
type = lib . types . listOf lib . types . int ;
description = ''
A list of TCP ports for the machiene running the firewall
'' ;
} ;
udp = lib . mkOption {
default = [ ] ;
type = lib . types . listOf lib . types . int ;
description = ''
A list of UDP ports for the machiene running the firewall
'' ;
} ;
} ;
} ;
} ;
} ;
config = lib . mkIf config . skynet_firewall . enable {
# disable default firewall to enable nftables
networking . firewall . enable = false ;
networking . nftables . enable = true ;
# fules for the firewall
# beware of EOL conversion.
networking . nftables . ruleset =
''
2023-01-28 15:31:46 +00:00
# using https://oxcrag.net/2021/12/25/build-your-own-router-with-nftables-part-1/ as a guide
# Clear out any existing rules
flush ruleset
# Our future selves will thank us for noting what cable goes where and labeling the relevant network interfaces if it isn't already done out-of-the-box.
# red cabled
define WANLINK = eno1
# yellow cable
define LANLINK = eno2
# We never expect to see the following address ranges on the Internet
define BOGONS4 = { 0.0.0.0/8 , 10.0.0.0/8 , 10.64.0.0/10 , 127.0.0.0/8 , 127 .0 .53 .53 , 169.254.0.0/16 , 172.16.0.0/12 , 192.0.0.0/24 , 192.0.2.0/24 , 192.168.0.0/16 , 198.18.0.0/15 , 198.51.100.0/24 , 203.0.113.0/24 , 224.0.0.0/4 , 240.0.0.0/4 , 255.255.255.255/32 }
# Network address translation: What allows us to glue together a private network with the Internet even though we only have one routable address, as per IPv4 limitations
2023-01-25 11:48:44 +00:00
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat ; policy accept ;
# forward anything with port 2222 to this specific ip
# tcp dport 2222 counter packets 0 bytes 0 dnat to 193.1.99.76:22
# forward http/s traffic from 76 to 123
# ip daddr 193.1.99.76 tcp dport 80 counter packets 0 bytes 0 dnat to 193.1.99.123:80
# ip daddr 193.1.99.76 tcp dport 443 counter packets 0 bytes 0 dnat to 193.1.99.123:443
}
chain postrouting {
type nat hook postrouting priority srcnat ; policy accept ;
# the internal network
ip saddr 172.20.20.0/23 counter packets 0 bytes 0 masquerade
}
chain output {
type nat hook output priority -100 ; policy accept ;
}
}
2023-01-28 15:31:46 +00:00
# The actual firewall starts here
table inet filter {
# Additional rules for traffic from the Internet
chain inbound_world {
# this is actually a good idea
# Drop obviously spoofed inbound traffic
ip saddr { $ BOGONS4 } drop
}
# Additional rules for traffic from our private network
chain inbound_private {
# We want to allow remote access over ssh, incoming DNS traffic, and incoming DHCP traffic
2023-01-25 11:48:44 +00:00
# for the host machiene
2023-01-28 15:31:46 +00:00
# hardcoded in so that we can always have access
tcp dport 22 counter packets 0 bytes 0 accept
2023-01-25 11:48:44 +00:00
# TCP
$ { lib . strings . concatMapStrings ( x : x + " \n " ) ( map ( port : " t c p d p o r t ${ toString port } c o u n t e r p a c k e t s 0 b y t e s 0 a c c e p t " ) config . skynet_firewall . own . ports . tcp ) }
# UDP
$ { lib . strings . concatMapStrings ( x : x + " \n " ) ( map ( port : " u d p d p o r t ${ toString port } c o u n t e r p a c k e t s 0 b y t e s 0 a c c e p t " ) config . skynet_firewall . own . ports . udp ) }
2023-01-28 15:31:46 +00:00
2023-01-25 11:48:44 +00:00
}
2023-01-28 15:31:46 +00:00
# Our funnel for inbound traffic from any network
chain inbound {
# Default Deny
type filter hook input priority 0 ; policy drop ;
2023-01-25 11:48:44 +00:00
2023-01-28 15:31:46 +00:00
# Allow established and related connections: Allows Internet servers to respond to requests from our Internal network
ct state vmap { established : accept , related : accept , invalid : drop } counter
2023-01-25 11:48:44 +00:00
2023-01-28 15:31:46 +00:00
# ICMP is - mostly - our friend. Limit incoming pings somewhat but allow necessary information.
ip protocol icmp icmp type {
destination-unreachable , echo-reply , echo-request , source-quench , time-exceeded
} limit rate 5/second accept
# guy must have had a lot of trouble with spoofed ips
# Drop obviously spoofed loopback traffic
iifname " l o " ip daddr != 127.0.0.0/8 drop
2023-01-25 11:48:44 +00:00
2023-01-28 15:31:46 +00:00
# Separate rules for traffic from Internet and from the internal network
iifname vmap { lo : accept , $ WANLINK : jump inbound_world , $ LANLINK : jump inbound_private }
2023-01-25 11:48:44 +00:00
}
2023-01-28 15:31:46 +00:00
# Rules for sending traffic from one network interface to another
chain forward {
# Default deny, again
type filter hook forward priority 0 ; policy drop ;
2023-01-25 11:48:44 +00:00
2023-01-28 15:31:46 +00:00
# Accept established and related traffic
ct state vmap { established : accept , related : accept , invalid : drop }
2023-01-25 11:48:44 +00:00
2023-01-28 15:31:46 +00:00
# Let traffic from this router and from the Internal network get out onto the Internet
iifname { lo , $ LANLINK } accept
# Only allow specific inbound traffic from the Internet (only relevant if we present services to the Internet).
# tcp dport { $PORTFORWARDS } counter
# can basically make each machiene responsibile for their own forwarding (in config at least)
$ { lib . strings . concatMapStrings ( x : x + " \n " ) config . skynet_firewall . forward }
2023-01-25 11:48:44 +00:00
}
}
'' ;
} ;
}