Initial Chef repository

This commit is contained in:
Greg Karékinian
2015-07-21 19:45:23 +02:00
parent 7e5401fc71
commit ee4079fa85
1151 changed files with 185163 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
firewall Cookbook CHANGELOG
=======================
This file is used to list changes made in each version of the firewall cookbook.
v1.2.0 (2015-05-28)
-------------------
* #64 - Support the newer version of poise
v1.1.2 (2015-05-19)
-------------------
* #60 - Always add /32 or /128 to ipv4 or ipv6 addresses, respectively.
- Make comment quoting optional; iptables on Ubuntu strips quotes on strings without any spaces
v1.1.1 (2015-05-11)
-------------------
* #57 - Suppress warning: already initialized constant XXX while Chefspec
v1.1.0 (2015-04-27)
-------------------
* #56 - Better ipv6 support for firewalld and iptables
* #54 - Document raw parameter
v1.0.2 (2015-04-03)
-------------------
* #52 - Typo in :masquerade action name
v1.0.1 (2015-03-28)
-------------------
* #49 - Fix position attribute of firewall_rule providers to be correctly used as a string in commands
v1.0.0 (2015-03-25)
-------------------
* Major upgrade and rewrite as HWRP using poise
* Adds support for iptables and firewalld
* Modernize tests and other files
* Fix many bugs from ufw defaults to multiport suppot
v0.11.8 (2014-05-20)
--------------------
* Corrects issue where on a secondary converge would not distinguish between inbound and outbound rules
v0.11.6 (2014-02-28)
--------------------
[COOK-4385] - UFW provider is broken
v0.11.4 (2014-02-25)
--------------------
[COOK-4140] Only notify when a rule is actually added
v0.11.2
-------
### Bug
- **[COOK-3615](https://tickets.opscode.com/browse/COOK-3615)** - Install required UFW package on Debian
v0.11.0
-------
### Improvement
- [COOK-2932]: ufw providers work on debian but cannot be used
v0.10.2
-------
- [COOK-2250] - improve readme
v0.10.0
------
- [COOK-1234] - allow multiple ports per rule
v0.9.2
------
- [COOK-1615] - Firewall example docs have incorrect direction syntax
v0.9.0
------
The default action for firewall LWRP is now :enable, the default action for firewall_rule LWRP is now :reject. This is in line with a "default deny" policy.
- [COOK-1429] - resolve foodcritic warnings
v0.8.0
------
- refactor all resources and providers into LWRPs
- removed :reset action from firewall resource (couldn't find a good way to make it idempotent)
- removed :logging action from firewall resource...just set desired level via the log_level attribute
v0.6.0
------
- [COOK-725] Firewall cookbook firewall_rule LWRP needs to support logging attribute.
- Firewall cookbook firewall LWRP needs to support :logging
v0.5.7
------
- [COOK-696] Firewall cookbook firewall_rule LWRP needs to support interface
- [COOK-697] Firewall cookbook firewall_rule LWRP needs to support the direction for the rules
v0.5.6
------
- [COOK-695] Firewall cookbook firewall_rule LWRP needs to support destination port
v0.5.5
------
- [COOK-709] fixed :nothing action for the 'firewall_rule' resource.
v0.5.4
------
- [COOK-694] added :reject action to the 'firewall_rule' resource.
v0.5.3
------
- [COOK-698] added :reset action to the 'firewall' resource.
v0.5.2
------
- Add missing 'requires' statements. fixes 'NameError: uninitialized constant' error.
thanks to Ernad Husremović for the fix.
v0.5.0
------
- [COOK-686] create firewall and firewall_rule resources
- [COOK-687] create UFW providers for all resources

View File

@@ -0,0 +1,189 @@
firewall Cookbook
=================
[![Build Status](https://secure.travis-ci.org/opscode-cookbooks/firewall.png?branch=master)](http://travis-ci.org/opscode-cookbooks/firewall)
Provides a set of primitives for managing firewalls and associated rules.
PLEASE NOTE - The resource/providers in this cookbook are under heavy development. An attempt is being made to keep the resource simple/stupid by starting with less sophisticated firewall implementations first and refactor/vet the resource definition with each successive provider.
Requirements
------------
### Platform
* Ubuntu
* Debian
* Redhat
* CentOS
Tested on:
* Ubuntu 12.04
* Ubuntu 14.04
* Debian 7.8
* CentOS 6.5
* CentOS 7.0
Recipes
-------
### default
The default recipe creates a firewall resource with action install, and if `node['firewall']['allow_ssh']`, opens port 22 from the world.
Attributes
----------
* `default['firewall']['ufw']['defaults']` hash for template `/etc/default/ufw`
Resources/Providers
-------------------
- See `librariez/z_provider_mapping.rb` for a full list of providers for each platform and version.
### firewall
#### Actions
- `:enable`: *Default action* enable the firewall. this will make any rules that have been defined 'active'.
- `:disable`: disable the firewall. drop any rules and put the node in an unprotected state.
- `:flush`: Runs `iptables -F`. Only supported by the iptables firewall provider.
- `:save`: Runs `service iptables save` under iptables, adds rules permanently under firewall. Not supported in ufw.
#### Attribute Parameters
- name: name attribute. arbitrary name to uniquely identify this resource
- log_level: level of verbosity the firewall should log at. valid values are: :low, :medium, :high, :full. default is :low.
#### Examples
```ruby
# enable platform default firewall
firewall 'ufw' do
action :enable
end
# increase logging past default of 'low'
firewall 'debug firewalls' do
log_level :high
action :enable
end
```
### firewall_rule
#### Actions
- `:allow`: the rule should allow incoming traffic.
- `:deny`: the rule should deny incoming traffic.
- `:reject`: *Default action: the rule should reject incoming traffic.
- `:masqerade`: Add masqerade rule
- `:redirect`: Add redirect-type rule
- `:log`: Configure logging
- `:remove`: Remove all rules
#### Attribute Parameters
- name: name attribute. arbitrary name to uniquely identify this firewall rule
- protocol: valid values are: :udp, :tcp. default is all protocols
- port: incoming port number (ie. 22 to allow inbound SSH), or an array of incoming port numbers (ie. [80,443] to allow inbound HTTP & HTTPS). NOTE: `protocol` attribute is required with multiple ports, or a range of incoming port numbers (ie. 60000..61000 to allow inbound mobile-shell. NOTE: `protocol`, or an attribute is required with a range of ports.
- source: ip address or subnet to filter on incoming traffic. default is `0.0.0.0/0` (ie Anywhere)
- destination: ip address or subnet to filter on outgoing traffic.
- dest_port: outgoing port number.
- position: position to insert rule at. if not provided rule is inserted at the end of the rule list.
- direction: direction of the rule. valid values are: :in, :out, default is :in
- interface: interface to apply rule (ie. 'eth0').
- logging: may be added to enable logging for a particular rule. valid values are: :connections, :packets. In the ufw provider, :connections logs new connections while :packets logs all packets.
- raw: for passing a raw command to the provider (for use with custom modules, also used by zap provider to clean up non-chef managed rules)
#### Examples
```ruby
# open standard ssh port, enable firewall
firewall_rule 'ssh' do
port 22
action :allow
notifies :enable, 'firewall[ufw]'
end
# open standard http port to tcp traffic only; insert as first rule
firewall_rule 'http' do
port 80
protocol :tcp
position 1
action :allow
end
# restrict port 13579 to 10.0.111.0/24 on eth0
firewall_rule 'myapplication' do
port 13579
source '10.0.111.0/24'
direction :in
interface 'eth0'
action :allow
end
# open UDP ports 60000..61000 for mobile shell (mosh.mit.edu), note
# that the protocol attribute is required when using port_range
firewall_rule 'mosh' do
protocol :udp
port 60000..61000
action :allow
end
# open multiple ports for http/https, note that the protocol
# attribute is required when using ports
firewall_rule 'http/https' do
protocol :tcp
port [80, 443]
action :allow
end
firewall 'ufw' do
action :nothing
end
```
Development
-----------
This section details "quick development" steps. For a detailed explanation, see [[Contributing.md]].
1. Clone this repository from GitHub:
$ git clone git@github.com:opscode-cookbooks/firewall.git
2. Create a git branch
$ git checkout -b my_bug_fix
3. Install dependencies:
$ bundle install
4. Make your changes/patches/fixes, committing appropiately
5. **Write tests**
6. Run the tests:
- `bundle exec foodcritic -f any .`
- `bundle exec rspec`
- `bundle exec rubocop`
- `bundle exec kitchen test`
In detail:
- Foodcritic will catch any Chef-specific style errors
- RSpec will run the unit tests
- Rubocop will check for Ruby-specific style errors
- Test Kitchen will run and converge the recipes
License & Authors
-----------------
- Author:: Seth Chisamore (<schisamo@opscode.com>)
```text
Copyright:: Copyright (c) 2011-2015 Opscode, Inc.
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.
```

View File

@@ -0,0 +1 @@
default['firewall']['allow_ssh'] = false

View File

@@ -0,0 +1,12 @@
default['firewall']['ufw']['defaults'] = {
:ipv6 => 'yes',
:manage_builtins => 'no',
:ipt_sysctl => '/etc/ufw/sysctl.conf',
:ipt_modules => 'nf_conntrack_ftp nf_nat_ftp nf_conntrack_netbios_ns',
:policy => {
:input => 'DROP',
:output => 'ACCEPT',
:forward => 'DROP',
:application => 'SKIP'
}
}

View File

@@ -0,0 +1,13 @@
module FirewallCookbook
module Helpers
def port_to_s(p)
if p && p.is_a?(Integer)
p.to_s
elsif p && p.is_a?(Array)
p.join(',')
elsif p && p.is_a?(Range)
"#{p.first}:#{p.last} "
end
end
end
end

View File

@@ -0,0 +1,32 @@
if defined?(ChefSpec)
ChefSpec.define_matcher(:firewall)
ChefSpec.define_matcher(:firewall_rule)
def enable_firewall(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall, :enable, resource)
end
def disable_firewall(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall, :disable, resource)
end
def allow_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :allow, resource)
end
def deny_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :deny, resource)
end
def reject_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :reject, resource)
end
def log_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :log, resource)
end
def remove_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :remove, resource)
end
end

