Commit 70675950 authored by m.pausch's avatar m.pausch
Browse files

Use modern design pattern for custom resource

parent 5ef17eb4
default['sys']['firewall']['manage'] = false
default['sys']['firewall']['disable'] = true
default['sys']['firewall']['allow_ssh'] = true
default['sys']['firewall']['allow_loopback'] = true
default['sys']['firewall']['allow_icmp'] = true
default['sys']['firewall']['allow_established'] = true
default['sys']['firewall']['defaults']['policy'] = {
'input' => 'drop',
'forward' => 'drop',
'output' => 'accept',
}
policy_output = node['sys']['firewall']['defaults']['policy']['output']
policy_input = node['sys']['firewall']['defaults']['policy']['input']
policy_forward = node['sys']['firewall']['defaults']['policy']['forward']
input = "add chain inet filter input { type filter hook input priority 0 ; policy #{policy_input}; }"
output = "add chain inet filter output { type filter hook output priority 0 ; policy #{policy_output}; }"
forward = "add chain inet filter forward { type filter hook forward priority 0 ; policy #{policy_forward}; }"
default['sys']['firewall']['defaults']['ruleset']['add table inet filter'] = 1
default['sys']['firewall']['defaults'][input] = 2
default['sys']['firewall']['defaults'][output] = 2
default['sys']['firewall']['defaults'][forward] = 2
if node['sys']['firewall']['table_ip_nat']
default['sys']['firewall']['defaults']['ruleset']['add table ip nat'] = 1
default['sys']['firewall']['defaults']['ruleset']['add chain ip nat postrouting { type nat hook postrouting priority 100 ;}'] = 2
default['sys']['firewall']['defaults']['ruleset']['add chain ip nat prerouting { type nat hook prerouting priority -100 ;}'] = 2
end
if node['sys']['firewall']['table_ip6_nat']
default['sys']['firewall']['defaults']['ruleset']['add table ip6 nat'] = 1
default['sys']['firewall']['defaults']['ruleset']['add chain ip6 nat postrouting { type nat hook postrouting priority 100 ;}'] = 2
default['sys']['firewall']['defaults']['ruleset']['add chain ip6 nat prerouting { type nat hook prerouting priority -100 ;}'] = 2
end
# `sys::firewall`
# `resource::firewall`
Use the firewall recipe to configure nftables.
Use the firewall resource to configure nftables.
`attributes/firewall.rb`
`recipes/firewall.rb`
`resources/firewall.rb`
`resources/firewall_rule.rb`
`libraries/sys_helpers_firewall.rb`
......@@ -14,39 +12,23 @@ Use the firewall recipe to configure nftables.
### Disable nftables
`node['sys']['firewall']['disable'] = true` will turn off *nftables*:
```ruby
node['sys']['firewall']['manage'] = true # To manage nftables at all
node['sys']['firewall']['disable'] = true # To disable nftables
firewall 'default' do
action :disable
end
```
### Enable nftables
The `sys::firewall` recipe **does nothing unless explicitly
activated**. To active the recipe, the following steps are required:
1. Include the recipe in your run list.
1. Set `node['sys']['firewall']['manage']` to `true`
1. Set `node['sys']['firewall']['disable']` to `false`
This will give you a rather permissive default-set of rules, since the
following attributes default to `true`. Adjust to your needs:
`node['sys']['firewall']['allow_established']`
`node['sys']['firewall']['allow_icmp']`
`node['sys']['firewall']['allow_loopback']`
`node['sys']['firewall']['allow_ssh']`
```ruby
firewall 'default'
```
This will give you the following default rules:
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif "lo" accept comment "allow loopback"
icmp type echo-request accept comment "allow icmp"
tcp dport ssh accept comment "allow world to ssh"
ct state established,related accept comment "established"
type filter hook input priority 0; policy accept;
}
chain output {
......@@ -57,24 +39,6 @@ This will give you the following default rules:
type filter hook forward priority 0; policy drop;
}
}
table ip6 nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
}
chain prerouting {
type nat hook prerouting priority -100; policy accept;
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
}
chain prerouting {
type nat hook prerouting priority -100; policy accept;
}
}
### You are scared and just want to take a look
......@@ -82,7 +46,9 @@ If you want to generate the nftables rule-set but not activate it, use
your own firewall-recipe, like so:
```RUBY
firewall 'default' # Default action is :install
firewall 'default' do
action [:rebuild, :disable]
end
firewall_rule 'example-ips from rfc5737' do
source ['192.0.2.0/24', '198.51.100.0/24', '203.0.113.0/24']
......@@ -90,25 +56,23 @@ firewall_rule 'example-ips from rfc5737' do
end
```
Make sure to [disable](#disable-nftables) the firewall. These steps
should disable nftables but still generate `/etc/nftables.conf`.
Setting `action` to [:rebuild, :disable] will disable nftables but
still generate `/etc/nftables.conf`.
## Using `sys::firewall` from other recipes
## Using the `firewall`-resource
Depend on the `sys`-cookbook in the `metadata.rb` and include
`sys::firewall` in the runlist. If access via ports `443` and `80`
should be possible, write a resource like this:
Depend on the `sys`-cookbook in the `metadata.rb`. Write a recipe to
configure nftables, e.g. to configure a ruleset which only allows
access via port `22`, write a recipe like this
```ruby
firewall 'default' do
input_policy 'drop'
end
firewall_rule 'allow http(s)' do
port [80,443]
end
```
If `sys::firewall` is not what you want, it is also sufficient to
define the resource `firewall['default']` instead of including
`sys::firewall` and build everything from scratch.
For further examples see the recipe
[sys::firewall](recipes/firewall.rb) and the recipe [firewall-test::default](test/fixtures/cookbooks/firewall-test/recipes/default.rb).
For further examples see [firewall-test::default](test/fixtures/cookbooks/firewall-test/recipes/default.rb).
......@@ -68,14 +68,6 @@ module Sys
end
end
def disabled?
node['sys']['firewall']['disable']
end
def managed?
node['sys']['firewall']['manage']
end
def build_rule_file(rules)
contents = []
sorted_values = rules.values.sort.uniq
......@@ -168,8 +160,25 @@ module Sys
Chef::Log.info('log_nftables timed out!')
end
def default_ruleset(current_node)
current_node['sys']['firewall']['defaults']['ruleset'].to_h
def default_ruleset(new_resource)
rules = {
'add table inet filter' => 1,
"add chain inet filter INPUT { type filter hook input priority 0 ; policy #{new_resource.input_policy}; }" => 2,
"add chain inet filter OUTPUT { type filter hook output priority 0 ; policy #{new_resource.output_policy}; }" => 2,
"add chain inet filter FOWARD { type filter hook forward priority 0 ; policy #{new_resource.forward_policy}; }" => 2,
}
if new_resource.table_ip_nat
rules['add table ip nat'] = 1
rules['add chain ip nat POSTROUTING { type nat hook postrouting priority 100 ;}'] = 2
rules['add chain ip nat PREROUTING { type nat hook prerouting priority -100 ;}'] = 2
end
if new_resource.table_ip6_nat
rules['add table ip6 nat'] = 1
rules['add chain ip6 nat POSTROUTING { type nat hook postrouting priority 100 ;}'] = 2
rules['add chain ip6 nat PREROUTING { type nat hook prerouting priority -100 ;}'] = 2
end
rules
end
def ensure_default_rules_exist(current_node, new_resource)
......
#
# Cookbook Name:: sys
# Recipe:: firewall
#
# Copyright 2022 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH
#
# Authors:
# Matthias Pausch (m.pausch@gsi.de)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This code is an adjustment of https://github.com/sous-chefs/firewall
#
return unless node['sys']['firewall']['manage']
return unless Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION))
fw_action = node['sys']['firewall']['disable'] ? :disable : :install
firewall 'default' do
action fw_action
end
firewall_rule 'allow loopback' do
interface 'lo'
protocol :none
command :allow
only_if { node['sys']['firewall']['allow_loopback'] }
end
firewall_rule 'allow icmp' do
protocol :icmp
command :allow
only_if { node['sys']['firewall']['allow_icmp'] }
end
firewall_rule 'allow world to ssh' do
port 22
only_if { node['sys']['firewall']['allow_ssh'] }
end
# allow established connections
firewall_rule 'established' do
position 40
stateful [:related, :established]
protocol :none # explicitly don't specify protocol
command :allow
only_if { node['sys']['firewall']['allow_established'] }
end
......@@ -56,14 +56,30 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
provides :firewall, os: 'linux', platform: %w(debian)
property :rules, Hash
property :input_policy,
String,
equal_to: %w(drop accept),
default: 'accept'
property :output_policy,
String,
equal_to: %w(drop accept),
default: 'accept'
property :forward_policy,
String,
equal_to: %w(drop accept),
default: 'accept'
property :table_ip_nat,
[true, false],
default: false
property :table_ip6_nat,
[true, false],
default: false
def whyrun_supported?
false
end
action :install do
return unless managed?
# Ensure the package is installed
nft_pkg = package 'nftables' do
action :nothing
......@@ -79,8 +95,6 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
end
action :rebuild do
return unless managed?
ensure_default_rules_exist(node, new_resource)
# prints all the firewall rules
log_nftables
......@@ -89,10 +103,8 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
nftables_file = lookup_or_create_rulesfile('/etc/nftables.conf')
nftables_file.content "#!/usr/sbin/nft -f\nflush ruleset\n#{build_rule_file(new_resource.rules)}"
nftables_file.run_action(:create)
if disabled?
new_resource.run_action(:disable)
return
end
return if new_resource.action.include?(:disable)
nftables_service = lookup_or_create_service('nftables')
nftables_service.run_action(:enable)
......@@ -105,13 +117,11 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
end
action :restart do
return unless managed?
nftables_service = lookup_or_create_service('nftables')
nftables_service.run_action(:restart)
end
action :disable do
return unless managed?
nftables_service = lookup_or_create_service('nftables')
%i(disable stop).each do |a|
nftables_service.run_action(a)
......
......@@ -30,8 +30,7 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
def return_early?(new_resource)
!new_resource.notify_firewall ||
!(new_resource.action.include?(:create) &&
!new_resource.should_skip?(:create)) ||
!managed?
!new_resource.should_skip?(:create))
end
end
......
return unless Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION))
include_recipe 'sys::firewall'
firewall 'default' do
table_ip_nat true
table_ip6_nat true
input_policy 'drop'
end
firewall_rule 'allow loopback' do
interface 'lo'
protocol :none
command :allow
only_if { node['sys']['firewall']['allow_loopback'] }
end
firewall_rule 'allow icmp' do
protocol :icmp
command :allow
only_if { node['sys']['firewall']['allow_icmp'] }
end
firewall_rule 'allow world to ssh' do
port 22
only_if { node['sys']['firewall']['allow_ssh'] }
end
# allow established connections
firewall_rule 'established' do
position 40
stateful [:related, :established]
protocol :none # explicitly don't specify protocol
command :allow
only_if { node['sys']['firewall']['allow_established'] }
end
firewall_rule 'ssh22' do
port 22
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment