trilobite

7 feb 2009

Moved to git projects

At some point split it from configuration repos into its own project.

Summary

Oh, it's yet another iptables wrapper.

No matter how many of them there are already, some people just keep on writing new ones, while lots of others will stick to "pro way" like typing it rule-by-rule in a bash script, so it'll f$%k up in the middle of it with deny policies set.

Yeah, there's iptables-restore, which at least will do a syntax check beforehand, and there are wise guys, who use "set -e" and accept policies before flushing it all with deny in the end. That certainly is some improvement.

But then, after typing "-A INPUT", "-j ACCEPT" and some daft $IPT var a tenth-hundredth time, amazing how few consider that there just might be some other, better, way. Well, prehaps this thought crossed some admins' mind, but then this admins must've been a very lazy ones, because of what I see on each machine I can get my hands on.

Well, without any further ado... here's the thing, bet you've got an idea what it's about.

Operation

Simple.

Input is a YAML file (yes, because I believe yaml is great, if not for interoperability) with pretty much the same rules in some meaningful order, plus a few switches like default policies and fallback mechanisms. Script itself is py.

Config is being parsed, forming the iptables-dump in the memory. Each line gets expanded w/ appropriate modules, chain name and jump-action. Actually, two dumps are being formed: one is IPv4, other 's v6, mostly, script decides which rules should be omitted in each table by looking at IP format.

After that, it pulls the old table (iptables-save, to tmp buffer) and tries to feed the new one to iptables-restore. If it's valid - it gets applied, if it's not - you'll see the error and nothing 'll be changed (or broken).

Then, if everything looks good, it dumps the new table, comparing it to the old dump to see if there are any diffirences (aside from comments and packet counters). If there are none - that's it, we've done nothing and there's no need to be any more enthusiastic about it, but if there are, it can (having both old and new table at hand) show the diffirences in cool diff (colordiff even) format.

And, since the table has changed, old one must be backed up, and it will be, rotating as many backups as you like. None of that daft "ten identical backups w/ really important one rotated out", ever.

It could've been the end of it, if I haven't been so damn absent-minded all the time, locking out ssh on bloody remote and inaccessible hosts at random... but I am. So, if user has gone away after new table sunk in, old one will be restored in 5 (by default) minutes. And if (s)he hasn't, five mins is more than enough to cancel at-jobs that's been created. It's just "iptables-restore < /path/to/backup", anyway.

The end. It can simply dump results, too, of course.

Example

Now that's the most important part, where all the magic gets explained.

In fact, that's the explaination of the example, which can be found here (same as linked below).

tablez:
  filter:
    input:
      - --state RELATED,ESTABLISHED
      - -v4 -p icmp
      - -v6 -p icmpv6
    forward:
    output:

Start of the rule definitions section and filter table init - you can actually replace chain definition, like "input:" by "input/-" to set policy to DROP, or "somechain/x" for REJECT. "+" is ACCEPT, which is default, same as for rules, if not overridden.

Three rules in input, first in this chain, will be same as...

$IPT4 -t filter -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
$IPT6 -t filter -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
$IPT4 -t filter -A INPUT -p icmp -j ACCEPT
$IPT6 -t filter -A INPUT -p icmp6 -j ACCEPT
		

...bash code will produce. Note the -v4 and -v6 switches and figure out what they mean.

Empty definitions are fine, too.

    minions_in:
      - -i vde -s 2001:470:1f0b:11de::20/124
      - <

Custom chain definition. First line will go to ipv6 table only, second is "-j RETURN" (yes, looks hacky, but works for me), which will produce passthrough chain in ipv4 and some nice check for ipv6. No policy is acceptable, of course.

      - -i lan -s 192.168.0.10 --mac-source 00:11:6b:28:7f:68 # wlan.v4c

Won't need "-m mac", since it's quite obvious that it should be present here, and it will be. Note the comment - it's valid yaml comment, and won't be parsed.

      # Azureus to coercion
      - -p tcp/udp -d 192.168.0.13 --dport 28637
      - -p udp -d 192.168.0.13 --dport 28638

Any number of "-p proto1/proto2..." will be expanded as multiple rules, one for each protocol, as tcp/udp in this case.

      - -p tcp -d 2001:470:1f0a:11de::2
      # Cut the rest
      -

Yep, last one is an empty rule, which will be interpreted as "-" - "-j DROP".

    svc:

      loopback:
        input-lo: +
        output-lo: +

Start of "services" section. Here you can group bunch of rules (which 'll go into specified chains) by specific service or purpose. Service name/purpose will go into the resulting dump as a comment.

Two rules here may look tricky, but they aren't.

"input-lo" is "chain-interface" specification - it's valid for input/output chains, and will add "-i interface" or "-o interface" to each rule specified.

"+" is just a yaml string, instead of hash ("name: ...") or list ("- ...") notation, seen above. One string is one rule, no need to make it a list, if it's just one, and simple as that. "+" is "-j ACCEPT", "x" for REJECT, "-" for DROP - same as with policies. So, the result is:

$IPT4 -A INPUT -i lo -j ACCEPT
$IPT6 -A INPUT -i lo -j ACCEPT
$IPT4 -A OUTPUT -o lo -j ACCEPT
$IPT6 -A OUTPUT -o lo -j ACCEPT
      core:
        input-lan: -j core_in
        output-lan: -j core_out

You can always specify "-j action/chain", as in vanilla tables.

      6to4_forwarding: -v6 -i tot

Just an svc name with a rule. Which chain it'll go to? Input, of course.

     telenet_segnet_drop: # dangerous high-bandwidth connections via local telenet router
        input-ppp2:
          - -s 90.157.91.0/24 -
          - -s 90.157.40.128/25 -
        output-ppp2:
          - -d 90.157.91.0/24 -
          - -d 90.157.40.128/25 -

Couple of DROP rules for ppp2 interface.

      ssh: -p tcp --dport ssh

Simple, huh?

      mail: -p tcp --dport smtp,pop3,imap,pop3s,imaps

"-m multiport" will be there.

      cost-ineffective_connz_reject:
        input-ppp2:
          - -s 90.157.0.0/17 x
          - -s 87.224.128.0/17 x
          - -v4

All for ip4tables. Last one is "-j DROP".

      finish: x

Last rule, "-j REJECT". Never liked this 'policy' stuff, anyway.

There's plenty of other functionality added since then, like ipsets support and various auto module-expansions, but the essence is the same.

Afterword

That's pretty much all there is. Take it easy, hacked it for myself in a few hours, probably saving me some days, and counting.

Feel free to use it as you like.

Links

Main

Deps

python
right now I use 2.6, but 2.5 is fine, too
PyYAML
py module, YAML used for configuration file
iptables
userspace netfilter control interface
ipset
optional, for fast ip and range-based filtering support