View File

@@ -0,0 +1,92 @@
#
# Author:: Ronald Doorn (<rdoorn@schubergphilis.com>)
# Cookbook Name:: firewall
# Resource:: default
#
# 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.
#
require 'poise'
class Chef
class Provider::FirewallFirewalld < Provider
include Poise
include Chef::Mixin::ShellOut
def action_enable
# prints all the firewall rules
# pp @new_resource.subresources
log_current_firewalld
if active?
Chef::Log.debug("#{@new_resource} already enabled.")
else
Chef::Log.debug("#{@new_resource} is about to be enabled")
shell_out!('service', 'firewalld', 'start')
shell_out!('firewall-cmd', '--set-default-zone=drop')
Chef::Log.info("#{@new_resource} enabled.")
new_resource.updated_by_last_action(true)
end
end
def action_disable
if active?
shell_out!('firewall-cmd', '--set-default-zone=public')
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
Chef::Log.info("#{@new_resource} disabled")
new_resource.updated_by_last_action(true)
else
Chef::Log.debug("#{@new_resource} already disabled.")
end
end
def action_flush
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
Chef::Log.info("#{@new_resource} flushed.")
end
def action_save
if shell_out!('firewall-cmd', '--direct', '--get-all-rules').stdout != shell_out!('firewall-cmd', '--direct', '--permanent', '--get-all-rules').stdout
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
shell_out!('firewall-cmd', '--direct', '--get-all-rules').stdout.lines do |line|
shell_out!("firewall-cmd --direct --permanent --add-rule #{line}")
end
Chef::Log.info("#{@new_resource} saved.")
new_resource.updated_by_last_action(true)
else
Chef::Log.info("#{@new_resource} already up-to-date.")
end
end
private
def active?
@active ||= begin
cmd = shell_out('firewall-cmd', '--state')
cmd.stdout =~ /^running$/
end
end
def log_current_firewalld
cmdstr = 'firewall-cmd --direct --get-all-rules'
Chef::Log.info("#{@new_resource} log_current_firewalld (#{cmdstr}):")
cmd = shell_out!(cmdstr)
Chef::Log.info(cmd.inspect)
rescue
Chef::Log.info("#{@new_resource} log_current_firewalld failed!")
end
end
end

