Skip to content
Snippets Groups Projects
Unverified Commit 203699d4 authored by Max Adamo's avatar Max Adamo
Browse files

initial commit

parent b9101a32
No related branches found
No related tags found
No related merge requests found
## module for app fw_builder
# Module for Firewall Builder
## Table of Contents
1. [Firewall builder](#firewall-builder)
2. [Fail2ban allow list](#fail2ban-allow-list)
## Firewall builder <a name="firewall-builder"></a>
This is a configuration example in Hiera:
```yaml
firewall:
# in this section you can group IPs
custom_ipset:
authz:
# specify a list of IPs, Networks, FQDNs under the name "authz"
# FQDN elements must have at least A or AAAA record
list:
- '150.254.166.19'
- '150.254.208.67'
- '2001:718:ff05:206::155'
- '2001:718:ff05:206::166'
- "www.geant.org"
- "www.google.uz"
rackspace:
# specify a list of IPs or Networks under the name "rackspace"
list:
- '134.213.42.227'
- '2a00:1a48:15e1:4:33d7:40d8:d11f:abb5'
- '134.213.42.228'
- '2a00:1a48:15e1:4:31d5:3397:4fd6:7ad3'
- '10.100.4.0/24'
- "2001:630:280:20::/64"
# run a hieradata lookup against an array of IPs, FQDNs existing in
# hiera and group them under the name "rackspace"
hieradata:
- "haproxy_servers"
- "db_servers"
# run a query on the puppetDB, produces a list of IPs
# and group them under the name "rackspace".
# An empty match will cause a puppet fail
puppetdb:
- { 'name': 'myserver\d+\.geant\.org' } # by default it matches the same environment of the host
- { 'name': 'otherserver\d+\.geant\.org', 'env': 'uat' }
- { 'name': 'moreservers\d+\.geant\.org', 'env': ['uat', 'test'] }
# this section contains addresses NOT belonging to the internal network
# if you do not specify an "ipset", the connection will be open world-wide
# ipsets from the public section will be added to "fail2ban" ignoreip list
public:
# open http/https to everyone
web:
port: [80, 443]
# open ldap/ldaps to ipset groups "authz" and "rackspace"
ldap_ports:
port: [389, 636]
proto: ['tcp']
ipset: ["authz", "rackspace"]
# this section contains addresses belonging to the internal network
# if you do not specify an "ipset" it will open to everyone in the internal network
trust:
# open the specified ports to all internal networks
trusted_ports:
port: [389, 443, 636, 3000, 3001, 3268, 6379, 6380, 8000]
proto: ['tcp']
# open DB ports to "haproxy_servers"
db_ports:
port: [3306]
proto: ['tcp']
ipset: "haproxy_servers"
```
## Fail2ban allow list <a name="fail2ban-allow-list"></a>
ipsets listed in the "public" section of the firewall builder will be added to Fail2ban allow-list.
# == 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']) {
# check if "env" was defined and it contains proper values
$pdb_filter = join(
$fw_conf['custom_ipset'][$name]['puppetdb'].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});
}
# 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]
}
# == Function: fw_builder::fw_builder_public_ips
#
# create an array of IPs listed in the public section
# of the firewall builder.
#
# === Parameters
#
# [*facts_fw_conf*]
# custom fact: fw builder configuration, including the public IPs
#
# [*facts_ipsets*]
# custom fact: ipsets pushed by fw builder
#
# === Variables
#
# [*public_ips*]
# IPs without subnet
#
# [*public_cidr*]
# IPs with subnet
#
function fw_builder::fw_builder_public_ips(
Variant[String, Hash, Undef] $facts_fw_conf,
Optional[Array] $facts_ipsets
) >> Array {
if $facts_fw_conf =~ Undef or $facts_ipsets =~ Undef {
# when puppet runs for the first time these facts are not available
$public_ipsets = []
} elsif $facts_fw_conf['public'] =~ String {
# if public is empty it's seen as empty string
$public_ipsets = []
} else {
if 'public' in $facts_fw_conf {
# this check is not needed, but it will be necessary if the
# code of fw_builder changes and "public" can be absent
$facts_fw_conf_public = $facts_fw_conf['public']
# create a list of lists with all the ipsets in public
$unflattened_public_ipsets = $facts_fw_conf_public.map |$app_key, $app_value| {
if 'ipset' in keys($facts_fw_conf_public[$app_key]) {
$facts_fw_conf_public[$app_key]['ipset']
}
}
# flatten the list of list into a list with unique elements, and remove any Undef
$public_ipsets_with_undef = unique(flatten($unflattened_public_ipsets))
$public_ipsets = $public_ipsets_with_undef.filter |$item| { $item !~ Undef }
} else {
$public_ipsets = []
}
}
# if we got ipsets in public, we parse them, we collect the corresponding IPs
# and we add them to "public_cidr" list
#
if $public_ipsets.length > 0 {
# create a list of lists with all the IPs associated with the ipsets in public
$unflattened_public_ips = $facts_ipsets.map |$index, $value| {
if keys($facts_ipsets[$index])[0] in $public_ipsets {
$key_name = keys($facts_ipsets[$index])[0]
$facts_ipsets[$index][$key_name]
}
}
# flatten the list of list into a list with unique elements, and remove any Undef
$public_ips_with_undef = unique(flatten($unflattened_public_ips))
$public_ips = $public_ips_with_undef.filter | $item | { $item !~ Undef }
# add /32 to IPv4, add /128 to IPv6, add nothing to CIDR
$public_cidr = $public_ips.map |$ip| {
if $ip =~ Stdlib::IP::Address::V4::Nosubnet {
"${ip}/32"
} elsif $ip =~ Stdlib::IP::Address::V6::Nosubnet {
"${ip}/128"
} elsif $ip =~ Stdlib::IP::Address::V4::CIDR or $ip =~ Stdlib::IP::Address::V6::CIDR {
$ip
}
}
} else {
# there are no ipsets in public: we don't need to change fail2ban
$public_cidr = []
}
$public_cidr
}
# == Function: fw_builder::parser
#
# parse elements and add subnet if necessary
# it does not work quite well with IPv4 and ipset but it doesn't cause any issue
#
# === Parameters
#
# [*facts_fw_conf*]
# custom fact: fw builder configuration, including the public IPs
#
# [*facts_ipsets*]
# custom fact: ipsets pushed by fw builder
#
# === Variables
#
# [*public_ips*]
# IPs without subnet
#
# [*public_cidr*]
# IPs with subnet
#
function fw_builder::parser(Array $ip_array) >> Array {
if $ip_array.length > 0 {
$unflattened_cidr_array = $ip_array.map |$ip| {
if $ip =~ Stdlib::IP::Address::V4::Nosubnet {
"${ip}/32"
} elsif $ip =~ Stdlib::IP::Address::V6::Nosubnet {
"${ip}/128"
} elsif $ip =~ Stdlib::IP::Address::V4::CIDR or $ip =~ Stdlib::IP::Address::V6::CIDR {
$ip
} elsif $ip =~ Stdlib::Fqdn {
$ipv4 = dns_a($ip)[0]
$ipv6 = dns_aaaa($ip)[0]
if ($ipv4) {
$ipv4_subnetted = "${ipv4}/32"
} else {
$ipv4_subnetted = undef
}
if ($ipv6) {
$ipv6_subnetted = downcase("${ipv6}/128")
} else {
$ipv6_subnetted = undef
}
# if we cannot resolve either ipv4 and ipv6 we fail here
if $ipv4 == undef and $ipv6 == undef { fail("${ip} does not have a DNS entry. Please amend the configuration") }
[$ipv4_subnetted, $ipv6_subnetted]
}
}
$cidr_array_with_undef = unique(flatten($unflattened_cidr_array))
$cidr_array = $cidr_array_with_undef.filter | $item | { $item !~ Undef }
} else {
$cidr_array = []
}
$cidr_array
}
#
# @summary Allowed types in Iplist
#
#
type Fw_builder::Iplist = Array[Stdlib::IP::Address]
#
# @summary Allowed types in List
#
#
type Fw_builder::List = Array[Variant[Stdlib::IP::Address, Stdlib::Fqdn]]
#
# @summary Allowed Puppet Environments
#
#
type Fw_builder::Puppet_environment = Variant[
Enum[
'test',
'uat',
'production'
],
Array[Enum[
'test',
'uat',
'production'
]
]
]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment