# == 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] }