I am playing around with the firewall cookbook: cookbook 'firewall', '~> 2.7.0'
Not sure if it's just me but the instructions are not very clear but I gave it the old college try...
What I am trying to do is build a recipe that allows me to use data_bags and environments to specify rules. Most of the nodes are going to be CentOS 7 but I have a few Ubuntu 16 too (maybe different versions pending third-party software). Most nodes will have 2 NICs, 1 with zone public
and another with trusted
. Due to this, I would prefer to try to stick to the firewall
cookbook if possible.
During development, I used rspec and everything worked as expected. When I went to Test Kitchen, well, that's when I found out nothing was working. So I shelled into the node and realized no rules were being applied.
Ideally, I first want the default zone to be :public
.
Then I wanted to apply rules to each zone from the json objects.
When testing with rspec, it all looked good.
When I used Test Kitchen, nothing is even happening.
Hopefully I am just doing something stupid. I am hoping for a push in the right direction here... It should be noted I started this a while back and stopped due to another project. Now I am back on this and I am trying to identify the issues.
Thanks for your time.
An example of an environment (dev):
{
"name": "dev",
"description": "DEV Environment for Nodes",
"chef_type": "environment",
"json_class": "Chef::Environment",
"default_attributes": {
"oly": {
"environment": "dev",
"type" : "node",
"firewall": {
"status": "enabled",
"zones": {
"public": {
"22": {
"private_ip_1": "10.0.0.0/8",
"private_ip_2": "172.16.0.0/12",
"private_ip_3": "192.168.0.0/16",
"private_ip_4": "169.254.0.0/16",
"private_ip_5": "100.64.0.0/10"
}
}
}
}
}
},
"cookbook_versions": {
"oly-client": "= 4.0.0"
}
}
The above environment has a firewall zone config that opens up port 22
for all private IP addresses.
An example of a data_bag (firewall:global) is:
{
"id": "global",
"zones": {
"public": {
"22": {
"office_1": "1.1.1.1/32",
"office_2": "2.2.2.2/32",
"office_3": "3.3.3.3/32",
"office_4": "4.4.4.4/32",
"office_5": "5.5.5.5/32"
}
}
}
}
Ideally, this allows global rules to be applied to the recipe.
The cookbook I am working on:
#
# Cookbook:: oly-client
# Recipe:: firewall
#
# TODO: Create a method to optimize code (code repetition is real here)
# Fetch firewall settings
_firewallSettings = node['oly']['firewall']
# Make sure we have firewall settings and that they are enabled
if (!_firewallSettings.to_a.empty? && _firewallSettings.key?("status") && 'enabled' == _firewallSettings['status'].downcase)
# include the base firewall recipe
include_recipe "firewall::default"
# Enable platform default firewall and set default zone
firewall "default" do
action [:install]
enabled_zone :public
end
# START global firewall rules
_globalFirewallRules = data_bag_item('firewall', 'global')
if (_globalFirewallRules && _globalFirewallRules.key?("zones"))
# Loop over each firewall zone and build rules from data
_globalFirewallRules['zones'].each do |_zone, _zoneData|
# Ensure we have zone data
if (_zoneData)
# Ensure the firewall is installed for the zone
firewall "#{_zone}" do
enabled_zone "#{_zone}".to_sym
action [:install]
end
# Process rules for firewall
_zoneData.each do |_port, _portRules|
# Verify rules exist
if (_portRules)
# Build rules
_portRules.each do |_ipComment, _ipAddress|
# Define rule
firewall_rule "#{_zone} - #{_port}: #{_ipComment} - #{_ipAddress}" do
firewall_name "#{_zone}"
port _port.to_i
source _ipAddress
direction :in
command :allow
end
end
end
end
# Save the firewall settings
firewall "#{_zone}" do
# action :save
action [:save]
end
end
end
end
# END global firewall rules
# Check if environment has any zones configured
if (_firewallSettings.key?("zones"))
# Loop over each firewall zone and build rules from data
_firewallSettings['zones'].each do |_zone, _zoneData|
# Ensure we have zone data
if (_zoneData)
# Ensure the firewall is installed for the zone (in case global zones does not include)
firewall "#{_zone}" do
enabled_zone "#{_zone}".to_sym
# action :install
action [:install]
end
# Process rules for firewall
_zoneData.each do |_port, _portRules|
# Verify rules exist
if (_portRules)
# Build rules
_portRules.each do |_ipComment, _ipAddress|
# Define rule
firewall_rule "#{_zone} - #{_port}: #{_ipComment} - #{_ipAddress}" do
firewall_name "#{_zone}"
port _port.to_i
source _ipAddress
direction :in
command :allow
end
end
end
end
# Save the firewall settings
firewall "#{_zone}" do
# action :save
action [:save]
end
end
end
end
# END environment firewall rules
# TODO Add logic for custom rules (with search capabilites, like users - Did not do yet as this is edge case if needed at all)
# Save the firewall settings
firewall "default" do
# action :save
action [:save]
end
else
# Firewall is disabled unless explicitly enabled
include_recipe 'firewall::disable_firewall'
end
My rspec test (reaplaced IPs but should work the same):
#
# Cookbook:: oly-client
# Spec:: default
#
# Copyright:: 2017, The Authors, All Rights Reserved.
require 'spec_helper'
describe 'oly-client::firewall' do
context 'on CentOS 7 Latest' do
let(:chef_run) do
ChefSpec::SoloRunner.new(platform: 'centos', version: '7') do |node|
# Build node attributes for tests
node.normal['oly']['firewall']['status'] = "enabled"
node.normal['oly']['firewall']['zones'] = {
"public": {
"22": {
"private_ip_1": "10.0.0.0/8",
"private_ip_2": "172.16.0.0/12",
"private_ip_3": "192.168.0.0/16",
"private_ip_4": "169.254.0.0/16"
}
},
"trusted": {
"22": {
"private_ip_5": "100.64.0.0/10"
}
}
}
# Firewall rules
node.normal['firewall']['allow_icmp'] = true
node.normal['firewall']['allow_ssh'] = true
node.normal['firewall']['allow_winrm'] = false
node.normal['firewall']['allow_mosh'] = false
end.converge(described_recipe)
end
# Stub databags
before do
stub_data_bag('firewall').and_return(['global'])
stub_data_bag_item('firewall', 'global').and_return({
"id": "global",
"zones": {
"public": {
"22": {
"office_1": "1.1.1.1/32",
"office_2": "2.2.2.2/32",
"office_3": "3.3.3.3/32"
}
},
"trusted": {
"22": {
"office_1": "1.1.1.1/32",
"office_3": "3.3.3.3/32",
"office_4": "4.4.4.4/32",
"office_5": "5.5.5.5/32"
}
}
}
})
end
it 'include the recipe to enable firewall' do
expect(chef_run).to include_recipe('firewall::default')
end
it 'enables the firewall' do
expect(chef_run).to install_firewall('public')
expect(chef_run).to install_firewall('trusted')
end
it 'creates some rules' do
_rules = [
"allow loopback",
"allow icmp",
"allow world to ssh",
"established",
"ipv6_icmp",
"public - 22: private_ip_1 - 10.0.0.0/8",
"public - 22: private_ip_2 - 172.16.0.0/12",
"public - 22: private_ip_3 - 192.168.0.0/16",
"public - 22: private_ip_4 - 169.254.0.0/16",
"trusted - 22: private_ip_5 - 100.64.0.0/10",
"public - 22: office_1 - 1.1.1.1/32",
"public - 22: office_2 - 2.2.2.2/32",
"public - 22: office_3 - 3.3.3.3/32",
"trusted - 22: office_1 - 1.1.1.1/32",
"trusted - 22: office_3 - 3.3.3.3/32",
"trusted - 22: office_4 - 4.4.4.4/32",
"trusted - 22: office_5 - 5.5.5.5/32"
]
_rules.each do |r|
expect(chef_run).to create_firewall_rule(r)
end
end
it 'not to creates some rules' do
_rules = [
"allow world to winrm",
"allow world to mosh",
"public - 22: office_4 - 4.4.4.4/32",
"public - 22: office_5 - 5.5.5.5/32",
"trusted - 22: office_2 - 2.2.2.2/32"
]
_rules.each do |r|
expect(chef_run).to_not create_firewall_rule(r)
end
end
end
end
Zones are not currently supported in the cookbook. I submitted a PR to add the support. While the documentation is not fully clear for the cookbook, the issues I posted here are due to a missing feature for firewalld.