Commit 9674e8c8 authored by m.pausch's avatar m.pausch
Browse files

rename firewall resources to nftables

parent 70675950
......@@ -42,9 +42,9 @@ kitchen:
- banner
- chef
- ferm
- firewall
- linuxlogo
- mail
- nftables
- nsswitch
- ohai
- resolv
......
......@@ -183,14 +183,9 @@ suites:
- 'policy ACCEPT;'
FORWARD:
- 'policy DROP;'
- name: sys_firewall
- name: sys_nftables
run_list:
- recipe[firewall-test::default]
attributes:
sys:
firewall:
manage: true
disable: false
- recipe[nftables-test::default]
- name: sys_linuxlogo
run_list:
- recipe[sys::linuxlogo]
......
......@@ -10,5 +10,5 @@ cookbook 'fixtures', path: 'test/unit/fixtures', group: :chefspec
cookbook 'line', github: 'sous-chefs/line', tag: "v0.6.3"
group :integration do
cookbook 'firewall-test', path: 'test/fixtures/cookbooks/firewall-test'
cookbook 'nftables-test', path: 'test/fixtures/cookbooks/nftables-test'
end
# `resource::firewall`
# `resource::nftables`
Use the firewall resource to configure nftables.
Use the nftables resource to configure nftables.
`resources/firewall.rb`
`resources/firewall_rule.rb`
`libraries/sys_helpers_firewall.rb`
`documents/firewall.rb`
`test/unit/recipies/firewall_spec.rb`
`resources/nftables.rb`
`resources/nftables_rule.rb`
`libraries/sys_helpers_nftables.rb`
`documents/nftables.rb`
`test/unit/recipies/nftables_spec.rb`
## Basic Usage
### Disable nftables
```ruby
firewall 'default' do
nftables 'default' do
action :disable
end
```
......@@ -21,7 +21,7 @@ end
### Enable nftables
```ruby
firewall 'default'
nftables 'default'
```
This will give you the following default rules:
......@@ -43,14 +43,14 @@ This will give you the following default rules:
### You are scared and just want to take a look
If you want to generate the nftables rule-set but not activate it, use
your own firewall-recipe, like so:
your own nftables-recipe, like so:
```RUBY
firewall 'default' do
nftables 'default' do
action [:rebuild, :disable]
end
firewall_rule 'example-ips from rfc5737' do
nftables_rule 'example-ips from rfc5737' do
source ['192.0.2.0/24', '198.51.100.0/24', '203.0.113.0/24']
port 22
end
......@@ -59,20 +59,20 @@ end
Setting `action` to [:rebuild, :disable] will disable nftables but
still generate `/etc/nftables.conf`.
## Using the `firewall`-resource
## Using the `nftables`-resource
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
nftables 'default' do
input_policy 'drop'
end
firewall_rule 'allow http(s)' do
nftables_rule 'allow http(s)' do
port [80,443]
end
```
For further examples see [firewall-test::default](test/fixtures/cookbooks/firewall-test/recipes/default.rb).
For further examples see [nftables-test::default](test/fixtures/cookbooks/nftables-test/recipes/default.rb).
#
# Cookbook Name:: sys
# Library:: Helpers::Firewall
# Library:: Helpers::Nftables
#
# Copyright 2022 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH
#
......@@ -24,7 +24,7 @@
module Sys
module Helpers
module Firewall
module Nftables
require 'ipaddr'
include Chef::Mixin::ShellOut
......@@ -101,7 +101,7 @@ module Sys
}.freeze
end
def build_firewall_rule(rule_resource)
def build_nftables_rule(rule_resource)
return rule_resource.raw.strip if rule_resource.raw
ip_family = rule_resource.family
......@@ -110,46 +110,46 @@ module Sys
else
'filter'
end
firewall_rule = if table == 'nat'
nftables_rule = if table == 'nat'
"add rule #{ip_family} #{table} "
else
"add rule inet #{table} "
end
firewall_rule << CHAIN.fetch(rule_resource.direction.to_sym)
firewall_rule << ' '
firewall_rule << "iif #{rule_resource.interface} " if rule_resource.interface
firewall_rule << "oif #{rule_resource.dest_interface} " if rule_resource.dest_interface
nftables_rule << CHAIN.fetch(rule_resource.direction.to_sym)
nftables_rule << ' '
nftables_rule << "iif #{rule_resource.interface} " if rule_resource.interface
nftables_rule << "oif #{rule_resource.outerface} " if rule_resource.dest_interface
if rule_resource.source
source_set = build_set_of_ips(rule_resource.source)
firewall_rule << "#{ip_family} saddr #{source_set} "
nftables_rule << "#{ip_family} saddr #{source_set} "
end
if rule_resource.destination
destination_set = build_set_of_ips(rule_resource.destination)
firewall_rule << "#{ip_family} daddr #{destination_set} "
nftables_rule << "#{ip_family} daddr #{destination_set} "
end
case rule_resource.protocol
when :icmp
firewall_rule << 'icmp type echo-request '
nftables_rule << 'icmp type echo-request '
when :'ipv6-icmp', :icmpv6
firewall_rule << 'icmpv6 type { echo-request, nd-router-solicit, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } '
nftables_rule << 'icmpv6 type { echo-request, nd-router-solicit, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } '
when :tcp, :udp
firewall_rule << "#{rule_resource.protocol} sport #{port_to_s(sport(rule_resource))} " if sport(rule_resource)
firewall_rule << "#{rule_resource.protocol} dport #{port_to_s(dport(rule_resource))} " if dport(rule_resource)
nftables_rule << "#{rule_resource.protocol} sport #{port_to_s(rule_resource.sport)} " if sport(rule_resource)
nftables_rule << "#{rule_resource.protocol} dport #{port_to_s(rule_resource.dport)} " if dport(rule_resource)
when :esp, :ah
firewall_rule << "#{ip_family} #{ip_family == :ip6 ? 'nexthdr' : 'protocol'} #{rule_resource.protocol} "
nftables_rule << "#{ip_family} #{ip_family == :ip6 ? 'nexthdr' : 'protocol'} #{rule_resource.protocol} "
# nothing to do default :ipv6, :none
end
firewall_rule << "ct state #{Array(rule_resource.stateful).join(',').downcase} " if rule_resource.stateful
firewall_rule << "#{TARGET[rule_resource.command.to_sym]} "
firewall_rule << " to #{rule_resource.redirect_port} " if rule_resource.command == :redirect
firewall_rule << "comment \"#{rule_resource.description}\" " if rule_resource.include_comment
firewall_rule.strip!
firewall_rule
nftables_rule << "ct state #{Array(rule_resource.stateful).join(',').downcase} " if rule_resource.stateful
nftables_rule << "#{TARGET[rule_resource.command.to_sym]} "
nftables_rule << " to #{rule_resource.redirect_port} " if rule_resource.command == :redirect
nftables_rule << "comment \"#{rule_resource.description}\" " if rule_resource.include_comment
nftables_rule.strip!
nftables_rule
end
def log_nftables
......
#
# Cookbook Name:: sys
# Resource:: firewall
# Resource:: nftables
#
# Copyright 2022 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH
#
......@@ -26,7 +26,7 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
action_class do
include Sys::Helpers::Firewall
include Sys::Helpers::Nftables
def lookup_or_create_service(name)
begin
......@@ -53,9 +53,10 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
#unified_mode true
provides :firewall, os: 'linux', platform: %w(debian)
provides :nftables, os: 'linux', platform: %w(debian)
property :rules, Hash
property :rules,
Hash
property :input_policy,
String,
equal_to: %w(drop accept),
......@@ -87,7 +88,7 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
nft_pkg.run_action(:install)
with_run_context :root do
edit_resource('sys_firewall', new_resource.name) do
edit_resource('sys_nftables', new_resource.name) do
action :nothing
delayed_action :rebuild
end
......@@ -96,7 +97,7 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
action :rebuild do
ensure_default_rules_exist(node, new_resource)
# prints all the firewall rules
# prints all the nftables rules
log_nftables
# this takes the commands in each hash entry and builds a rule file
......
#
# Cookbook Name:: sys
# Resource:: firewall_rule
# Resource:: nftables_rule
#
# Copyright 2022 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH
#
......@@ -25,79 +25,103 @@
if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION))
require 'ipaddr'
action_class do
include Sys::Helpers::Firewall
include Sys::Helpers::Nftables
def return_early?(new_resource)
!new_resource.notify_firewall ||
!new_resource.notify_nftables ||
!(new_resource.action.include?(:create) &&
!new_resource.should_skip?(:create))
end
end
provides :firewall_rule
provides :nftables_rule
default_action :create
property :firewall_name, String, default: 'default'
property :command, Symbol, default: :allow, equal_to: %i[
accept allow deny drop log masquerade redirect reject
]
property :protocol, [Integer, Symbol], default: :tcp,
callbacks: { 'must be either :tcp, :udp, :icmp, :\'ipv6-icmp\', :icmpv6, :none, or a valid IP protocol number' =>
->(p) do
%i[udp tcp icmp icmpv6 ipv6-icmp esp ah ipv6 none].include?(p) || (0..142).include?(p)
end }
property :direction, Symbol, equal_to: [:in, :out, :pre, :post, :forward], default: :in
property :logging, Symbol, equal_to: [:connections, :packets]
property :nftables_name,
String,
default: 'default'
property :command,
Symbol,
default: :allow,
equal_to: %i[
accept allow deny drop log masquerade redirect reject
]
property :protocol,
[Integer, Symbol],
default: :tcp,
callbacks: { 'must be valid IP protocol specification' =>
lambda(p) do
%i[udp tcp icmp icmpv6 ipv6-icmp esp ah ipv6 none].include?(p) || (0..142).include?(p)
end }
property :direction,
Symbol,
equal_to: [:in, :out, :pre, :post, :forward],
default: :in
property :logging,
Symbol,
equal_to: [:connections, :packets]
# nftables handles ip6 and ip simultaneously. Except for directions
# :pre and :post, where where either :ip6 or :ip must be specified.
# callback should prevent from mixing that up.
property :family, Symbol, equal_to: [:ip6, :ip], default: :ip
property :source, [String, Array], callbacks: {
'must be a valid ip address' => ->(ips) do
Array(ips).inject(false) do |a, ip|
a || !!IPAddr.new(ip)
end
end
}
property :source_port, [Integer, String, Array, Range] # source port
property :interface, String
# I would rather call them sport and dport, without alternatives.
# However, firewall rules should be kept compatible with future
# releases of the firewall cookbook from the sous-chefs.
property :port, [Integer, String, Array, Range] # shorthand for dest_port
property :destination, [String, Array], callbacks: {
'must be a valid ip address' => ->(ips) do
Array(ips).inject(false) do |a, ip|
a || !!IPAddr.new(ip)
end
end
}
property :dest_port, [Integer, String, Array, Range]
property :dest_interface, String
property :position, Integer, default: 50
property :stateful, [Symbol, Array]
property :redirect_port, Integer
property :description, String, name_property: true
property :include_comment, [true, false], default: true
property :family,
Symbol,
equal_to: [:ip6, :ip],
default: :ip
property :source,
[String, Array],
callbacks: {
'must be a valid ip address' => lambda(ips) do
Array(ips).inject(false) do |a, ip|
a || !!IPAddr.new(ip)
end
end
}
property :sport,
[Integer, String, Array, Range]
property :interface,
String
property :dport,
[Integer, String, Array, Range]
property :destination,
[String, Array],
callbacks: {
'must be a valid ip address' => lambda(ips) do
Array(ips).inject(false) do |a, ip|
a || !!IPAddr.new(ip)
end
end
}
property :outerface,
String
property :position,
Integer,
default: 50
property :stateful,
[Symbol, Array]
property :redirect_port,
Integer
property :description,
String,
name_property: true
property :include_comment,
[true, false],
default: true
# for when you just want to pass a raw rule
property :raw, String
# do you want this rule to notify the firewall to recalculate
# (and potentially reapply) the firewall_rule(s) it finds?
property :notify_firewall, [true, false], default: true
property :raw,
String
# do you want this rule to notify the nftables to recalculate
# (and potentially reapply) the nftables_rule(s) it finds?
property :notify_nftables,
[true, false],
default: true
action :create do
return if return_early?(new_resource)
fwr = build_firewall_rule(new_resource)
fwr = build_nftables_rule(new_resource)
with_run_context :root do
begin
edit_resource!('sys_firewall', new_resource.firewall_name) do |fw_rule|
edit_resource!('sys_nftables', new_resource.nftables_name) do |fw_rule|
r = rules.dup || {}
r.merge!({
fwr => fw_rule.position
......@@ -106,7 +130,7 @@ if Gem::Requirement.new('>= 12.15').satisfied_by?(Gem::Version.new(Chef::VERSION
delayed_action :rebuild
end
rescue Chef::Exceptions::ResourceNotFound
Chef::Log.warn "Resource firewall['#{new_resource.firewall_name}'] not found in resource collection. Not configuring firewall."
Chef::Log.warn "Resource nftables['#{new_resource.nftables_name}'] not found in resource collection. Not configuring nftables."
end
end
end
......
default['sys']['firewall']['table_ip_nat'] = true
default['sys']['firewall']['table_ip6_nat'] = true
#
# Cookbook Name:: sys
# Integration tests for sys::firewall
# Integration tests for resource nftables
#
# Copyright 2022 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH
#
......
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