-
-
Save d6e/9d08c283960aded7fb0f6e7666d643ef to your computer and use it in GitHub Desktop.
Finn's Amazing iptables Thing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /etc/port-forward.json dont forget to delete this line since comments are not valid JSON | |
// These are just sample values, obviously you'll want to change most of it | |
{ | |
"public-if": "enp5s0", | |
"port-forwards": { | |
"80": "10.5.0.80:80", | |
"443": "10.5.0.80:443", | |
"8443": "10.5.0.60:8443", | |
"5222": "10.5.0.198:5222", | |
"5269": "10.5.0.198:5269", | |
"34197": "10.5.0.228:34197" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# This goes at /etc/network/if-pre-up.d/iptables | |
import json | |
import sys | |
rules = { | |
"filter": [ | |
"-A INPUT -i lo -j ACCEPT", | |
"-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT" | |
], | |
"nat": [] | |
} | |
with open(sys.argv[1]) as f: | |
config = json.load(f) | |
public_if = config["public-if"] | |
for extport, dest in config['port-forwards'].items(): | |
rules['nat'].append("-A PREROUTING -i {} -p tcp -m tcp --dport {} -j DNAT --to-destination {}".format(public_if, extport, dest)) | |
rules['nat'].append("-A PREROUTING -i {} -p udp -m udp --dport {} -j DNAT --to-destination {}".format(public_if, extport, dest)) | |
rules['filter'].append("-A FORWARD -i {} -p tcp -m tcp --dport {} -m state --state NEW -j ACCEPT".format(public_if, extport)) | |
rules['filter'].append("-A FORWARD -i {} -p udp -m udp --dport {} -m state --state NEW -j ACCEPT".format(public_if, extport)) | |
rules['nat'].append("-A POSTROUTING -o {} -j MASQUERADE".format(public_if)) | |
rules['filter'].append("-A INPUT -i {} -j DROP".format(public_if)) | |
for t in ['filter', 'nat']: | |
print("*{}".format(t)) | |
print("\n".join(rules[t])) | |
print("COMMIT") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# This goes at /etc/network/if-pre-up.d/iptables | |
set -ex | |
env | logger -t $0 | |
/opt/port-forward.py /etc/port-forwards.json | /sbin/iptables-restore | |
/opt/port-forward6.py /etc/port-forwards.json | /sbin/ip6tables-restore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# This goes at /opt | |
import json | |
import sys | |
rules = { | |
"filter": [ | |
"-A INPUT -i lo -j ACCEPT", | |
"-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT", | |
"-A INPUT -p ipv6-icmp -j ACCEPT" # ipv6 is more dependent on ICMP | |
], | |
"nat": [] | |
} | |
with open(sys.argv[1]) as f: | |
config = json.load(f) | |
public_if = config["public-if"] | |
for extport, dest in config['port-forwards'].items(): | |
rules['nat'].append("-A PREROUTING -i {} -p tcp -m tcp --dport {} -j DNAT --to-destination {}".format(public_if, extport, dest)) | |
rules['nat'].append("-A PREROUTING -i {} -p udp -m udp --dport {} -j DNAT --to-destination {}".format(public_if, extport, dest)) | |
rules['filter'].append("-A FORWARD -i {} -p tcp -m tcp --dport {} -m state --state NEW -j ACCEPT".format(public_if, extport)) | |
rules['filter'].append("-A FORWARD -i {} -p udp -m udp --dport {} -m state --state NEW -j ACCEPT".format(public_if, extport)) | |
rules['filter'].append("-A INPUT -i {} -m state --state NEW -m udp -p udp --dport 546 -d fe80::/64 -j ACCEPT".format(public_if)) # for upstream mosh | |
rules['filter'].append("-A INPUT -i {} -m state --state NEW -m tcp -p tcp --dport 5510 -j ACCEPT".format(public_if)) # for ssh | |
rules['filter'].append("-A INPUT -i {} -p udp -m udp --dport 60001:60010 -j ACCEPT".format(public_if)) # for mosh | |
rules['filter'].append("-A INPUT -i {} -j REJECT --reject-with icmp6-port-unreachable".format(public_if)) | |
rules['filter'].append("-A FORWARD -i {} -j REJECT --reject-with icmp6-port-unreachable".format(public_if)) | |
for t in ['filter', 'nat']: | |
print("*{}".format(t)) | |
print("\n".join(rules[t])) | |
print("COMMIT") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I made the jump over to nftables because it seems to offer a format that people are willing to face to be honest though I'm really not sure how I feel about it because there are several aspects of this that I've still had to compensate for:
https://github.com/philoctetes409bc/docker-hybrid/blob/master/host/nftables/nftables.exterior.rules
like the oif/iif names, back in the day with pf you could assign variables but even that now days to me I think is a huge leap for somebody who's intention is to take it and install it and trust that it'll work without having to make their own 1-off edits. So I adopted the name WAN, and provided the systemd-network .link and .network files to rename an interface (you do have to specify the MAC though in the .link file, documented here): https://github.com/philoctetes409bc/docker-hybrid/tree/master/host#boot-network-configuration
If you have the ability to create a docker host with something like docker-machine it gives you some leverage but for something like ec2 my first thought is that it should be specified in create-network-interface, but thats not an option there. Also systemd-networkd wont touch the interface until it's "set down" so this method has its downsides, too.
other things maybe worth pointing out:
https://en.wikipedia.org/wiki/Netsniff-ng it would be nice if possible to marry the comment lables from the nftable ruleset with the flow logs in flowtop if possible if theres not something like that already ( I always thought comments were nice in pfl)
ipsets are included in the nftables' language but I couldn't easily find a translation online (the persistent packages provide scripts for converting iptables between nftables and back though)
you can mix ip6 and ip4 now if you define a table as
table inet hybrid {
instead oftable ip hybrid {
but you cannot define nat table rules in an inet table, only ip (ip4) for obvious reasons. Ive gone through 6 briefly in nft but I still settled for separate tables because folding and neatness and readability are still not strong points of stateful firewall description: https://github.com/philoctetes409bc/linux_configs/blob/master/nftables/nftables.rules#L72here at least a single package provides a systemd unit to restore @ boot (it comes with the nft userland package.)
You actually can define multiple different tables with any of the relevant chains and set their matching order using the priority value but it it's a little less intuitive when you start mixing tables which use different default policies in chains.
I'm not super keen on devoting my life's work to making a conntrack masterpiece now that things like EVPN and vxlan, openflow, frr are so well established http://docs.frrouting.org/en/latest/pbr.html but they are for sure still much more complex, and I simply just dont have the money for that $500+ chelsio 100GBe card / 10 year old QFX5100 yet which without begs the question why bother (don't know if you understand layer 2 hardware filtering at all I just barely grasp that it uses fixed size ASIC like memory called TCAM which is an advantage that conntrack can't offer you.
TC flower offload is another one that has some advantages but I don't know a lot about it http://www.openvswitch.org/support/ovscon2017/horman.pdf
I wrote a docker network driver in python that makes use of openvswitch, but there's no documentation for the python lib that openvswitch provides so I had to figure it out which wouldn't be that big of a deal ordinarily but it's very complicated, however I got it this far and it worked reliably enough where I left off, so I guess the next step would be to figure out how to make it with with frr daemons, I loved this idea so much I made iptables rules that emulate the behavior I want (now implemented in nftables) where I've used several broad reaching /20 scopes to define a traffic direction / behavior from which smaller allocations are taken. Here's an example of how I've been segmenting networks within these scopes https://github.com/philoctetes409bc/docker-hybrid/blob/master/hb_hub/docker-compose.yml#L3
I also wrote a python based, schema-driven IPAM driver for Docker to reflect the model of scoping / segmenting I'm using that ideally would be a relief from having to switch between my IRC bot's %cidr fantasy command to assist me in verification as I was implementing all of the various 1-to-1 networks for docker-hybrid. It's not quite where I want it to be yet but I left it in a pretty decent state where I left off. I can't decide if I want to deliberate more on making use of CGNAT and TESTNET-1 or if I want to to put it aside and move on to using ULAs for non globally routed networks (a problem that my IPAM driver would largely address in docker-compose as it stands, due to the lack of ipv6 support that it currently has; ideally the docker-compose should only need to describe: 1. tags which match a scope (one of /20) 2. a prefixlen for the network, 3. an order in which addresses from the specified prefixlen shall be allocated as it corresponds to interpolation of the prefixlen? Did I explain that right? Ex: expanding 192.168.1.0/24 the second usable address from the pool would be 192.168.1.2/32 and so far my IPAM module is perfectly capable of disseminating addresses in this manner I just need to change the schema a bit and test it some more until it's 100%.
I really gotta tell yout though, ipv6, is wonderful imo
https://www.arin.net/resources/fees/fee_schedule/
he.net will give you up to 5 free single ended /64 over a six-in-four tunnel as well as a /48 routed via the /64 per tunnel, they will even delegate DNS NS to your DNS servers for all of it, free. You can't really do agg/deagg/peering on the global table for anything smaller than a /64, of which there's a lot in a /48 but 6in4 .... good for a hobbyist... really cool that they're still doing this imo.
Well anyway you seem like someone with real simple needs, this is probably all too much but if you find it interesting or informative feel free to build on it or take away from it whatever you like. If by chance you do have any feedback I'm a complete n00b. I'm not really sure what I'm trying to accomplish with docker-hybrid, just one I've always wanted to create. Always a kick to see another scripter with an iptables arsenal, I'm trying to ditch it though people just can't grasp or reproduce it easy enough, admittedly it was hard for me to make the jump from pf to iptables too, it just grew on me though and I became lazy