View File

@@ -0,0 +1,110 @@
#
# Author:: Seth Chisamore (<schisamo@opscode.com>)
# Cookbook Name:: firewall
# Resource:: default
#
# Copyright:: 2011, Opscode, Inc.
#
# 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.
#
require 'poise'
class Chef
class Provider::FirewallIptables < Provider
include Poise
include Chef::Mixin::ShellOut
def action_enable
converge_by('install package iptables and default DROP if no rules exist') do
package 'iptables' do
action :install
end
# prints all the firewall rules
# pp new_resource.subresources
log_current_iptables
if active?
Chef::Log.info("#{new_resource} already enabled.")
else
Chef::Log.debug("#{new_resource} is about to be enabled")
shell_out!('iptables -P INPUT DROP')
shell_out!('iptables -P OUTPUT DROP')
shell_out!('iptables -P FORWARD DROP')
shell_out!('ip6tables -P INPUT DROP')
shell_out!('ip6tables -P OUTPUT DROP')
shell_out!('ip6tables -P FORWARD DROP')
Chef::Log.info("#{new_resource} enabled.")
new_resource.updated_by_last_action(true)
end
end
end
def action_disable
if active?
shell_out!('iptables -P INPUT ACCEPT')
shell_out!('iptables -P OUTPUT ACCEPT')
shell_out!('iptables -P FORWARD ACCEPT')
shell_out!('iptables -F')
shell_out!('ip6tables -P INPUT ACCEPT')
shell_out!('ip6tables -P OUTPUT ACCEPT')
shell_out!('ip6tables -P FORWARD ACCEPT')
shell_out!('ip6tables -F')
Chef::Log.info("#{new_resource} disabled")
new_resource.updated_by_last_action(true)
else
Chef::Log.debug("#{new_resource} already disabled.")
end
end
def action_flush
shell_out!('iptables -F')
shell_out!('ip6tables -F')
Chef::Log.info("#{new_resource} flushed.")
end
def action_save
shell_out!('service iptables save')
shell_out!('service ip6tables save')
Chef::Log.info("#{new_resource} saved.")
end
private
def active?
@active ||= begin
cmd = shell_out!('iptables-save')
cmd.stdout =~ /INPUT ACCEPT/
end
@active_v6 ||= begin
cmd = shell_out!('ip6tables-save')
cmd.stdout =~ /INPUT ACCEPT/
end
@active && @active_v6
end
def log_current_iptables
cmdstr = 'iptables -L'
Chef::Log.info("#{new_resource} log_current_iptables (#{cmdstr}):")
cmd = shell_out!(cmdstr)
Chef::Log.info(cmd.inspect)
cmdstr = 'ip6tables -L'
Chef::Log.info("#{new_resource} log_current_iptables (#{cmdstr}):")
cmd = shell_out!(cmdstr)
Chef::Log.info(cmd.inspect)
rescue
Chef::Log.info("#{new_resource} log_current_iptables failed!")
end
end
end

