How to work around lack of array support in puppetlabs-firewall?

After a couple of irritating firewalling oversights I decided to have a go at replacing my hacked-together firewall management scripts with the puppetlabs-firewall module.

It’s going okay, but one thing I’ve found quite irritating is the lack of support for arrays of things such as source IPs or ICMP types.

For example, let’s say I have a sequence of shell commands like this:

#!/bin/bash
 
readonly IPT=/sbin/iptables
 
for icmptype in redirect router-advertisement router-solicitation \
                address-mask-request address-mask-reply; do
    $IPT -A INPUT -p icmp --icmp-type ${icmptype} -j DROP
done

You’d think that with puppetlabs-firewall you could do this:

class bffirewall::prev4 {
    Firewall { require => undef, }
 
    firewall { '00002 Disallow possibly harmful ICMP':
        proto    => 'icmp',
        icmp     => [ 'redirect', 'router-advertisement',
                      'router-solicitation', 'address-mask-request',
                      'address-mask-reply' ],
        action   => 'drop',
        provider => 'iptables',
    }
}

Well it is correct syntax which installs fine on the client, but taking a closer look it hasn’t worked. It’s just applied the first firewall rule out of the array, i.e.:

iptables -A INPUT -p icmp --icmp-type redirect -j DROP

There’s already a bug in Puppet’s JIRA about this.

Similarly, what if you need to add a similar rule for each of a set of source hosts? For example:

readonly MONITORS="192.168.0.244 192.168.0.238 192.168.4.71"
readonly CACTI="192.168.0.246"
readonly ENTROPY="192.168.0.215"
 
# Allow access from:
# - monitoring hosts
# - cacti
# - the entropy VIP
for host in ${MONITORS} ${CACTI} ${ENTROPY}; do
    $IPT -A INPUT -p tcp --dport 8888 -s ${host} -j ACCEPT
done

Again, your assumption about what would work…

    firewall { '08888 Allow egd connections':
        proto    => 'tcp',
        dport    => '8888',
        source   => [ '192.168.0.244', '192.168.0.238', '192.168.4.71',
                      '192.168.0.246', '192.168.0.215' ],
        action   => 'accept',
        provider => 'iptables',
    }

…just results in the inclusion of a rule for only the first source host, with the rest being silently discarded.

This one seems to have an existing bug too; though it has a status of closed/fixed it certainly isn’t working in the most recent release. Maybe I need to be using the head of the repository for that one.

So, what to do?

Duplicating the firewall {} blocks is one option that’s always going to work as a last resort.

Puppet’s DSL doesn’t support any kind of iteration as far as I’m aware, though it will in future — no surprise, as iteration and looping is kind of a glaring omission.

Until then, does anyone know any tricks to cut down on the repetition here?

7 thoughts on “How to work around lack of array support in puppetlabs-firewall?

  1. The usual way to do iteration at the moment (when not using the future parser/evaluator) is a defined type, e.g.

    define firewall_egd() {
    firewall { “08888 Allow egd connections from ${title}”:
    proto => ‘tcp’,
    dport => ‘8888’,
    source => $title,
    action => ‘accept’,
    provider => ‘iptables’,
    }
    }
    firewall_egd { [‘192.168.0.244’, ‘192.168.0.238’, ‘192.168.4.71’,
    ‘192.168.0.246’, ‘192.168.0.215’]: }

    This works because you can declare resources using an array as the title, which creates an instance of that resource per item in the array. By using $title inside the define, you can then get the “variable” within the “loop”. This only works effectively for a single variable, else you start needing hashes or to split strings.

    Side note, avoid using resource defaults (as in your first DSL example), they’re leaky due to dynamic scopes and can introduce surprises when you include other classes.

  2. [Replying to a very old post, I know, but this problem still exists in the puppetlabs-firewall module, although workarounds have got a bit neater 🙂 ]

    The iteration functions allow expanding this sort of thing in a neater way:

    e.g.
    each($subnets) |$subnet| {
    validate_ip_address($subnet)
    # Expand subnets array into multiple rules
    firewall { “100 snat for internal network ${subnet}”:
    chain => ‘POSTROUTING’,
    jump => ‘MASQUERADE’,
    proto => ‘all’,
    outiface => $external,
    # Only allow particular source subnets if specified:
    source => $subnet,
    table => ‘nat’,
    }
    }

    Better than having to make a define.

Leave a Reply

Your email address will not be published. Required fields are marked *