Skip to content
Snippets Groups Projects
fw_builder.pp 7.45 KiB
# == Function: fw_builder::fw_builder
#
# tries to build the ipset and firewall rule set comming from hieradata 
# this function only affects the host firewall not the VMware crap
#
# == Example
#
# Please check the main README: ../README.md
#
function fw_builder::fw_builder() {
  # time to retrieve hieradata for the firewall 
  #
  $fw_conf = lookup('firewall', Hash, 'deep')
  $fw_conf_hash = { fw_conf => $fw_conf }

  file {
    ['/etc/facter/', '/etc/facter/facts.d/']:
      ensure => 'directory';
    '/etc/facter/facts.d/fw_conf.yaml':
      content => to_yaml($fw_conf_hash),
  }

  # first define if we need to generate all the custom ipset lists that have been
  # define, for latter use 
  #
  if ($fw_conf['custom_ipset']) {

    $ipsets = $fw_conf['custom_ipset'].keys().map |$name| {

      # check if key names are valid
      #
      $ipset_keys = keys($fw_conf['custom_ipset'][$name])
      $ipset_keys.each |$key| {
        unless $key in ['list', 'hieradata', 'puppetdb'] {
          fail("${key} is not a valid key. Valid keys are: 'list', 'hieradata', and 'puppetdb'")
        }
      }

      # ipset name is limited to 26 characters: 7 characters are surrounding the string as follows: "fwb_${name}_v4"
      if $name.length() > 19 { fail("ipset name ${name} cannot exceed 19 characters") }

      # getting list of IPs, Networks, and/or FQDNs
      #
      if ($fw_conf['custom_ipset'][$name]['list']) {
        $_list = $fw_conf['custom_ipset'][$name]['list']
        if $_list !~ Fw_builder::List { fail("${_list} types are not IPs, Networks or FQDNs") }
        $list = fw_builder::parser($_list)
      }

      # getting IPs or FQDNs from 'hieradata' lookup, if 'hieradata' is defined
      #
      if ($fw_conf['custom_ipset'][$name]['hieradata']) {
        $_hieradata = flatten($fw_conf['custom_ipset'][$name]['hieradata'].map |$hash_name| {
          lookup($hash_name, Array, 'deep')
        })
        if $_hieradata !~ Fw_builder::List { fail("${_hieradata} types are not IPs, Networks or FQDNs") }
        $hieradata = fw_builder::parser($_hieradata)
      }

      # querying 'puppetDB', if 'puppetdb' is defined
      #
      if ($fw_conf['custom_ipset'][$name]['puppetdb']) {
        $pdb_hash = $fw_conf['custom_ipset'][$name]['puppetdb']
        if $pdb_hash !~ Array[Hash] { fail("${pdb_hash} must be a hash") }
        # check if "env" was defined and it contains proper values
        $pdb_filter = join(
          $pdb_hash.map |$hash| {
            if $hash['env'] {
              # wrong setting for "env" creates an empty fact and breaks puppet on the host
              if $hash['env'] !~ Fw_builder::Puppet_environment {
                fail("${hash['env']} is an unacceptable value for 'env'. Valid values are 'test', 'uat', or 'production'")
              } else {
                if $hash['env'] =~ String {
                  $env_string = "= '${hash['env']}'"
                } elsif $hash['env'] =~ Array {
                  # if we use something like [%{::environment}, 'test'] we need unique
                  $_env_string = join(unique($hash['env']), '|')
                  $env_string = "~ '(${_env_string})'"
                }
              }
            } else {
              # we use the same environment of the agent
              $env_string = "= '${::environment}'"
            }
            "facts.fqdn ~ '${hash[name]}' and facts.agent_specified_environment ${env_string}"
          },
          ') or ('
        )
        # $pdb_filter example: 
        # facts.fqdn ~ 'nomad\d+\.geant\.org' and facts.agent_specified_environment = 'test') or (facts.fqdn ~ ... 
        $query = "inventory[facts.hostname, facts.ipaddress, facts.ipaddress6, facts.fqdn] { (${pdb_filter}) order by certname }"
        $full_list = puppetdb_query($query)
        $searchlist = $full_list.map |$hash| { $hash['facts.ipaddress']} + $full_list.map |$hash| { $hash['facts.ipaddress6'] }
        # an empty list creates an empty fact, it means that the regex is not working
        # and the firewall setting is ineffective. We better fail here
        if $searchlist !~ Fw_builder::Iplist {
          fail('PuppetDB query for Firewall Builder did not match any host. You may want to review you regex')
        }
      }

      # create a list with all the ip's 
      #
      $full_ip_list = flatten([$list, $fqdnlist, $searchlist, $hieradata]).filter |$val| { $val =~ NotUndef }

      # if we have a non zero list then let's create / update it 
      #
      if $full_ip_list.length() > 0 {
        $full_ip_list_sorted = sort($full_ip_list)
        # time to create the ipset with all the data/ip's .... 
        ipset::set {
          default:
            type   => 'hash:net',
            ensure => 'present';
          "fwb_${name}_v4":
            set  => $full_ip_list_sorted.filter |$ip| { $ip =~ Stdlib::IP::Address::V4 };
          "fwb_${name}_v6":
            set     => $full_ip_list_sorted.filter |$ip| { $ip =~ Stdlib::IP::Address::V6 },
            options => {
              'family' => 'inet6'
            }
        };
        { $name => $full_ip_list }
      }
    }
  } else {
    $ipsets = []
  }

  file { '/etc/facter/facts.d/fw_ipsets.yaml':
    content => to_yaml({fw_ipsets => $ipsets});
  }

  # emit warning if the key is deinfed and it's empty
  #
  ['public', 'trust'].each() |$zone| {
    if $zone in $fw_conf and empty($fw_conf[$zone]) {
      echo { "WARNING FW Builder zone ${zone}":
        message => "key '${zone}' is defined but it\'s empty";
      }
    }
  }

  # this section will setup / create all the fwb rules
  #
  ['public', 'trust'].each() |$zone| {
    if $fw_conf[$zone] {
      $fw_conf[$zone].each |$name , $conf| {
        $ports_spaces = $conf['port'] ? {
          Array => join($conf['port'], ' '),
          String => $conf['port'],
          Integer => $conf['port'],
          default => fail("'port' can only be Array, String or Integer")
        }

        if $conf['ipset'] {
          # this part will generate all the rule that are restricted with ipset
          $ipset_array = $conf['ipset'] ? {
            String => [$conf['ipset']],
            Array => $conf['ipset'],
            default => fail("'ipset' can only be Array, or String")
          }
          $ipset_array.each |$ipset_element| {
            firewall_multi {
              default:
                chain  => "INPUT_${zone}",
                proto  => $conf[proto],
                dport  => $conf[port],
                action => accept;
              "150 fwb INPUT_${zone} Allow inbound ${name} port(s): ${ports_spaces} ipset:fwb_${ipset_element}_v4":
                ipset    => "fwb_${ipset_element}_v4 src",
                provider => 'iptables';
              "150 fwb INPUT_${zone} Allow inbound ${name} port(s): ${ports_spaces} ipset:fwb_${ipset_element}_v6":
                ipset    => "fwb_${ipset_element}_v6 src",
                provider => 'ip6tables';
            }
          }
        } else {
          firewall_multi {
            default:
              chain  => "INPUT_${zone}",
              proto  => $conf[proto],
              dport  => $conf[port],
              action => accept;
            "150 fwb INPUT_${zone} Allow inbound ${name} port(s): ${ports_spaces} v4":
              provider => 'iptables';
            "150 fwb INPUT_${zone} Allow inbound ${name} port(s): ${ports_spaces} v6":
              provider => 'ip6tables';
          }
        }
      }
    } else {
      echo { "FW Builder zone ${zone}": message => "No work to do in ${zone} !! (this could be normal)" }
    }
  }

  [$fw_conf, $ipsets]
}