View File

@@ -0,0 +1,213 @@
#
# Author:: Ronald Doorn (<rdoorn@schubergphilis.com>)
# Cookbook Name:: firewall
# Provider:: rule_iptables
#
# 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.
#
require 'poise'
class Chef
class Provider::FirewallRuleFirewalld < Provider
include Poise
include Chef::Mixin::ShellOut
include FirewallCookbook::Helpers
def action_allow
apply_rule(:allow)
end
def action_deny
apply_rule(:deny)
end
def action_reject
apply_rule(:reject)
end
def action_redirect
apply_rule(:redirect)
end
def action_masquerade
apply_rule(:masquerade)
end
def action_log
apply_rule(:log)
end
def action_remove
# TODO: specify which target to delete
# for now this will remove raw + all targeted lines
remove_rule(:allow)
remove_rule(:deny)
remove_rule(:reject)
remove_rule(:redirect)
remove_rule(:masquerade)
end
private
CHAIN = { :in => 'INPUT', :out => 'OUTPUT', :pre => 'PREROUTING', :post => 'POSTROUTING' } unless defined? CHAIN # , nil => "FORWARD"}
TARGET = { :allow => 'ACCEPT', :reject => 'REJECT', :deny => 'DROP', :masquerade => 'MASQUERADE', :redirect => 'REDIRECT', :log => 'LOG --log-prefix \'iptables: \' --log-level 7' } unless defined? TARGET
def apply_rule(type = nil)
ip_versions.each do |ip_version|
firewall_command = 'firewall-cmd --direct --add-rule '
# TODO: implement logging for :connections :packets
firewall_rule = build_firewall_rule(type, ip_version)
Chef::Log.debug("#{new_resource}: #{firewall_rule}")
if rule_exists?(firewall_rule)
Chef::Log.info("#{new_resource} #{type} rule exists... won't apply")
else
cmdstr = firewall_command + firewall_rule
converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do
notifying_block do
shell_out!(cmdstr) # shell_out! is already logged
new_resource.updated_by_last_action(true)
end
end
end
end
end
def remove_rule(type = nil)
ip_versions.each do |_ip_version|
firewall_command = 'firewall-cmd --direct --remove-rule '
# TODO: implement logging for :connections :packets
firewall_rule = build_firewall_rule(type)
Chef::Log.debug("#{new_resource}: #{firewall_rule}")
if rule_exists?(firewall_rule)
cmdstr = firewall_command + firewall_rule
converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do
notifying_block do
shell_out!(cmdstr) # shell_out! is already logged
new_resource.updated_by_last_action(true)
end
end
else
Chef::Log.info("#{new_resource} #{type} rule does not exists... won't remove")
end
end
end
def ipv4_rule?
if (new_resource.source && IPAddr.new(new_resource.source).ipv4?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv4?)
true
else
false
end
end
def ipv6_rule?
if (new_resource.source && IPAddr.new(new_resource.source).ipv6?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv6?)
true
else
false
end
end
def ip_versions
if ipv4_rule?
versions = ['ipv4']
elsif ipv6_rule?
versions = ['ipv6']
else # no source or destination address, add rules for both ipv4 and ipv6
versions = %w(ipv4 ipv6)
end
versions
end
def build_firewall_rule(type = nil, ip_version = 'ipv4')
if new_resource.raw
firewall_rule = new_resource.raw.strip
else
firewall_rule = "#{ip_version} filter "
if new_resource.direction
firewall_rule << "#{CHAIN[new_resource.direction.to_sym]} "
else
firewall_rule << 'FORWARD '
end
firewall_rule << "#{new_resource.position ? new_resource.position : 1} "
if [:pre, :post].include?(new_resource.direction)
firewall_rule << '-t nat '
end
# Firewalld order of prameters is important here see example output below:
# ipv4 filter INPUT 1 -s 1.2.3.4/32 -d 5.6.7.8/32 -i lo -p tcp -m tcp -m state --state NEW -m comment --comment "hello" -j DROP
firewall_rule << "-s #{ip_with_mask(new_resource.source)} " if new_resource.source && new_resource.source != '0.0.0.0/0'
firewall_rule << "-d #{new_resource.destination} " if new_resource.destination
firewall_rule << "-i #{new_resource.interface} " if new_resource.interface
firewall_rule << "-o #{new_resource.dest_interface} " if new_resource.dest_interface
firewall_rule << "-p #{new_resource.protocol} " if new_resource.protocol
firewall_rule << '-m tcp ' if new_resource.protocol.to_sym == :tcp
# using multiport here allows us to simplify our greps and rule building
firewall_rule << "-m multiport --sports #{port_to_s(new_resource.source_port)} " if new_resource.source_port
firewall_rule << "-m multiport --dports #{port_to_s(dport_calc)} " if dport_calc
firewall_rule << "-m state --state #{new_resource.stateful.is_a?(Array) ? new_resource.stateful.join(',').upcase : new_resource.stateful.upcase} " if new_resource.stateful
firewall_rule << "-m comment --comment '#{new_resource.description}' "
firewall_rule << "-j #{TARGET[type]} "
firewall_rule << "--to-ports #{new_resource.redirect_port} " if type == 'redirect'
firewall_rule.strip!
end
firewall_rule
end
def rule_exists?(rule)
fail 'no rule supplied' unless rule
# match quotes generously
detect_rule = rule.gsub(/'/, "'*")
detect_rule = detect_rule.gsub(/"/, '"*')
match = shell_out!('firewall-cmd --direct --get-all-rules').stdout.lines.find do |line|
# Chef::Log.debug("matching: [#{detect_rule}] to [#{line.chomp.rstrip}]")
line =~ /#{detect_rule}/
end
match
rescue Mixlib::ShellOut::ShellCommandFailed
Chef::Log.debug("#{new_resource} check fails with: " + match.inspect)
Chef::Log.debug("#{new_resource} assuming #{rule} rule does not exist")
false
end
def dport_calc
new_resource.dest_port || new_resource.port
end
def ip_with_mask(ip)
if ip.include?('/')
ip
elsif ipv4_rule?
"#{ip}/32"
elsif ipv6_rule?
"#{ip}/128"
else
ip
end
end
end
end

View File

@@ -0,0 +1,231 @@
#
# Cookbook Name:: firewall
# Provider:: rule_iptables
#
# Copyright 2012, computerlyrik
#
# 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.
#
require 'poise'
class Chef
class Provider::FirewallRuleIptables < Provider
include Poise
include Chef::Mixin::ShellOut
include FirewallCookbook::Helpers
def action_allow
apply_rule(:allow)
end
def action_deny
apply_rule(:deny)
end
def action_reject
apply_rule(:reject)
end
def action_redirect
apply_rule(:redirect)
end
def action_masquerade
apply_rule(:masquerade)
end
def action_log
apply_rule(:log)
end
def action_remove
# TODO: specify which target to delete
# for now this will remove raw + all targeted lines
remove_rule(:allow)
remove_rule(:deny)
remove_rule(:reject)
remove_rule(:redirect)
remove_rule(:masquerade)
end
private
CHAIN = { :in => 'INPUT', :out => 'OUTPUT', :pre => 'PREROUTING', :post => 'POSTROUTING' } unless defined? CHAIN # , nil => "FORWARD"}
TARGET = { :allow => 'ACCEPT', :reject => 'REJECT', :deny => 'DROP', :masquerade => 'MASQUERADE', :redirect => 'REDIRECT', :log => 'LOG --log-prefix "iptables: " --log-level 7' } unless defined? TARGET
def apply_rule(type = nil)
firewall_commands = determine_iptables_commands
firewall_commands.each do |firewall_command|
ipv6 = (firewall_command == 'ip6tables') ? true : false
if new_resource.position
firewall_command << ' -I '
else
firewall_command << ' -A '
end
# TODO: implement logging for :connections :packets
firewall_rule = build_firewall_rule(type)
Chef::Log.debug("#{new_resource}: #{firewall_rule}")
if rule_exists?(firewall_rule, ipv6)
Chef::Log.info("#{new_resource} #{type} rule exists... won't apply")
else
cmdstr = firewall_command + firewall_rule
converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do
notifying_block do
shell_out!(cmdstr) # shell_out! is already logged
new_resource.updated_by_last_action(true)
end
end
end
end
end
def remove_rule(type = nil)
firewall_commands = determine_iptables_commands
firewall_commands.each do |firewall_command|
ipv6 = (firewall_command == 'ip6tables') ? true : false
# TODO: implement logging for :connections :packets
firewall_rule = build_firewall_rule(type)
Chef::Log.debug("#{new_resource}: #{firewall_rule}")
if rule_exists?(firewall_rule, ipv6)
cmdstr = firewall_command + firewall_rule
converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do
notifying_block do
shell_out!(cmdstr) # shell_out! is already logged
new_resource.updated_by_last_action(true)
end
end
else
Chef::Log.info("#{new_resource} #{type} rule does not exist... won't remove")
end
end
end
def ipv4_rule?
if (new_resource.source && IPAddr.new(new_resource.source).ipv4?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv4?)
true
else
false
end
end
def ipv6_rule?
if (new_resource.source && IPAddr.new(new_resource.source).ipv6?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv6?)
true
else
false
end
end
def determine_iptables_commands
if ipv4_rule?
commands = ['iptables']
elsif ipv6_rule?
commands = ['ip6tables']
else # no source or destination address, add rules for both ipv4 and ipv6
commands = %w(iptables ip6tables)
end
commands
end
def build_firewall_rule(type = nil)
if new_resource.raw
firewall_rule = new_resource.raw.strip
else
firewall_rule = ''
if new_resource.direction
firewall_rule << "#{CHAIN[new_resource.direction.to_sym]} "
else
firewall_rule << 'FORWARD '
end
firewall_rule << "#{new_resource.position} " if new_resource.position
if [:pre, :post].include?(new_resource.direction)
firewall_rule << '-t nat '
end
# Iptables order of prameters is important here see example output below:
# -A INPUT -s 1.2.3.4/32 -d 5.6.7.8/32 -i lo -p tcp -m tcp -m state --state NEW -m comment --comment "hello" -j DROP
firewall_rule << "-s #{ip_with_mask(new_resource.source)} " if new_resource.source && new_resource.source != '0.0.0.0/0'
firewall_rule << "-d #{new_resource.destination} " if new_resource.destination
firewall_rule << "-i #{new_resource.interface} " if new_resource.interface
firewall_rule << "-o #{new_resource.dest_interface} " if new_resource.dest_interface
firewall_rule << "-p #{new_resource.protocol} " if new_resource.protocol
firewall_rule << '-m tcp ' if new_resource.protocol.to_sym == :tcp
# using multiport here allows us to simplify our greps and rule building
firewall_rule << "-m multiport --sports #{port_to_s(new_resource.source_port)} " if new_resource.source_port
firewall_rule << "-m multiport --dports #{port_to_s(dport_calc)} " if dport_calc
firewall_rule << "-m state --state #{new_resource.stateful.is_a?(Array) ? new_resource.stateful.join(',').upcase : new_resource.stateful.upcase} " if new_resource.stateful
firewall_rule << "-m comment --comment \"#{new_resource.description}\" "
firewall_rule << "-j #{TARGET[type]} "
firewall_rule << "--to-ports #{new_resource.redirect_port} " if type == 'redirect'
firewall_rule.strip!
end
firewall_rule
end
def rule_exists?(rule, ipv6 = false)
fail 'no rule supplied' unless rule
if new_resource.position
detect_rule = rule.gsub(/#{CHAIN[new_resource.direction]}\s(\d+)/, '\1' + " -A #{CHAIN[new_resource.direction]}")
else
detect_rule = rule
end
# match quotes generously
detect_rule = detect_rule.gsub(/'/, "'*")
detect_rule = detect_rule.gsub(/"/, '"*')
line_number = 0
match = shell_out!(ipv6 ? 'ip6tables-save' : 'iptables-save').stdout.lines.find do |line|
next if line !~ /#{CHAIN[new_resource.direction]}/
next if line[0] != '-'
line_number += 1
line = "#{line_number} #{line}" if new_resource.position
# Chef::Log.debug("matching: [#{detect_rule}] to [#{line.chomp.rstrip}]")
line =~ /#{detect_rule}/
end
match
rescue Mixlib::ShellOut::ShellCommandFailed
Chef::Log.debug("#{new_resource} check fails with: " + match.inspect)
Chef::Log.debug("#{new_resource} assuming #{rule} rule does not exist")
false
end
def dport_calc
new_resource.dest_port || new_resource.port
end
def ip_with_mask(ip)
if ip.include?('/')
ip
elsif ipv4_rule?
"#{ip}/32"
elsif ipv6_rule?
"#{ip}/128"
else
ip
end
end
end
end

View File

@@ -0,0 +1,241 @@
#
# Author:: Seth Chisamore (<schisamo@opscode.com>)
# Cookbook Name:: firwall
# Resource:: rule
#
# Copyright:: 2011, Opscode, Inc.
#
# 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.
#
require 'poise'
class Chef
class Provider::FirewallRuleUfw < Provider
include Poise
include Chef::Mixin::ShellOut
include FirewallCookbook::Helpers
def action_allow
if rule_exists?
Chef::Log.info("#{new_resource.name} already allowed, skipping")
else
apply_rule(:allow)
end
end
def action_deny
if rule_exists?
Chef::Log.info("#{new_resource.name} already denied, skipping")
else
apply_rule(:deny)
end
end
def action_reject
if rule_exists?
Chef::Log.info("#{new_resource.name} already rejected, skipping")
else
apply_rule(:reject)
end
end
private
def apply_rule(type = nil) # rubocop:disable MethodLength
Chef::Log.info("#{new_resource.name} apply_rule #{type}")
# if we don't do this, we may see some bugs where traffic is opened on all ports to all hosts when only RELATED,ESTABLISHED was intended
if new_resource.stateful
msg = ''
msg << "firewall_rule[#{new_resource.name}] was asked to "
msg << "#{type} a stateful rule using #{new_resource.stateful} "
msg << 'but ufw does not support this kind of rule. Consider guarding by platform_family.'
fail msg
end
# some examples:
# ufw allow from 192.168.0.4 to any port 22
# ufw deny proto tcp from 10.0.0.0/8 to 192.168.0.1 port 25
# ufw insert 1 allow proto tcp from 0.0.0.0/0 to 192.168.0.1 port 25
ufw_command = ['ufw']
if new_resource.position
ufw_command << 'insert'
ufw_command << new_resource.position.to_s
end
ufw_command << type.to_s
ufw_command << rule.split
converge_by("firewall_rule[#{new_resource.name}] #{rule}") do
notifying_block do
# fail 'should be no actions'
shell_out!(*ufw_command.flatten)
shell_out!('ufw', 'status', 'verbose') # purely for the Chef::Log.debug output
new_resource.updated_by_last_action(true)
end
end
end
def rule
rule = ''
rule << rule_interface
rule << rule_logging
rule << rule_proto
rule << rule_dest_port
rule << rule_source_port
rule.strip
end
def rule_interface
rule = ''
rule << "#{new_resource.direction} " if new_resource.direction
if new_resource.interface
if new_resource.direction
rule << "on #{new_resource.interface} "
else
rule << "in on #{new_resource.interface} "
end
end
rule
end
def rule_proto
rule = ''
rule << "proto #{new_resource.protocol} " if new_resource.protocol
rule
end
def rule_dest_port
rule = ''
if new_resource.destination
rule << "to #{new_resource.destination} "
else
rule << 'to any '
end
rule << "port #{port_to_s(dport_calc)} " if dport_calc
rule
end
def rule_source_port
rule = ''
if new_resource.source
rule << "from #{new_resource.source} "
else
rule << 'from any '
end
if new_resource.source_port
rule << "port #{port_to_s(new_resource.source_port)} "
end
rule
end
def rule_logging
case new_resource.logging && new_resource.logging.to_sym
when :connections
'log '
when :packets
'log-all '
else
''
end
end
# TODO: currently only works when firewall is enabled
def rule_exists?
Chef::Log.info("#{new_resource.name} rule_exists?")
# To Action From
# -- ------ ----
# 22 ALLOW Anywhere
# 192.168.0.1 25/tcp DENY 10.0.0.0/8
# 22 ALLOW Anywhere
# 3309 on eth9 ALLOW Anywhere
# Anywhere ALLOW Anywhere
# 80 ALLOW Anywhere (log)
# 8080 DENY 192.168.1.0/24
# 1.2.3.5 5469/udp ALLOW 1.2.3.4 5469/udp
# 3308 ALLOW OUT Anywhere on eth8
to = rule_exists_to? # col 1
action = rule_exists_action? # col 2
from = rule_exists_from? # col 3
# full regex from columns
regex = rule_exists_regex?(to, action, from)
match = shell_out!('ufw', 'status').stdout.lines.find do |line|
# TODO: support IPv6
return false if line =~ /\(v6\)$/
line =~ regex
end
match
end
def rule_exists_to?
to = ''
to << rule_exists_dest?
proto = rule_exists_proto?
to << proto if proto
if to.empty?
to << "Anywhere\s"
else
to
end
end
def rule_exists_action?
action = new_resource.action
action = action.first if action.is_a?(Enumerable)
"#{Regexp.escape(action.to_s.upcase)}\s"
end
def rule_exists_from?
if new_resource.source && new_resource.source != '0.0.0.0/0'
Regexp.escape(new_resource.source)
elsif new_resource.source
Regexp.escape('Anywhere')
end
end
def rule_exists_dest?
if new_resource.destination
"#{Regexp.escape(new_resource.destination)}\s"
else
''
end
end
def rule_exists_regex?(to, action, from)
if to && new_resource.direction && new_resource.direction.to_sym == :out
/^#{to}.*#{action}OUT\s.*#{from}$/
elsif to
/^#{to}.*#{action}.*#{from}$/
end
end
def rule_exists_proto?
if new_resource.protocol && dport_calc
"#{Regexp.escape(port_to_s(dport_calc))}/#{Regexp.escape(new_resource.protocol)}\s "
elsif dport_calc
"#{Regexp.escape(port_to_s(dport_calc))}\s "
end
end
def dport_calc
new_resource.dest_port || new_resource.port
end
end
end

View File

@@ -0,0 +1,77 @@
#
# Author:: Seth Chisamore (<schisamo@opscode.com>)
# Cookbook Name:: firewall
# Resource:: default
#
# Copyright:: 2011, Opscode, Inc.
#
# 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.
#
require 'poise'
class Chef
class Provider::FirewallUfw < Provider
include Poise
include Chef::Mixin::ShellOut
def action_enable
converge_by('install ufw, template some defaults, and ufw enable') do
package 'ufw' do
action :nothing
end.run_action(:install) # need this now if running in a provider
template '/etc/default/ufw' do
action [:create]
owner 'root'
group 'root'
mode '0644'
source 'ufw/default.erb'
cookbook 'firewall'
action :nothing
end.run_action(:create) # need this now if running in a provider
# new_resource.subresources contains all the firewall rules
if active?
Chef::Log.debug("#{new_resource} already enabled.")
else
shell_out!('ufw', 'enable', :input => 'yes')
Chef::Log.info("#{new_resource} enabled")
if new_resource.log_level
shell_out!('ufw', 'logging', new_resource.log_level.to_s)
Chef::Log.info("#{new_resource} logging enabled at '#{new_resource.log_level}' level")
end
new_resource.updated_by_last_action(true)
end
end
end
def action_disable
if active?
shell_out!('ufw', 'disable')
Chef::Log.info("#{new_resource} disabled")
new_resource.updated_by_last_action(true)
else
Chef::Log.debug("#{new_resource} already disabled.")
end
end
private
def active?
@active ||= begin
cmd = shell_out!('ufw', 'status')
cmd.stdout =~ /^Status:\sactive/
end
end
end
end

View File

@@ -0,0 +1,10 @@
require 'poise'
class Chef
class Resource::Firewall < Resource
include Poise(:container => true)
actions(:enable, :disable, :flush, :save)
attribute(:log_level, :kind_of => [Symbol, String], :equal_to => [:low, :medium, :high, :full, 'low', 'medium', 'high', 'full'], :default => :low)
end
end

View File

@@ -0,0 +1,36 @@
require 'poise'
class Chef
class Resource::FirewallRule < Resource
include Poise(Chef::Resource::Firewall)
actions(:reject, :allow, :deny, :masquerade, :redirect, :log, :remove)
attribute(:protocol, :kind_of => [Symbol, String], :equal_to => [:udp, :tcp, :icmp, 'tcp', 'udp', 'icmp'], :default => :tcp)
attribute(:direction, :kind_of => [Symbol, String], :equal_to => [:in, :out, :pre, :post, 'in', 'out', 'pre', 'post'], :default => :in)
attribute(:logging, :kind_of => [Symbol, String], :equal_to => [:connections, :packets, 'connections', 'packets'])
attribute(:source, :callbacks => { 'must be a valid ip address' => ->(s) { valid_ip?(s) } })
attribute(:source_port, :kind_of => [Integer, Array, Range]) # source port
attribute(:interface, :kind_of => String)
attribute(:port, :kind_of => [Integer, Array, Range]) # shorthand for dest_port
attribute(:destination, :callbacks => { 'must be a valid ip address' => ->(s) { valid_ip?(s) } })
attribute(:dest_port, :kind_of => [Integer, Array, Range])
attribute(:dest_interface, :kind_of => String)
attribute(:position, :kind_of => Integer)
attribute(:stateful, :kind_of => [Symbol, String, Array])
attribute(:redirect_port, :kind_of => Integer)
attribute(:description, :kind_of => String, :name_attribute => true)
# for when you just want to pass a raw rule
attribute(:raw, :kind_of => String)
def self.valid_ip?(ip)
IPAddr.new(ip) ? true : false
rescue
false
end
end
end

View File

@@ -0,0 +1,22 @@
# provider mappings for Chef 11
# https://www.chef.io/blog/2015/02/10/chef-12-provider-resolver/
#########
# firewall
#########
Chef::Platform.set platform: :centos, version: '< 7.0', resource: :firewall, provider: Chef::Provider::FirewallIptables
Chef::Platform.set platform: :centos, version: '>= 7.0', resource: :firewall, provider: Chef::Provider::FirewallFirewalld
Chef::Platform.set platform: :redhat, version: '< 7.0', resource: :firewall, provider: Chef::Provider::FirewallIptables
Chef::Platform.set platform: :redhat, version: '>= 7.0', resource: :firewall, provider: Chef::Provider::FirewallFirewalld
Chef::Platform.set platform: :debian, resource: :firewall, provider: Chef::Provider::FirewallUfw
Chef::Platform.set platform: :ubuntu, resource: :firewall, provider: Chef::Provider::FirewallUfw
#########
# firewall_rule
#########
Chef::Platform.set platform: :centos, version: '< 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleIptables
Chef::Platform.set platform: :centos, version: '>= 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleFirewalld
Chef::Platform.set platform: :redhat, version: '< 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleIptables
Chef::Platform.set platform: :redhat, version: '>= 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleFirewalld
Chef::Platform.set platform: :debian, resource: :firewall_rule, provider: Chef::Provider::FirewallRuleUfw
Chef::Platform.set platform: :ubuntu, resource: :firewall_rule, provider: Chef::Provider::FirewallRuleUfw

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,29 @@
#
# Cookbook Name:: firewall
# Recipe:: default
#
# Copyright 2011, Opscode, Inc.
#
# 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.
#
firewall 'default' do
action :enable
end
firewall_rule 'allow world to ssh' do
port 22
source '0.0.0.0/0'
action [:allow]
only_if { node['firewall']['allow_ssh'] }
end

View File

@@ -0,0 +1,13 @@
# /etc/default/ufw
# This file is managed by Chef. Do not edit.
IPV6=<%= node['firewall']['ufw']['defaults']['ipv6'] %>
MANAGE_BUILTINS=<%= node['firewall']['ufw']['defaults']['manage_builtins'] %>
<% node['firewall']['ufw']['defaults']['policy'].each do |policy, value| -%>
<%= "DEFAULT_#{policy.upcase}_POLICY=\"#{value}\"" %>
<% end -%>
IPT_SYSCTL="<%= node['firewall']['ufw']['defaults']['ipt_sysctl'] %>"
IPT_MODULES="<%= node['firewall']['ufw']['defaults']['ipt_modules'] %>"