intial implementation of vxlan plugin for fuel

Change-Id: If2290ca2ea4e947a98f5818ab54ada30e247f42b
This commit is contained in:
sbartel 2015-06-30 17:36:33 +02:00
parent 1b583d1568
commit ccff8066a8
100 changed files with 10967 additions and 0 deletions

202
LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

100
README.md Normal file
View File

@ -0,0 +1,100 @@
VXLAN plugin for Fuel
===================
VXLAN plugin
---------------
Overview
--------
By default the openstack environment is configured with gre or vlan segmentation type. This plugin allows to create vxlan private network.
This repo contains all necessary files to build vxlan Fuel plugin.
Requirements
------------
| Requirement | Version/Comment |
|----------------------------------|---------------------------------------------------------|
| Mirantis Openstack compatibility | 6.0 |
|----------------------------------|---------------------------------------------------------|
Recommendations
---------------
None.
Limitations
-----------
None.
Installation Guide
==================
Vxlan plugin installation
----------------------------
1. Clone the fuel-plugin repo from: https://github.com/stackforge/fuel-plugin-vxlan.git
``git clone``
2. Install the Fuel Plugin Builder:
``pip install fuel-plugin-builder``
3. Build vxlan Fuel plugin:
``fpb --build fuel-plugin-vxlan/``
4. The vxlan-<x.x.x>.fp file will be created in the plugin folder (fuel-plugin-vxlan)
5. Move this file to the Fuel Master node with secure copy (scp):
``scp vxlan-<x.x.x>.fp root@:<the_Fuel_Master_node_IP address>:/tmp``
``cd /tmp``
6. Install the vxlan plugin:
``fuel plugins --install vxlan-<x.x.x>.fp``
6. Plugin is ready to use and can be enabled on the Settings tab of the Fuel web UI.
User Guide
==========
https plugin configuration
-----------------------------
1. Create a new environment with the Fuel UI wizard with gre segmentation type selected
2. Add a node with the "Compute" role.
3. Click on the settings tab of the Fuel web UI
4. Scroll down the page, select the "vxlan plugin" checkbox
Deployment details
------------------
Configure neutron/ml2plugin to use vxlan as default segmentation type
Configureboth controller and compute neutron/ml2plugin to create vxlan tunneling
Restart neutron services
Known issues
------------
None.
Release Notes
-------------
**1.0.0**
* Initial release of the plugin

View File

@ -0,0 +1,2 @@
$fuel_settings = parseyaml($astute_settings_yaml)
class {'vxlan::compute':}

View File

@ -0,0 +1,2 @@
$fuel_settings = parseyaml($astute_settings_yaml)
class {'vxlan::controller':}

View File

@ -0,0 +1,3 @@
fixtures:
symlinks:
"firewall": "#{source_dir}"

View File

@ -0,0 +1,9 @@
pkg/
Gemfile.lock
# TODO: Ignore this for now until we decide what to do with it
spec/fixtures/manifests/
.ruby-version
.rspec_system
.bundle
.vagrant
vendor/

View File

@ -0,0 +1,31 @@
---
default_set: 'centos-64-x64'
sets:
'centos-59-x64':
nodes:
"main.foo.vm":
prefab: 'centos-59-x64'
'centos-64-x64':
nodes:
"main.foo.vm":
prefab: 'centos-64-x64'
'fedora-18-x64':
nodes:
"main.foo.vm":
prefab: 'fedora-18-x64'
'debian-607-x64':
nodes:
"main.foo.vm":
prefab: 'debian-607-x64'
'debian-70rc1-x64':
nodes:
"main.foo.vm":
prefab: 'debian-70rc1-x64'
'ubuntu-server-10044-x64':
nodes:
"main.foo.vm":
prefab: 'ubuntu-server-10044-x64'
'ubuntu-server-12042-x64':
nodes:
"main.foo.vm":
prefab: 'ubuntu-server-12042-x64'

View File

@ -0,0 +1,29 @@
---
language: ruby
bundler_args: --without development
script: "bundle exec rake ci SPEC_OPTS='--format documentation'"
rvm:
- 1.8.7
- 1.9.3
- 2.0.0
env:
- PUPPET_GEM_VERSION="~> 2.7.0"
- PUPPET_GEM_VERSION="~> 3.0.0"
- PUPPET_GEM_VERSION="~> 3.1.0"
- PUPPET_GEM_VERSION="~> 3.2.0"
- PUPPET_GEM_VERSION="~> 3.4.0"
matrix:
fast_finish: true
exclude:
- rvm: 1.9.3
env: PUPPET_GEM_VERSION="~> 2.7.0"
- rvm: 2.0.0
env: PUPPET_GEM_VERSION="~> 2.7.0"
- rvm: 2.0.0
env: PUPPET_GEM_VERSION="~> 3.0.0"
- rvm: 2.0.0
env: PUPPET_GEM_VERSION="~> 3.1.0"
- rvm: 1.8.7
env: PUPPET_GEM_VERSION="~> 3.2.0"
notifications:
email: false

View File

@ -0,0 +1,390 @@
## 2014-03-04 Supported Release 1.0.2
###Summary
This is a supported release. This release removes a testing symlink that can
cause trouble on systems where /var is on a seperate filesystem from the
modulepath.
####Features
####Bugfixes
####Known Bugs
* For Oracle, the `owner` and `socket` parameters require a workaround to function. Please see the Limitations section of the README.
### Supported release - 2014-03-04 1.0.1
####Summary
An important bugfix was made to the offset calculation for unmanaged rules
to handle rules with 9000+ in the name.
####Features
####Bugfixes
- Offset calculations assumed unmanaged rules were numbered 9000+.
- Gracefully fail to manage ip6tables on iptables 1.3.x
####Known Bugs
* For Oracle, the `owner` and `socket` parameters require a workaround to function. Please see the Limitations section of the README.
---
### 1.0.0 - 2014-02-11
No changes, just renumbering to 1.0.0.
---
### 0.5.0 - 2014-02-10
##### Summary:
This is a bigger release that brings in "recent" connection limiting (think
"port knocking"), firewall chain purging on a per-chain/per-table basis, and
support for a few other use cases. This release also fixes a major bug which
could cause modifications to the wrong rules when unmanaged rules are present.
##### New Features:
* Add "recent" limiting via parameters `rdest`, `reap`, `recent`, `rhitcount`,
`rname`, `rseconds`, `rsource`, and `rttl`
* Add negation support for source and destination
* Add per-chain/table purging support to `firewallchain`
* IPv4 specific
* Add random port forwarding support
* Add ipsec policy matching via `ipsec_dir` and `ipsec_policy`
* IPv6 specific
* Add support for hop limiting via `hop_limit` parameter
* Add fragmentation matchers via `ishasmorefrags`, `islastfrag`, and `isfirstfrag`
* Add support for conntrack stateful firewall matching via `ctstate`
##### Bugfixes:
- Boolean fixups allowing false values
- Better detection of unmanaged rules
- Fix multiport rule detection
- Fix sport/dport rule detection
- Make INPUT, OUTPUT, and FORWARD not autorequired for firewall chain filter
- Allow INPUT with the nat table
- Fix `src_range` & `dst_range` order detection
- Documentation clarifications
- Fixes to spec tests
---------------------------------------
### 0.4.2 - 2013-09-10
Another attempt to fix the packaging issue. We think we understand exactly
what is failing and this should work properly for the first time.
---------------------------------------
### 0.4.1 - 2013-08-09
Bugfix release to fix a packaging issue that may have caused puppet module
install commands to fail.
---------------------------------------
### 0.4.0 - 2013-07-11
This release adds support for address type, src/dest ip ranges, and adds
additional testing and bugfixes.
#### Features
* Add `src_type` and `dst_type` attributes (Nick Stenning)
* Add `src_range` and `dst_range` attributes (Lei Zhang)
* Add SL and SLC operatingsystems as supported (Steve Traylen)
#### Bugfixes
* Fix parser for bursts other than 5 (Chris Rutter)
* Fix parser for -f in --comment (Georg Koester)
* Add doc headers to class files (Dan Carley)
* Fix lint warnings/errors (Wolf Noble)
---------------------------------------
### 0.3.1 - 2013/6/10
This minor release provides some bugfixes and additional tests.
#### Changes
* Update tests for rspec-system-puppet 2 (Ken Barber)
* Update rspec-system tests for rspec-system-puppet 1.5 (Ken Barber)
* Ensure all services have 'hasstatus => true' for Puppet 2.6 (Ken Barber)
* Accept pre-existing rule with invalid name (Joe Julian)
* Swap log_prefix and log_level order to match the way it's saved (Ken Barber)
* Fix log test to replicate bug #182 (Ken Barber)
* Split argments while maintaining quoted strings (Joe Julian)
* Add more log param tests (Ken Barber)
* Add extra tests for logging parameters (Ken Barber)
* Clarify OS support (Ken Barber)
---------------------------------------
### 0.3.0 - 2013/4/25
This release introduces support for Arch Linux and extends support for Fedora 15 and up. There are also lots of bugs fixed and improved testing to prevent regressions.
##### Changes
* Fix error reporting for insane hostnames (Tomas Doran)
* Support systemd on Fedora 15 and up (Eduardo Gutierrez)
* Move examples to docs (Ken Barber)
* Add support for Arch Linux platform (Ingmar Steen)
* Add match rule for fragments (Georg Koester)
* Fix boolean rules being recognized as changed (Georg Koester)
* Same rules now get deleted (Anastasis Andronidis)
* Socket params test (Ken Barber)
* Ensure parameter can disable firewall (Marc Tardif)
---------------------------------------
### 0.2.1 - 2012/3/13
This maintenance release introduces the new README layout, and fixes a bug with iptables_persistent_version.
##### Changes
* (GH-139) Throw away STDERR from dpkg-query in Fact
* Update README to be consistent with module documentation template
* Fix failing spec tests due to dpkg change in iptables_persistent_version
---------------------------------------
### 0.2.0 - 2012/3/3
This release introduces automatic persistence, removing the need for the previous manual dependency requirement for persistent the running rules to the OS persistence file.
Previously you would have required the following in your site.pp (or some other global location):
# Always persist firewall rules
exec { 'persist-firewall':
command => $operatingsystem ? {
'debian' => '/sbin/iptables-save > /etc/iptables/rules.v4',
/(RedHat|CentOS)/ => '/sbin/iptables-save > /etc/sysconfig/iptables',
},
refreshonly => true,
}
Firewall {
notify => Exec['persist-firewall'],
before => Class['my_fw::post'],
require => Class['my_fw::pre'],
}
Firewallchain {
notify => Exec['persist-firewall'],
}
resources { "firewall":
purge => true
}
You only need:
class { 'firewall': }
Firewall {
before => Class['my_fw::post'],
require => Class['my_fw::pre'],
}
To install pre-requisites and to create dependencies on your pre & post rules. Consult the README for more information.
##### Changes
* Firewall class manifests (Dan Carley)
* Firewall and firewallchain persistence (Dan Carley)
* (GH-134) Autorequire iptables related packages (Dan Carley)
* Typo in #persist_iptables OS normalisation (Dan Carley)
* Tests for #persist_iptables (Dan Carley)
* (GH-129) Replace errant return in autoreq block (Dan Carley)
---------------------------------------
### 0.1.1 - 2012/2/28
This release primarily fixes changing parameters in 3.x
##### Changes
* (GH-128) Change method_missing usage to define_method for 3.x compatibility
* Update travis.yml gem specifications to actually test 2.6
* Change source in Gemfile to use a specific URL for Ruby 2.0.0 compatibility
---------------------------------------
### 0.1.0 - 2012/2/24
This release is somewhat belated, so no summary as there are far too many changes this time around. Hopefully we won't fall this far behind again :-).
##### Changes
* Add support for MARK target and set-mark property (Johan Huysmans)
* Fix broken call to super for ruby-1.9.2 in munge (Ken Barber)
* simple fix of the error message for allowed values of the jump property (Daniel Black)
* Adding OSPF(v3) protocol to puppetlabs-firewall (Arnoud Vermeer)
* Display multi-value: port, sport, dport and state command seperated (Daniel Black)
* Require jump=>LOG for log params (Daniel Black)
* Reject and document icmp => "any" (Dan Carley)
* add firewallchain type and iptables_chain provider (Daniel Black)
* Various fixes for firewallchain resource (Ken Barber)
* Modify firewallchain name to be chain:table:protocol (Ken Barber)
* Fix allvalidchain iteration (Ken Barber)
* Firewall autorequire Firewallchains (Dan Carley)
* Tests and docstring for chain autorequire (Dan Carley)
* Fix README so setup instructions actually work (Ken Barber)
* Support vlan interfaces (interface containing ".") (Johan Huysmans)
* Add tests for VLAN support for iniface/outiface (Ken Barber)
* Add the table when deleting rules (Johan Huysmans)
* Fix tests since we are now prefixing -t)
* Changed 'jump' to 'action', commands to lower case (Jason Short)
* Support interface names containing "+" (Simon Deziel)
* Fix for when iptables-save spews out "FATAL" errors (Sharif Nassar)
* Fix for incorrect limit command arguments for ip6tables provider (Michael Hsu)
* Document Util::Firewall.host_to_ip (Dan Carley)
* Nullify addresses with zero prefixlen (Dan Carley)
* Add support for --tcp-flags (Thomas Vander Stichele)
* Make tcp_flags support a feature (Ken Barber)
* OUTPUT is a valid chain for the mangle table (Adam Gibbins)
* Enable travis-ci support (Ken Barber)
* Convert an existing test to CIDR (Dan Carley)
* Normalise iptables-save to CIDR (Dan Carley)
* be clearer about what distributions we support (Ken Barber)
* add gre protocol to list of acceptable protocols (Jason Hancock)
* Added pkttype property (Ashley Penney)
* Fix mark to not repeat rules with iptables 1.4.1+ (Sharif Nassar)
* Stub iptables_version for now so tests run on non-Linux hosts (Ken Barber)
* Stub iptables facts for set_mark tests (Dan Carley)
* Update formatting of README to meet Puppet Labs best practices (Will Hopper)
* Support for ICMP6 type code resolutions (Dan Carley)
* Insert order hash included chains from different tables (Ken Barber)
* rspec 2.11 compatibility (Jonathan Boyett)
* Add missing class declaration in README (sfozz)
* array_matching is contraindicated (Sharif Nassar)
* Convert port Fixnum into strings (Sharif Nassar)
* Update test framework to the modern age (Ken Barber)
* working with ip6tables support (wuwx)
* Remove gemfile.lock and add to gitignore (William Van Hevelingen)
* Update travis and gemfile to be like stdlib travis files (William Van Hevelingen)
* Add support for -m socket option (Ken Barber)
* Add support for single --sport and --dport parsing (Ken Barber)
* Fix tests for Ruby 1.9.3 from 3e13bf3 (Dan Carley)
* Mock Resolv.getaddress in #host_to_ip (Dan Carley)
* Update docs for source and dest - they are not arrays (Ken Barber)
---------------------------------------
### 0.0.4 - 2011/12/05
This release adds two new parameters, 'uid' and 'gid'. As a part of the owner module, these params allow you to specify a uid, username, gid, or group got a match:
firewall { '497 match uid':
port => '123',
proto => 'mangle',
chain => 'OUTPUT',
action => 'drop'
uid => '123'
}
This release also adds value munging for the 'log_level', 'source', and 'destination' parameters. The 'source' and 'destination' now support hostnames:
firewall { '498 accept from puppetlabs.com':
port => '123',
proto => 'tcp',
source => 'puppetlabs.com',
action => 'accept'
}
The 'log_level' parameter now supports using log level names, such as 'warn', 'debug', and 'panic':
firewall { '499 logging':
port => '123',
proto => 'udp',
log_level => 'debug',
action => 'drop'
}
Additional changes include iptables and ip6tables version facts, general whitespace cleanup, and adding additional unit tests.
##### Changes
* (#10957) add iptables_version and ip6tables_version facts
* (#11093) Improve log_level property so it converts names to numbers
* (#10723) Munge hostnames and IPs to IPs with CIDR
* (#10718) Add owner-match support
* (#10997) Add fixtures for ipencap
* (#11034) Whitespace cleanup
* (#10690) add port property support to ip6tables
---------------------------------------
### 0.0.3 - 2011/11/12
This release introduces a new parameter 'port' which allows you to set both
source and destination ports for a match:
firewall { "500 allow NTP requests":
port => "123",
proto => "udp",
action => "accept",
}
We also have the limit parameter finally working:
firewall { "500 limit HTTP requests":
dport => 80,
proto => tcp,
limit => "60/sec",
burst => 30,
action => accept,
}
State ordering has been fixed now, and more characters are allowed in the
namevar:
* Alphabetical
* Numbers
* Punctuation
* Whitespace
##### Changes
* (#10693) Ensure -m limit is added for iptables when using 'limit' param
* (#10690) Create new port property
* (#10700) allow additional characters in comment string
* (#9082) Sort iptables --state option values internally to keep it consistent across runs
* (#10324) Remove extraneous whitespace from iptables rule line in spec tests
---------------------------------------
### 0.0.2 - 2011/10/26
This is largely a maintanence and cleanup release, but includes the ability to
specify ranges of ports in the sport/dport parameter:
firewall { "500 allow port range":
dport => ["3000-3030","5000-5050"],
sport => ["1024-65535"],
action => "accept",
}
##### Changes
* (#10295) Work around bug #4248 whereby the puppet/util paths are not being loaded correctly on the puppetmaster
* (#10002) Change to dport and sport to handle ranges, and fix handling of name to name to port
* (#10263) Fix tests on Puppet 2.6.x
* (#10163) Cleanup some of the inline documentation and README file to align with general forge usage
---------------------------------------
### 0.0.1 - 2011/10/18
Initial release.
##### Changes
* (#9362) Create action property and perform transformation for accept, drop, reject value for iptables jump parameter
* (#10088) Provide a customised version of CONTRIBUTING.md
* (#10026) Re-arrange provider and type spec files to align with Puppet
* (#10026) Add aliases for test,specs,tests to Rakefile and provide -T as default
* (#9439) fix parsing and deleting existing rules
* (#9583) Fix provider detection for gentoo and unsupported linuxes for the iptables provider
* (#9576) Stub provider so it works properly outside of Linux
* (#9576) Align spec framework with Puppet core
* and lots of other earlier development tasks ...

View File

@ -0,0 +1,292 @@
Checklist (and a short version for the impatient)
=================================================
* Commits:
- Make commits of logical units.
- Check for unnecessary whitespace with "git diff --check" before
committing.
- Commit using Unix line endings (check the settings around "crlf" in
git-config(1)).
- Do not check in commented out code or unneeded files.
- The first line of the commit message should be a short
description (50 characters is the soft limit, excluding ticket
number(s)), and should skip the full stop.
- Associated the Redmine ticket in the message. The first line
should include the ticket number in the form "(#XXXX) Rest of
message".
- The body should provide a meaningful commit message, which:
- uses the imperative, present tense: "change", not "changed" or
"changes".
- includes motivation for the change, and contrasts its
implementation with the previous behavior.
- Make sure that you have tests for the bug you are fixing, or
feature you are adding.
- Make sure the test suite passes after your commit (rake spec unit).
* Submission:
* Pre-requisites:
- Make sure you have a [Redmine account](http://projects.puppetlabs.com)
- Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign)
- [Create a ticket](http://projects.puppetlabs.com/projects/modules/issues/new), or [watch the ticket](http://projects.puppetlabs.com/projects/modules/issues) you are patching for.
* Preferred method:
- Fork the repository on GitHub.
- Push your changes to a topic branch in your fork of the
repository. (the format ticket/1234-short_description_of_change is
usually preferred for this project).
- Submit a pull request to the repository in the puppetlabs
organization.
The long version
================
0. Decide what to base your work on.
In general, you should always base your work on the oldest
branch that your change is relevant to.
- A bug fix should be based on the current stable series. If the
bug is not present in the current stable release, then base it on
`master`.
- A new feature should be based on `master`.
- Security fixes should be based on the current maintenance series
(that is, the previous stable series). If the security issue
was not present in the maintenance series, then it should be
based on the current stable series if it was introduced there,
or on `master` if it is not yet present in a stable release.
1. Make separate commits for logically separate changes.
Please break your commits down into logically consistent units
which include new or changed tests relevent to the rest of the
change. The goal of doing this is to make the diff easier to
read for whoever is reviewing your code. In general, the easier
your diff is to read, the more likely someone will be happy to
review it and get it into the code base.
If you're going to refactor a piece of code, please do so as a
separate commit from your feature or bug fix changes.
We also really appreciate changes that include tests to make
sure the bug isn't re-introduced, and that the feature isn't
accidentally broken.
Describe the technical detail of the change(s). If your
description starts to get too long, that's a good sign that you
probably need to split up your commit into more finely grained
pieces.
Commits which plainly describe the things which help
reviewers check the patch and future developers understand the
code are much more likely to be merged in with a minimum of
bike-shedding or requested changes. Ideally, the commit message
would include information, and be in a form suitable for
inclusion in the release notes for the version of Puppet that
includes them.
Please also check that you are not introducing any trailing
whitespaces or other "whitespace errors". You can do this by
running "git diff --check" on your changes before you commit.
2. Sign the Contributor License Agreement
Before we can accept your changes, we do need a signed Puppet
Labs Contributor License Agreement (CLA).
You can access the CLA via the
[Contributor License Agreement link](https://projects.puppetlabs.com/contributor_licenses/sign)
in the top menu bar of our Redmine instance. Once you've signed
the CLA, a badge will show up next to your name on the
[Puppet Project Overview Page](http://projects.puppetlabs.com/projects/modules?jump=welcome),
and your name will be listed under "Contributor License Signers"
section.
If you have any questions about the CLA, please feel free to
contact Puppet Labs via email at cla-submissions@puppetlabs.com.
3. Sending your patches
We accept multiple ways of submitting your changes for
inclusion. They are listed below in order of preference.
Please keep in mind that any method that involves sending email
to the mailing list directly requires you to be subscribed to
the mailing list, and that your first post to the list will be
held in a moderation queue.
* GitHub Pull Requests
To submit your changes via a GitHub pull request, we _highly_
recommend that you have them on a topic branch, instead of
directly on "master" or one of the release, or RC branches.
It makes things much easier to keep track of, especially if
you decide to work on another thing before your first change
is merged in.
GitHub has some pretty good
[general documentation](http://help.github.com/) on using
their site. They also have documentation on
[creating pull requests](http://help.github.com/send-pull-requests/).
In general, after pushing your topic branch up to your
repository on GitHub, you'll switch to the branch in the
GitHub UI and click "Pull Request" towards the top of the page
in order to open a pull request.
You'll want to make sure that you have the appropriate
destination branch in the repository under the puppetlabs
organization. This should be the same branch that you based
your changes off of.
* Other pull requests
If you already have a publicly accessible version of the
repository hosted elsewhere, and don't wish to or cannot use
GitHub, you can submit your change by requesting that we pull
the changes from your repository by sending an email to the
puppet-dev Google Groups mailing list.
`git-request-pull(1)` provides a handy way to generate the text
for the email requesting that we pull your changes (and does
some helpful sanity checks in the process).
* Mailing patches to the mailing list
If neither of the previous methods works for you, then you can
also mail the patches inline to the puppet-dev Google Group
using either `rake mail_patches`, or by using
`git-format-patch(1)`, and `git-send-email(1)` directly.
`rake mail_patches` handles setting the appropriate flags to
`git-format-patch(1)` and `git-send-email(1)` for you, but
doesn't allow adding any commentary between the '---', and the
diffstat in the resulting email. It also requires that you
have created your topic branch in the form
`<type>/<parent>/<name>`.
If you decide to use `git-format-patch(1)` and
`git-send-email(1)` directly, please be sure to use the
following flags for `git-format-patch(1)`: -C -M -s -n
--subject-prefix='PATCH/puppet'
* Attaching patches to Redmine
As a method of last resort you can also directly attach the
output of `git-format-patch(1)`, or `git-diff(1)` to a Redmine
ticket.
If you are generating the diff outside of Git, please be sure
to generate a unified diff.
4. Update the related Redmine ticket.
If there's a Redmine ticket associated with the change you
submitted, then you should update the ticket to include the
location of your branch, and change the status to "In Topic
Branch Pending Merge", along with any other commentary you may
wish to make.
How to track the status of your change after it's been submitted
================================================================
Shortly after opening a pull request on GitHub, there should be an
automatic message sent to the puppet-dev Google Groups mailing list
notifying people of this. This notification is used to let the Puppet
development community know about your requested change to give them a
chance to review, test, and comment on the change(s).
If you submitted your change via manually sending a pull request or
mailing the patches, then we keep track of these using
[patchwork](https://patchwork.puppetlabs.com). When code is merged
into the project it is automatically removed from patchwork, and the
Redmine ticket is manually updated with the commit SHA1. In addition,
the ticket status must be updated by the person who merges the topic
branch to a status of "Merged - Pending Release"
We do our best to comment on or merge submitted changes within a week.
However, if there hasn't been any commentary on the pull request or
mailed patches, and it hasn't been merged in after a week, then feel
free to ask for an update by replying on the mailing list to the
automatic notification or mailed patches. It probably wasn't
intentional, and probably just slipped through the cracks.
Additional Resources
====================
* [Getting additional help](http://projects.puppetlabs.com/projects/puppet/wiki/Getting_Help)
* [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests)
* [Bug tracker (Redmine)](http://projects.puppetlabs.com/projects/modules)
* [Patchwork](https://patchwork.puppetlabs.com)
* [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign)
* [General GitHub documentation](http://help.github.com/)
* [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
If you have commit access to the repository
===========================================
Even if you have commit access to the repository, you'll still need to
go through the process above, and have someone else review and merge
in your changes. The rule is that all changes must be reviewed by a
developer on the project (that didn't write the code) to ensure that
all changes go through a code review process.
Having someone other than the author of the topic branch recorded as
performing the merge is the record that they performed the code
review.
* Merging topic branches
When merging code from a topic branch into the integration branch
(Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge
commit. You can accomplish this by always providing the `--no-ff`
flag to `git merge`.
git merge --no-ff --log ticket/1234-fix-something-broken
The reason for always forcing this merge commit is that it
provides a consistent way to look up what changes & commits were
in a topic branch, whether that topic branch had one, or 500
commits. For example, if the merge commit had an abbreviated
SHA-1 of `coffeebad`, then you could use the following `git log`
invocation to show you which commits it brought in:
git log coffeebad^1..coffeebad^2
The following would show you which changes were made on the topic
branch:
git diff coffeebad^1...coffeebad^2
Because we _always_ merge the topic branch into the integration
branch the first parent (`^1`) of a merge commit will be the most
recent commit on the integration branch from just before we merged
in the topic, and the second parent (`^2`) will always be the most
recent commit that was made in the topic branch. This also serves
as the record of who performed the code review, as mentioned
above.

View File

@ -0,0 +1,257 @@
## puppetlabs-firewall changelog
Release notes for puppetlabs-firewall module.
---------------------------------------
#### 0.2.1 - 2012/3/13
This maintenance release introduces the new README layout, and fixes a bug with iptables_persistent_version.
##### Changes
* (GH-139) Throw away STDERR from dpkg-query in Fact
* Update README to be consistent with module documentation template
* Fix failing spec tests due to dpkg change in iptables_persistent_version
---------------------------------------
#### 0.2.0 - 2012/3/3
This release introduces automatic persistence, removing the need for the previous manual dependency requirement for persistent the running rules to the OS persistence file.
Previously you would have required the following in your site.pp (or some other global location):
# Always persist firewall rules
exec { 'persist-firewall':
command => $operatingsystem ? {
'debian' => '/sbin/iptables-save > /etc/iptables/rules.v4',
/(RedHat|CentOS)/ => '/sbin/iptables-save > /etc/sysconfig/iptables',
},
refreshonly => true,
}
Firewall {
notify => Exec['persist-firewall'],
before => Class['my_fw::post'],
require => Class['my_fw::pre'],
}
Firewallchain {
notify => Exec['persist-firewall'],
}
resources { "firewall":
purge => true
}
You only need:
class { 'firewall': }
Firewall {
before => Class['my_fw::post'],
require => Class['my_fw::pre'],
}
To install pre-requisites and to create dependencies on your pre & post rules. Consult the README for more information.
##### Changes
* Firewall class manifests (Dan Carley)
* Firewall and firewallchain persistence (Dan Carley)
* (GH-134) Autorequire iptables related packages (Dan Carley)
* Typo in #persist_iptables OS normalisation (Dan Carley)
* Tests for #persist_iptables (Dan Carley)
* (GH-129) Replace errant return in autoreq block (Dan Carley)
---------------------------------------
#### 0.1.1 - 2012/2/28
This release primarily fixes changing parameters in 3.x
##### Changes
* (GH-128) Change method_missing usage to define_method for 3.x compatibility
* Update travis.yml gem specifications to actually test 2.6
* Change source in Gemfile to use a specific URL for Ruby 2.0.0 compatibility
---------------------------------------
#### 0.1.0 - 2012/2/24
This release is somewhat belated, so no summary as there are far too many changes this time around. Hopefully we won't fall this far behind again :-).
##### Changes
* Add support for MARK target and set-mark property (Johan Huysmans)
* Fix broken call to super for ruby-1.9.2 in munge (Ken Barber)
* simple fix of the error message for allowed values of the jump property (Daniel Black)
* Adding OSPF(v3) protocol to puppetlabs-firewall (Arnoud Vermeer)
* Display multi-value: port, sport, dport and state command seperated (Daniel Black)
* Require jump=>LOG for log params (Daniel Black)
* Reject and document icmp => "any" (Dan Carley)
* add firewallchain type and iptables_chain provider (Daniel Black)
* Various fixes for firewallchain resource (Ken Barber)
* Modify firewallchain name to be chain:table:protocol (Ken Barber)
* Fix allvalidchain iteration (Ken Barber)
* Firewall autorequire Firewallchains (Dan Carley)
* Tests and docstring for chain autorequire (Dan Carley)
* Fix README so setup instructions actually work (Ken Barber)
* Support vlan interfaces (interface containing ".") (Johan Huysmans)
* Add tests for VLAN support for iniface/outiface (Ken Barber)
* Add the table when deleting rules (Johan Huysmans)
* Fix tests since we are now prefixing -t)
* Changed 'jump' to 'action', commands to lower case (Jason Short)
* Support interface names containing "+" (Simon Deziel)
* Fix for when iptables-save spews out "FATAL" errors (Sharif Nassar)
* Fix for incorrect limit command arguments for ip6tables provider (Michael Hsu)
* Document Util::Firewall.host_to_ip (Dan Carley)
* Nullify addresses with zero prefixlen (Dan Carley)
* Add support for --tcp-flags (Thomas Vander Stichele)
* Make tcp_flags support a feature (Ken Barber)
* OUTPUT is a valid chain for the mangle table (Adam Gibbins)
* Enable travis-ci support (Ken Barber)
* Convert an existing test to CIDR (Dan Carley)
* Normalise iptables-save to CIDR (Dan Carley)
* be clearer about what distributions we support (Ken Barber)
* add gre protocol to list of acceptable protocols (Jason Hancock)
* Added pkttype property (Ashley Penney)
* Fix mark to not repeat rules with iptables 1.4.1+ (Sharif Nassar)
* Stub iptables_version for now so tests run on non-Linux hosts (Ken Barber)
* Stub iptables facts for set_mark tests (Dan Carley)
* Update formatting of README to meet Puppet Labs best practices (Will Hopper)
* Support for ICMP6 type code resolutions (Dan Carley)
* Insert order hash included chains from different tables (Ken Barber)
* rspec 2.11 compatibility (Jonathan Boyett)
* Add missing class declaration in README (sfozz)
* array_matching is contraindicated (Sharif Nassar)
* Convert port Fixnum into strings (Sharif Nassar)
* Update test framework to the modern age (Ken Barber)
* working with ip6tables support (wuwx)
* Remove gemfile.lock and add to gitignore (William Van Hevelingen)
* Update travis and gemfile to be like stdlib travis files (William Van Hevelingen)
* Add support for -m socket option (Ken Barber)
* Add support for single --sport and --dport parsing (Ken Barber)
* Fix tests for Ruby 1.9.3 from 3e13bf3 (Dan Carley)
* Mock Resolv.getaddress in #host_to_ip (Dan Carley)
* Update docs for source and dest - they are not arrays (Ken Barber)
---------------------------------------
#### 0.0.4 - 2011/12/05
This release adds two new parameters, 'uid' and 'gid'. As a part of the owner module, these params allow you to specify a uid, username, gid, or group got a match:
firewall { '497 match uid':
port => '123',
proto => 'mangle',
chain => 'OUTPUT',
action => 'drop'
uid => '123'
}
This release also adds value munging for the 'log_level', 'source', and 'destination' parameters. The 'source' and 'destination' now support hostnames:
firewall { '498 accept from puppetlabs.com':
port => '123',
proto => 'tcp',
source => 'puppetlabs.com',
action => 'accept'
}
The 'log_level' parameter now supports using log level names, such as 'warn', 'debug', and 'panic':
firewall { '499 logging':
port => '123',
proto => 'udp',
log_level => 'debug',
action => 'drop'
}
Additional changes include iptables and ip6tables version facts, general whitespace cleanup, and adding additional unit tests.
##### Changes
* (#10957) add iptables_version and ip6tables_version facts
* (#11093) Improve log_level property so it converts names to numbers
* (#10723) Munge hostnames and IPs to IPs with CIDR
* (#10718) Add owner-match support
* (#10997) Add fixtures for ipencap
* (#11034) Whitespace cleanup
* (#10690) add port property support to ip6tables
---------------------------------------
#### 0.0.3 - 2011/11/12
This release introduces a new parameter 'port' which allows you to set both
source and destination ports for a match:
firewall { "500 allow NTP requests":
port => "123",
proto => "udp",
action => "accept",
}
We also have the limit parameter finally working:
firewall { "500 limit HTTP requests":
dport => 80,
proto => tcp,
limit => "60/sec",
burst => 30,
action => accept,
}
State ordering has been fixed now, and more characters are allowed in the
namevar:
* Alphabetical
* Numbers
* Punctuation
* Whitespace
##### Changes
* (#10693) Ensure -m limit is added for iptables when using 'limit' param
* (#10690) Create new port property
* (#10700) allow additional characters in comment string
* (#9082) Sort iptables --state option values internally to keep it consistent across runs
* (#10324) Remove extraneous whitespace from iptables rule line in spec tests
---------------------------------------
#### 0.0.2 - 2011/10/26
This is largely a maintanence and cleanup release, but includes the ability to
specify ranges of ports in the sport/dport parameter:
firewall { "500 allow port range":
dport => ["3000-3030","5000-5050"],
sport => ["1024-65535"],
action => "accept",
}
##### Changes
* (#10295) Work around bug #4248 whereby the puppet/util paths are not being loaded correctly on the puppetmaster
* (#10002) Change to dport and sport to handle ranges, and fix handling of name to name to port
* (#10263) Fix tests on Puppet 2.6.x
* (#10163) Cleanup some of the inline documentation and README file to align with general forge usage
---------------------------------------
#### 0.0.1 - 2011/10/18
Initial release.
##### Changes
* (#9362) Create action property and perform transformation for accept, drop, reject value for iptables jump parameter
* (#10088) Provide a customised version of CONTRIBUTING.md
* (#10026) Re-arrange provider and type spec files to align with Puppet
* (#10026) Add aliases for test,specs,tests to Rakefile and provide -T as default
* (#9439) fix parsing and deleting existing rules
* (#9583) Fix provider detection for gentoo and unsupported linuxes for the iptables provider
* (#9576) Stub provider so it works properly outside of Linux
* (#9576) Align spec framework with Puppet core
* and lots of other earlier development tasks ...

View File

@ -0,0 +1,18 @@
source ENV['GEM_SOURCE'] || "https://rubygems.org"
group :development, :test do
gem 'puppetlabs_spec_helper', :require => false
gem 'rspec-puppet', :require => false
gem 'serverspec', :require => false
gem 'beaker-rspec', :require => false
gem 'puppet-lint', :require => false
gem 'pry', :require => false
end
if puppetversion = ENV['PUPPET_GEM_VERSION']
gem 'puppet', puppetversion, :require => false
else
gem 'puppet', :require => false
end
# vim:ft=ruby

View File

@ -0,0 +1,25 @@
Puppet Firewall Module - Puppet module for managing Firewalls
Copyright (C) 2011-2013 Puppet Labs, Inc.
Copyright (C) 2011 Jonathan Boyett
Copyright (C) 2011 Media Temple, Inc.
Some of the iptables code was taken from puppet-iptables which was:
Copyright (C) 2011 Bob.sh Limited
Copyright (C) 2008 Camptocamp Association
Copyright (C) 2007 Dmitri Priimak
Puppet Labs can be contacted at: info@puppetlabs.com
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,8 @@
name 'puppetlabs-firewall'
version '1.0.2'
source 'git://github.com/puppetlabs/puppetlabs-firewall.git'
author 'puppetlabs'
license 'ASL 2.0'
summary 'Firewall Module'
description 'Manages Firewalls such as iptables'
project_page 'http://forge.puppetlabs.com/puppetlabs/firewall'

View File

@ -0,0 +1,429 @@
#firewall
[![Build Status](https://travis-ci.org/puppetlabs/puppetlabs-firewall.png?branch=master)](https://travis-ci.org/puppetlabs/puppetlabs-firewall)
####Table of Contents
1. [Overview - What is the Firewall module?](#overview)
2. [Module Description - What does the module do?](#module-description)
3. [Setup - The basics of getting started with Firewall](#setup)
* [What Firewall affects](#what-firewall-affects)
* [Setup Requirements](#setup-requirements)
* [Beginning with Firewall](#beginning-with-firewall)
* [Upgrading](#upgrading)
4. [Usage - Configuration and customization options](#usage)
* [Default rules - Setting up general configurations for all firewalls](#default-rules)
* [Application-specific rules - Options for configuring and managing firewalls across applications](#application-specific-rules)
* [Other Rules](#other-rules)
5. [Reference - An under-the-hood peek at what the module is doing](#reference)
6. [Limitations - OS compatibility, etc.](#limitations)
7. [Development - Guide for contributing to the module](#development)
* [Tests - Testing your configuration](#tests)
##Overview
The Firewall module lets you manage firewall rules with Puppet.
##Module Description
PuppetLabs' Firewall introduces the resource `firewall`, which is used to manage and configure firewall rules from within the Puppet DSL. This module offers support for iptables, ip6tables, and ebtables.
The module also introduces the resource `firewallchain`, which allows you to manage chains or firewall lists. At the moment, only iptables and ip6tables chains are supported.
##Setup
###What Firewall affects:
* every node running a firewall
* system's firewall settings
* connection settings for managed nodes
* unmanaged resources (get purged)
* site.pp
###Setup Requirements
Firewall uses Ruby-based providers, so you must have [pluginsync enabled](http://docs.puppetlabs.com/guides/plugins_in_modules.html#enabling-pluginsync).
###Beginning with Firewall
To begin, you need to provide some initial top-scope configuration to ensure your firewall configurations are ordered properly and you do not lock yourself out of your box or lose any configuration.
Persistence of rules between reboots is handled automatically, although there are known issues with ip6tables on older Debian/Ubuntu, as well as known issues with ebtables.
In your `site.pp` (or some similarly top-scope file), set up a metatype to purge unmanaged firewall resources. This will clear any existing rules and make sure that only rules defined in Puppet exist on the machine.
resources { "firewall":
purge => true
}
Next, set up the default parameters for all of the firewall rules you will be establishing later. These defaults will ensure that the pre and post classes (you will be setting up in just a moment) are run in the correct order to avoid locking you out of your box during the first puppet run.
Firewall {
before => Class['my_fw::post'],
require => Class['my_fw::pre'],
}
You also need to declare the `my_fw::pre` & `my_fw::post` classes so that dependencies are satisfied. This can be achieved using an External Node Classifier or the following
class { ['my_fw::pre', 'my_fw::post']: }
Finally, you should include the `firewall` class to ensure the correct packages are installed.
class { 'firewall': }
Now to create the `my_fw::pre` and `my_fw::post` classes. Firewall acts on your running firewall, making immediate changes as the catalog executes. Defining default pre and post rules allows you provide global defaults for your hosts before and after any custom rules; it is also required to avoid locking yourself out of your own boxes when Puppet runs. This approach employs a whitelist setup, so you can define what rules you want and everything else is ignored rather than removed.
The `pre` class should be located in `my_fw/manifests/pre.pp` and should contain any default rules to be applied first.
class my_fw::pre {
Firewall {
require => undef,
}
# Default firewall rules
firewall { '000 accept all icmp':
proto => 'icmp',
action => 'accept',
}->
firewall { '001 accept all to lo interface':
proto => 'all',
iniface => 'lo',
action => 'accept',
}->
firewall { '002 accept related established rules':
proto => 'all',
ctstate => ['RELATED', 'ESTABLISHED'],
action => 'accept',
}
}
The rules in `pre` should allow basic networking (such as ICMP and TCP), as well as ensure that existing connections are not closed.
The `post` class should be located in `my_fw/manifests/post.pp` and include any default rules to be applied last.
class my_fw::post {
firewall { '999 drop all':
proto => 'all',
action => 'drop',
before => undef,
}
}
To put it all together: the `require` parameter in `Firewall {}` ensures `my_fw::pre` is run before any other rules and the `before` parameter ensures `my_fw::post` is run after any other rules. So the run order is:
* run the rules in `my_fw::pre`
* run your rules (defined in code)
* run the rules in `my_fw::post`
###Upgrading
####Upgrading from version 0.2.0 and newer
Upgrade the module with the puppet module tool as normal:
puppet module upgrade puppetlabs/firewall
####Upgrading from version 0.1.1 and older
Start by upgrading the module using the puppet module tool:
puppet module upgrade puppetlabs/firewall
Previously, you would have required the following in your `site.pp` (or some other global location):
# Always persist firewall rules
exec { 'persist-firewall':
command => $operatingsystem ? {
'debian' => '/sbin/iptables-save > /etc/iptables/rules.v4',
/(RedHat|CentOS)/ => '/sbin/iptables-save > /etc/sysconfig/iptables',
},
refreshonly => true,
}
Firewall {
notify => Exec['persist-firewall'],
before => Class['my_fw::post'],
require => Class['my_fw::pre'],
}
Firewallchain {
notify => Exec['persist-firewall'],
}
resources { "firewall":
purge => true
}
With the latest version, we now have in-built persistence, so this is no longer needed. However, you will still need some basic setup to define pre & post rules.
resources { "firewall":
purge => true
}
Firewall {
before => Class['my_fw::post'],
require => Class['my_fw::pre'],
}
class { ['my_fw::pre', 'my_fw::post']: }
class { 'firewall': }
Consult the the documentation below for more details around the classes `my_fw::pre` and `my_fw::post`.
##Usage
There are two kinds of firewall rules you can use with Firewall: default rules and application-specific rules. Default rules apply to general firewall settings, whereas application-specific rules manage firewall settings of a specific application, node, etc.
All rules employ a numbering system in the resource's title that is used for ordering. When titling your rules, make sure you prefix the rule with a number.
000 this runs first
999 this runs last
###Default rules
You can place default rules in either `my_fw::pre` or `my_fw::post`, depending on when you would like them to run. Rules placed in the `pre` class will run first, rules in the `post` class, last.
Depending on the provider, the title of the rule can be stored using the comment feature of the underlying firewall subsystem. Values can match `/^\d+[[:alpha:][:digit:][:punct:][:space:]]+$/`.
####Examples of default rules
Basic accept ICMP request example:
firewall { "000 accept all icmp requests":
proto => "icmp",
action => "accept",
}
Drop all:
firewall { "999 drop all other requests":
action => "drop",
}
###Application-specific rules
Puppet doesn't care where you define rules, and this means that you can place
your firewall resources as close to the applications and services that you
manage as you wish. If you use the [roles and profiles
pattern](https://puppetlabs.com/learn/roles-profiles-introduction) then it
would make sense to create your firewall rules in the profiles, so that they
remain close to the services managed by the profile.
An example of this might be:
```puppet
class profile::apache {
include apache
apache::vhost { 'mysite': ensure => present }
firewall { '100 allow http and https access':
port => [80, 443],
proto => tcp,
action => accept,
}
}
```
However, if you're not using that pattern then you can place them directly into
the individual module that manages a service, such as:
```puppet
class apache {
firewall { '100 allow http and https access':
port => [80, 443],
proto => tcp,
action => accept,
}
# ... the rest of your code ...
}
```
This means if someone includes either the profile:
```puppet
include profile::apache
```
Or the module, if you're not using roles and profiles:
```puppet
include ::apache
```
Then they would automatically get appropriate firewall rules.
###Other rules
You can also apply firewall rules to specific nodes. Usually, you will want to put the firewall rule in another class and apply that class to a node. But you can apply a rule to a node.
node 'foo.bar.com' {
firewall { '111 open port 111':
dport => 111
}
}
You can also do more complex things with the `firewall` resource. Here we are doing some NAT configuration.
firewall { '100 snat for network foo2':
chain => 'POSTROUTING',
jump => 'MASQUERADE',
proto => 'all',
outiface => "eth0",
source => '10.1.2.0/24',
table => 'nat',
}
In the below example, we are creating a new chain and forwarding any port 5000 access to it.
firewall { '100 forward to MY_CHAIN':
chain => 'INPUT',
jump => 'MY_CHAIN',
}
# The namevar here is in the format chain_name:table:protocol
firewallchain { 'MY_CHAIN:filter:IPv4':
ensure => present,
}
firewall { '100 my rule':
chain => 'MY_CHAIN',
action => 'accept',
proto => 'tcp',
dport => 5000,
}
###Additional Information
You can access the inline documentation:
puppet describe firewall
Or
puppet doc -r type
(and search for firewall)
##Reference
Classes:
* [firewall](#class-firewall)
Types:
* [firewall](#type-firewall)
* [firewallchain](#type-firewallchain)
Facts:
* [ip6tables_version](#fact-ip6tablesversion)
* [iptables_version](#fact-iptablesversion)
* [iptables_persistent_version](#fact-iptablespersistentversion)
###Class: firewall
This class is provided to do the basic setup tasks required for using the firewall resources.
At the moment this takes care of:
* iptables-persistent package installation
You should include the class for nodes that need to use the resources in this module. For example
class { 'firewall': }
####`ensure`
Indicates the state of `iptables` on your system, allowing you to disable `iptables` if desired.
Can either be `running` or `stopped`. Default to `running`.
###Type: firewall
This type provides the capability to manage firewall rules within puppet.
For more documentation on the type, access the 'Types' tab on the Puppet Labs Forge:
<http://forge.puppetlabs.com/puppetlabs/firewall#types>
###Type:: firewallchain
This type provides the capability to manage rule chains for firewalls.
For more documentation on the type, access the 'Types' tab on the Puppet Labs Forge:
<http://forge.puppetlabs.com/puppetlabs/firewall#types>
###Fact: ip6tables_version
The module provides a Facter fact that can be used to determine what the default version of ip6tables is for your operating system/distribution.
###Fact: iptables_version
The module provides a Facter fact that can be used to determine what the default version of iptables is for your operating system/distribution.
###Fact: iptables_persistent_version
Retrieves the version of iptables-persistent from your OS. This is a Debian/Ubuntu specific fact.
##Limitations
###SLES
The `socket` parameter is not supported on SLES. In this release it will cause
the catalog to fail with iptables failures, rather than correctly warn you that
the features are unusable.
###Oracle Enterprise Linux
The `socket` and `owner` parameters are unsupported on Oracle Enterprise Linux
when the "Unbreakable" kernel is used. These may function correctly when using
the stock RedHat kernel instead. Declaring either of these parameters on an
unsupported system will result in iptable rules failing to apply.
###Other
Bugs can be reported using Github Issues:
<http://github.com/puppetlabs/puppetlabs-firewall/issues>
##Development
Puppet Labs modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We cant access the huge number of platforms and myriad of hardware, software, and deployment configurations that Puppet is intended to serve.
We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things.
You can read the complete module contribution guide [on the Puppet Labs wiki.](http://projects.puppetlabs.com/projects/module-site/wiki/Module_contributing)
For this particular module, please also read CONTRIBUTING.md before contributing.
Currently we support:
* iptables
* ip6tables
* ebtables (chains only)
But plans are to support lots of other firewall implementations:
* FreeBSD (ipf)
* Mac OS X (ipfw)
* OpenBSD (pf)
* Cisco (ASA and basic access lists)
If you have knowledge in these technologies, know how to code, and wish to contribute to this project, we would welcome the help.
###Testing
Make sure you have:
* rake
* bundler
Install the necessary gems:
bundle install
And run the tests from the root of the source code:
rake test
If you have a copy of Vagrant 1.1.0 you can also run the system tests:
RSPEC_SET=debian-606-x64 rake spec:system
RSPEC_SET=centos-58-x64 rake spec:system
*Note:* system testing is fairly alpha at this point, your mileage may vary.

View File

@ -0,0 +1,14 @@
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint/tasks/puppet-lint'
PuppetLint.configuration.ignore_paths = ['vendor/**/*.pp']
task :default do
sh %{rake -T}
end
desc 'Run reasonably quick tests for CI'
task :ci => [
:lint,
:spec,
]

View File

@ -0,0 +1,11 @@
Facter.add(:ip6tables_version) do
confine :kernel => :linux
setcode do
version = Facter::Util::Resolution.exec('ip6tables --version')
if version
version.match(/\d+\.\d+\.\d+/).to_s
else
nil
end
end
end

View File

@ -0,0 +1,15 @@
Facter.add(:iptables_persistent_version) do
confine :operatingsystem => %w{Debian Ubuntu}
setcode do
# Throw away STDERR because dpkg >= 1.16.7 will make some noise if the
# package isn't currently installed.
cmd = "dpkg-query -Wf '${Version}' iptables-persistent 2>/dev/null"
version = Facter::Util::Resolution.exec(cmd)
if version.nil? or !version.match(/\d+\.\d+/)
nil
else
version
end
end
end

View File

@ -0,0 +1,11 @@
Facter.add(:iptables_version) do
confine :kernel => :linux
setcode do
version = Facter::Util::Resolution.exec('iptables --version')
if version
version.match(/\d+\.\d+\.\d+/).to_s
else
nil
end
end
end

View File

@ -0,0 +1,34 @@
class Puppet::Provider::Firewall < Puppet::Provider
# Prefetch our rule list. This is ran once every time before any other
# action (besides initialization of each object).
def self.prefetch(resources)
debug("[prefetch(resources)]")
instances.each do |prov|
if resource = resources[prov.name] || resources[prov.name.downcase]
resource.provider = prov
end
end
end
# Look up the current status. This allows us to conventiently look up
# existing status with properties[:foo].
def properties
if @property_hash.empty?
@property_hash = query || {:ensure => :absent}
@property_hash[:ensure] = :absent if @property_hash.empty?
end
@property_hash.dup
end
# Pull the current state of the list from the full list. We're
# getting some double entendre here....
def query
self.class.instances.each do |instance|
if instance.name == self.name or instance.name.downcase == self.name
return instance.properties
end
end
nil
end
end

View File

@ -0,0 +1,131 @@
Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source => :iptables do
@doc = "Ip6tables type provider"
has_feature :iptables
has_feature :hop_limiting
has_feature :rate_limiting
has_feature :recent_limiting
has_feature :snat
has_feature :dnat
has_feature :interface_match
has_feature :icmp_match
has_feature :owner
has_feature :state_match
has_feature :reject_type
has_feature :log_level
has_feature :log_prefix
has_feature :mark
has_feature :tcp_flags
has_feature :pkttype
has_feature :ishasmorefrags
has_feature :islastfrag
has_feature :isfirstfrag
optional_commands({
:ip6tables => 'ip6tables',
:ip6tables_save => 'ip6tables-save',
})
def initialize(*args)
if Facter.fact('ip6tables_version').value.match /1\.3\.\d/
raise ArgumentError, 'The ip6tables provider is not supported on version 1.3 of iptables'
else
super
end
end
def self.iptables(*args)
ip6tables(*args)
end
def self.iptables_save(*args)
ip6tables_save(*args)
end
@protocol = "IPv6"
@resource_map = {
:burst => "--limit-burst",
:ctstate => "-m conntrack --ctstate",
:destination => "-d",
:dport => "-m multiport --dports",
:gid => "-m owner --gid-owner",
:icmp => "-m icmp6 --icmpv6-type",
:iniface => "-i",
:jump => "-j",
:hop_limit => "-m hl --hl-eq",
:limit => "-m limit --limit",
:log_level => "--log-level",
:log_prefix => "--log-prefix",
:name => "-m comment --comment",
:outiface => "-o",
:port => '-m multiport --ports',
:proto => "-p",
:rdest => "--rdest",
:reap => "--reap",
:recent => "-m recent",
:reject => "--reject-with",
:rhitcount => "--hitcount",
:rname => "--name",
:rseconds => "--seconds",
:rsource => "--rsource",
:rttl => "--rttl",
:source => "-s",
:state => "-m state --state",
:sport => "-m multiport --sports",
:table => "-t",
:todest => "--to-destination",
:toports => "--to-ports",
:tosource => "--to-source",
:uid => "-m owner --uid-owner",
:pkttype => "-m pkttype --pkt-type",
:ishasmorefrags => "-m frag --fragid 0 --fragmore",
:islastfrag => "-m frag --fragid 0 --fraglast",
:isfirstfrag => "-m frag --fragid 0 --fragfirst",
}
# These are known booleans that do not take a value, but we want to munge
# to true if they exist.
@known_booleans = [:ishasmorefrags, :islastfrag, :isfirstfrag, :rsource, :rdest, :reap, :rttl]
# Create property methods dynamically
(@resource_map.keys << :chain << :table << :action).each do |property|
if @known_booleans.include?(property) then
# The boolean properties default to '' which should be read as false
define_method "#{property}" do
@property_hash[property] = :false if @property_hash[property] == nil
@property_hash[property.to_sym]
end
else
define_method "#{property}" do
@property_hash[property.to_sym]
end
end
if property == :chain
define_method "#{property}=" do |value|
if @property_hash[:chain] != value
raise ArgumentError, "Modifying the chain for existing rules is not supported."
end
end
else
define_method "#{property}=" do |value|
@property_hash[:needs_change] = true
end
end
end
# This is the order of resources as they appear in iptables-save output,
# we need it to properly parse and apply rules, if the order of resource
# changes between puppet runs, the changed rules will be re-applied again.
# This order can be determined by going through iptables source code or just tweaking and trying manually
# (Note: on my CentOS 6.4 ip6tables-save returns -m frag on the place
# I put it when calling the command. So compability with manual changes
# not provided with current parser [georg.koester])
@resource_list = [:table, :source, :destination, :iniface, :outiface,
:proto, :ishasmorefrags, :islastfrag, :isfirstfrag, :gid, :uid, :sport, :dport,
:port, :pkttype, :name, :state, :ctstate, :icmp, :hop_limit, :limit, :burst,
:recent, :rseconds, :reap, :rhitcount, :rttl, :rname, :rsource, :rdest,
:jump, :todest, :tosource, :toports, :log_level, :log_prefix, :reject]
end

View File

@ -0,0 +1,499 @@
require 'puppet/provider/firewall'
require 'digest/md5'
Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Firewall do
include Puppet::Util::Firewall
@doc = "Iptables type provider"
has_feature :iptables
has_feature :rate_limiting
has_feature :recent_limiting
has_feature :snat
has_feature :dnat
has_feature :interface_match
has_feature :icmp_match
has_feature :owner
has_feature :state_match
has_feature :reject_type
has_feature :log_level
has_feature :log_prefix
has_feature :mark
has_feature :tcp_flags
has_feature :pkttype
has_feature :isfragment
has_feature :socket
has_feature :address_type
has_feature :iprange
has_feature :ipsec_dir
has_feature :ipsec_policy
has_feature :mac
optional_commands({
:iptables => 'iptables',
:iptables_save => 'iptables-save',
})
defaultfor :kernel => :linux
iptables_version = Facter.fact('iptables_version').value
if (iptables_version and Puppet::Util::Package.versioncmp(iptables_version, '1.4.1') < 0)
mark_flag = '--set-mark'
else
mark_flag = '--set-xmark'
end
@protocol = "IPv4"
@resource_map = {
:burst => "--limit-burst",
:ctstate => "-m conntrack --ctstate",
:destination => "-d",
:dst_type => "-m addrtype --dst-type",
:dst_range => "-m iprange --dst-range",
:dport => ["-m multiport --dports", "--dport"],
:gid => "-m owner --gid-owner",
:icmp => "-m icmp --icmp-type",
:iniface => "-i",
:jump => "-j",
:limit => "-m limit --limit",
:log_level => "--log-level",
:log_prefix => "--log-prefix",
:name => "-m comment --comment",
:outiface => "-o",
:port => '-m multiport --ports',
:proto => "-p",
:random => "--random",
:rdest => "--rdest",
:reap => "--reap",
:recent => "-m recent",
:reject => "--reject-with",
:rhitcount => "--hitcount",
:rname => "--name",
:rseconds => "--seconds",
:rsource => "--rsource",
:rttl => "--rttl",
:set_mark => mark_flag,
:socket => "-m socket",
:source => "-s",
:src_type => "-m addrtype --src-type",
:src_range => "-m iprange --src-range",
:sport => ["-m multiport --sports", "--sport"],
:state => "-m state --state",
:table => "-t",
:tcp_flags => "-m tcp --tcp-flags",
:todest => "--to-destination",
:toports => "--to-ports",
:tosource => "--to-source",
:uid => "-m owner --uid-owner",
:pkttype => "-m pkttype --pkt-type",
:isfragment => "-f",
:ipsec_dir => "-m policy --dir",
:ipsec_policy => "--pol",
:mac_source => "-m mac --mac-source",
:mac_destination => "-m mac --mac-destination",
}
# These are known booleans that do not take a value, but we want to munge
# to true if they exist.
@known_booleans = [
:isfragment,
:random,
:rdest,
:reap,
:rsource,
:rttl,
:socket
]
# Create property methods dynamically
(@resource_map.keys << :chain << :table << :action).each do |property|
if @known_booleans.include?(property) then
# The boolean properties default to '' which should be read as false
define_method "#{property}" do
@property_hash[property] = :false if @property_hash[property] == nil
@property_hash[property.to_sym]
end
else
define_method "#{property}" do
@property_hash[property.to_sym]
end
end
if property == :chain
define_method "#{property}=" do |value|
if @property_hash[:chain] != value
raise ArgumentError, "Modifying the chain for existing rules is not supported."
end
end
else
define_method "#{property}=" do |value|
@property_hash[:needs_change] = true
end
end
end
# This is the order of resources as they appear in iptables-save output,
# we need it to properly parse and apply rules, if the order of resource
# changes between puppet runs, the changed rules will be re-applied again.
# This order can be determined by going through iptables source code or just tweaking and trying manually
@resource_list = [
:table, :source, :destination, :iniface, :outiface, :proto, :isfragment,
:src_range, :dst_range, :tcp_flags, :gid, :uid, :sport, :dport, :port,
:dst_type, :src_type, :socket, :pkttype, :name, :ipsec_dir, :ipsec_policy,
:state, :ctstate, :icmp, :limit, :burst, :recent, :rseconds, :reap,
:rhitcount, :rttl, :rname, :rsource, :rdest, :jump, :todest, :tosource,
:toports, :random, :log_prefix, :log_level, :reject, :set_mark, :mac_source, :mac_destination
]
def insert
debug 'Inserting rule %s' % resource[:name]
iptables insert_args
end
def update
debug 'Updating rule %s' % resource[:name]
iptables update_args
end
def delete
debug 'Deleting rule %s' % resource[:name]
iptables delete_args
end
def exists?
properties[:ensure] != :absent
end
# Flush the property hash once done.
def flush
debug("[flush]")
if @property_hash.delete(:needs_change)
notice("Properties changed - updating rule")
update
end
persist_iptables(self.class.instance_variable_get(:@protocol))
@property_hash.clear
end
def self.instances
debug "[instances]"
table = nil
rules = []
counter = 1
# String#lines would be nice, but we need to support Ruby 1.8.5
iptables_save.split("\n").each do |line|
unless line =~ /^\#\s+|^\:\S+|^COMMIT|^FATAL/
if line =~ /^\*/
table = line.sub(/\*/, "")
else
if hash = rule_to_hash(line, table, counter)
rules << new(hash)
counter += 1
end
end
end
end
rules
end
def self.rule_to_hash(line, table, counter)
hash = {}
keys = []
values = line.dup
####################
# PRE-PARSE CLUDGING
####################
# --tcp-flags takes two values; we cheat by adding " around it
# so it behaves like --comment
values = values.sub(/--tcp-flags (\S*) (\S*)/, '--tcp-flags "\1 \2"')
# we do a similar thing for negated address masks (source and destination).
values = values.sub(/(-\S+) (!)\s?(\S*)/,'\1 "\2 \3"')
# the actual rule will have the ! mark before the option.
values = values.sub(/(!)\s*(-\S+)\s*(\S*)/, '\2 "\1 \3"')
# The match extension for tcp & udp are optional and throws off the @resource_map.
values = values.sub(/-m (tcp|udp) (--(s|d)port|-m multiport)/, '\2')
# Trick the system for booleans
@known_booleans.each do |bool|
# append "true" because all params are expected to have values
if bool == :isfragment then
# -f requires special matching:
# only replace those -f that are not followed by an l to
# distinguish between -f and the '-f' inside of --tcp-flags.
values = values.sub(/-f(?!l)(?=.*--comment)/, '-f true')
else
values = values.sub(/#{@resource_map[bool]}/, "#{@resource_map[bool]} true")
end
end
############
# Populate parser_list with used value, in the correct order
############
map_index={}
@resource_map.each_pair do |map_k,map_v|
[map_v].flatten.each do |v|
ind=values.index(/\s#{v}/)
next unless ind
map_index[map_k]=ind
end
end
# Generate parser_list based on the index of the found option
parser_list=[]
map_index.sort_by{|k,v| v}.each{|mapi| parser_list << mapi.first }
############
# MAIN PARSE
############
# Here we iterate across our values to generate an array of keys
parser_list.reverse.each do |k|
resource_map_key = @resource_map[k]
[resource_map_key].flatten.each do |opt|
if values.slice!(/\s#{opt}/)
keys << k
break
end
end
end
# Manually remove chain
values.slice!('-A')
keys << :chain
# Here we generate the main hash
keys.zip(values.scan(/"[^"]*"|\S+/).reverse) { |f, v| hash[f] = v.gsub(/"/, '') }
#####################
# POST PARSE CLUDGING
#####################
# Normalise all rules to CIDR notation.
[:source, :destination].each do |prop|
next if hash[prop].nil?
m = hash[prop].match(/(!?)\s?(.*)/)
# skip this line if parsing was incorrect
return unless m
neg = "! " if m[1] == "!"
hash[prop] = "#{neg}#{Puppet::Util::IPCidr.new(m[2]).cidr}"
end
[:dport, :sport, :port, :state, :ctstate].each do |prop|
hash[prop] = hash[prop].split(',') if ! hash[prop].nil?
end
# Convert booleans removing the previous cludge we did
@known_booleans.each do |bool|
if hash[bool] != nil then
if hash[bool] != "true" then
raise "Parser error: #{bool} was meant to be a boolean but received value: #{hash[bool]}."
end
end
end
# Our type prefers hyphens over colons for ranges so ...
# Iterate across all ports replacing colons with hyphens so that ranges match
# the types expectations.
[:dport, :sport, :port].each do |prop|
next unless hash[prop]
hash[prop] = hash[prop].collect do |elem|
elem.gsub(/:/,'-')
end
end
# States should always be sorted. This ensures that the output from
# iptables-save and user supplied resources is consistent.
hash[:state] = hash[:state].sort unless hash[:state].nil?
hash[:ctstate] = hash[:ctstate].sort unless hash[:ctstate].nil?
# This forces all existing, commentless rules or rules with invalid comments to be moved
# to the bottom of the stack.
# Puppet-firewall requires that all rules have comments (resource names) and match this
# regex and will fail if a rule in iptables does not have a comment. We get around this
# by appending a high level
if ! hash[:name]
num = 9000 + counter
hash[:name] = "#{num} #{Digest::MD5.hexdigest(line)}"
elsif not /^\d+[[:alpha:][:digit:][:punct:][:space:]]+$/ =~ hash[:name]
num = 9000 + counter
hash[:name] = "#{num} #{/([[:alpha:][:digit:][:punct:][:space:]]+)/.match(hash[:name])[1]}"
end
# Iptables defaults to log_level '4', so it is omitted from the output of iptables-save.
# If the :jump value is LOG and you don't have a log-level set, we assume it to be '4'.
if hash[:jump] == 'LOG' && ! hash[:log_level]
hash[:log_level] = '4'
end
# Iptables defaults to burst '5', so it is ommitted from the output of iptables-save.
# If the :limit value is set and you don't have a burst set, we assume it to be '5'.
if hash[:limit] && ! hash[:burst]
hash[:burst] = '5'
end
hash[:line] = line
hash[:provider] = self.name.to_s
hash[:table] = table
hash[:ensure] = :present
# Munge some vars here ...
# Proto should equal 'all' if undefined
hash[:proto] = "all" if !hash.include?(:proto)
# If the jump parameter is set to one of: ACCEPT, REJECT or DROP then
# we should set the action parameter instead.
if ['ACCEPT','REJECT','DROP'].include?(hash[:jump]) then
hash[:action] = hash[:jump].downcase
hash.delete(:jump)
end
hash
end
def insert_args
args = []
args << ["-I", resource[:chain], insert_order]
args << general_args
args
end
def update_args
args = []
args << ["-R", resource[:chain], insert_order]
args << general_args
args
end
def delete_args
# Split into arguments
line = properties[:line].gsub(/\-A/, '-D').split(/\s(?=(?:[^"]|"[^"]*")*$)/).map{|v| v.gsub(/"/, '')}
line.unshift("-t", properties[:table])
end
# This method takes the resource, and attempts to generate the command line
# arguments for iptables.
def general_args
debug "Current resource: %s" % resource.class
args = []
resource_list = self.class.instance_variable_get('@resource_list')
resource_map = self.class.instance_variable_get('@resource_map')
known_booleans = self.class.instance_variable_get('@known_booleans')
resource_list.each do |res|
resource_value = nil
if (resource[res]) then
resource_value = resource[res]
# If socket is true then do not add the value as -m socket is standalone
if known_booleans.include?(res) then
if resource[res] == :true then
resource_value = nil
else
# If the property is not :true then we don't want to add the value
# to the args list
next
end
end
elsif res == :jump and resource[:action] then
# In this case, we are substituting jump for action
resource_value = resource[:action].to_s.upcase
else
next
end
args << [resource_map[res]].flatten.first.split(' ')
# On negations, the '!' has to be before the option (eg: "! -d 1.2.3.4")
if resource_value.is_a?(String) and resource_value.sub!(/^!\s*/, '') then
# we do this after adding the 'dash' argument because of ones like "-m multiport --dports", where we want it before the "--dports" but after "-m multiport".
# so we insert before whatever the last argument is
args.insert(-2, '!')
end
# For sport and dport, convert hyphens to colons since the type
# expects hyphens for ranges of ports.
if [:sport, :dport, :port].include?(res) then
resource_value = resource_value.collect do |elem|
elem.gsub(/-/, ':')
end
end
# our tcp_flags takes a single string with comma lists separated
# by space
# --tcp-flags expects two arguments
if res == :tcp_flags
one, two = resource_value.split(' ')
args << one
args << two
elsif resource_value.is_a?(Array)
args << resource_value.join(',')
elsif !resource_value.nil?
args << resource_value
end
end
args
end
def insert_order
debug("[insert_order]")
rules = []
# Find list of current rules based on chain and table
self.class.instances.each do |rule|
if rule.chain == resource[:chain].to_s and rule.table == resource[:table].to_s
rules << rule.name
end
end
# No rules at all? Just bail now.
return 1 if rules.empty?
# Add our rule to the end of the array of known rules
my_rule = resource[:name].to_s
rules << my_rule
unmanaged_rule_regex = /^9[0-9]{3}\s[a-f0-9]{32}$/
# Find if this is a new rule or an existing rule, then find how many
# unmanaged rules preceed it.
if rules.length == rules.uniq.length
# This is a new rule so find its ordered location.
new_rule_location = rules.sort.uniq.index(my_rule)
if new_rule_location == 0
# The rule will be the first rule in the chain because nothing came
# before it.
offset_rule = rules[0]
else
# This rule will come after other managed rules, so find the rule
# immediately preceeding it.
offset_rule = rules.sort.uniq[new_rule_location - 1]
end
else
# This is a pre-existing rule, so find the offset from the original
# ordering.
offset_rule = my_rule
end
# Count how many unmanaged rules are ahead of the target rule so we know
# how much to add to the insert order
unnamed_offset = rules[0..rules.index(offset_rule)].inject(0) do |sum,rule|
# This regex matches the names given to unmanaged rules (a number
# 9000-9999 followed by an MD5 hash).
sum + (rule.match(unmanaged_rule_regex) ? 1 : 0)
end
# We want our rule to come before unmanaged rules if it's not a 9-rule
if offset_rule.match(unmanaged_rule_regex) and ! my_rule.match(/^9/)
unnamed_offset -= 1
end
# Insert our new or updated rule in the correct order of named rules, but
# offset for unnamed rules.
rules.reject{|r|r.match(unmanaged_rule_regex)}.sort.index(my_rule) + 1 + unnamed_offset
end
end

View File

@ -0,0 +1,178 @@
Puppet::Type.type(:firewallchain).provide :iptables_chain do
include Puppet::Util::Firewall
@doc = "Iptables chain provider"
has_feature :iptables_chain
has_feature :policy
optional_commands({
:iptables => 'iptables',
:iptables_save => 'iptables-save',
:ip6tables => 'ip6tables',
:ip6tables_save => 'ip6tables-save',
:ebtables => 'ebtables',
:ebtables_save => 'ebtables-save',
})
defaultfor :kernel => :linux
# chain name is greedy so we anchor from the end.
# [\d+:\d+] doesn't exist on ebtables
Mapping = {
:IPv4 => {
:tables => method(:iptables),
:save => method(:iptables_save),
:re => /^:(.+)\s(\S+)\s\[\d+:\d+\]$/,
},
:IPv6 => {
:tables => method(:ip6tables),
:save => method(:ip6tables_save),
:re => /^:(.+)\s(\S+)\s\[\d+:\d+\]$/,
},
:ethernet => {
:tables => method(:ebtables),
:save => method(:ebtables_save),
:re => /^:(.+)\s(\S+)$/,
}
}
InternalChains = /^(PREROUTING|POSTROUTING|BROUTING|INPUT|FORWARD|OUTPUT)$/
Tables = 'nat|mangle|filter|raw|rawpost|broute'
Nameformat = /^(.+):(#{Tables}):(IP(v[46])?|ethernet)$/
def create
allvalidchains do |t, chain, table, protocol|
if chain =~ InternalChains
# can't create internal chains
warning "Attempting to create internal chain #{@resource[:name]}"
end
if properties[:ensure] == protocol
debug "Skipping Inserting chain #{chain} on table #{table} (#{protocol}) already exists"
else
debug "Inserting chain #{chain} on table #{table} (#{protocol}) using #{t}"
t.call ['-t',table,'-N',chain]
unless @resource[:policy].nil?
t.call ['-t',table,'-P',chain,@resource[:policy].to_s.upcase]
end
end
end
end
def destroy
allvalidchains do |t, chain, table|
if chain =~ InternalChains
# can't delete internal chains
warning "Attempting to destroy internal chain #{@resource[:name]}"
end
debug "Deleting chain #{chain} on table #{table}"
t.call ['-t',table,'-X',chain]
end
end
def exists?
allvalidchains do |t, chain|
if chain =~ InternalChains
# If the chain isn't present, it's likely because the module isn't loaded.
# If this is true, then we fall into 2 cases
# 1) It'll be loaded on demand
# 2) It won't be loaded on demand, and we throw an error
# This is the intended behavior as it's not the provider's job to load kernel modules
# So we pretend it exists...
return true
end
end
properties[:ensure] == :present
end
def policy=(value)
return if value == :empty
allvalidchains do |t, chain, table|
p = ['-t',table,'-P',chain,value.to_s.upcase]
debug "[set policy] #{t} #{p}"
t.call p
end
end
def policy
debug "[get policy] #{@resource[:name]} =#{@property_hash[:policy].to_s.downcase}"
return @property_hash[:policy].to_s.downcase
end
def self.prefetch(resources)
debug("[prefetch(resources)]")
instances.each do |prov|
if resource = resources[prov.name]
resource.provider = prov
end
end
end
def flush
debug("[flush]")
persist_iptables(@resource[:name].match(Nameformat)[3])
# Clear the property hash so we re-initialize with updated values
@property_hash.clear
end
# Look up the current status. This allows us to conventiently look up
# existing status with properties[:foo].
def properties
if @property_hash.empty?
@property_hash = query || {:ensure => :absent}
end
@property_hash.dup
end
# Pull the current state of the list from the full list.
def query
self.class.instances.each do |instance|
if instance.name == self.name
debug "query found #{self.name}" % instance.properties.inspect
return instance.properties
end
end
nil
end
def self.instances
debug "[instances]"
table = nil
chains = []
Mapping.each { |p, c|
begin
c[:save].call.each_line do |line|
if line =~ c[:re] then
name = $1 + ':' + (table == 'filter' ? 'filter' : table) + ':' + p.to_s
policy = $2 == '-' ? nil : $2.downcase.to_sym
chains << new({
:name => name,
:policy => policy,
:ensure => :present,
})
debug "[instance] '#{name}' #{policy}"
elsif line =~ /^\*(\S+)/
table = $1
else
next
end
end
rescue Puppet::Error
# ignore command not found for ebtables or anything that doesn't exist
end
}
chains
end
def allvalidchains
@resource[:name].match(Nameformat)
chain = $1
table = $2
protocol = $3
yield Mapping[protocol.to_sym][:tables],chain,table,protocol.to_sym
end
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,222 @@
# This is a workaround for bug: #4248 whereby ruby files outside of the normal
# provider/type path do not load until pluginsync has occured on the puppetmaster
#
# In this case I'm trying the relative path first, then falling back to normal
# mechanisms. This should be fixed in future versions of puppet but it looks
# like we'll need to maintain this for some time perhaps.
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"..",".."))
require 'puppet/util/firewall'
Puppet::Type.newtype(:firewallchain) do
include Puppet::Util::Firewall
@doc = <<-EOS
This type provides the capability to manage rule chains for firewalls.
Currently this supports only iptables, ip6tables and ebtables on Linux. And
provides support for setting the default policy on chains and tables that
allow it.
**Autorequires:**
If Puppet is managing the iptables or iptables-persistent packages, and
the provider is iptables_chain, the firewall resource will autorequire
those packages to ensure that any required binaries are installed.
EOS
feature :iptables_chain, "The provider provides iptables chain features."
feature :policy, "Default policy (inbuilt chains only)"
ensurable do
defaultvalues
defaultto :present
end
newparam(:name) do
desc <<-EOS
The canonical name of the chain.
For iptables the format must be {chain}:{table}:{protocol}.
EOS
isnamevar
validate do |value|
if value !~ Nameformat then
raise ArgumentError, "Inbuilt chains must be in the form {chain}:{table}:{protocol} where {table} is one of FILTER, NAT, MANGLE, RAW, RAWPOST, BROUTE or empty (alias for filter), chain can be anything without colons or one of PREROUTING, POSTROUTING, BROUTING, INPUT, FORWARD, OUTPUT for the inbuilt chains, and {protocol} being IPv4, IPv6, ethernet (ethernet bridging) got '#{value}' table:'#{$1}' chain:'#{$2}' protocol:'#{$3}'"
else
chain = $1
table = $2
protocol = $3
case table
when 'filter'
if chain =~ /^(PREROUTING|POSTROUTING|BROUTING)$/
raise ArgumentError, "INPUT, OUTPUT and FORWARD are the only inbuilt chains that can be used in table 'filter'"
end
when 'mangle'
if chain =~ InternalChains && chain == 'BROUTING'
raise ArgumentError, "PREROUTING, POSTROUTING, INPUT, FORWARD and OUTPUT are the only inbuilt chains that can be used in table 'mangle'"
end
when 'nat'
if chain =~ /^(BROUTING|FORWARD)$/
raise ArgumentError, "PREROUTING, POSTROUTING, INPUT, and OUTPUT are the only inbuilt chains that can be used in table 'nat'"
end
if protocol =~/^(IP(v6)?)?$/
raise ArgumentError, "table nat isn't valid in IPv6. You must specify ':IPv4' as the name suffix"
end
when 'raw'
if chain =~ /^(POSTROUTING|BROUTING|INPUT|FORWARD)$/
raise ArgumentError,'PREROUTING and OUTPUT are the only inbuilt chains in the table \'raw\''
end
when 'broute'
if protocol != 'ethernet'
raise ArgumentError,'BROUTE is only valid with protocol \'ethernet\''
end
if chain =~ /^PREROUTING|POSTROUTING|INPUT|FORWARD|OUTPUT$/
raise ArgumentError,'BROUTING is the only inbuilt chain allowed on on table \'broute\''
end
end
if chain == 'BROUTING' && ( protocol != 'ethernet' || table!='broute')
raise ArgumentError,'BROUTING is the only inbuilt chain allowed on on table \'BROUTE\' with protocol \'ethernet\' i.e. \'broute:BROUTING:enternet\''
end
end
end
end
newproperty(:policy) do
desc <<-EOS
This is the action to when the end of the chain is reached.
It can only be set on inbuilt chains (INPUT, FORWARD, OUTPUT,
PREROUTING, POSTROUTING) and can be one of:
* accept - the packet is accepted
* drop - the packet is dropped
* queue - the packet is passed userspace
* return - the packet is returned to calling (jump) queue
or the default of inbuilt chains
EOS
newvalues(:accept, :drop, :queue, :return)
defaultto do
# ethernet chain have an ACCEPT default while other haven't got an
# allowed value
if @resource[:name] =~ /:ethernet$/
:accept
else
nil
end
end
end
newparam(:purge, :boolean => true) do
desc <<-EOS
Purge unmanaged firewall rules in this chain
EOS
newvalues(:false, :true)
defaultto :false
end
newparam(:ignore) do
desc <<-EOS
Regex to perform on firewall rules to exempt unmanaged rules from purging (when enabled).
This is matched against the output of `iptables-save`.
This can be a single regex, or an array of them.
To support flags, use the ruby inline flag mechanism.
Meaning a regex such as
/foo/i
can be written as
'(?i)foo' or '(?i:foo)'
Full example:
firewallchain { 'INPUT:filter:IPv4':
purge => true,
ignore => [
'-j fail2ban-ssh', # ignore the fail2ban jump rule
'--comment "[^"]*(?i:ignore)[^"]*"', # ignore any rules with "ignore" (case insensitive) in the comment in the rule
],
}
EOS
validate do |value|
unless value.is_a?(Array) or value.is_a?(String) or value == false
self.devfail "Ignore must be a string or an Array"
end
end
munge do |patterns| # convert into an array of {Regex}es
patterns = [patterns] if patterns.is_a?(String)
patterns.map{|p| Regexp.new(p)}
end
end
# Classes would be a better abstraction, pending:
# http://projects.puppetlabs.com/issues/19001
autorequire(:package) do
case value(:provider)
when :iptables_chain
%w{iptables iptables-persistent}
else
[]
end
end
validate do
debug("[validate]")
value(:name).match(Nameformat)
chain = $1
table = $2
protocol = $3
# Check that we're not removing an internal chain
if chain =~ InternalChains && value(:ensure) == :absent
self.fail "Cannot remove in-built chains"
end
if value(:policy).nil? && protocol == 'ethernet'
self.fail "you must set a non-empty policy on all ethernet table chains"
end
# Check that we're not setting a policy on a user chain
if chain !~ InternalChains &&
!value(:policy).nil? &&
protocol != 'ethernet'
self.fail "policy can only be set on in-built chains (with the exception of ethernet chains) (table:#{table} chain:#{chain} protocol:#{protocol})"
end
# no DROP policy on nat table
if table == 'nat' &&
value(:policy) == :drop
self.fail 'The "nat" table is not intended for filtering, the use of DROP is therefore inhibited'
end
end
def generate
return [] unless self.purge?
value(:name).match(Nameformat)
chain = $1
table = $2
protocol = $3
provider = case protocol
when 'IPv4'
:iptables
when 'IPv6'
:ip6tables
end
# gather a list of all rules present on the system
rules_resources = Puppet::Type.type(:firewall).instances
# Keep only rules in this chain
rules_resources.delete_if { |res| (res[:provider] != provider or res.provider.properties[:table].to_s != table or res.provider.properties[:chain] != chain) }
# Remove rules which match our ignore filter
rules_resources.delete_if {|res| value(:ignore).find_index{|f| res.provider.properties[:line].match(f)}} if value(:ignore)
# We mark all remaining rules for deletion, and then let the catalog override us on rules which should be present
rules_resources.each {|res| res[:ensure] = :absent}
rules_resources
end
end

View File

@ -0,0 +1,220 @@
require 'socket'
require 'resolv'
require 'puppet/util/ipcidr'
# Util module for puppetlabs-firewall
module Puppet::Util::Firewall
# Translate the symbolic names for icmp packet types to integers
def icmp_name_to_number(value_icmp, protocol)
if value_icmp =~ /\d{1,2}$/
value_icmp
elsif protocol == 'inet'
case value_icmp
when "echo-reply" then "0"
when "destination-unreachable" then "3"
when "source-quench" then "4"
when "redirect" then "6"
when "echo-request" then "8"
when "router-advertisement" then "9"
when "router-solicitation" then "10"
when "time-exceeded" then "11"
when "parameter-problem" then "12"
when "timestamp-request" then "13"
when "timestamp-reply" then "14"
when "address-mask-request" then "17"
when "address-mask-reply" then "18"
else nil
end
elsif protocol == 'inet6'
case value_icmp
when "destination-unreachable" then "1"
when "time-exceeded" then "3"
when "parameter-problem" then "4"
when "echo-request" then "128"
when "echo-reply" then "129"
when "router-solicitation" then "133"
when "router-advertisement" then "134"
when "redirect" then "137"
else nil
end
else
raise ArgumentError, "unsupported protocol family '#{protocol}'"
end
end
# Convert log_level names to their respective numbers
def log_level_name_to_number(value)
#TODO make this 0-7 only
if value =~ /\d/
value
else
case value
when "panic" then "0"
when "alert" then "1"
when "crit" then "2"
when "err" then "3"
when "error" then "3"
when "warn" then "4"
when "warning" then "4"
when "not" then "5"
when "notice" then "5"
when "info" then "6"
when "debug" then "7"
else nil
end
end
end
# This method takes a string and a protocol and attempts to convert
# it to a port number if valid.
#
# If the string already contains a port number or perhaps a range of ports
# in the format 22:1000 for example, it simply returns the string and does
# nothing.
def string_to_port(value, proto)
proto = proto.to_s
unless proto =~ /^(tcp|udp)$/
proto = 'tcp'
end
if value.kind_of?(String)
if value.match(/^\d+(-\d+)?$/)
return value
else
return Socket.getservbyname(value, proto).to_s
end
else
Socket.getservbyname(value.to_s, proto).to_s
end
end
# Takes an address and returns it in CIDR notation.
#
# If the address is:
#
# - A hostname:
# It will be resolved
# - An IPv4 address:
# It will be qualified with a /32 CIDR notation
# - An IPv6 address:
# It will be qualified with a /128 CIDR notation
# - An IP address with a CIDR notation:
# It will be normalised
# - An IP address with a dotted-quad netmask:
# It will be converted to CIDR notation
# - Any address with a resulting prefix length of zero:
# It will return nil which is equivilent to not specifying an address
#
def host_to_ip(value)
begin
value = Puppet::Util::IPCidr.new(value)
rescue
value = Puppet::Util::IPCidr.new(Resolv.getaddress(value))
end
return nil if value.prefixlen == 0
value.cidr
end
# Takes an address mask and converts the host portion to CIDR notation.
#
# This takes into account you can negate a mask but follows all rules
# defined in host_to_ip for the host/address part.
#
def host_to_mask(value)
match = value.match /(!)\s?(.*)$/
return host_to_ip(value) unless match
cidr = host_to_ip(match[2])
return nil if cidr == nil
"#{match[1]} #{cidr}"
end
# Validates the argument is int or hex, and returns valid hex
# conversion of the value or nil otherwise.
def to_hex32(value)
begin
value = Integer(value)
if value.between?(0, 0xffffffff)
return '0x' + value.to_s(16)
end
rescue ArgumentError
# pass
end
return nil
end
def persist_iptables(proto)
debug("[persist_iptables]")
# Basic normalisation for older Facter
os_key = Facter.value(:osfamily)
os_key ||= case Facter.value(:operatingsystem)
when 'RedHat', 'CentOS', 'Fedora', 'Scientific', 'SL', 'SLC', 'Ascendos', 'CloudLinux', 'PSBM', 'OracleLinux', 'OVS', 'OEL', 'Amazon', 'XenServer'
'RedHat'
when 'Debian', 'Ubuntu'
'Debian'
else
Facter.value(:operatingsystem)
end
# Older iptables-persistent doesn't provide save action.
if os_key == 'Debian'
persist_ver = Facter.value(:iptables_persistent_version)
if (persist_ver and Puppet::Util::Package.versioncmp(persist_ver, '0.5.0') < 0)
os_key = 'Debian_manual'
end
end
# Fedora 15 and newer use systemd for to persist iptable rules
if os_key == 'RedHat' && Facter.value(:operatingsystem) == 'Fedora' && Facter.value(:operatingsystemrelease).to_i >= 15
os_key = 'Fedora'
end
cmd = case os_key.to_sym
when :RedHat
case proto.to_sym
when :IPv4
%w{/sbin/service iptables save}
when :IPv6
%w{/sbin/service ip6tables save}
end
when :Fedora
case proto.to_sym
when :IPv4
%w{/usr/libexec/iptables.init save}
when :IPv6
%w{/usr/libexec/ip6tables.init save}
end
when :Debian
case proto.to_sym
when :IPv4, :IPv6
%w{/usr/sbin/service iptables-persistent save}
end
when :Debian_manual
case proto.to_sym
when :IPv4
["/bin/sh", "-c", "/sbin/iptables-save > /etc/iptables/rules"]
end
when :Archlinux
case proto.to_sym
when :IPv4
["/bin/sh", "-c", "/usr/sbin/iptables-save > /etc/iptables/iptables.rules"]
when :IPv6
["/bin/sh", "-c", "/usr/sbin/ip6tables-save > /etc/iptables/ip6tables.rules"]
end
end
# Catch unsupported OSs from the case statement above.
if cmd.nil?
debug('firewall: Rule persistence is not supported for this type/OS')
return
end
begin
execute(cmd)
rescue Puppet::ExecutionFailure => detail
warning("Unable to persist firewall rules: #{detail}")
end
end
end

View File

@ -0,0 +1,42 @@
require 'ipaddr'
# IPCidr object wrapper for IPAddr
module Puppet
module Util
class IPCidr < IPAddr
def initialize(ipaddr)
begin
super(ipaddr)
rescue ArgumentError => e
if e.message =~ /invalid address/
raise ArgumentError, "Invalid address from IPAddr.new: #{ipaddr}"
else
raise e
end
end
end
def netmask
_to_string(@mask_addr)
end
def prefixlen
m = case @family
when Socket::AF_INET
IN4MASK
when Socket::AF_INET6
IN6MASK
else
raise "unsupported address family"
end
return $1.length if /\A(1*)(0*)\z/ =~ (@mask_addr & m).to_s(2)
raise "bad addr_mask format"
end
def cidr
cidr = sprintf("%s/%s", self.to_s, self.prefixlen)
cidr
end
end
end
end

View File

@ -0,0 +1,36 @@
# = Class: firewall
#
# Manages packages and services required by the firewall type/provider.
#
# This class includes the appropriate sub-class for your operating system,
# where supported.
#
# == Parameters:
#
# [*ensure*]
# Ensure parameter passed onto Service[] resources.
# Default: running
#
class firewall (
$ensure = running
) {
case $ensure {
/^(running|stopped)$/: {
# Do nothing.
}
default: {
fail("${title}: Ensure value '${ensure}' is not supported")
}
}
case $::kernel {
'Linux': {
class { "${title}::linux":
ensure => $ensure,
}
}
default: {
fail("${title}: Kernel '${::kernel}' is not currently supported")
}
}
}

View File

@ -0,0 +1,51 @@
# = Class: firewall::linux
#
# Installs the `iptables` package for Linux operating systems and includes
# the appropriate sub-class for any distribution specific services and
# additional packages.
#
# == Parameters:
#
# [*ensure*]
# Ensure parameter passed onto Service[] resources. When `running` the
# service will be started on boot, and when `stopped` it will not.
# Default: running
#
class firewall::linux (
$ensure = running
) {
$enable = $ensure ? {
running => true,
stopped => false,
}
package { 'iptables':
ensure => present,
}
case $::operatingsystem {
'RedHat', 'CentOS', 'Fedora', 'Scientific', 'SL', 'SLC', 'Ascendos',
'CloudLinux', 'PSBM', 'OracleLinux', 'OVS', 'OEL', 'Amazon', 'XenServer': {
class { "${title}::redhat":
ensure => $ensure,
enable => $enable,
require => Package['iptables'],
}
}
'Debian', 'Ubuntu': {
class { "${title}::debian":
ensure => $ensure,
enable => $enable,
require => Package['iptables'],
}
}
'Archlinux': {
class { "${title}::archlinux":
ensure => $ensure,
enable => $enable,
require => Package['iptables'],
}
}
default: {}
}
}

View File

@ -0,0 +1,41 @@
# = Class: firewall::linux::archlinux
#
# Manages `iptables` and `ip6tables` services, and creates files used for
# persistence, on Arch Linux systems.
#
# == Parameters:
#
# [*ensure*]
# Ensure parameter passed onto Service[] resources.
# Default: running
#
# [*enable*]
# Enable parameter passed onto Service[] resources.
# Default: true
#
class firewall::linux::archlinux (
$ensure = 'running',
$enable = true
) {
service { 'iptables':
ensure => $ensure,
enable => $enable,
hasstatus => true,
}
service { 'ip6tables':
ensure => $ensure,
enable => $enable,
hasstatus => true,
}
file { '/etc/iptables/iptables.rules':
ensure => present,
before => Service['iptables'],
}
file { '/etc/iptables/ip6tables.rules':
ensure => present,
before => Service['ip6tables'],
}
}

View File

@ -0,0 +1,44 @@
# = Class: firewall::linux::debian
#
# Installs the `iptables-persistent` package for Debian-alike systems. This
# allows rules to be stored to file and restored on boot.
#
# == Parameters:
#
# [*ensure*]
# Ensure parameter passed onto Service[] resources.
# Default: running
#
# [*enable*]
# Enable parameter passed onto Service[] resources.
# Default: true
#
class firewall::linux::debian (
$ensure = running,
$enable = true
) {
package { 'iptables-persistent':
ensure => present,
}
if($::operatingsystemrelease =~ /^6\./ and $enable == true
and versioncmp($::iptables_persistent_version, '0.5.0') < 0 ) {
# This fixes a bug in the iptables-persistent LSB headers in 6.x, without it
# we lose idempotency
exec { 'iptables-persistent-enable':
logoutput => on_failure,
command => '/usr/sbin/update-rc.d iptables-persistent enable',
unless => '/usr/bin/test -f /etc/rcS.d/S*iptables-persistent',
require => Package['iptables-persistent'],
}
} else {
# This isn't a real service/daemon. The start action loads rules, so just
# needs to be called on system boot.
service { 'iptables-persistent':
ensure => undef,
enable => $enable,
hasstatus => true,
require => Package['iptables-persistent'],
}
}
}

View File

@ -0,0 +1,24 @@
# = Class: firewall::linux::redhat
#
# Manages the `iptables` service on RedHat-alike systems.
#
# == Parameters:
#
# [*ensure*]
# Ensure parameter passed onto Service[] resources.
# Default: running
#
# [*enable*]
# Enable parameter passed onto Service[] resources.
# Default: true
#
class firewall::linux::redhat (
$ensure = running,
$enable = true
) {
service { 'iptables':
ensure => $ensure,
enable => $enable,
hasstatus => true,
}
}

View File

@ -0,0 +1,77 @@
require 'spec_helper_acceptance'
describe 'firewall type' do
describe 'reset' do
it 'deletes all rules' do
shell('iptables --flush; iptables -t nat --flush; iptables -t mangle --flush')
end
end
describe 'when unmanaged rules exist' do
it 'applies with 8.0.0.1 first' do
pp = <<-EOS
class { '::firewall': }
firewall { '101 test source changes':
proto => tcp,
port => '101',
action => accept,
source => '8.0.0.1',
}
firewall { '100 test source static':
proto => tcp,
port => '100',
action => accept,
source => '8.0.0.2',
}
EOS
apply_manifest(pp, :catch_failures => true)
end
it 'adds a unmanaged rule without a comment' do
shell('iptables -A INPUT -t filter -s 8.0.0.3/32 -p tcp -m multiport --ports 102 -j ACCEPT')
expect(shell('iptables-save').stdout).to match(/-A INPUT -s 8\.0\.0\.3(\/32)? -p tcp -m multiport --ports 102 -j ACCEPT/)
end
it 'contains the changable 8.0.0.1 rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/-A INPUT -s 8\.0\.0\.1(\/32)? -p tcp -m multiport --ports 101 -m comment --comment "101 test source changes" -j ACCEPT/)
end
end
it 'contains the static 8.0.0.2 rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/-A INPUT -s 8\.0\.0\.2(\/32)? -p tcp -m multiport --ports 100 -m comment --comment "100 test source static" -j ACCEPT/)
end
end
it 'changes to 8.0.0.4 second' do
pp = <<-EOS
class { '::firewall': }
firewall { '101 test source changes':
proto => tcp,
port => '101',
action => accept,
source => '8.0.0.4',
}
EOS
expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/Notice: \/Stage\[main\]\/Main\/Firewall\[101 test source changes\]\/source: source changed '8\.0\.0\.1\/32' to '8\.0\.0\.4\/32'/)
end
it 'does not contain the old changing 8.0.0.1 rule' do
shell('iptables-save') do |r|
expect(r.stdout).to_not match(/8\.0\.0\.1/)
end
end
it 'contains the staic 8.0.0.2 rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/-A INPUT -s 8\.0\.0\.2(\/32)? -p tcp -m multiport --ports 100 -m comment --comment "100 test source static" -j ACCEPT/)
end
end
it 'contains the changing new 8.0.0.4 rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/-A INPUT -s 8\.0\.0\.4(\/32)? -p tcp -m multiport --ports 101 -m comment --comment "101 test source changes" -j ACCEPT/)
end
end
end
end

View File

@ -0,0 +1,27 @@
require 'spec_helper_acceptance'
describe "firewall class:" do
it 'should run successfully' do
pp = "class { 'firewall': }"
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero
end
it 'ensure => stopped:' do
pp = "class { 'firewall': ensure => stopped }"
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero
end
it 'ensure => running:' do
pp = "class { 'firewall': ensure => running }"
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero
end
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,125 @@
require 'spec_helper_acceptance'
describe 'puppet resource firewallchain command:' do
before :all do
iptables_flush_all_tables
end
describe 'ensure' do
context 'present' do
it 'applies cleanly' do
pp = <<-EOS
firewallchain { 'MY_CHAIN:filter:IPv4':
ensure => present,
}
EOS
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
end
it 'finds the chain' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/MY_CHAIN/)
end
end
end
context 'absent' do
it 'applies cleanly' do
pp = <<-EOS
firewallchain { 'MY_CHAIN:filter:IPv4':
ensure => absent,
}
EOS
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
end
it 'fails to find the chain' do
shell('iptables-save') do |r|
expect(r.stdout).to_not match(/MY_CHAIN/)
end
end
end
end
# XXX purge => false is not yet implemented
#context 'adding a firewall rule to a chain:' do
# it 'applies cleanly' do
# pp = <<-EOS
# firewallchain { 'MY_CHAIN:filter:IPv4':
# ensure => present,
# }
# firewall { '100 my rule':
# chain => 'MY_CHAIN',
# action => 'accept',
# proto => 'tcp',
# dport => 5000,
# }
# EOS
# # Run it twice and test for idempotency
# apply_manifest(pp, :catch_failures => true)
# apply_manifest(pp, :catch_changes => true)
# end
#end
#context 'not purge firewallchain chains:' do
# it 'does not purge the rule' do
# pp = <<-EOS
# firewallchain { 'MY_CHAIN:filter:IPv4':
# ensure => present,
# purge => false,
# before => Resources['firewall'],
# }
# resources { 'firewall':
# purge => true,
# }
# EOS
# # Run it twice and test for idempotency
# apply_manifest(pp, :catch_failures => true) do |r|
# expect(r.stdout).to_not match(/removed/)
# expect(r.stderr).to eq('')
# end
# apply_manifest(pp, :catch_changes => true)
# end
# it 'still has the rule' do
# pp = <<-EOS
# firewall { '100 my rule':
# chain => 'MY_CHAIN',
# action => 'accept',
# proto => 'tcp',
# dport => 5000,
# }
# EOS
# # Run it twice and test for idempotency
# apply_manifest(pp, :catch_changes => true)
# end
#end
describe 'policy' do
after :all do
shell('iptables -t filter -P FORWARD ACCEPT')
end
context 'DROP' do
it 'applies cleanly' do
pp = <<-EOS
firewallchain { 'FORWARD:filter:IPv4':
policy => 'drop',
}
EOS
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
end
it 'finds the chain' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/FORWARD DROP/)
end
end
end
end
end

View File

@ -0,0 +1,114 @@
require 'spec_helper_acceptance'
if default['platform'] =~ /el-5/
describe "firewall ip6tables doesn't work on 1.3.5 because --comment is missing" do
before :all do
ip6tables_flush_all_tables
end
it "can't use ip6tables" do
pp = <<-EOS
class { '::firewall': }
firewall { '599 - test':
ensure => present,
proto => 'tcp',
provider => 'ip6tables',
}
EOS
expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/ip6tables provider is not supported/)
end
end
else
describe 'firewall ishasmorefrags/islastfrag/isfirstfrag properties' do
before :all do
ip6tables_flush_all_tables
end
shared_examples "is idempotent" do |values, line_match|
it "changes the values to #{values}" do
pp = <<-EOS
class { '::firewall': }
firewall { '599 - test':
ensure => present,
proto => 'tcp',
provider => 'ip6tables',
#{values}
}
EOS
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
shell('ip6tables-save') do |r|
expect(r.stdout).to match(/#{line_match}/)
end
end
end
shared_examples "doesn't change" do |values, line_match|
it "doesn't change the values to #{values}" do
pp = <<-EOS
class { '::firewall': }
firewall { '599 - test':
ensure => present,
proto => 'tcp',
provider => 'ip6tables',
#{values}
}
EOS
apply_manifest(pp, :catch_changes => true)
shell('ip6tables-save') do |r|
expect(r.stdout).to match(/#{line_match}/)
end
end
end
describe 'adding a rule' do
context 'when unset' do
before :all do
ip6tables_flush_all_tables
end
it_behaves_like 'is idempotent', '', /-A INPUT -p tcp -m comment --comment "599 - test"/
end
context 'when set to true' do
before :all do
ip6tables_flush_all_tables
end
it_behaves_like "is idempotent", 'ishasmorefrags => true, islastfrag => true, isfirstfrag => true', /-A INPUT -p tcp -m frag --fragid 0 --fragmore -m frag --fragid 0 --fraglast -m frag --fragid 0 --fragfirst -m comment --comment "599 - test"/
end
context 'when set to false' do
before :all do
ip6tables_flush_all_tables
end
it_behaves_like "is idempotent", 'ishasmorefrags => false, islastfrag => false, isfirstfrag => false', /-A INPUT -p tcp -m comment --comment "599 - test"/
end
end
describe 'editing a rule' do
context 'when unset or false' do
before :each do
ip6tables_flush_all_tables
shell('ip6tables -A INPUT -p tcp -m comment --comment "599 - test"')
end
context 'and current value is false' do
it_behaves_like "doesn't change", 'ishasmorefrags => false, islastfrag => false, isfirstfrag => false', /-A INPUT -p tcp -m comment --comment "599 - test"/
end
context 'and current value is true' do
it_behaves_like "is idempotent", 'ishasmorefrags => true, islastfrag => true, isfirstfrag => true', /-A INPUT -p tcp -m frag --fragid 0 --fragmore -m frag --fragid 0 --fraglast -m frag --fragid 0 --fragfirst -m comment --comment "599 - test"/
end
end
context 'when set to true' do
before :each do
ip6tables_flush_all_tables
shell('ip6tables -A INPUT -p tcp -m frag --fragid 0 --fragmore -m frag --fragid 0 --fraglast -m frag --fragid 0 --fragfirst -m comment --comment "599 - test"')
end
context 'and current value is false' do
it_behaves_like "is idempotent", 'ishasmorefrags => false, islastfrag => false, isfirstfrag => false', /-A INPUT -p tcp -m comment --comment "599 - test"/
end
context 'and current value is true' do
it_behaves_like "doesn't change", 'ishasmorefrags => true, islastfrag => true, isfirstfrag => true', /-A INPUT -p tcp -m frag --fragid 0 --fragmore -m frag --fragid 0 --fraglast -m frag --fragid 0 --fragfirst -m comment --comment "599 - test"/
end
end
end
end
end

View File

@ -0,0 +1,92 @@
require 'spec_helper_acceptance'
describe 'firewall isfragment property' do
before :all do
iptables_flush_all_tables
end
shared_examples "is idempotent" do |value, line_match|
it "changes the value to #{value}" do
pp = <<-EOS
class { '::firewall': }
firewall { '597 - test':
ensure => present,
proto => 'tcp',
#{value}
}
EOS
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
shell('iptables-save') do |r|
expect(r.stdout).to match(/#{line_match}/)
end
end
end
shared_examples "doesn't change" do |value, line_match|
it "doesn't change the value to #{value}" do
pp = <<-EOS
class { '::firewall': }
firewall { '597 - test':
ensure => present,
proto => 'tcp',
#{value}
}
EOS
apply_manifest(pp, :catch_changes => true)
shell('iptables-save') do |r|
expect(r.stdout).to match(/#{line_match}/)
end
end
end
describe 'adding a rule' do
context 'when unset' do
before :all do
iptables_flush_all_tables
end
it_behaves_like 'is idempotent', '', /-A INPUT -p tcp -m comment --comment "597 - test"/
end
context 'when set to true' do
before :all do
iptables_flush_all_tables
end
it_behaves_like 'is idempotent', 'isfragment => true,', /-A INPUT -p tcp -f -m comment --comment "597 - test"/
end
context 'when set to false' do
before :all do
iptables_flush_all_tables
end
it_behaves_like "is idempotent", 'isfragment => false,', /-A INPUT -p tcp -m comment --comment "597 - test"/
end
end
describe 'editing a rule' do
context 'when unset or false' do
before :each do
iptables_flush_all_tables
shell('iptables -A INPUT -p tcp -m comment --comment "597 - test"')
end
context 'and current value is false' do
it_behaves_like "doesn't change", 'isfragment => false,', /-A INPUT -p tcp -m comment --comment "597 - test"/
end
context 'and current value is true' do
it_behaves_like "is idempotent", 'isfragment => true,', /-A INPUT -p tcp -f -m comment --comment "597 - test"/
end
end
context 'when set to true' do
before :each do
iptables_flush_all_tables
shell('iptables -A INPUT -p tcp -f -m comment --comment "597 - test"')
end
context 'and current value is false' do
it_behaves_like "is idempotent", 'isfragment => false,', /-A INPUT -p tcp -m comment --comment "597 - test"/
end
context 'and current value is true' do
it_behaves_like "doesn't change", 'isfragment => true,', /-A INPUT -p tcp -f -m comment --comment "597 - test"/
end
end
end
end

View File

@ -0,0 +1,12 @@
HOSTS:
centos-59-x64:
roles:
- master
- database
- console
platform: el-5-x86_64
box : centos-59-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-59-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: pe

View File

@ -0,0 +1,10 @@
HOSTS:
centos-59-x64:
roles:
- master
platform: el-5-x86_64
box : centos-59-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-59-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: foss

View File

@ -0,0 +1,10 @@
HOSTS:
centos-64-x64:
roles:
- master
platform: el-6-x86_64
box : centos-64-x64-fusion503-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-fusion503-nocm.box
hypervisor : fusion
CONFIG:
type: foss

View File

@ -0,0 +1,12 @@
HOSTS:
centos-64-x64:
roles:
- master
- database
- dashboard
platform: el-6-x86_64
box : centos-64-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: pe

View File

@ -0,0 +1,10 @@
HOSTS:
centos-64-x64:
roles:
- master
platform: el-6-x86_64
box : centos-64-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: foss

View File

@ -0,0 +1,10 @@
HOSTS:
debian-607-x64:
roles:
- master
platform: debian-6-amd64
box : debian-607-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/debian-607-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: git

View File

@ -0,0 +1,10 @@
HOSTS:
debian-70rc1-x64:
roles:
- master
platform: debian-7-amd64
box : debian-70rc1-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/debian-70rc1-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: git

View File

@ -0,0 +1,10 @@
HOSTS:
centos-64-x64:
roles:
- master
platform: el-6-x86_64
box : centos-64-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: foss

View File

@ -0,0 +1,10 @@
HOSTS:
fedora-18-x64:
roles:
- master
platform: fedora-18-x86_64
box : fedora-18-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/fedora-18-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: git

View File

@ -0,0 +1,10 @@
HOSTS:
sles-11sp1-x64:
roles:
- master
platform: sles-11-x86_64
box : sles-11sp1-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/sles-11sp1-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: git

View File

@ -0,0 +1,10 @@
HOSTS:
ubuntu-server-10044-x64:
roles:
- master
platform: ubuntu-10.04-amd64
box : ubuntu-server-10044-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: git

View File

@ -0,0 +1,10 @@
HOSTS:
ubuntu-server-12042-x64:
roles:
- master
platform: ubuntu-12.04-amd64
box : ubuntu-server-12042-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box
hypervisor : vagrant
CONFIG:
type: foss

View File

@ -0,0 +1,154 @@
require 'spec_helper_acceptance'
describe "param based tests:" do
# Takes a hash and converts it into a firewall resource
def pp(params)
name = params.delete('name') || '100 test'
pm = <<-EOS
firewall { '#{name}':
EOS
params.each do |k,v|
pm += <<-EOS
#{k} => #{v},
EOS
end
pm += <<-EOS
}
EOS
pm
end
it 'test various params', :unless => (default['platform'].match(/el-5/) || fact('operatingsystem') == 'SLES') do
iptables_flush_all_tables
ppm = pp({
'table' => "'raw'",
'socket' => 'true',
'chain' => "'PREROUTING'",
'jump' => 'LOG',
'log_level' => 'debug',
})
expect(apply_manifest(ppm, :catch_failures => true).exit_code).to eq(2)
expect(apply_manifest(ppm, :catch_failures => true).exit_code).to be_zero
end
it 'test log rule' do
iptables_flush_all_tables
ppm = pp({
'name' => '998 log all',
'proto' => 'all',
'jump' => 'LOG',
'log_level' => 'debug',
})
expect(apply_manifest(ppm, :catch_failures => true).exit_code).to eq(2)
expect(apply_manifest(ppm, :catch_failures => true).exit_code).to be_zero
end
it 'test log rule - changing names' do
iptables_flush_all_tables
ppm1 = pp({
'name' => '004 log all INVALID packets',
'chain' => 'INPUT',
'proto' => 'all',
'ctstate' => 'INVALID',
'jump' => 'LOG',
'log_level' => '3',
'log_prefix' => '"IPTABLES dropped invalid: "',
})
ppm2 = pp({
'name' => '003 log all INVALID packets',
'chain' => 'INPUT',
'proto' => 'all',
'ctstate' => 'INVALID',
'jump' => 'LOG',
'log_level' => '3',
'log_prefix' => '"IPTABLES dropped invalid: "',
})
expect(apply_manifest(ppm1, :catch_failures => true).exit_code).to eq(2)
ppm = <<-EOS + "\n" + ppm2
resources { 'firewall':
purge => true,
}
EOS
expect(apply_manifest(ppm, :catch_failures => true).exit_code).to eq(2)
end
it 'test chain - changing names' do
iptables_flush_all_tables
ppm1 = pp({
'name' => '004 with a chain',
'chain' => 'INPUT',
'proto' => 'all',
})
ppm2 = pp({
'name' => '004 with a chain',
'chain' => 'OUTPUT',
'proto' => 'all',
})
apply_manifest(ppm1, :expect_changes => true)
ppm = <<-EOS + "\n" + ppm2
resources { 'firewall':
purge => true,
}
EOS
expect(apply_manifest(ppm2, :expect_failures => true).stderr).to match(/is not supported/)
end
it 'test log rule - idempotent' do
iptables_flush_all_tables
ppm1 = pp({
'name' => '004 log all INVALID packets',
'chain' => 'INPUT',
'proto' => 'all',
'ctstate' => 'INVALID',
'jump' => 'LOG',
'log_level' => '3',
'log_prefix' => '"IPTABLES dropped invalid: "',
})
expect(apply_manifest(ppm1, :catch_failures => true).exit_code).to eq(2)
expect(apply_manifest(ppm1, :catch_failures => true).exit_code).to be_zero
end
it 'test src_range rule' do
iptables_flush_all_tables
ppm = pp({
'name' => '997 block src ip range',
'chain' => 'INPUT',
'proto' => 'all',
'action' => 'drop',
'src_range' => '"10.0.0.1-10.0.0.10"',
})
expect(apply_manifest(ppm, :catch_failures => true).exit_code).to eq(2)
expect(apply_manifest(ppm, :catch_failures => true).exit_code).to be_zero
end
it 'test dst_range rule' do
iptables_flush_all_tables
ppm = pp({
'name' => '998 block dst ip range',
'chain' => 'INPUT',
'proto' => 'all',
'action' => 'drop',
'dst_range' => '"10.0.0.2-10.0.0.20"',
})
expect(apply_manifest(ppm, :catch_failures => true).exit_code).to eq(2)
expect(apply_manifest(ppm, :catch_failures => true).exit_code).to be_zero
end
end

View File

@ -0,0 +1,124 @@
require 'spec_helper_acceptance'
describe "purge tests:" do
context('resources purge') do
before(:all) do
iptables_flush_all_tables
shell('iptables -A INPUT -s 1.2.1.2')
shell('iptables -A INPUT -s 1.2.1.2')
end
it 'make sure duplicate existing rules get purged' do
pp = <<-EOS
class { 'firewall': }
resources { 'firewall':
purge => true,
}
EOS
apply_manifest(pp, :expect_changes => true)
end
it 'saves' do
shell('iptables-save') do |r|
expect(r.stdout).to_not match(/1\.2\.1\.2/)
expect(r.stderr).to eq("")
end
end
end
context('chain purge') do
before(:each) do
iptables_flush_all_tables
shell('iptables -A INPUT -p tcp -s 1.2.1.1')
shell('iptables -A INPUT -p udp -s 1.2.1.1')
shell('iptables -A OUTPUT -s 1.2.1.2 -m comment --comment "010 output-1.2.1.2"')
end
it 'purges only the specified chain' do
pp = <<-EOS
class { 'firewall': }
firewallchain { 'INPUT:filter:IPv4':
purge => true,
}
EOS
apply_manifest(pp, :expect_changes => true)
shell('iptables-save') do |r|
expect(r.stdout).to match(/010 output-1\.2\.1\.2/)
expect(r.stdout).to_not match(/1\.2\.1\.1/)
expect(r.stderr).to eq("")
end
end
it 'ignores managed rules' do
pp = <<-EOS
class { 'firewall': }
firewallchain { 'OUTPUT:filter:IPv4':
purge => true,
}
firewall { '010 output-1.2.1.2':
chain => 'OUTPUT',
proto => 'all',
source => '1.2.1.2',
}
EOS
apply_manifest(pp, :catch_changes => true)
end
it 'ignores specified rules' do
pp = <<-EOS
class { 'firewall': }
firewallchain { 'INPUT:filter:IPv4':
purge => true,
ignore => [
'-s 1\.2\.1\.1',
],
}
EOS
apply_manifest(pp, :catch_changes => true)
end
it 'adds managed rules with ignored rules' do
pp = <<-EOS
class { 'firewall': }
firewallchain { 'INPUT:filter:IPv4':
purge => true,
ignore => [
'-s 1\.2\.1\.1',
],
}
firewall { '014 input-1.2.1.6':
chain => 'INPUT',
proto => 'all',
source => '1.2.1.6',
}
-> firewall { '013 input-1.2.1.5':
chain => 'INPUT',
proto => 'all',
source => '1.2.1.5',
}
-> firewall { '012 input-1.2.1.4':
chain => 'INPUT',
proto => 'all',
source => '1.2.1.4',
}
-> firewall { '011 input-1.2.1.3':
chain => 'INPUT',
proto => 'all',
source => '1.2.1.3',
}
EOS
apply_manifest(pp, :catch_failures => true)
expect(shell('iptables-save').stdout).to match(/-A INPUT -s 1\.2\.1\.1(\/32)? -p tcp\s?\n-A INPUT -s 1\.2\.1\.1(\/32)? -p udp/)
end
end
end

View File

@ -0,0 +1,93 @@
require 'spec_helper_acceptance'
# Here we want to test the the resource commands ability to work with different
# existing ruleset scenarios. This will give the parsing capabilities of the
# code a good work out.
describe 'puppet resource firewall command:' do
context 'make sure it returns no errors when executed on a clean machine' do
it do
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, some boxes come with rules, that is normal
r.stderr.should be_empty
end
end
end
context 'flush iptables and make sure it returns nothing afterwards' do
before(:all) do
iptables_flush_all_tables
end
# No rules, means no output thanks. And no errors as well.
it do
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
r.stderr.should be_empty
r.stdout.should == "\n"
end
end
end
context 'accepts rules without comments' do
before(:all) do
iptables_flush_all_tables
shell('iptables -A INPUT -j ACCEPT -p tcp --dport 80')
end
it do
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
r.stderr.should be_empty
end
end
end
context 'accepts rules with invalid comments' do
before(:all) do
iptables_flush_all_tables
shell('iptables -A INPUT -j ACCEPT -p tcp --dport 80 -m comment --comment "http"')
end
it do
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
r.stderr.should be_empty
end
end
end
context 'accepts rules with negation' do
before :all do
iptables_flush_all_tables
shell('iptables -t nat -A POSTROUTING -s 192.168.122.0/24 ! -d 192.168.122.0/24 -p tcp -j MASQUERADE --to-ports 1024-65535')
shell('iptables -t nat -A POSTROUTING -s 192.168.122.0/24 ! -d 192.168.122.0/24 -p udp -j MASQUERADE --to-ports 1024-65535')
shell('iptables -t nat -A POSTROUTING -s 192.168.122.0/24 ! -d 192.168.122.0/24 -j MASQUERADE')
end
it do
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
r.stderr.should be_empty
end
end
end
context 'accepts rules with match extension tcp flag' do
before :all do
iptables_flush_all_tables
shell('iptables -t mangle -A PREROUTING -d 1.2.3.4 -p tcp -m tcp -m multiport --dports 80,443,8140 -j MARK --set-mark 42')
end
it do
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
r.stderr.should be_empty
end
end
end
end

View File

@ -0,0 +1,252 @@
require 'spec_helper_acceptance'
describe 'complex ruleset 1' do
before :all do
iptables_flush_all_tables
end
after :all do
shell('iptables -t filter -P INPUT ACCEPT')
shell('iptables -t filter -P FORWARD ACCEPT')
shell('iptables -t filter -P OUTPUT ACCEPT')
shell('iptables -t filter --flush')
end
it 'applies cleanly' do
pp = <<-EOS
firewall { '090 forward allow local':
chain => 'FORWARD',
proto => 'all',
source => '10.0.0.0/8',
destination => '10.0.0.0/8',
action => 'accept',
}
firewall { '100 forward standard allow tcp':
chain => 'FORWARD',
source => '10.0.0.0/8',
destination => '!10.0.0.0/8',
proto => 'tcp',
state => 'NEW',
port => [80,443,21,20,22,53,123,43,873,25,465],
action => 'accept',
}
firewall { '100 forward standard allow udp':
chain => 'FORWARD',
source => '10.0.0.0/8',
destination => '!10.0.0.0/8',
proto => 'udp',
port => [53,123],
action => 'accept',
}
firewall { '100 forward standard allow icmp':
chain => 'FORWARD',
source => '10.0.0.0/8',
destination => '!10.0.0.0/8',
proto => 'icmp',
action => 'accept',
}
firewall { '090 ignore ipsec':
table => 'nat',
chain => 'POSTROUTING',
outiface => 'eth0',
ipsec_policy => 'ipsec',
ipsec_dir => 'out',
action => 'accept',
}
firewall { '093 ignore 10.0.0.0/8':
table => 'nat',
chain => 'POSTROUTING',
outiface => 'eth0',
destination => '10.0.0.0/8',
action => 'accept',
}
firewall { '093 ignore 172.16.0.0/12':
table => 'nat',
chain => 'POSTROUTING',
outiface => 'eth0',
destination => '172.16.0.0/12',
action => 'accept',
}
firewall { '093 ignore 192.168.0.0/16':
table => 'nat',
chain => 'POSTROUTING',
outiface => 'eth0',
destination => '192.168.0.0/16',
action => 'accept',
}
firewall { '100 masq outbound':
table => 'nat',
chain => 'POSTROUTING',
outiface => 'eth0',
jump => 'MASQUERADE',
}
firewall { '101 redirect port 1':
table => 'nat',
chain => 'PREROUTING',
iniface => 'eth0',
proto => 'tcp',
dport => '1',
toports => '22',
jump => 'REDIRECT',
}
EOS
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero
end
it 'contains appropriate rules' do
shell('iptables-save') do |r|
[
/INPUT ACCEPT/,
/FORWARD ACCEPT/,
/OUTPUT ACCEPT/,
/-A FORWARD -s 10.0.0.0\/(8|255\.0\.0\.0) -d 10.0.0.0\/(8|255\.0\.0\.0) -m comment --comment \"090 forward allow local\" -j ACCEPT/,
/-A FORWARD -s 10.0.0.0\/(8|255\.0\.0\.0) (! -d|-d !) 10.0.0.0\/(8|255\.0\.0\.0) -p icmp -m comment --comment \"100 forward standard allow icmp\" -j ACCEPT/,
/-A FORWARD -s 10.0.0.0\/(8|255\.0\.0\.0) (! -d|-d !) 10.0.0.0\/(8|255\.0\.0\.0) -p tcp -m multiport --ports 80,443,21,20,22,53,123,43,873,25,465 -m comment --comment \"100 forward standard allow tcp\" -m state --state NEW -j ACCEPT/,
/-A FORWARD -s 10.0.0.0\/(8|255\.0\.0\.0) (! -d|-d !) 10.0.0.0\/(8|255\.0\.0\.0) -p udp -m multiport --ports 53,123 -m comment --comment \"100 forward standard allow udp\" -j ACCEPT/
].each do |line|
expect(r.stdout).to match(line)
end
end
end
end
describe 'complex ruleset 2' do
after :all do
shell('iptables -t filter -P INPUT ACCEPT')
shell('iptables -t filter -P FORWARD ACCEPT')
shell('iptables -t filter -P OUTPUT ACCEPT')
shell('iptables -t filter --flush')
expect(shell('iptables -t filter -X LOCAL_INPUT').stderr).to eq("")
expect(shell('iptables -t filter -X LOCAL_INPUT_PRE').stderr).to eq("")
end
it 'applies cleanly' do
pp = <<-EOS
class { '::firewall': }
Firewall {
proto => 'all',
stage => 'pre',
}
Firewallchain {
stage => 'pre',
purge => 'true',
ignore => [
'--comment "[^"]*(?i:ignore)[^"]*"',
],
}
firewall { '010 INPUT allow established and related':
proto => 'all',
state => ['ESTABLISHED', 'RELATED'],
action => 'accept',
before => Firewallchain['INPUT:filter:IPv4'],
}
firewall { '012 accept loopback':
iniface => 'lo',
action => 'accept',
before => Firewallchain['INPUT:filter:IPv4'],
}
firewall { '020 ssh':
proto => 'tcp',
dport => '22',
state => 'NEW',
action => 'accept',
before => Firewallchain['INPUT:filter:IPv4'],
}
firewall { '013 icmp echo-request':
proto => 'icmp',
icmp => 'echo-request',
action => 'accept',
source => '10.0.0.0/8',
}
firewall { '013 icmp destination-unreachable':
proto => 'icmp',
icmp => 'destination-unreachable',
action => 'accept',
}
firewall { '013 icmp time-exceeded':
proto => 'icmp',
icmp => 'time-exceeded',
action => 'accept',
}
firewall { '999 reject':
action => 'reject',
reject => 'icmp-host-prohibited',
}
firewallchain { 'LOCAL_INPUT_PRE:filter:IPv4': }
firewall { '001 LOCAL_INPUT_PRE':
jump => 'LOCAL_INPUT_PRE',
require => Firewallchain['LOCAL_INPUT_PRE:filter:IPv4'],
}
firewallchain { 'LOCAL_INPUT:filter:IPv4': }
firewall { '900 LOCAL_INPUT':
jump => 'LOCAL_INPUT',
require => Firewallchain['LOCAL_INPUT:filter:IPv4'],
}
firewallchain { 'INPUT:filter:IPv4':
policy => 'drop',
ignore => [
'-j fail2ban-ssh',
'--comment "[^"]*(?i:ignore)[^"]*"',
],
}
firewall { '010 allow established and related':
chain => 'FORWARD',
proto => 'all',
state => ['ESTABLISHED','RELATED'],
action => 'accept',
before => Firewallchain['FORWARD:filter:IPv4'],
}
firewallchain { 'FORWARD:filter:IPv4':
policy => 'drop',
}
firewallchain { 'OUTPUT:filter:IPv4': }
# purge unknown rules from mangle table
firewallchain { ['PREROUTING:mangle:IPv4', 'INPUT:mangle:IPv4', 'FORWARD:mangle:IPv4', 'OUTPUT:mangle:IPv4', 'POSTROUTING:mangle:IPv4']: }
# and the nat table
firewallchain { ['PREROUTING:nat:IPv4', 'INPUT:nat:IPv4', 'OUTPUT:nat:IPv4', 'POSTROUTING:nat:IPv4']: }
EOS
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
end
it 'contains appropriate rules' do
shell('iptables-save') do |r|
[
/INPUT DROP/,
/FORWARD DROP/,
/OUTPUT ACCEPT/,
/LOCAL_INPUT/,
/LOCAL_INPUT_PRE/,
/-A INPUT -m comment --comment \"001 LOCAL_INPUT_PRE\" -j LOCAL_INPUT_PRE/,
/-A INPUT -m comment --comment \"010 INPUT allow established and related\" -m state --state RELATED,ESTABLISHED -j ACCEPT/,
/-A INPUT -i lo -m comment --comment \"012 accept loopback\" -j ACCEPT/,
/-A INPUT -p icmp -m comment --comment \"013 icmp destination-unreachable\" -m icmp --icmp-type 3 -j ACCEPT/,
/-A INPUT -s 10.0.0.0\/(8|255\.0\.0\.0) -p icmp -m comment --comment \"013 icmp echo-request\" -m icmp --icmp-type 8 -j ACCEPT/,
/-A INPUT -p icmp -m comment --comment \"013 icmp time-exceeded\" -m icmp --icmp-type 11 -j ACCEPT/,
/-A INPUT -p tcp -m multiport --dports 22 -m comment --comment \"020 ssh\" -m state --state NEW -j ACCEPT/,
/-A INPUT -m comment --comment \"900 LOCAL_INPUT\" -j LOCAL_INPUT/,
/-A INPUT -m comment --comment \"999 reject\" -j REJECT --reject-with icmp-host-prohibited/,
/-A FORWARD -m comment --comment \"010 allow established and related\" -m state --state RELATED,ESTABLISHED -j ACCEPT/
].each do |line|
expect(r.stdout).to match(line)
end
end
end
end

View File

@ -0,0 +1,97 @@
require 'spec_helper_acceptance'
# RHEL5 does not support -m socket
describe 'firewall socket property', :unless => (default['platform'] =~ /el-5/ || fact('operatingsystem') == 'SLES') do
before :all do
iptables_flush_all_tables
end
shared_examples "is idempotent" do |value, line_match|
it "changes the value to #{value}" do
pp = <<-EOS
class { '::firewall': }
firewall { '598 - test':
ensure => present,
proto => 'tcp',
chain => 'PREROUTING',
table => 'raw',
#{value}
}
EOS
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
shell('iptables-save -t raw') do |r|
expect(r.stdout).to match(/#{line_match}/)
end
end
end
shared_examples "doesn't change" do |value, line_match|
it "doesn't change the value to #{value}" do
pp = <<-EOS
class { '::firewall': }
firewall { '598 - test':
ensure => present,
proto => 'tcp',
chain => 'PREROUTING',
table => 'raw',
#{value}
}
EOS
apply_manifest(pp, :catch_changes => true)
shell('iptables-save -t raw') do |r|
expect(r.stdout).to match(/#{line_match}/)
end
end
end
describe 'adding a rule' do
context 'when unset' do
before :all do
iptables_flush_all_tables
end
it_behaves_like 'is idempotent', '', /-A PREROUTING -p tcp -m comment --comment "598 - test"/
end
context 'when set to true' do
before :all do
iptables_flush_all_tables
end
it_behaves_like 'is idempotent', 'socket => true,', /-A PREROUTING -p tcp -m socket -m comment --comment "598 - test"/
end
context 'when set to false' do
before :all do
iptables_flush_all_tables
end
it_behaves_like "is idempotent", 'socket => false,', /-A PREROUTING -p tcp -m comment --comment "598 - test"/
end
end
describe 'editing a rule' do
context 'when unset or false' do
before :each do
iptables_flush_all_tables
shell('iptables -t raw -A PREROUTING -p tcp -m comment --comment "598 - test"')
end
context 'and current value is false' do
it_behaves_like "doesn't change", 'socket => false,', /-A PREROUTING -p tcp -m comment --comment "598 - test"/
end
context 'and current value is true' do
it_behaves_like "is idempotent", 'socket => true,', /-A PREROUTING -p tcp -m socket -m comment --comment "598 - test"/
end
end
context 'when set to true' do
before :each do
iptables_flush_all_tables
shell('iptables -t raw -A PREROUTING -p tcp -m socket -m comment --comment "598 - test"')
end
context 'and current value is false' do
it_behaves_like "is idempotent", 'socket => false,', /-A PREROUTING -p tcp -m comment --comment "598 - test"/
end
context 'and current value is true' do
it_behaves_like "doesn't change", 'socket => true,', /-A PREROUTING -p tcp -m socket -m comment --comment "598 - test"/
end
end
end
end

View File

@ -0,0 +1,55 @@
require 'spec_helper_acceptance'
# Some tests for the standard recommended usage
describe 'standard usage tests:' do
it 'applies twice' do
pp = <<-EOS
class my_fw::pre {
Firewall {
require => undef,
}
# Default firewall rules
firewall { '000 accept all icmp':
proto => 'icmp',
action => 'accept',
}->
firewall { '001 accept all to lo interface':
proto => 'all',
iniface => 'lo',
action => 'accept',
}->
firewall { '002 accept related established rules':
proto => 'all',
ctstate => ['RELATED', 'ESTABLISHED'],
action => 'accept',
}
}
class my_fw::post {
firewall { '999 drop all':
proto => 'all',
action => 'drop',
before => undef,
}
}
resources { "firewall":
purge => true
}
Firewall {
before => Class['my_fw::post'],
require => Class['my_fw::pre'],
}
class { ['my_fw::pre', 'my_fw::post']: }
class { 'firewall': }
firewall { '500 open up port 22':
action => 'accept',
proto => 'tcp',
dport => 22,
}
EOS
# Run it twice and test for idempotency
apply_manifest(pp, :catch_failures => true)
expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero
end
end

View File

@ -0,0 +1,107 @@
# These hashes allow us to iterate across a series of test data
# creating rspec examples for each parameter to ensure the input :line
# extrapolates to the desired value for the parameter in question. And
# vice-versa
# This hash is for testing a line conversion to a hash of parameters
# which will be used to create a resource.
ARGS_TO_HASH6 = {
'source_destination_ipv6_no_cidr' => {
:line => '-A INPUT -s 2001:db8:85a3::8a2e:370:7334 -d 2001:db8:85a3::8a2e:370:7334 -m comment --comment "000 source destination ipv6 no cidr"',
:table => 'filter',
:provider => 'ip6tables',
:params => {
:source => '2001:db8:85a3::8a2e:370:7334/128',
:destination => '2001:db8:85a3::8a2e:370:7334/128',
},
},
'source_destination_ipv6_netmask' => {
:line => '-A INPUT -s 2001:db8:1234::/ffff:ffff:ffff:0000:0000:0000:0000:0000 -d 2001:db8:4321::/ffff:ffff:ffff:0000:0000:0000:0000:0000 -m comment --comment "000 source destination ipv6 netmask"',
:table => 'filter',
:provider => 'ip6tables',
:params => {
:source => '2001:db8:1234::/48',
:destination => '2001:db8:4321::/48',
},
},
}
# This hash is for testing converting a hash to an argument line.
HASH_TO_ARGS6 = {
'zero_prefixlen_ipv6' => {
:params => {
:name => '100 zero prefix length ipv6',
:table => 'filter',
:provider => 'ip6tables',
:source => '::/0',
:destination => '::/0',
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'comment', '--comment', '100 zero prefix length ipv6'],
},
'source_destination_ipv4_no_cidr' => {
:params => {
:name => '000 source destination ipv4 no cidr',
:table => 'filter',
:provider => 'ip6tables',
:source => '1.1.1.1',
:destination => '2.2.2.2',
},
:args => ['-t', :filter, '-s', '1.1.1.1/32', '-d', '2.2.2.2/32', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv4 no cidr'],
},
'source_destination_ipv6_no_cidr' => {
:params => {
:name => '000 source destination ipv6 no cidr',
:table => 'filter',
:provider => 'ip6tables',
:source => '2001:db8:1234::',
:destination => '2001:db8:4321::',
},
:args => ['-t', :filter, '-s', '2001:db8:1234::/128', '-d', '2001:db8:4321::/128', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv6 no cidr'],
},
'source_destination_ipv6_netmask' => {
:params => {
:name => '000 source destination ipv6 netmask',
:table => 'filter',
:provider => 'ip6tables',
:source => '2001:db8:1234::/ffff:ffff:ffff:0000:0000:0000:0000:0000',
:destination => '2001:db8:4321::/ffff:ffff:ffff:0000:0000:0000:0000:0000',
},
:args => ['-t', :filter, '-s', '2001:db8:1234::/48', '-d', '2001:db8:4321::/48', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv6 netmask'],
},
'frag_ishasmorefrags' => {
:params => {
:name => "100 has more fragments",
:ishasmorefrags => true,
:provider => 'ip6tables',
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "frag", "--fragid", "0", "--fragmore", "-m", "comment", "--comment", "100 has more fragments"],
},
'frag_islastfrag' => {
:params => {
:name => "100 last fragment",
:islastfrag => true,
:provider => 'ip6tables',
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "frag", "--fragid", "0", "--fraglast", "-m", "comment", "--comment", "100 last fragment"],
},
'frag_isfirstfrags' => {
:params => {
:name => "100 first fragment",
:isfirstfrag => true,
:provider => 'ip6tables',
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "frag", "--fragid", "0", "--fragfirst", "-m", "comment", "--comment", "100 first fragment"],
},
'hop_limit' => {
:params => {
:name => "100 hop limit",
:hop_limit => 255,
:provider => 'ip6tables',
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "comment", "--comment", "100 hop limit", "-m", "hl", "--hl-eq", 255],
},
}

View File

@ -0,0 +1,871 @@
# These hashes allow us to iterate across a series of test data
# creating rspec examples for each parameter to ensure the input :line
# extrapolates to the desired value for the parameter in question. And
# vice-versa
# This hash is for testing a line conversion to a hash of parameters
# which will be used to create a resource.
ARGS_TO_HASH = {
'dport_and_sport' => {
:line => '-A nova-compute-FORWARD -s 0.0.0.0/32 -d 255.255.255.255/32 -p udp -m udp --sport 68 --dport 67 -j ACCEPT',
:table => 'filter',
:params => {
:action => 'accept',
:chain => 'nova-compute-FORWARD',
:source => '0.0.0.0/32',
:destination => '255.255.255.255/32',
:sport => ['68'],
:dport => ['67'],
:proto => 'udp',
},
},
'long_rule_1' => {
:line => '-A INPUT -s 1.1.1.1/32 -d 1.1.1.1/32 -p tcp -m multiport --dports 7061,7062 -m multiport --sports 7061,7062 -m comment --comment "000 allow foo" -j ACCEPT',
:table => 'filter',
:compare_all => true,
:params => {
:action => "accept",
:chain => "INPUT",
:destination => "1.1.1.1/32",
:dport => ["7061","7062"],
:ensure => :present,
:line => '-A INPUT -s 1.1.1.1/32 -d 1.1.1.1/32 -p tcp -m multiport --dports 7061,7062 -m multiport --sports 7061,7062 -m comment --comment "000 allow foo" -j ACCEPT',
:name => "000 allow foo",
:proto => "tcp",
:provider => "iptables",
:source => "1.1.1.1/32",
:sport => ["7061","7062"],
:table => "filter",
},
},
'action_drop_1' => {
:line => '-A INPUT -m comment --comment "000 allow foo" -j DROP',
:table => 'filter',
:params => {
:jump => nil,
:action => "drop",
},
},
'action_reject_1' => {
:line => '-A INPUT -m comment --comment "000 allow foo" -j REJECT',
:table => 'filter',
:params => {
:jump => nil,
:action => "reject",
},
},
'action_nil_1' => {
:line => '-A INPUT -m comment --comment "000 allow foo"',
:table => 'filter',
:params => {
:jump => nil,
:action => nil,
},
},
'jump_custom_chain_1' => {
:line => '-A INPUT -m comment --comment "000 allow foo" -j custom_chain',
:table => 'filter',
:params => {
:jump => "custom_chain",
:action => nil,
},
},
'source_destination_ipv4_no_cidr' => {
:line => '-A INPUT -s 1.1.1.1 -d 2.2.2.2 -m comment --comment "000 source destination ipv4 no cidr"',
:table => 'filter',
:params => {
:source => '1.1.1.1/32',
:destination => '2.2.2.2/32',
},
},
'source_destination_ipv6_no_cidr' => {
:line => '-A INPUT -s 2001:db8:85a3::8a2e:370:7334 -d 2001:db8:85a3::8a2e:370:7334 -m comment --comment "000 source destination ipv6 no cidr"',
:table => 'filter',
:params => {
:source => '2001:db8:85a3::8a2e:370:7334/128',
:destination => '2001:db8:85a3::8a2e:370:7334/128',
},
},
'source_destination_ipv4_netmask' => {
:line => '-A INPUT -s 1.1.1.0/255.255.255.0 -d 2.2.0.0/255.255.0.0 -m comment --comment "000 source destination ipv4 netmask"',
:table => 'filter',
:params => {
:source => '1.1.1.0/24',
:destination => '2.2.0.0/16',
},
},
'source_destination_ipv6_netmask' => {
:line => '-A INPUT -s 2001:db8:1234::/ffff:ffff:ffff:0000:0000:0000:0000:0000 -d 2001:db8:4321::/ffff:ffff:ffff:0000:0000:0000:0000:0000 -m comment --comment "000 source destination ipv6 netmask"',
:table => 'filter',
:params => {
:source => '2001:db8:1234::/48',
:destination => '2001:db8:4321::/48',
},
},
'source_destination_negate_source' => {
:line => '-A INPUT ! -s 1.1.1.1 -d 2.2.2.2 -m comment --comment "000 negated source address"',
:table => 'filter',
:params => {
:source => '! 1.1.1.1/32',
:destination => '2.2.2.2/32',
},
},
'source_destination_negate_destination' => {
:line => '-A INPUT -s 1.1.1.1 ! -d 2.2.2.2 -m comment --comment "000 negated destination address"',
:table => 'filter',
:params => {
:source => '1.1.1.1/32',
:destination => '! 2.2.2.2/32',
},
},
'source_destination_negate_destination_alternative' => {
:line => '-A INPUT -s 1.1.1.1 -d ! 2.2.2.2 -m comment --comment "000 negated destination address alternative"',
:table => 'filter',
:params => {
:source => '1.1.1.1/32',
:destination => '! 2.2.2.2/32',
},
},
'dport_range_1' => {
:line => '-A INPUT -m multiport --dports 1:1024 -m comment --comment "000 allow foo"',
:table => 'filter',
:params => {
:dport => ["1-1024"],
},
},
'dport_range_2' => {
:line => '-A INPUT -m multiport --dports 15,512:1024 -m comment --comment "000 allow foo"',
:table => 'filter',
:params => {
:dport => ["15","512-1024"],
},
},
'sport_range_1' => {
:line => '-A INPUT -m multiport --sports 1:1024 -m comment --comment "000 allow foo"',
:table => 'filter',
:params => {
:sport => ["1-1024"],
},
},
'sport_range_2' => {
:line => '-A INPUT -m multiport --sports 15,512:1024 -m comment --comment "000 allow foo"',
:table => 'filter',
:params => {
:sport => ["15","512-1024"],
},
},
'dst_type_1' => {
:line => '-A INPUT -m addrtype --dst-type LOCAL',
:table => 'filter',
:params => {
:dst_type => 'LOCAL',
},
},
'src_type_1' => {
:line => '-A INPUT -m addrtype --src-type LOCAL',
:table => 'filter',
:params => {
:src_type => 'LOCAL',
},
},
'dst_range_1' => {
:line => '-A INPUT -m iprange --dst-range 10.0.0.2-10.0.0.20',
:table => 'filter',
:params => {
:dst_range => '10.0.0.2-10.0.0.20',
},
},
'src_range_1' => {
:line => '-A INPUT -m iprange --src-range 10.0.0.2-10.0.0.20',
:table => 'filter',
:params => {
:src_range => '10.0.0.2-10.0.0.20',
},
},
'tcp_flags_1' => {
:line => '-A INPUT -p tcp -m tcp --tcp-flags SYN,RST,ACK,FIN SYN -m comment --comment "000 initiation"',
:table => 'filter',
:compare_all => true,
:chain => 'INPUT',
:proto => 'tcp',
:params => {
:chain => "INPUT",
:ensure => :present,
:line => '-A INPUT -p tcp -m tcp --tcp-flags SYN,RST,ACK,FIN SYN -m comment --comment "000 initiation"',
:name => "000 initiation",
:proto => "tcp",
:provider => "iptables",
:table => "filter",
:tcp_flags => "SYN,RST,ACK,FIN SYN",
},
},
'state_returns_sorted_values' => {
:line => '-A INPUT -m state --state INVALID,RELATED,ESTABLISHED',
:table => 'filter',
:params => {
:state => ['ESTABLISHED', 'INVALID', 'RELATED'],
:action => nil,
},
},
'ctstate_returns_sorted_values' => {
:line => '-A INPUT -m conntrack --ctstate INVALID,RELATED,ESTABLISHED',
:table => 'filter',
:params => {
:ctstate => ['ESTABLISHED', 'INVALID', 'RELATED'],
:action => nil,
},
},
'comment_string_character_validation' => {
:line => '-A INPUT -s 192.168.0.1/32 -m comment --comment "000 allow from 192.168.0.1, please"',
:table => 'filter',
:params => {
:source => '192.168.0.1/32',
},
},
'log_level_debug' => {
:line => '-A INPUT -m comment --comment "956 INPUT log-level" -m state --state NEW -j LOG --log-level 7',
:table => 'filter',
:params => {
:state => ['NEW'],
:log_level => '7',
:jump => 'LOG'
},
},
'log_level_warn' => {
:line => '-A INPUT -m comment --comment "956 INPUT log-level" -m state --state NEW -j LOG',
:table => 'filter',
:params => {
:state => ['NEW'],
:log_level => '4',
:jump => 'LOG'
},
},
'load_limit_module_and_implicit_burst' => {
:line => '-A INPUT -m multiport --dports 123 -m comment --comment "057 INPUT limit NTP" -m limit --limit 15/hour',
:table => 'filter',
:params => {
:dport => ['123'],
:limit => '15/hour',
:burst => '5'
},
},
'limit_with_explicit_burst' => {
:line => '-A INPUT -m multiport --dports 123 -m comment --comment "057 INPUT limit NTP" -m limit --limit 30/hour --limit-burst 10',
:table => 'filter',
:params => {
:dport => ['123'],
:limit => '30/hour',
:burst => '10'
},
},
'proto_ipencap' => {
:line => '-A INPUT -p ipencap -m comment --comment "0100 INPUT accept ipencap"',
:table => 'filter',
:params => {
:proto => 'ipencap',
}
},
'load_uid_owner_filter_module' => {
:line => '-A OUTPUT -m owner --uid-owner root -m comment --comment "057 OUTPUT uid root only" -j ACCEPT',
:table => 'filter',
:params => {
:action => 'accept',
:uid => 'root',
:chain => 'OUTPUT',
},
},
'load_uid_owner_postrouting_module' => {
:line => '-t mangle -A POSTROUTING -m owner --uid-owner root -m comment --comment "057 POSTROUTING uid root only" -j ACCEPT',
:table => 'mangle',
:params => {
:action => 'accept',
:chain => 'POSTROUTING',
:uid => 'root',
},
},
'load_gid_owner_filter_module' => {
:line => '-A OUTPUT -m owner --gid-owner root -m comment --comment "057 OUTPUT gid root only" -j ACCEPT',
:table => 'filter',
:params => {
:action => 'accept',
:chain => 'OUTPUT',
:gid => 'root',
},
},
'load_gid_owner_postrouting_module' => {
:line => '-t mangle -A POSTROUTING -m owner --gid-owner root -m comment --comment "057 POSTROUTING gid root only" -j ACCEPT',
:table => 'mangle',
:params => {
:action => 'accept',
:chain => 'POSTROUTING',
:gid => 'root',
},
},
'mark_set-mark' => {
:line => '-t mangle -A PREROUTING -j MARK --set-xmark 0x3e8/0xffffffff',
:table => 'mangle',
:params => {
:jump => 'MARK',
:chain => 'PREROUTING',
:set_mark => '0x3e8/0xffffffff',
}
},
'iniface_1' => {
:line => '-A INPUT -i eth0 -m comment --comment "060 iniface" -j DROP',
:table => 'filter',
:params => {
:action => 'drop',
:chain => 'INPUT',
:iniface => 'eth0',
},
},
'iniface_with_vlans_1' => {
:line => '-A INPUT -i eth0.234 -m comment --comment "060 iniface" -j DROP',
:table => 'filter',
:params => {
:action => 'drop',
:chain => 'INPUT',
:iniface => 'eth0.234',
},
},
'iniface_with_plus_1' => {
:line => '-A INPUT -i eth+ -m comment --comment "060 iniface" -j DROP',
:table => 'filter',
:params => {
:action => 'drop',
:chain => 'INPUT',
:iniface => 'eth+',
},
},
'outiface_1' => {
:line => '-A OUTPUT -o eth0 -m comment --comment "060 outiface" -j DROP',
:table => 'filter',
:params => {
:action => 'drop',
:chain => 'OUTPUT',
:outiface => 'eth0',
},
},
'outiface_with_vlans_1' => {
:line => '-A OUTPUT -o eth0.234 -m comment --comment "060 outiface" -j DROP',
:table => 'filter',
:params => {
:action => 'drop',
:chain => 'OUTPUT',
:outiface => 'eth0.234',
},
},
'outiface_with_plus_1' => {
:line => '-A OUTPUT -o eth+ -m comment --comment "060 outiface" -j DROP',
:table => 'filter',
:params => {
:action => 'drop',
:chain => 'OUTPUT',
:outiface => 'eth+',
},
},
'pkttype multicast' => {
:line => '-A INPUT -m pkttype --pkt-type multicast -j ACCEPT',
:table => 'filter',
:params => {
:action => 'accept',
:pkttype => 'multicast',
},
},
'socket_option' => {
:line => '-A PREROUTING -m socket -j ACCEPT',
:table => 'mangle',
:params => {
:action => 'accept',
:chain => 'PREROUTING',
:socket => true,
},
},
'isfragment_option' => {
:line => '-A INPUT -f -m comment --comment "010 a-f comment with dashf" -j ACCEPT',
:table => 'filter',
:params => {
:name => '010 a-f comment with dashf',
:action => 'accept',
:isfragment => true,
},
},
'single_tcp_sport' => {
:line => '-A OUTPUT -s 10.94.100.46/32 -p tcp -m tcp --sport 20443 -j ACCEPT',
:table => 'mangle',
:params => {
:action => 'accept',
:chain => 'OUTPUT',
:source => "10.94.100.46/32",
:proto => "tcp",
:sport => ["20443"],
},
},
'single_udp_sport' => {
:line => '-A OUTPUT -s 10.94.100.46/32 -p udp -m udp --sport 20443 -j ACCEPT',
:table => 'mangle',
:params => {
:action => 'accept',
:chain => 'OUTPUT',
:source => "10.94.100.46/32",
:proto => "udp",
:sport => ["20443"],
},
},
'single_tcp_dport' => {
:line => '-A OUTPUT -s 10.94.100.46/32 -p tcp -m tcp --dport 20443 -j ACCEPT',
:table => 'mangle',
:params => {
:action => 'accept',
:chain => 'OUTPUT',
:source => "10.94.100.46/32",
:proto => "tcp",
:dport => ["20443"],
},
},
'single_udp_dport' => {
:line => '-A OUTPUT -s 10.94.100.46/32 -p udp -m udp --dport 20443 -j ACCEPT',
:table => 'mangle',
:params => {
:action => 'accept',
:chain => 'OUTPUT',
:source => "10.94.100.46/32",
:proto => "udp",
:dport => ["20443"],
},
},
}
# This hash is for testing converting a hash to an argument line.
HASH_TO_ARGS = {
'long_rule_1' => {
:params => {
:action => "accept",
:chain => "INPUT",
:destination => "1.1.1.1",
:dport => ["7061","7062"],
:ensure => :present,
:name => "000 allow foo",
:proto => "tcp",
:source => "1.1.1.1",
:sport => ["7061","7062"],
:table => "filter",
},
:args => ["-t", :filter, "-s", "1.1.1.1/32", "-d", "1.1.1.1/32", "-p", :tcp, "-m", "multiport", "--sports", "7061,7062", "-m", "multiport", "--dports", "7061,7062", "-m", "comment", "--comment", "000 allow foo", "-j", "ACCEPT"],
},
'long_rule_2' => {
:params => {
:chain => "INPUT",
:destination => "2.10.13.3/24",
:dport => ["7061"],
:ensure => :present,
:jump => "my_custom_chain",
:name => "700 allow bar",
:proto => "udp",
:source => "1.1.1.1",
:sport => ["7061","7062"],
:table => "filter",
},
:args => ["-t", :filter, "-s", "1.1.1.1/32", "-d", "2.10.13.0/24", "-p", :udp, "-m", "multiport", "--sports", "7061,7062", "-m", "multiport", "--dports", "7061", "-m", "comment", "--comment", "700 allow bar", "-j", "my_custom_chain"],
},
'no_action' => {
:params => {
:name => "100 no action",
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "comment", "--comment",
"100 no action"],
},
'zero_prefixlen_ipv4' => {
:params => {
:name => '100 zero prefix length ipv4',
:table => 'filter',
:source => '0.0.0.0/0',
:destination => '0.0.0.0/0',
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'comment', '--comment', '100 zero prefix length ipv4'],
},
'zero_prefixlen_ipv6' => {
:params => {
:name => '100 zero prefix length ipv6',
:table => 'filter',
:source => '::/0',
:destination => '::/0',
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'comment', '--comment', '100 zero prefix length ipv6'],
},
'source_destination_ipv4_no_cidr' => {
:params => {
:name => '000 source destination ipv4 no cidr',
:table => 'filter',
:source => '1.1.1.1',
:destination => '2.2.2.2',
},
:args => ['-t', :filter, '-s', '1.1.1.1/32', '-d', '2.2.2.2/32', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv4 no cidr'],
},
'source_destination_ipv6_no_cidr' => {
:params => {
:name => '000 source destination ipv6 no cidr',
:table => 'filter',
:source => '2001:db8:1234::',
:destination => '2001:db8:4321::',
},
:args => ['-t', :filter, '-s', '2001:db8:1234::/128', '-d', '2001:db8:4321::/128', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv6 no cidr'],
},
'source_destination_ipv4_netmask' => {
:params => {
:name => '000 source destination ipv4 netmask',
:table => 'filter',
:source => '1.1.1.0/255.255.255.0',
:destination => '2.2.0.0/255.255.0.0',
},
:args => ['-t', :filter, '-s', '1.1.1.0/24', '-d', '2.2.0.0/16', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv4 netmask'],
},
'source_destination_ipv6_netmask' => {
:params => {
:name => '000 source destination ipv6 netmask',
:table => 'filter',
:source => '2001:db8:1234::/ffff:ffff:ffff:0000:0000:0000:0000:0000',
:destination => '2001:db8:4321::/ffff:ffff:ffff:0000:0000:0000:0000:0000',
},
:args => ['-t', :filter, '-s', '2001:db8:1234::/48', '-d', '2001:db8:4321::/48', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv6 netmask'],
},
'sport_range_1' => {
:params => {
:name => "100 sport range",
:sport => ["1-1024"],
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "multiport", "--sports", "1:1024", "-m", "comment", "--comment", "100 sport range"],
},
'sport_range_2' => {
:params => {
:name => "100 sport range",
:sport => ["15","512-1024"],
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "multiport", "--sports", "15,512:1024", "-m", "comment", "--comment", "100 sport range"],
},
'dport_range_1' => {
:params => {
:name => "100 sport range",
:dport => ["1-1024"],
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "multiport", "--dports", "1:1024", "-m", "comment", "--comment", "100 sport range"],
},
'dport_range_2' => {
:params => {
:name => "100 sport range",
:dport => ["15","512-1024"],
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "multiport", "--dports", "15,512:1024", "-m", "comment", "--comment", "100 sport range"],
},
'dst_type_1' => {
:params => {
:name => '000 dst_type',
:table => 'filter',
:dst_type => 'LOCAL',
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--dst-type', :LOCAL, '-m', 'comment', '--comment', '000 dst_type'],
},
'src_type_1' => {
:params => {
:name => '000 src_type',
:table => 'filter',
:src_type => 'LOCAL',
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--src-type', :LOCAL, '-m', 'comment', '--comment', '000 src_type'],
},
'dst_range_1' => {
:params => {
:name => '000 dst_range',
:table => 'filter',
:dst_range => '10.0.0.1-10.0.0.10',
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'iprange', '--dst-range', '10.0.0.1-10.0.0.10', '-m', 'comment', '--comment', '000 dst_range'],
},
'src_range_1' => {
:params => {
:name => '000 src_range',
:table => 'filter',
:dst_range => '10.0.0.1-10.0.0.10',
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'iprange', '--dst-range', '10.0.0.1-10.0.0.10', '-m', 'comment', '--comment', '000 src_range'],
},
'tcp_flags_1' => {
:params => {
:name => "000 initiation",
:tcp_flags => "SYN,RST,ACK,FIN SYN",
:table => "filter",
},
:args => ["-t", :filter, "-p", :tcp, "-m", "tcp", "--tcp-flags", "SYN,RST,ACK,FIN", "SYN", "-m", "comment", "--comment", "000 initiation",]
},
'states_set_from_array' => {
:params => {
:name => "100 states_set_from_array",
:table => "filter",
:state => ['ESTABLISHED', 'INVALID']
},
:args => ["-t", :filter, "-p", :tcp, "-m", "comment", "--comment", "100 states_set_from_array",
"-m", "state", "--state", "ESTABLISHED,INVALID"],
},
'ctstates_set_from_array' => {
:params => {
:name => "100 ctstates_set_from_array",
:table => "filter",
:ctstate => ['ESTABLISHED', 'INVALID']
},
:args => ["-t", :filter, "-p", :tcp, "-m", "comment", "--comment", "100 ctstates_set_from_array",
"-m", "conntrack", "--ctstate", "ESTABLISHED,INVALID"],
},
'comment_string_character_validation' => {
:params => {
:name => "000 allow from 192.168.0.1, please",
:table => 'filter',
:source => '192.168.0.1'
},
:args => ['-t', :filter, '-s', '192.168.0.1/32', '-p', :tcp, '-m', 'comment', '--comment', '000 allow from 192.168.0.1, please'],
},
'port_property' => {
:params => {
:name => '001 port property',
:table => 'filter',
:port => '80',
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'multiport', '--ports', '80', '-m', 'comment', '--comment', '001 port property'],
},
'log_level_debug' => {
:params => {
:name => '956 INPUT log-level',
:table => 'filter',
:state => 'NEW',
:jump => 'LOG',
:log_level => 'debug'
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'comment', '--comment', '956 INPUT log-level', '-m', 'state', '--state', 'NEW', '-j', 'LOG', '--log-level', '7'],
},
'log_level_warn' => {
:params => {
:name => '956 INPUT log-level',
:table => 'filter',
:state => 'NEW',
:jump => 'LOG',
:log_level => 'warn'
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'comment', '--comment', '956 INPUT log-level', '-m', 'state', '--state', 'NEW', '-j', 'LOG', '--log-level', '4'],
},
'load_limit_module_and_implicit_burst' => {
:params => {
:name => '057 INPUT limit NTP',
:table => 'filter',
:dport => '123',
:limit => '15/hour'
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'multiport', '--dports', '123', '-m', 'comment', '--comment', '057 INPUT limit NTP', '-m', 'limit', '--limit', '15/hour'],
},
'limit_with_explicit_burst' => {
:params => {
:name => '057 INPUT limit NTP',
:table => 'filter',
:dport => '123',
:limit => '30/hour',
:burst => '10'
},
:args => ['-t', :filter, '-p', :tcp, '-m', 'multiport', '--dports', '123', '-m', 'comment', '--comment', '057 INPUT limit NTP', '-m', 'limit', '--limit', '30/hour', '--limit-burst', '10'],
},
'proto_ipencap' => {
:params => {
:name => '0100 INPUT accept ipencap',
:table => 'filter',
:proto => 'ipencap',
},
:args => ['-t', :filter, '-p', :ipencap, '-m', 'comment', '--comment', '0100 INPUT accept ipencap'],
},
'load_uid_owner_filter_module' => {
:params => {
:name => '057 OUTPUT uid root only',
:table => 'filter',
:uid => 'root',
:action => 'accept',
:chain => 'OUTPUT',
:proto => 'all',
},
:args => ['-t', :filter, '-p', :all, '-m', 'owner', '--uid-owner', 'root', '-m', 'comment', '--comment', '057 OUTPUT uid root only', '-j', 'ACCEPT'],
},
'load_uid_owner_postrouting_module' => {
:params => {
:name => '057 POSTROUTING uid root only',
:table => 'mangle',
:uid => 'root',
:action => 'accept',
:chain => 'POSTROUTING',
:proto => 'all',
},
:args => ['-t', :mangle, '-p', :all, '-m', 'owner', '--uid-owner', 'root', '-m', 'comment', '--comment', '057 POSTROUTING uid root only', '-j', 'ACCEPT'],
},
'load_gid_owner_filter_module' => {
:params => {
:name => '057 OUTPUT gid root only',
:table => 'filter',
:chain => 'OUTPUT',
:gid => 'root',
:action => 'accept',
:proto => 'all',
},
:args => ['-t', :filter, '-p', :all, '-m', 'owner', '--gid-owner', 'root', '-m', 'comment', '--comment', '057 OUTPUT gid root only', '-j', 'ACCEPT'],
},
'load_gid_owner_postrouting_module' => {
:params => {
:name => '057 POSTROUTING gid root only',
:table => 'mangle',
:gid => 'root',
:action => 'accept',
:chain => 'POSTROUTING',
:proto => 'all',
},
:args => ['-t', :mangle, '-p', :all, '-m', 'owner', '--gid-owner', 'root', '-m', 'comment', '--comment', '057 POSTROUTING gid root only', '-j', 'ACCEPT'],
},
'mark_set-mark_int' => {
:params => {
:name => '058 set-mark 1000',
:table => 'mangle',
:jump => 'MARK',
:chain => 'PREROUTING',
:set_mark => '1000',
},
:args => ['-t', :mangle, '-p', :tcp, '-m', 'comment', '--comment', '058 set-mark 1000', '-j', 'MARK', '--set-xmark', '0x3e8/0xffffffff'],
},
'mark_set-mark_hex' => {
:params => {
:name => '058 set-mark 0x32',
:table => 'mangle',
:jump => 'MARK',
:chain => 'PREROUTING',
:set_mark => '0x32',
},
:args => ['-t', :mangle, '-p', :tcp, '-m', 'comment', '--comment', '058 set-mark 0x32', '-j', 'MARK', '--set-xmark', '0x32/0xffffffff'],
},
'mark_set-mark_hex_with_hex_mask' => {
:params => {
:name => '058 set-mark 0x32/0xffffffff',
:table => 'mangle',
:jump => 'MARK',
:chain => 'PREROUTING',
:set_mark => '0x32/0xffffffff',
},
:args => ['-t', :mangle, '-p', :tcp, '-m', 'comment', '--comment', '058 set-mark 0x32/0xffffffff', '-j', 'MARK', '--set-xmark', '0x32/0xffffffff'],
},
'mark_set-mark_hex_with_mask' => {
:params => {
:name => '058 set-mark 0x32/4',
:table => 'mangle',
:jump => 'MARK',
:chain => 'PREROUTING',
:set_mark => '0x32/4',
},
:args => ['-t', :mangle, '-p', :tcp, '-m', 'comment', '--comment', '058 set-mark 0x32/4', '-j', 'MARK', '--set-xmark', '0x32/0x4'],
},
'iniface_1' => {
:params => {
:name => '060 iniface',
:table => 'filter',
:action => 'drop',
:chain => 'INPUT',
:iniface => 'eth0',
},
:args => ["-t", :filter, "-i", "eth0", "-p", :tcp, "-m", "comment", "--comment", "060 iniface", "-j", "DROP"],
},
'iniface_with_vlans_1' => {
:params => {
:name => '060 iniface',
:table => 'filter',
:action => 'drop',
:chain => 'INPUT',
:iniface => 'eth0.234',
},
:args => ["-t", :filter, "-i", "eth0.234", "-p", :tcp, "-m", "comment", "--comment", "060 iniface", "-j", "DROP"],
},
'iniface_with_plus_1' => {
:params => {
:name => '060 iniface',
:table => 'filter',
:action => 'drop',
:chain => 'INPUT',
:iniface => 'eth+',
},
:args => ["-t", :filter, "-i", "eth+", "-p", :tcp, "-m", "comment", "--comment", "060 iniface", "-j", "DROP"],
},
'outiface_1' => {
:params => {
:name => '060 outiface',
:table => 'filter',
:action => 'drop',
:chain => 'OUTPUT',
:outiface => 'eth0',
},
:args => ["-t", :filter, "-o", "eth0", "-p", :tcp, "-m", "comment", "--comment", "060 outiface", "-j", "DROP"],
},
'outiface_with_vlans_1' => {
:params => {
:name => '060 outiface',
:table => 'filter',
:action => 'drop',
:chain => 'OUTPUT',
:outiface => 'eth0.234',
},
:args => ["-t", :filter, "-o", "eth0.234", "-p", :tcp, "-m", "comment", "--comment", "060 outiface", "-j", "DROP"],
},
'outiface_with_plus_1' => {
:params => {
:name => '060 outiface',
:table => 'filter',
:action => 'drop',
:chain => 'OUTPUT',
:outiface => 'eth+',
},
:args => ["-t", :filter, "-o", "eth+", "-p", :tcp, "-m", "comment", "--comment", "060 outiface", "-j", "DROP"],
},
'pkttype multicast' => {
:params => {
:name => '062 pkttype multicast',
:table => "filter",
:action => 'accept',
:chain => 'INPUT',
:iniface => 'eth0',
:pkttype => 'multicast',
},
:args => ["-t", :filter, "-i", "eth0", "-p", :tcp, "-m", "pkttype", "--pkt-type", :multicast, "-m", "comment", "--comment", "062 pkttype multicast", "-j", "ACCEPT"],
},
'socket_option' => {
:params => {
:name => '050 socket option',
:table => 'mangle',
:action => 'accept',
:chain => 'PREROUTING',
:socket => true,
},
:args => ['-t', :mangle, '-p', :tcp, '-m', 'socket', '-m', 'comment', '--comment', '050 socket option', '-j', 'ACCEPT'],
},
'isfragment_option' => {
:params => {
:name => '050 isfragment option',
:table => 'filter',
:proto => :all,
:action => 'accept',
:isfragment => true,
},
:args => ['-t', :filter, '-p', :all, '-f', '-m', 'comment', '--comment', '050 isfragment option', '-j', 'ACCEPT'],
},
'isfragment_option not changing -f in comment' => {
:params => {
:name => '050 testcomment-with-fdashf',
:table => 'filter',
:proto => :all,
:action => 'accept',
},
:args => ['-t', :filter, '-p', :all, '-m', 'comment', '--comment', '050 testcomment-with-fdashf', '-j', 'ACCEPT'],
},
}

View File

@ -0,0 +1 @@
../../../../lib

View File

@ -0,0 +1 @@
../../../../manifests

View File

@ -0,0 +1,29 @@
dir = File.expand_path(File.dirname(__FILE__))
$LOAD_PATH.unshift File.join(dir, 'lib')
# Don't want puppet getting the command line arguments for rake or autotest
ARGV.clear
require 'rubygems'
require 'bundler/setup'
require 'rspec-puppet'
Bundler.require :default, :test
require 'pathname'
require 'tmpdir'
Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour|
require behaviour.relative_path_from(Pathname.new(dir))
end
fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))
RSpec.configure do |config|
config.tty = true
config.mock_with :rspec do |c|
c.syntax = :expect
end
config.module_path = File.join(fixture_path, 'modules')
config.manifest_dir = File.join(fixture_path, 'manifests')
end

View File

@ -0,0 +1,44 @@
require 'beaker-rspec'
def iptables_flush_all_tables
['filter', 'nat', 'mangle', 'raw'].each do |t|
expect(shell("iptables -t #{t} -F").stderr).to eq("")
end
end
def ip6tables_flush_all_tables
['filter'].each do |t|
expect(shell("ip6tables -t #{t} -F").stderr).to eq("")
end
end
unless ENV['RS_PROVISION'] == 'no'
hosts.each do |host|
# Install Puppet
if host.is_pe?
install_pe
else
install_package host, 'rubygems'
on host, 'gem install puppet --no-ri --no-rdoc'
on host, "mkdir -p #{host['distmoduledir']}"
end
end
end
RSpec.configure do |c|
# Project root
proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
# Readable test descriptions
c.formatter = :documentation
# Configure all nodes in nodeset
c.before :suite do
# Install module and dependencies
puppet_module_install(:source => proj_root, :module_name => 'firewall')
hosts.each do |host|
shell('/bin/touch /etc/puppet/hiera.yaml')
shell('puppet module install puppetlabs-stdlib --version 3.2.0', { :acceptable_exit_codes => [0,1] })
end
end
end

View File

@ -0,0 +1,49 @@
# This helper file is specific to the system tests for puppetlabs-firewall
# and should be included by all tests under spec/system
require 'rspec-system/spec_helper'
require 'rspec-system-puppet/helpers'
# Just some helpers specific to this module
module LocalHelpers
# This helper flushes all tables on the default machine.
#
# It checks that the flush command returns with no errors.
#
# @return [void]
# @todo Need to optionally do the newer tables
# @example
# it 'should flush tables' do
# iptables_flush_all_tables
# end
def iptables_flush_all_tables
['filter', 'nat', 'mangle', 'raw'].each do |t|
system_run("/sbin/iptables -t #{t} -F") do |r|
r[:exit_code].should == 0
r[:stderr].should == ''
end
end
end
end
RSpec.configure do |c|
# Project root for the firewall code
proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
# Enable colour in Jenkins
c.tty = true
# Import in our local helpers
c.include ::LocalHelpers
# This is where we 'setup' the nodes before running our tests
c.system_setup_block = proc do
# TODO: find a better way of importing this into this namespace
include RSpecSystemPuppet::Helpers
# Install puppet
puppet_install
# Copy this module into the module path of the test node
puppet_module_install(:source => proj_root, :module_name => 'firewall')
end
end

View File

@ -0,0 +1,13 @@
require 'spec_helper_system'
# Here we put the more basic fundamental tests, ultra obvious stuff.
describe "basic tests:" do
it 'make sure we have copied the module across' do
# No point diagnosing any more if the module wasn't copied properly
system_run("ls /etc/puppet/modules/firewall") do |r|
r[:exit_code].should == 0
r[:stdout].should =~ /Modulefile/
r[:stderr].should == ''
end
end
end

View File

@ -0,0 +1,23 @@
require 'spec_helper_system'
describe "firewall class:" do
let(:pp) do
pp = <<-EOS
class { 'firewall': }
EOS
end
it "should run without event" do
puppet_apply(pp) do |r|
r[:stderr].should == ''
r[:exit_code].should_not eq(1)
end
end
it "should be idempotent" do
puppet_apply(pp) do |r|
r[:stderr].should == ''
r[:exit_code].should == 0
end
end
end

View File

@ -0,0 +1,48 @@
require 'spec_helper_system'
describe "param based tests:" do
def pp(params)
pm = <<-EOS
firewall { '100 test':
EOS
params.each do |k,v|
pm += <<-EOS
#{k} => #{v},
EOS
end
pm += <<-EOS
}
EOS
pm
end
it 'test socket param' do
facts = system_node.facts
unless (facts['operatingsystem'] == 'CentOS') && \
facts['operatingsystemrelease'] =~ /^5\./ then
iptables_flush_all_tables
param = {
'table' => "'raw'",
'socket' => 'true',
'chain' => "'PREROUTING'",
}
ppm = pp(param)
puppet_apply(ppm) do |r|
r[:stderr].should == ''
r[:exit_code].should == 2
end
# check idempotency
puppet_apply(ppm) do |r|
r[:stderr].should == ''
r[:exit_code].should == 0
end
end
end
end

View File

@ -0,0 +1,25 @@
require 'spec_helper_system'
describe "purge tests:" do
it 'make sure duplicate existing rules get purged' do
iptables_flush_all_tables
system_run('/sbin/iptables -A INPUT -s 1.2.1.2')
system_run('/sbin/iptables -A INPUT -s 1.2.1.2')
pp = <<-EOS
class { 'firewall': }
resources { 'firewall':
purge => true,
}
EOS
puppet_apply(pp) do |r|
r[:stderr].should == ''
r[:exit_code].should == 2
end
system_run('/sbin/iptables-save') do |r|
r[:stdout].should_not =~ /1\.2\.1\.2/
r[:stderr].should == ''
end
end
end

View File

@ -0,0 +1,25 @@
require 'spec_helper_system'
# Here we want to test the the resource commands ability to work with different
# existing ruleset scenarios. This will give the parsing capabilities of the
# code a good work out.
describe 'puppet resource firewall command:' do
it 'make sure it returns no errors when executed on a clean machine' do
puppet_resource('firewall') do |r|
r[:exit_code].should == 0
# don't check stdout, some boxes come with rules, that is normal
r[:stderr].should == ''
end
end
it 'flush iptables and make sure it returns nothing afterwards' do
iptables_flush_all_tables
# No rules, means no output thanks. And no errors as well.
puppet_resource('firewall') do |r|
r[:exit_code].should == 0
r[:stderr].should == ''
r[:stdout].should == "\n"
end
end
end

View File

@ -0,0 +1,65 @@
require 'spec_helper_system'
# Some tests for the standard recommended usage
describe "standard usage:" do
let(:pp) do
pp = <<-EOS
class my_fw::pre {
Firewall {
require => undef,
}
# Default firewall rules
firewall { '000 accept all icmp':
proto => 'icmp',
action => 'accept',
}->
firewall { '001 accept all to lo interface':
proto => 'all',
iniface => 'lo',
action => 'accept',
}->
firewall { '002 accept related established rules':
proto => 'all',
state => ['RELATED', 'ESTABLISHED'],
action => 'accept',
}
}
class my_fw::post {
firewall { '999 drop all':
proto => 'all',
action => 'drop',
before => undef,
}
}
resources { "firewall":
purge => true
}
Firewall {
before => Class['my_fw::post'],
require => Class['my_fw::pre'],
}
class { ['my_fw::pre', 'my_fw::post']: }
class { 'firewall': }
firewall { '500 open up port 22':
action => 'accept',
proto => 'tcp',
dport => 22,
}
EOS
end
it 'make sure it runs without error' do
puppet_apply(pp) do |r|
r[:stderr].should == ''
r[:exit_code].should_not eq(1)
end
end
it 'should be idempotent' do
puppet_apply(pp) do |r|
r[:stderr].should == ''
r[:exit_code].should == 0
end
end
end

View File

@ -0,0 +1,32 @@
require 'spec_helper'
describe 'firewall::linux::archlinux', :type => :class do
it { should contain_service('iptables').with(
:ensure => 'running',
:enable => 'true'
)}
it { should contain_service('ip6tables').with(
:ensure => 'running',
:enable => 'true'
)}
context 'ensure => stopped' do
let(:params) {{ :ensure => 'stopped' }}
it { should contain_service('iptables').with(
:ensure => 'stopped'
)}
it { should contain_service('ip6tables').with(
:ensure => 'stopped'
)}
end
context 'enable => false' do
let(:params) {{ :enable => 'false' }}
it { should contain_service('iptables').with(
:enable => 'false'
)}
it { should contain_service('ip6tables').with(
:enable => 'false'
)}
end
end

View File

@ -0,0 +1,19 @@
require 'spec_helper'
describe 'firewall::linux::debian', :type => :class do
it { should contain_package('iptables-persistent').with(
:ensure => 'present'
)}
it { should contain_service('iptables-persistent').with(
:ensure => nil,
:enable => 'true',
:require => 'Package[iptables-persistent]'
)}
context 'enable => false' do
let(:params) {{ :enable => 'false' }}
it { should contain_service('iptables-persistent').with(
:enable => 'false'
)}
end
end

View File

@ -0,0 +1,22 @@
require 'spec_helper'
describe 'firewall::linux::redhat', :type => :class do
it { should contain_service('iptables').with(
:ensure => 'running',
:enable => 'true'
)}
context 'ensure => stopped' do
let(:params) {{ :ensure => 'stopped' }}
it { should contain_service('iptables').with(
:ensure => 'stopped'
)}
end
context 'enable => false' do
let(:params) {{ :enable => 'false' }}
it { should contain_service('iptables').with(
:enable => 'false'
)}
end
end

View File

@ -0,0 +1,24 @@
require 'spec_helper'
describe 'firewall::linux', :type => :class do
let(:facts_default) {{ :kernel => 'Linux' }}
it { should contain_package('iptables').with_ensure('present') }
context 'RedHat like' do
%w{RedHat CentOS Fedora}.each do |os|
context "operatingsystem => #{os}" do
let(:facts) { facts_default.merge({ :operatingsystem => os }) }
it { should contain_class('firewall::linux::redhat').with_require('Package[iptables]') }
end
end
end
context 'Debian like' do
%w{Debian Ubuntu}.each do |os|
context "operatingsystem => #{os}" do
let(:facts) { facts_default.merge({ :operatingsystem => os }) }
it { should contain_class('firewall::linux::debian').with_require('Package[iptables]') }
end
end
end
end

View File

@ -0,0 +1,25 @@
require 'spec_helper'
describe 'firewall', :type => :class do
context 'kernel => Linux' do
let(:facts) {{ :kernel => 'Linux' }}
it { should contain_class('firewall::linux').with_ensure('running') }
end
context 'kernel => Windows' do
let(:facts) {{ :kernel => 'Windows' }}
it { expect { should contain_class('firewall::linux') }.to raise_error(Puppet::Error) }
end
context 'ensure => stopped' do
let(:facts) {{ :kernel => 'Linux' }}
let(:params) {{ :ensure => 'stopped' }}
it { should contain_class('firewall::linux').with_ensure('stopped') }
end
context 'ensure => test' do
let(:facts) {{ :kernel => 'Linux' }}
let(:params) {{ :ensure => 'test' }}
it { expect { should contain_class('firewall::linux') }.to raise_error(Puppet::Error) }
end
end

View File

@ -0,0 +1,35 @@
require 'spec_helper'
describe "Facter::Util::Fact iptables_persistent_version" do
before { Facter.clear }
let(:dpkg_cmd) { "dpkg-query -Wf '${Version}' iptables-persistent 2>/dev/null" }
{
"Debian" => "0.0.20090701",
"Ubuntu" => "0.5.3ubuntu2",
}.each do |os, ver|
describe "#{os} package installed" do
before {
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return(os)
allow(Facter::Util::Resolution).to receive(:exec).with(dpkg_cmd).
and_return(ver)
}
it { Facter.fact(:iptables_persistent_version).value.should == ver }
end
end
describe 'Ubuntu package not installed' do
before {
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Ubuntu')
allow(Facter::Util::Resolution).to receive(:exec).with(dpkg_cmd).
and_return(nil)
}
it { Facter.fact(:iptables_persistent_version).value.should be_nil }
end
describe 'CentOS not supported' do
before { allow(Facter.fact(:operatingsystem)).to receive(:value).
and_return("CentOS") }
it { Facter.fact(:iptables_persistent_version).value.should be_nil }
end
end

View File

@ -0,0 +1,23 @@
require 'spec_helper'
describe "Facter::Util::Fact" do
before {
Facter.clear
allow(Facter.fact(:kernel)).to receive(:value).and_return('Linux')
allow(Facter.fact(:kernelrelease)).to receive(:value).and_return('2.6')
}
describe 'iptables_version' do
it {
allow(Facter::Util::Resolution).to receive(:exec).with('iptables --version').
and_return('iptables v1.4.7')
Facter.fact(:iptables_version).value.should == '1.4.7'
}
end
describe 'ip6tables_version' do
before { allow(Facter::Util::Resolution).to receive(:exec).
with('ip6tables --version').and_return('ip6tables v1.4.7') }
it { Facter.fact(:ip6tables_version).value.should == '1.4.7' }
end
end

View File

@ -0,0 +1,227 @@
#!/usr/bin/env rspec
require 'spec_helper'
if Puppet.version < '3.4.0'
require 'puppet/provider/confine/exists'
else
require 'puppet/confine/exists'
end
describe 'iptables chain provider detection' do
if Puppet.version < '3.4.0'
let(:exists) {
Puppet::Provider::Confine::Exists
}
else
let(:exists) {
Puppet::Confine::Exists
}
end
before :each do
# Reset the default provider
Puppet::Type.type(:firewallchain).defaultprovider = nil
end
it "should default to iptables provider if /sbin/(eb|ip|ip6)tables[-save] exists" do
# Stub lookup for /sbin/iptables & /sbin/iptables-save
allow(exists).to receive(:which).with("ebtables").
and_return "/sbin/ebtables"
allow(exists).to receive(:which).with("ebtables-save").
and_return "/sbin/ebtables-save"
allow(exists).to receive(:which).with("iptables").
and_return "/sbin/iptables"
allow(exists).to receive(:which).with("iptables-save").
and_return "/sbin/iptables-save"
allow(exists).to receive(:which).with("ip6tables").
and_return "/sbin/ip6tables"
allow(exists).to receive(:which).with("ip6tables-save").
and_return "/sbin/ip6tables-save"
# Every other command should return false so we don't pick up any
# other providers
allow(exists).to receive(:which).with() { |value|
value !~ /(eb|ip|ip6)tables(-save)?$/
}.and_return false
# Create a resource instance and make sure the provider is iptables
resource = Puppet::Type.type(:firewallchain).new({
:name => 'test:filter:IPv4',
})
expect(resource.provider.class.to_s).to eq("Puppet::Type::Firewallchain::ProviderIptables_chain")
end
end
describe 'iptables chain provider' do
let(:provider) { Puppet::Type.type(:firewallchain).provider(:iptables_chain) }
let(:resource) {
Puppet::Type.type(:firewallchain).new({
:name => ':test:',
})
}
before :each do
allow(Puppet::Type::Firewallchain).to receive(:defaultprovider).and_return provider
allow(provider).to receive(:command).with(:ebtables_save).and_return "/sbin/ebtables-save"
allow(provider).to receive(:command).with(:iptables_save).and_return "/sbin/iptables-save"
allow(provider).to receive(:command).with(:ip6tables_save).and_return "/sbin/ip6tables-save"
end
it 'should be able to get a list of existing rules' do
# Pretend to return nil from iptables
allow(provider).to receive(:execute).with(['/sbin/ip6tables-save']).and_return("")
allow(provider).to receive(:execute).with(['/sbin/ebtables-save']).and_return("")
allow(provider).to receive(:execute).with(['/sbin/iptables-save']).and_return("")
provider.instances.each do |chain|
expect(chain).to be_instance_of(provider)
expect(chain.properties[:provider].to_s).to eq(provider.name.to_s)
end
end
end
describe 'iptables chain resource parsing' do
let(:provider) { Puppet::Type.type(:firewallchain).provider(:iptables_chain) }
before :each do
ebtables = ['BROUTE:BROUTING:ethernet',
'BROUTE:broute:ethernet',
':INPUT:ethernet',
':FORWARD:ethernet',
':OUTPUT:ethernet',
':filter:ethernet',
':filterdrop:ethernet',
':filterreturn:ethernet',
'NAT:PREROUTING:ethernet',
'NAT:OUTPUT:ethernet',
'NAT:POSTROUTING:ethernet',
]
allow(provider).to receive(:execute).with(['/sbin/ebtables-save']).and_return('
*broute
:BROUTING ACCEPT
:broute ACCEPT
*filter
:INPUT ACCEPT
:FORWARD ACCEPT
:OUTPUT ACCEPT
:filter ACCEPT
:filterdrop DROP
:filterreturn RETURN
*nat
:PREROUTING ACCEPT
:OUTPUT ACCEPT
:POSTROUTING ACCEPT
')
iptables = [
'raw:PREROUTING:IPv4',
'raw:OUTPUT:IPv4',
'raw:raw:IPv4',
'mangle:PREROUTING:IPv4',
'mangle:INPUT:IPv4',
'mangle:FORWARD:IPv4',
'mangle:OUTPUT:IPv4',
'mangle:POSTROUTING:IPv4',
'mangle:mangle:IPv4',
'NAT:PREROUTING:IPv4',
'NAT:OUTPUT:IPv4',
'NAT:POSTROUTING:IPv4',
'NAT:mangle:IPv4',
'NAT:mangle:IPv4',
'NAT:mangle:IPv4',
':$5()*&%\'"^$): :IPv4',
]
allow(provider).to receive(:execute).with(['/sbin/iptables-save']).and_return('
# Generated by iptables-save v1.4.9 on Mon Jan 2 01:20:06 2012
*raw
:PREROUTING ACCEPT [12:1780]
:OUTPUT ACCEPT [19:1159]
:raw - [0:0]
COMMIT
# Completed on Mon Jan 2 01:20:06 2012
# Generated by iptables-save v1.4.9 on Mon Jan 2 01:20:06 2012
*mangle
:PREROUTING ACCEPT [12:1780]
:INPUT ACCEPT [12:1780]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [19:1159]
:POSTROUTING ACCEPT [19:1159]
:mangle - [0:0]
COMMIT
# Completed on Mon Jan 2 01:20:06 2012
# Generated by iptables-save v1.4.9 on Mon Jan 2 01:20:06 2012
*nat
:PREROUTING ACCEPT [2242:639750]
:OUTPUT ACCEPT [5176:326206]
:POSTROUTING ACCEPT [5162:325382]
COMMIT
# Completed on Mon Jan 2 01:20:06 2012
# Generated by iptables-save v1.4.9 on Mon Jan 2 01:20:06 2012
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [5673:420879]
:$5()*&%\'"^$): - [0:0]
COMMIT
# Completed on Mon Jan 2 01:20:06 2012
')
ip6tables = [
'raw:PREROUTING:IPv6',
'raw:OUTPUT:IPv6',
'raw:ff:IPv6',
'mangle:PREROUTING:IPv6',
'mangle:INPUT:IPv6',
'mangle:FORWARD:IPv6',
'mangle:OUTPUT:IPv6',
'mangle:POSTROUTING:IPv6',
'mangle:ff:IPv6',
':INPUT:IPv6',
':FORWARD:IPv6',
':OUTPUT:IPv6',
':test:IPv6',
]
allow(provider).to receive(:execute).with(['/sbin/ip6tables-save']).and_return('
# Generated by ip6tables-save v1.4.9 on Mon Jan 2 01:31:39 2012
*raw
:PREROUTING ACCEPT [2173:489241]
:OUTPUT ACCEPT [0:0]
:ff - [0:0]
COMMIT
# Completed on Mon Jan 2 01:31:39 2012
# Generated by ip6tables-save v1.4.9 on Mon Jan 2 01:31:39 2012
*mangle
:PREROUTING ACCEPT [2301:518373]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:ff - [0:0]
COMMIT
# Completed on Mon Jan 2 01:31:39 2012
# Generated by ip6tables-save v1.4.9 on Mon Jan 2 01:31:39 2012
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [20:1292]
:test - [0:0]
COMMIT
# Completed on Mon Jan 2 01:31:39 2012
')
@all = ebtables + iptables + ip6tables
# IPv4 and IPv6 names also exist as resources {table}:{chain}:IP and {table}:{chain}:
iptables.each { |name| @all += [ name[0..-3], name[0..-5] ] }
ip6tables.each { |name| @all += [ name[0..-3], name[0..-5] ] }
end
it 'should have all in parsed resources' do
provider.instances.each do |resource|
@all.include?(resource.name)
end
end
end

View File

@ -0,0 +1,410 @@
#!/usr/bin/env rspec
require 'spec_helper'
if Puppet.version < '3.4.0'
require 'puppet/provider/confine/exists'
else
require 'puppet/confine/exists'
end
describe 'iptables provider detection' do
if Puppet.version < '3.4.0'
let(:exists) {
Puppet::Provider::Confine::Exists
}
else
let(:exists) {
Puppet::Confine::Exists
}
end
before :each do
# Reset the default provider
Puppet::Type.type(:firewall).defaultprovider = nil
end
it "should default to iptables provider if /sbin/iptables[-save] exists" do
# Stub lookup for /sbin/iptables & /sbin/iptables-save
allow(exists).to receive(:which).with("iptables").
and_return "/sbin/iptables"
allow(exists).to receive(:which).with("iptables-save").
and_return "/sbin/iptables-save"
# Every other command should return false so we don't pick up any
# other providers
allow(exists).to receive(:which).with() { |value|
! ["iptables","iptables-save"].include?(value)
}.and_return false
# Create a resource instance and make sure the provider is iptables
resource = Puppet::Type.type(:firewall).new({
:name => '000 test foo',
})
expect(resource.provider.class.to_s).to eq("Puppet::Type::Firewall::ProviderIptables")
end
end
describe 'iptables provider' do
let(:provider) { Puppet::Type.type(:firewall).provider(:iptables) }
let(:resource) {
Puppet::Type.type(:firewall).new({
:name => '000 test foo',
:action => 'accept',
})
}
before :each do
allow(Puppet::Type::Firewall).to receive(:defaultprovider).and_return provider
allow(provider).to receive(:command).with(:iptables_save).and_return "/sbin/iptables-save"
# Stub iptables version
allow(Facter.fact(:iptables_version)).to receive(:value).and_return("1.4.2")
allow(Puppet::Util::Execution).to receive(:execute).and_return ""
allow(Puppet::Util).to receive(:which).with("iptables-save").
and_return "/sbin/iptables-save"
end
it 'should be able to get a list of existing rules' do
provider.instances.each do |rule|
expect(rule).to be_instance_of(provider)
expect(rule.properties[:provider].to_s).to eq(provider.name.to_s)
end
end
it 'should ignore lines with fatal errors' do
allow(Puppet::Util::Execution).to receive(:execute).with(['/sbin/iptables-save']).
and_return("FATAL: Could not load /lib/modules/2.6.18-028stab095.1/modules.dep: No such file or directory")
expect(provider.instances.length).to be_zero
end
describe '#insert_order' do
let(:iptables_save_output) { [
'-A INPUT -s 8.0.0.2/32 -p tcp -m multiport --ports 100 -m comment --comment "100 test" -j ACCEPT',
'-A INPUT -s 8.0.0.3/32 -p tcp -m multiport --ports 200 -m comment --comment "200 test" -j ACCEPT',
'-A INPUT -s 8.0.0.4/32 -p tcp -m multiport --ports 300 -m comment --comment "300 test" -j ACCEPT'
] }
let(:resources) do
iptables_save_output.each_with_index.collect { |l,index| provider.rule_to_hash(l, 'filter', index) }
end
let(:providers) do
resources.collect { |r| provider.new(r) }
end
it 'understands offsets for adding rules to the beginning' do
resource = Puppet::Type.type(:firewall).new({ :name => '001 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(1) # 1-indexed
end
it 'understands offsets for editing rules at the beginning' do
resource = Puppet::Type.type(:firewall).new({ :name => '100 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(1)
end
it 'understands offsets for adding rules to the middle' do
resource = Puppet::Type.type(:firewall).new({ :name => '101 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(2)
end
it 'understands offsets for editing rules at the middle' do
resource = Puppet::Type.type(:firewall).new({ :name => '200 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(2)
end
it 'understands offsets for adding rules to the end' do
resource = Puppet::Type.type(:firewall).new({ :name => '301 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(4)
end
it 'understands offsets for editing rules at the end' do
resource = Puppet::Type.type(:firewall).new({ :name => '300 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(3)
end
context 'with unname rules between' do
let(:iptables_save_output) { [
'-A INPUT -s 8.0.0.2/32 -p tcp -m multiport --ports 100 -m comment --comment "100 test" -j ACCEPT',
'-A INPUT -s 8.0.0.2/32 -p tcp -m multiport --ports 150 -m comment --comment "150 test" -j ACCEPT',
'-A INPUT -s 8.0.0.3/32 -p tcp -m multiport --ports 200 -j ACCEPT',
'-A INPUT -s 8.0.0.3/32 -p tcp -m multiport --ports 250 -j ACCEPT',
'-A INPUT -s 8.0.0.4/32 -p tcp -m multiport --ports 300 -m comment --comment "300 test" -j ACCEPT',
'-A INPUT -s 8.0.0.4/32 -p tcp -m multiport --ports 350 -m comment --comment "350 test" -j ACCEPT',
] }
it 'understands offsets for adding rules before unnamed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '001 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(1)
end
it 'understands offsets for editing rules before unnamed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '100 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(1)
end
it 'understands offsets for adding rules between managed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '120 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(2)
end
it 'understands offsets for adding rules between unnamed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '151 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(3)
end
it 'understands offsets for adding rules after unnamed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '351 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(7)
end
end
context 'with unname rules before and after' do
let(:iptables_save_output) { [
'-A INPUT -s 8.0.0.3/32 -p tcp -m multiport --ports 050 -j ACCEPT',
'-A INPUT -s 8.0.0.3/32 -p tcp -m multiport --ports 090 -j ACCEPT',
'-A INPUT -s 8.0.0.2/32 -p tcp -m multiport --ports 100 -m comment --comment "100 test" -j ACCEPT',
'-A INPUT -s 8.0.0.2/32 -p tcp -m multiport --ports 150 -m comment --comment "150 test" -j ACCEPT',
'-A INPUT -s 8.0.0.3/32 -p tcp -m multiport --ports 200 -j ACCEPT',
'-A INPUT -s 8.0.0.3/32 -p tcp -m multiport --ports 250 -j ACCEPT',
'-A INPUT -s 8.0.0.4/32 -p tcp -m multiport --ports 300 -m comment --comment "300 test" -j ACCEPT',
'-A INPUT -s 8.0.0.4/32 -p tcp -m multiport --ports 350 -m comment --comment "350 test" -j ACCEPT',
'-A INPUT -s 8.0.0.5/32 -p tcp -m multiport --ports 400 -j ACCEPT',
'-A INPUT -s 8.0.0.5/32 -p tcp -m multiport --ports 450 -j ACCEPT',
] }
it 'understands offsets for adding rules before unnamed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '001 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(1)
end
it 'understands offsets for editing rules before unnamed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '100 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(3)
end
it 'understands offsets for adding rules between managed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '120 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(4)
end
it 'understands offsets for adding rules between unnamed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '151 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(5)
end
it 'understands offsets for adding rules after unnamed rules' do
resource = Puppet::Type.type(:firewall).new({ :name => '351 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(9)
end
it 'understands offsets for adding rules at the end' do
resource = Puppet::Type.type(:firewall).new({ :name => '950 test', })
allow(resource.provider.class).to receive(:instances).and_return(providers)
expect(resource.provider.insert_order).to eq(11)
end
end
end
# Load in ruby hash for test fixtures.
load 'spec/fixtures/iptables/conversion_hash.rb'
describe 'when converting rules to resources' do
ARGS_TO_HASH.each do |test_name,data|
describe "for test data '#{test_name}'" do
let(:resource) { provider.rule_to_hash(data[:line], data[:table], 0) }
# If this option is enabled, make sure the parameters exactly match
if data[:compare_all] then
it "the parameter hash keys should be the same as returned by rules_to_hash" do
expect(resource.keys).to match_array(data[:params].keys)
end
end
# Iterate across each parameter, creating an example for comparison
data[:params].each do |param_name, param_value|
it "the parameter '#{param_name.to_s}' should match #{param_value.inspect}" do
# booleans get cludged to string "true"
if param_value == true then
expect(resource[param_name]).to be_true
else
expect(resource[param_name]).to eq(data[:params][param_name])
end
end
end
end
end
end
describe 'when working out general_args' do
HASH_TO_ARGS.each do |test_name,data|
describe "for test data '#{test_name}'" do
let(:resource) { Puppet::Type.type(:firewall).new(data[:params]) }
let(:provider) { Puppet::Type.type(:firewall).provider(:iptables) }
let(:instance) { provider.new(resource) }
it 'general_args should be valid' do
expect(instance.general_args.flatten).to eq(data[:args])
end
end
end
end
describe 'when converting rules without comments to resources' do
let(:sample_rule) {
'-A INPUT -s 1.1.1.1 -d 1.1.1.1 -p tcp -m multiport --dports 7061,7062 -m multiport --sports 7061,7062 -j ACCEPT'
}
let(:resource) { provider.rule_to_hash(sample_rule, 'filter', 0) }
let(:instance) { provider.new(resource) }
it 'rule name contains a MD5 sum of the line' do
expect(resource[:name]).to eq("9000 #{Digest::MD5.hexdigest(resource[:line])}")
end
it 'parsed the rule arguments correctly' do
expect(resource[:chain]).to eq('INPUT')
expect(resource[:source]).to eq('1.1.1.1/32')
expect(resource[:destination]).to eq('1.1.1.1/32')
expect(resource[:proto]).to eq('tcp')
expect(resource[:dport]).to eq(['7061', '7062'])
expect(resource[:sport]).to eq(['7061', '7062'])
expect(resource[:action]).to eq('accept')
end
end
describe 'when converting existing rules generates by system-config-firewall-tui to resources' do
let(:sample_rule) {
# as generated by iptables-save from rules created with system-config-firewall-tui
'-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT'
}
let(:resource) { provider.rule_to_hash(sample_rule, 'filter', 0) }
let(:instance) { provider.new(resource) }
it 'rule name contains a MD5 sum of the line' do
expect(resource[:name]).to eq("9000 #{Digest::MD5.hexdigest(resource[:line])}")
end
it 'parse arguments' do
expect(resource[:chain]).to eq('INPUT')
expect(resource[:proto]).to eq('tcp')
expect(resource[:dport]).to eq(['22'])
expect(resource[:state]).to eq(['NEW'])
expect(resource[:action]).to eq('accept')
end
end
describe 'when creating resources' do
let(:instance) { provider.new(resource) }
it 'insert_args should be an array' do
expect(instance.insert_args.class).to eq(Array)
end
end
describe 'when modifying resources' do
let(:instance) { provider.new(resource) }
it 'update_args should be an array' do
expect(instance.update_args.class).to eq(Array)
end
it 'fails when modifying the chain' do
expect { instance.chain = "OUTPUT" }.to raise_error(/is not supported/)
end
end
describe 'when deleting resources' do
let(:sample_rule) {
'-A INPUT -s 1.1.1.1 -d 1.1.1.1 -p tcp -m multiport --dports 7061,7062 -m multiport --sports 7061,7062 -j ACCEPT'
}
let(:resource) { provider.rule_to_hash(sample_rule, 'filter', 0) }
let(:instance) { provider.new(resource) }
it 'resource[:line] looks like the original rule' do
resource[:line] == sample_rule
end
it 'delete_args is an array' do
expect(instance.delete_args.class).to eq(Array)
end
it 'delete_args is the same as the rule string when joined' do
expect(instance.delete_args.join(' ')).to eq(sample_rule.gsub(/\-A/,
'-t filter -D'))
end
end
end
describe 'ip6tables provider' do
let(:provider6) { Puppet::Type.type(:firewall).provider(:ip6tables) }
let(:resource) {
Puppet::Type.type(:firewall).new({
:name => '000 test foo',
:action => 'accept',
:provider => "ip6tables",
})
}
before :each do
allow(Puppet::Type::Firewall).to receive(:ip6tables).and_return provider6
allow(provider6).to receive(:command).with(:ip6tables_save).and_return "/sbin/ip6tables-save"
# Stub iptables version
allow(Facter.fact(:ip6tables_version)).to receive(:value).and_return '1.4.7'
allow(Puppet::Util::Execution).to receive(:execute).and_return ''
allow(Puppet::Util).to receive(:which).with("ip6tables-save").
and_return "/sbin/ip6tables-save"
end
it 'should be able to get a list of existing rules' do
provider6.instances.each do |rule|
rule.should be_instance_of(provider6)
rule.properties[:provider6].to_s.should == provider6.name.to_s
end
end
it 'should ignore lines with fatal errors' do
allow(Puppet::Util::Execution).to receive(:execute).with(['/sbin/ip6tables-save']).
and_return("FATAL: Could not load /lib/modules/2.6.18-028stab095.1/modules.dep: No such file or directory")
provider6.instances.length.should == 0
end
# Load in ruby hash for test fixtures.
load 'spec/fixtures/ip6tables/conversion_hash.rb'
describe 'when converting rules to resources' do
ARGS_TO_HASH6.each do |test_name,data|
describe "for test data '#{test_name}'" do
let(:resource) { provider6.rule_to_hash(data[:line], data[:table], 0) }
# If this option is enabled, make sure the parameters exactly match
if data[:compare_all] then
it "the parameter hash keys should be the same as returned by rules_to_hash" do
resource.keys.should =~ data[:params].keys
end
end
# Iterate across each parameter, creating an example for comparison
data[:params].each do |param_name, param_value|
it "the parameter '#{param_name.to_s}' should match #{param_value.inspect}" do
resource[param_name].should == data[:params][param_name]
end
end
end
end
end
describe 'when working out general_args' do
HASH_TO_ARGS6.each do |test_name,data|
describe "for test data '#{test_name}'" do
let(:resource) { Puppet::Type.type(:firewall).new(data[:params]) }
let(:provider6) { Puppet::Type.type(:firewall).provider(:ip6tables) }
let(:instance) { provider6.new(resource) }
it 'general_args should be valid' do
instance.general_args.flatten.should == data[:args]
end
end
end
end
end

View File

@ -0,0 +1,650 @@
#!/usr/bin/env rspec
require 'spec_helper'
firewall = Puppet::Type.type(:firewall)
describe firewall do
before :each do
@class = firewall
@provider = double 'provider'
allow(@provider).to receive(:name).and_return(:iptables)
allow(Puppet::Type::Firewall).to receive(:defaultprovider).and_return @provider
@resource = @class.new({:name => '000 test foo'})
# Stub iptables version
allow(Facter.fact(:iptables_version)).to receive(:value).and_return('1.4.2')
allow(Facter.fact(:ip6tables_version)).to receive(:value).and_return('1.4.2')
# Stub confine facts
allow(Facter.fact(:kernel)).to receive(:value).and_return('Linux')
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Debian')
end
it 'should have :name be its namevar' do
@class.key_attributes.should == [:name]
end
describe ':name' do
it 'should accept a name' do
@resource[:name] = '000-test-foo'
@resource[:name].should == '000-test-foo'
end
it 'should not accept a name with non-ASCII chars' do
lambda { @resource[:name] = '%*#^(#$' }.should raise_error(Puppet::Error)
end
end
describe ':action' do
it "should have no default" do
res = @class.new(:name => "000 test")
res.parameters[:action].should == nil
end
[:accept, :drop, :reject].each do |action|
it "should accept value #{action}" do
@resource[:action] = action
@resource[:action].should == action
end
end
it 'should fail when value is not recognized' do
lambda { @resource[:action] = 'not valid' }.should raise_error(Puppet::Error)
end
end
describe ':chain' do
[:INPUT, :FORWARD, :OUTPUT, :PREROUTING, :POSTROUTING].each do |chain|
it "should accept chain value #{chain}" do
@resource[:chain] = chain
@resource[:chain].should == chain
end
end
it 'should fail when the chain value is not recognized' do
lambda { @resource[:chain] = 'not valid' }.should raise_error(Puppet::Error)
end
end
describe ':table' do
[:nat, :mangle, :filter, :raw].each do |table|
it "should accept table value #{table}" do
@resource[:table] = table
@resource[:table].should == table
end
end
it "should fail when table value is not recognized" do
lambda { @resource[:table] = 'not valid' }.should raise_error(Puppet::Error)
end
end
describe ':proto' do
[:tcp, :udp, :icmp, :esp, :ah, :vrrp, :igmp, :ipencap, :ospf, :gre, :all].each do |proto|
it "should accept proto value #{proto}" do
@resource[:proto] = proto
@resource[:proto].should == proto
end
end
it "should fail when proto value is not recognized" do
lambda { @resource[:proto] = 'foo' }.should raise_error(Puppet::Error)
end
end
describe ':jump' do
it "should have no default" do
res = @class.new(:name => "000 test")
res.parameters[:jump].should == nil
end
['QUEUE', 'RETURN', 'DNAT', 'SNAT', 'LOG', 'MASQUERADE', 'REDIRECT', 'MARK'].each do |jump|
it "should accept jump value #{jump}" do
@resource[:jump] = jump
@resource[:jump].should == jump
end
end
['ACCEPT', 'DROP', 'REJECT'].each do |jump|
it "should now fail when value #{jump}" do
lambda { @resource[:jump] = jump }.should raise_error(Puppet::Error)
end
end
it "should fail when jump value is not recognized" do
lambda { @resource[:jump] = '%^&*' }.should raise_error(Puppet::Error)
end
end
[:source, :destination].each do |addr|
describe addr do
it "should accept a #{addr} as a string" do
@resource[addr] = '127.0.0.1'
@resource[addr].should == '127.0.0.1/32'
end
['0.0.0.0/0', '::/0'].each do |prefix|
it "should be nil for zero prefix length address #{prefix}" do
@resource[addr] = prefix
@resource[addr].should == nil
end
end
it "should accept a negated #{addr} as a string" do
@resource[addr] = '! 127.0.0.1'
@resource[addr].should == '! 127.0.0.1/32'
end
end
end
[:dport, :sport].each do |port|
describe port do
it "should accept a #{port} as string" do
@resource[port] = '22'
@resource[port].should == ['22']
end
it "should accept a #{port} as an array" do
@resource[port] = ['22','23']
@resource[port].should == ['22','23']
end
it "should accept a #{port} as a number" do
@resource[port] = 22
@resource[port].should == ['22']
end
it "should accept a #{port} as a hyphen separated range" do
@resource[port] = ['22-1000']
@resource[port].should == ['22-1000']
end
it "should accept a #{port} as a combination of arrays of single and " \
"hyphen separated ranges" do
@resource[port] = ['22-1000','33','3000-4000']
@resource[port].should == ['22-1000','33','3000-4000']
end
it "should convert a port name for #{port} to its number" do
@resource[port] = 'ssh'
@resource[port].should == ['22']
end
it "should not accept something invalid for #{port}" do
expect { @resource[port] = 'something odd' }.to raise_error(Puppet::Error, /^Parameter .+ failed.+Munging failed for value ".+" in class .+: no such service/)
end
it "should not accept something invalid in an array for #{port}" do
expect { @resource[port] = ['something odd','something even odder'] }.to raise_error(Puppet::Error, /^Parameter .+ failed.+Munging failed for value ".+" in class .+: no such service/)
end
end
end
[:dst_type, :src_type].each do |addrtype|
describe addrtype do
it "should have no default" do
res = @class.new(:name => "000 test")
res.parameters[addrtype].should == nil
end
end
[:UNSPEC, :UNICAST, :LOCAL, :BROADCAST, :ANYCAST, :MULTICAST, :BLACKHOLE,
:UNREACHABLE, :PROHIBIT, :THROW, :NAT, :XRESOLVE].each do |type|
it "should accept #{addrtype} value #{type}" do
@resource[addrtype] = type
@resource[addrtype].should == type
end
end
it "should fail when #{addrtype} value is not recognized" do
lambda { @resource[addrtype] = 'foo' }.should raise_error(Puppet::Error)
end
end
[:iniface, :outiface].each do |iface|
describe iface do
it "should accept #{iface} value as a string" do
@resource[iface] = 'eth1'
@resource[iface].should == 'eth1'
end
end
end
[:tosource, :todest].each do |addr|
describe addr do
it "should accept #{addr} value as a string" do
@resource[addr] = '127.0.0.1'
end
end
end
describe ':log_level' do
values = {
'panic' => '0',
'alert' => '1',
'crit' => '2',
'err' => '3',
'warn' => '4',
'warning' => '4',
'not' => '5',
'notice' => '5',
'info' => '6',
'debug' => '7'
}
values.each do |k,v|
it {
@resource[:log_level] = k
@resource[:log_level].should == v
}
it {
@resource[:log_level] = 3
@resource[:log_level].should == 3
}
it { lambda { @resource[:log_level] = 'foo' }.should raise_error(Puppet::Error) }
end
end
describe ':icmp' do
icmp_codes = {
:iptables => {
'0' => 'echo-reply',
'3' => 'destination-unreachable',
'4' => 'source-quench',
'6' => 'redirect',
'8' => 'echo-request',
'9' => 'router-advertisement',
'10' => 'router-solicitation',
'11' => 'time-exceeded',
'12' => 'parameter-problem',
'13' => 'timestamp-request',
'14' => 'timestamp-reply',
'17' => 'address-mask-request',
'18' => 'address-mask-reply'
},
:ip6tables => {
'1' => 'destination-unreachable',
'3' => 'time-exceeded',
'4' => 'parameter-problem',
'128' => 'echo-request',
'129' => 'echo-reply',
'133' => 'router-solicitation',
'134' => 'router-advertisement',
'137' => 'redirect'
}
}
icmp_codes.each do |provider, values|
describe provider do
values.each do |k,v|
it 'should convert icmp string to number' do
@resource[:provider] = provider
@resource[:provider].should == provider
@resource[:icmp] = v
@resource[:icmp].should == k
end
end
end
end
it 'should accept values as integers' do
@resource[:icmp] = 9
@resource[:icmp].should == 9
end
it 'should fail if icmp type is "any"' do
lambda { @resource[:icmp] = 'any' }.should raise_error(Puppet::Error)
end
it 'should fail if icmp type cannot be mapped to a numeric' do
lambda { @resource[:icmp] = 'foo' }.should raise_error(Puppet::Error)
end
end
describe ':state' do
it 'should accept value as a string' do
@resource[:state] = :INVALID
@resource[:state].should == [:INVALID]
end
it 'should accept value as an array' do
@resource[:state] = [:INVALID, :NEW]
@resource[:state].should == [:INVALID, :NEW]
end
it 'should sort values alphabetically' do
@resource[:state] = [:NEW, :ESTABLISHED]
@resource[:state].should == [:ESTABLISHED, :NEW]
end
end
describe ':ctstate' do
it 'should accept value as a string' do
@resource[:ctstate] = :INVALID
@resource[:ctstate].should == [:INVALID]
end
it 'should accept value as an array' do
@resource[:ctstate] = [:INVALID, :NEW]
@resource[:ctstate].should == [:INVALID, :NEW]
end
it 'should sort values alphabetically' do
@resource[:ctstate] = [:NEW, :ESTABLISHED]
@resource[:ctstate].should == [:ESTABLISHED, :NEW]
end
end
describe ':burst' do
it 'should accept numeric values' do
@resource[:burst] = 12
@resource[:burst].should == 12
end
it 'should fail if value is not numeric' do
lambda { @resource[:burst] = 'foo' }.should raise_error(Puppet::Error)
end
end
describe ':recent' do
['set', 'update', 'rcheck', 'remove'].each do |recent|
it "should accept recent value #{recent}" do
@resource[:recent] = recent
@resource[:recent].should == "--#{recent}"
end
end
end
describe ':action and :jump' do
it 'should allow only 1 to be set at a time' do
expect {
@class.new(
:name => "001-test",
:action => "accept",
:jump => "custom_chain"
)
}.to raise_error(Puppet::Error, /Only one of the parameters 'action' and 'jump' can be set$/)
end
end
describe ':gid and :uid' do
it 'should allow me to set uid' do
@resource[:uid] = 'root'
@resource[:uid].should == 'root'
end
it 'should allow me to set uid as an array, and silently hide my error' do
@resource[:uid] = ['root', 'bobby']
@resource[:uid].should == 'root'
end
it 'should allow me to set gid' do
@resource[:gid] = 'root'
@resource[:gid].should == 'root'
end
it 'should allow me to set gid as an array, and silently hide my error' do
@resource[:gid] = ['root', 'bobby']
@resource[:gid].should == 'root'
end
end
describe ':set_mark' do
['1.3.2', '1.4.2'].each do |iptables_version|
describe "with iptables #{iptables_version}" do
before {
Facter.clear
allow(Facter.fact(:iptables_version)).to receive(:value).and_return iptables_version
allow(Facter.fact(:ip6tables_version)).to receive(:value).and_return iptables_version
}
if iptables_version == '1.3.2'
it 'should allow me to set set-mark without mask' do
@resource[:set_mark] = '0x3e8'
@resource[:set_mark].should == '0x3e8'
end
it 'should convert int to hex without mask' do
@resource[:set_mark] = '1000'
@resource[:set_mark].should == '0x3e8'
end
it 'should fail if mask is present' do
lambda { @resource[:set_mark] = '0x3e8/0xffffffff'}.should raise_error(
Puppet::Error, /iptables version #{iptables_version} does not support masks on MARK rules$/
)
end
end
if iptables_version == '1.4.2'
it 'should allow me to set set-mark with mask' do
@resource[:set_mark] = '0x3e8/0xffffffff'
@resource[:set_mark].should == '0x3e8/0xffffffff'
end
it 'should convert int to hex and add a 32 bit mask' do
@resource[:set_mark] = '1000'
@resource[:set_mark].should == '0x3e8/0xffffffff'
end
it 'should add a 32 bit mask' do
@resource[:set_mark] = '0x32'
@resource[:set_mark].should == '0x32/0xffffffff'
end
it 'should use the mask provided' do
@resource[:set_mark] = '0x32/0x4'
@resource[:set_mark].should == '0x32/0x4'
end
it 'should use the mask provided and convert int to hex' do
@resource[:set_mark] = '1000/0x4'
@resource[:set_mark].should == '0x3e8/0x4'
end
it 'should fail if mask value is more than 32 bits' do
lambda { @resource[:set_mark] = '1/4294967296'}.should raise_error(
Puppet::Error, /MARK mask must be integer or hex between 0 and 0xffffffff$/
)
end
it 'should fail if mask is malformed' do
lambda { @resource[:set_mark] = '1000/0xq4'}.should raise_error(
Puppet::Error, /MARK mask must be integer or hex between 0 and 0xffffffff$/
)
end
end
['/', '1000/', 'pwnie'].each do |bad_mark|
it "should fail with malformed mark '#{bad_mark}'" do
lambda { @resource[:set_mark] = bad_mark}.should raise_error(Puppet::Error)
end
end
it 'should fail if mark value is more than 32 bits' do
lambda { @resource[:set_mark] = '4294967296'}.should raise_error(
Puppet::Error, /MARK value must be integer or hex between 0 and 0xffffffff$/
)
end
end
end
end
[:chain, :jump].each do |param|
describe param do
it 'should autorequire fwchain when table and provider are undefined' do
@resource[param] = 'FOO'
@resource[:table].should == :filter
@resource[:provider].should == :iptables
chain = Puppet::Type.type(:firewallchain).new(:name => 'FOO:filter:IPv4')
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource chain
rel = @resource.autorequire[0]
rel.source.ref.should == chain.ref
rel.target.ref.should == @resource.ref
end
it 'should autorequire fwchain when table is undefined and provider is ip6tables' do
@resource[param] = 'FOO'
@resource[:table].should == :filter
@resource[:provider] = :ip6tables
chain = Puppet::Type.type(:firewallchain).new(:name => 'FOO:filter:IPv6')
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource chain
rel = @resource.autorequire[0]
rel.source.ref.should == chain.ref
rel.target.ref.should == @resource.ref
end
it 'should autorequire fwchain when table is raw and provider is undefined' do
@resource[param] = 'FOO'
@resource[:table] = :raw
@resource[:provider].should == :iptables
chain = Puppet::Type.type(:firewallchain).new(:name => 'FOO:raw:IPv4')
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource chain
rel = @resource.autorequire[0]
rel.source.ref.should == chain.ref
rel.target.ref.should == @resource.ref
end
it 'should autorequire fwchain when table is raw and provider is ip6tables' do
@resource[param] = 'FOO'
@resource[:table] = :raw
@resource[:provider] = :ip6tables
chain = Puppet::Type.type(:firewallchain).new(:name => 'FOO:raw:IPv6')
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource chain
rel = @resource.autorequire[0]
rel.source.ref.should == chain.ref
rel.target.ref.should == @resource.ref
end
# test where autorequire is still needed (table != filter)
['INPUT', 'OUTPUT', 'FORWARD'].each do |test_chain|
it "should autorequire fwchain #{test_chain} when table is mangle and provider is undefined" do
@resource[param] = test_chain
@resource[:table] = :mangle
@resource[:provider].should == :iptables
chain = Puppet::Type.type(:firewallchain).new(:name => "#{test_chain}:mangle:IPv4")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource chain
rel = @resource.autorequire[0]
rel.source.ref.should == chain.ref
rel.target.ref.should == @resource.ref
end
it "should autorequire fwchain #{test_chain} when table is mangle and provider is ip6tables" do
@resource[param] = test_chain
@resource[:table] = :mangle
@resource[:provider] = :ip6tables
chain = Puppet::Type.type(:firewallchain).new(:name => "#{test_chain}:mangle:IPv6")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource chain
rel = @resource.autorequire[0]
rel.source.ref.should == chain.ref
rel.target.ref.should == @resource.ref
end
end
# test of case where autorequire should not happen
['INPUT', 'OUTPUT', 'FORWARD'].each do |test_chain|
it "should not autorequire fwchain #{test_chain} when table and provider are undefined" do
@resource[param] = test_chain
@resource[:table].should == :filter
@resource[:provider].should == :iptables
chain = Puppet::Type.type(:firewallchain).new(:name => "#{test_chain}:filter:IPv4")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource chain
rel = @resource.autorequire[0]
rel.should == nil
end
it "should not autorequire fwchain #{test_chain} when table is undefined and provider is ip6tables" do
@resource[param] = test_chain
@resource[:table].should == :filter
@resource[:provider] = :ip6tables
chain = Puppet::Type.type(:firewallchain).new(:name => "#{test_chain}:filter:IPv6")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource chain
rel = @resource.autorequire[0]
rel.should == nil
end
end
end
end
describe ":chain and :jump" do
it 'should autorequire independent fwchains' do
@resource[:chain] = 'FOO'
@resource[:jump] = 'BAR'
@resource[:table].should == :filter
@resource[:provider].should == :iptables
chain_foo = Puppet::Type.type(:firewallchain).new(:name => 'FOO:filter:IPv4')
chain_bar = Puppet::Type.type(:firewallchain).new(:name => 'BAR:filter:IPv4')
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource chain_foo
catalog.add_resource chain_bar
rel = @resource.autorequire
rel[0].source.ref.should == chain_foo.ref
rel[0].target.ref.should == @resource.ref
rel[1].source.ref.should == chain_bar.ref
rel[1].target.ref.should == @resource.ref
end
end
describe ':pkttype' do
[:multicast, :broadcast, :unicast].each do |pkttype|
it "should accept pkttype value #{pkttype}" do
@resource[:pkttype] = pkttype
@resource[:pkttype].should == pkttype
end
end
it 'should fail when the pkttype value is not recognized' do
lambda { @resource[:pkttype] = 'not valid' }.should raise_error(Puppet::Error)
end
end
describe 'autorequire packages' do
[:iptables, :ip6tables].each do |provider|
it "provider #{provider} should autorequire package iptables" do
@resource[:provider] = provider
@resource[:provider].should == provider
package = Puppet::Type.type(:package).new(:name => 'iptables')
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
catalog.add_resource package
rel = @resource.autorequire[0]
rel.source.ref.should == package.ref
rel.target.ref.should == @resource.ref
end
it "provider #{provider} should autorequire packages iptables and iptables-persistent" do
@resource[:provider] = provider
@resource[:provider].should == provider
packages = [
Puppet::Type.type(:package).new(:name => 'iptables'),
Puppet::Type.type(:package).new(:name => 'iptables-persistent')
]
catalog = Puppet::Resource::Catalog.new
catalog.add_resource @resource
packages.each do |package|
catalog.add_resource package
end
packages.zip(@resource.autorequire) do |package, rel|
rel.source.ref.should == package.ref
rel.target.ref.should == @resource.ref
end
end
end
end
end

View File

@ -0,0 +1,185 @@
#!/usr/bin/env rspec
require 'spec_helper'
firewallchain = Puppet::Type.type(:firewallchain)
describe firewallchain do
before(:each) do
# Stub confine facts
allow(Facter.fact(:kernel)).to receive(:value).and_return('Linux')
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Debian')
end
let(:klass) { firewallchain }
let(:provider) {
prov = double 'provider'
allow(prov).to receive(:name).and_return(:iptables_chain)
prov
}
let(:resource) {
allow(Puppet::Type::Firewallchain).to receive(:defaultprovider).and_return provider
klass.new({:name => 'INPUT:filter:IPv4', :policy => :accept })
}
it 'should have :name be its namevar' do
klass.key_attributes.should == [:name]
end
describe ':name' do
{'nat' => ['PREROUTING', 'POSTROUTING', 'INPUT', 'OUTPUT'],
'mangle' => [ 'PREROUTING', 'POSTROUTING', 'INPUT', 'FORWARD', 'OUTPUT' ],
'filter' => ['INPUT','OUTPUT','FORWARD'],
'raw' => [ 'PREROUTING', 'OUTPUT'],
'broute' => ['BROUTING']
}.each_pair do |table, allowedinternalchains|
['IPv4', 'IPv6', 'ethernet'].each do |protocol|
[ 'test', '$5()*&%\'"^$09):' ].each do |chainname|
name = "#{chainname}:#{table}:#{protocol}"
if table == 'nat' && protocol == 'IPv6'
it "should fail #{name}" do
expect { resource[:name] = name }.to raise_error(Puppet::Error)
end
elsif protocol != 'ethernet' && table == 'broute'
it "should fail #{name}" do
expect { resource[:name] = name }.to raise_error(Puppet::Error)
end
else
it "should accept name #{name}" do
resource[:name] = name
resource[:name].should == name
end
end
end # chainname
end # protocol
[ 'PREROUTING', 'POSTROUTING', 'BROUTING', 'INPUT', 'FORWARD', 'OUTPUT' ].each do |internalchain|
name = internalchain + ':' + table + ':'
if internalchain == 'BROUTING'
name += 'ethernet'
elsif table == 'nat'
name += 'IPv4'
else
name += 'IPv4'
end
if allowedinternalchains.include? internalchain
it "should allow #{name}" do
resource[:name] = name
resource[:name].should == name
end
else
it "should fail #{name}" do
expect { resource[:name] = name }.to raise_error(Puppet::Error)
end
end
end # internalchain
end # table, allowedinternalchainnames
it 'should fail with invalid table names' do
expect { resource[:name] = 'wrongtablename:test:IPv4' }.to raise_error(Puppet::Error)
end
it 'should fail with invalid protocols names' do
expect { resource[:name] = 'test:filter:IPv5' }.to raise_error(Puppet::Error)
end
end
describe ':policy' do
[:accept, :drop, :queue, :return].each do |policy|
it "should accept policy #{policy}" do
resource[:policy] = policy
resource[:policy].should == policy
end
end
it 'should fail when value is not recognized' do
expect { resource[:policy] = 'not valid' }.to raise_error(Puppet::Error)
end
[:accept, :drop, :queue, :return].each do |policy|
it "non-inbuilt chains should not accept policy #{policy}" do
expect { klass.new({:name => 'testchain:filter:IPv4', :policy => policy }) }.to raise_error(Puppet::Error)
end
it "non-inbuilt chains can accept policies on protocol = ethernet (policy #{policy})" do
klass.new({:name => 'testchain:filter:ethernet', :policy => policy })
end
end
end
describe 'autorequire packages' do
it "provider iptables_chain should autorequire package iptables" do
resource[:provider].should == :iptables_chain
package = Puppet::Type.type(:package).new(:name => 'iptables')
catalog = Puppet::Resource::Catalog.new
catalog.add_resource resource
catalog.add_resource package
rel = resource.autorequire[0]
rel.source.ref.should == package.ref
rel.target.ref.should == resource.ref
end
it "provider iptables_chain should autorequire packages iptables and iptables-persistent" do
resource[:provider].should == :iptables_chain
packages = [
Puppet::Type.type(:package).new(:name => 'iptables'),
Puppet::Type.type(:package).new(:name => 'iptables-persistent')
]
catalog = Puppet::Resource::Catalog.new
catalog.add_resource resource
packages.each do |package|
catalog.add_resource package
end
packages.zip(resource.autorequire) do |package, rel|
rel.source.ref.should == package.ref
rel.target.ref.should == resource.ref
end
end
end
describe 'purge iptables rules' do
before(:each) do
allow(Puppet::Type.type(:firewall).provider(:iptables)).to receive(:iptables_save).and_return(<<EOS
# Completed on Sun Jan 5 19:30:21 2014
# Generated by iptables-save v1.4.12 on Sun Jan 5 19:30:21 2014
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:LOCAL_FORWARD - [0:0]
:LOCAL_FORWARD_PRE - [0:0]
:LOCAL_INPUT - [0:0]
:LOCAL_INPUT_PRE - [0:0]
:fail2ban-ssh - [0:0]
-A INPUT -p tcp -m multiport --dports 22 -j fail2ban-ssh
-A INPUT -i lo -m comment --comment "012 accept loopback" -j ACCEPT
-A INPUT -p tcp -m multiport --dports 22 -m comment --comment "020 ssh" -j ACCEPT
-A OUTPUT -d 1.2.1.2 -j DROP
-A fail2ban-ssh -j RETURN
COMMIT
# Completed on Sun Jan 5 19:30:21 2014
EOS
)
end
it 'should generate iptables resources' do
resource = Puppet::Type::Firewallchain.new(:name => 'INPUT:filter:IPv4', :purge => true)
expect(resource.generate.size).to eq(3)
end
it 'should not generate ignored iptables rules' do
resource = Puppet::Type::Firewallchain.new(:name => 'INPUT:filter:IPv4', :purge => true, :ignore => ['-j fail2ban-ssh'])
expect(resource.generate.size).to eq(2)
end
it 'should not generate iptables resources when not enabled' do
resource = Puppet::Type::Firewallchain.new(:name => 'INPUT:filter:IPv4')
expect(resource.generate.size).to eq(0)
end
end
end

View File

@ -0,0 +1,186 @@
require 'spec_helper'
describe 'Puppet::Util::Firewall' do
let(:resource) {
type = Puppet::Type.type(:firewall)
provider = double 'provider'
allow(provider).to receive(:name).and_return(:iptables)
allow(Puppet::Type::Firewall).to receive(:defaultprovider).and_return(provider)
type.new({:name => '000 test foo'})
}
before(:each) { resource }
describe '#host_to_ip' do
subject { resource }
specify {
expect(Resolv).to receive(:getaddress).with('puppetlabs.com').and_return('96.126.112.51')
subject.host_to_ip('puppetlabs.com').should == '96.126.112.51/32'
}
specify { subject.host_to_ip('96.126.112.51').should == '96.126.112.51/32' }
specify { subject.host_to_ip('96.126.112.51/32').should == '96.126.112.51/32' }
specify { subject.host_to_ip('2001:db8:85a3:0:0:8a2e:370:7334').should == '2001:db8:85a3::8a2e:370:7334/128' }
specify { subject.host_to_ip('2001:db8:1234::/48').should == '2001:db8:1234::/48' }
specify { subject.host_to_ip('0.0.0.0/0').should == nil }
specify { subject.host_to_ip('::/0').should == nil }
end
describe '#host_to_mask' do
subject { resource }
specify {
expect(Resolv).to receive(:getaddress).at_least(:once).with('puppetlabs.com').and_return('96.126.112.51')
subject.host_to_mask('puppetlabs.com').should == '96.126.112.51/32'
subject.host_to_mask('!puppetlabs.com').should == '! 96.126.112.51/32'
}
specify { subject.host_to_mask('96.126.112.51').should == '96.126.112.51/32' }
specify { subject.host_to_mask('!96.126.112.51').should == '! 96.126.112.51/32' }
specify { subject.host_to_mask('96.126.112.51/32').should == '96.126.112.51/32' }
specify { subject.host_to_mask('! 96.126.112.51/32').should == '! 96.126.112.51/32' }
specify { subject.host_to_mask('2001:db8:85a3:0:0:8a2e:370:7334').should == '2001:db8:85a3::8a2e:370:7334/128' }
specify { subject.host_to_mask('!2001:db8:85a3:0:0:8a2e:370:7334').should == '! 2001:db8:85a3::8a2e:370:7334/128' }
specify { subject.host_to_mask('2001:db8:1234::/48').should == '2001:db8:1234::/48' }
specify { subject.host_to_mask('! 2001:db8:1234::/48').should == '! 2001:db8:1234::/48' }
specify { subject.host_to_mask('0.0.0.0/0').should == nil }
specify { subject.host_to_mask('!0.0.0.0/0').should == nil }
specify { subject.host_to_mask('::/0').should == nil }
specify { subject.host_to_mask('! ::/0').should == nil }
end
describe '#icmp_name_to_number' do
describe 'proto unsupported' do
subject { resource }
%w{inet5 inet8 foo}.each do |proto|
it "should reject invalid proto #{proto}" do
expect { subject.icmp_name_to_number('echo-reply', proto) }.
to raise_error(ArgumentError, "unsupported protocol family '#{proto}'")
end
end
end
describe 'proto IPv4' do
proto = 'inet'
subject { resource }
specify { subject.icmp_name_to_number('echo-reply', proto).should == '0' }
specify { subject.icmp_name_to_number('destination-unreachable', proto).should == '3' }
specify { subject.icmp_name_to_number('source-quench', proto).should == '4' }
specify { subject.icmp_name_to_number('redirect', proto).should == '6' }
specify { subject.icmp_name_to_number('echo-request', proto).should == '8' }
specify { subject.icmp_name_to_number('router-advertisement', proto).should == '9' }
specify { subject.icmp_name_to_number('router-solicitation', proto).should == '10' }
specify { subject.icmp_name_to_number('time-exceeded', proto).should == '11' }
specify { subject.icmp_name_to_number('parameter-problem', proto).should == '12' }
specify { subject.icmp_name_to_number('timestamp-request', proto).should == '13' }
specify { subject.icmp_name_to_number('timestamp-reply', proto).should == '14' }
specify { subject.icmp_name_to_number('address-mask-request', proto).should == '17' }
specify { subject.icmp_name_to_number('address-mask-reply', proto).should == '18' }
end
describe 'proto IPv6' do
proto = 'inet6'
subject { resource }
specify { subject.icmp_name_to_number('destination-unreachable', proto).should == '1' }
specify { subject.icmp_name_to_number('time-exceeded', proto).should == '3' }
specify { subject.icmp_name_to_number('parameter-problem', proto).should == '4' }
specify { subject.icmp_name_to_number('echo-request', proto).should == '128' }
specify { subject.icmp_name_to_number('echo-reply', proto).should == '129' }
specify { subject.icmp_name_to_number('router-solicitation', proto).should == '133' }
specify { subject.icmp_name_to_number('router-advertisement', proto).should == '134' }
specify { subject.icmp_name_to_number('redirect', proto).should == '137' }
end
end
describe '#string_to_port' do
subject { resource }
specify { subject.string_to_port('80','tcp').should == '80' }
specify { subject.string_to_port(80,'tcp').should == '80' }
specify { subject.string_to_port('http','tcp').should == '80' }
specify { subject.string_to_port('domain','udp').should == '53' }
end
describe '#to_hex32' do
subject { resource }
specify { subject.to_hex32('0').should == '0x0' }
specify { subject.to_hex32('0x32').should == '0x32' }
specify { subject.to_hex32('42').should == '0x2a' }
specify { subject.to_hex32('4294967295').should == '0xffffffff' }
specify { subject.to_hex32('4294967296').should == nil }
specify { subject.to_hex32('-1').should == nil }
specify { subject.to_hex32('bananas').should == nil }
end
describe '#persist_iptables' do
before { Facter.clear }
subject { resource }
describe 'when proto is IPv4' do
let(:proto) { 'IPv4' }
it 'should exec for RedHat identified from osfamily' do
allow(Facter.fact(:osfamily)).to receive(:value).and_return('RedHat')
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('RedHat')
expect(subject).to receive(:execute).with(%w{/sbin/service iptables save})
subject.persist_iptables(proto)
end
it 'should exec for systemd if running Fedora 15 or greater' do
allow(Facter.fact(:osfamily)).to receive(:value).and_return('RedHat')
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Fedora')
allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return('15')
expect(subject).to receive(:execute).with(%w{/usr/libexec/iptables.init save})
subject.persist_iptables(proto)
end
it 'should exec for CentOS identified from operatingsystem' do
allow(Facter.fact(:osfamily)).to receive(:value).and_return(nil)
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('CentOS')
expect(subject).to receive(:execute).with(%w{/sbin/service iptables save})
subject.persist_iptables(proto)
end
it 'should exec for Archlinux identified from osfamily' do
allow(Facter.fact(:osfamily)).to receive(:value).and_return('Archlinux')
expect(subject).to receive(:execute).with(['/bin/sh', '-c', '/usr/sbin/iptables-save > /etc/iptables/iptables.rules'])
subject.persist_iptables(proto)
end
it 'should raise a warning when exec fails' do
allow(Facter.fact(:osfamily)).to receive(:value).and_return('RedHat')
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('RedHat')
expect(subject).to receive(:execute).with(%w{/sbin/service iptables save}).
and_raise(Puppet::ExecutionFailure, 'some error')
expect(subject).to receive(:warning).with('Unable to persist firewall rules: some error')
subject.persist_iptables(proto)
end
end
describe 'when proto is IPv6' do
let(:proto) { 'IPv6' }
it 'should exec for newer Ubuntu' do
allow(Facter.fact(:osfamily)).to receive(:value).and_return(nil)
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Ubuntu')
allow(Facter.fact(:iptables_persistent_version)).to receive(:value).and_return('0.5.3ubuntu2')
expect(subject).to receive(:execute).with(%w{/usr/sbin/service iptables-persistent save})
subject.persist_iptables(proto)
end
it 'should not exec for older Ubuntu which does not support IPv6' do
allow(Facter.fact(:osfamily)).to receive(:value).and_return(nil)
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Ubuntu')
allow(Facter.fact(:iptables_persistent_version)).to receive(:value).and_return('0.0.20090701')
expect(subject).to receive(:execute).never
subject.persist_iptables(proto)
end
it 'should not exec for Suse which is not supported' do
allow(Facter.fact(:osfamily)).to receive(:value).and_return('Suse')
expect(subject).to receive(:execute).never
subject.persist_iptables(proto)
end
end
end
end

View File

@ -0,0 +1,67 @@
require 'spec_helper'
describe 'Puppet::Util::IPCidr' do
describe 'ipv4 address' do
before { @ipaddr = Puppet::Util::IPCidr.new('96.126.112.51') }
subject { @ipaddr }
specify { subject.cidr.should == '96.126.112.51/32' }
specify { subject.prefixlen.should == 32 }
specify { subject.netmask.should == '255.255.255.255' }
end
describe 'single ipv4 address with cidr' do
before { @ipcidr = Puppet::Util::IPCidr.new('96.126.112.51/32') }
subject { @ipcidr }
specify { subject.cidr.should == '96.126.112.51/32' }
specify { subject.prefixlen.should == 32 }
specify { subject.netmask.should == '255.255.255.255' }
end
describe 'ipv4 address range with cidr' do
before { @ipcidr = Puppet::Util::IPCidr.new('96.126.112.0/24') }
subject { @ipcidr }
specify { subject.cidr.should == '96.126.112.0/24' }
specify { subject.prefixlen.should == 24 }
specify { subject.netmask.should == '255.255.255.0' }
end
describe 'ipv4 open range with cidr' do
before { @ipcidr = Puppet::Util::IPCidr.new('0.0.0.0/0') }
subject { @ipcidr }
specify { subject.cidr.should == '0.0.0.0/0' }
specify { subject.prefixlen.should == 0 }
specify { subject.netmask.should == '0.0.0.0' }
end
describe 'ipv6 address' do
before { @ipaddr = Puppet::Util::IPCidr.new('2001:db8:85a3:0:0:8a2e:370:7334') }
subject { @ipaddr }
specify { subject.cidr.should == '2001:db8:85a3::8a2e:370:7334/128' }
specify { subject.prefixlen.should == 128 }
specify { subject.netmask.should == 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' }
end
describe 'single ipv6 addr with cidr' do
before { @ipaddr = Puppet::Util::IPCidr.new('2001:db8:85a3:0:0:8a2e:370:7334/128') }
subject { @ipaddr }
specify { subject.cidr.should == '2001:db8:85a3::8a2e:370:7334/128' }
specify { subject.prefixlen.should == 128 }
specify { subject.netmask.should == 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' }
end
describe 'ipv6 addr range with cidr' do
before { @ipaddr = Puppet::Util::IPCidr.new('2001:db8:1234::/48') }
subject { @ipaddr }
specify { subject.cidr.should == '2001:db8:1234::/48' }
specify { subject.prefixlen.should == 48 }
specify { subject.netmask.should == 'ffff:ffff:ffff:0000:0000:0000:0000:0000' }
end
describe 'ipv6 open range with cidr' do
before { @ipaddr = Puppet::Util::IPCidr.new('::/0') }
subject { @ipaddr }
specify { subject.cidr.should == '::/0' }
specify { subject.prefixlen.should == 0 }
specify { subject.netmask.should == '0000:0000:0000:0000:0000:0000:0000:0000' }
end
end

View File

@ -0,0 +1,7 @@
source 'https://rubygems.org'
puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 3.3']
gem 'puppet', puppetversion
gem 'puppetlabs_spec_helper', '>= 0.1.0'
gem 'puppet-lint', '>= 0.3.2'
gem 'facter', '>= 1.7.0'

View File

@ -0,0 +1,18 @@
require 'rubygems'
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint/tasks/puppet-lint'
PuppetLint.configuration.send('disable_80chars')
PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]
desc "Validate manifests, templates, and ruby files"
task :validate do
Dir['manifests/**/*.pp'].each do |manifest|
sh "puppet parser validate --noop #{manifest}"
end
Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file|
sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/
end
Dir['templates/**/*.erb'].each do |template|
sh "erb -P -x -T '-' #{template} | ruby -c"
end
end

View File

@ -0,0 +1,22 @@
Puppet::Type.type(:ml2_config).provide(
:ini_setting,
:parent => Puppet::Type.type(:ini_setting).provider(:ruby)
) do
def section
resource[:name].split('/', 2).first
end
def setting
resource[:name].split('/', 2).last
end
def separator
'='
end
def file_path
'/etc/neutron/plugins/ml2/ml2_conf.ini'
end
end

View File

@ -0,0 +1,42 @@
Puppet::Type.newtype(:ml2_config) do
ensurable
newparam(:name, :namevar => true) do
desc 'Section/setting name to manage from /etc/neutron/plugins/ml2/ml2_conf.ini'
newvalues(/\S+\/\S+/)
end
newproperty(:value) do
desc 'The value of the setting to be defined.'
munge do |value|
value = value.to_s.strip
value.capitalize! if value =~ /^(true|false)$/i
value
end
def is_to_s( currentvalue )
if resource.secret?
return '[old secret redacted]'
else
return currentvalue
end
end
def should_to_s( newvalue )
if resource.secret?
return '[new secret redacted]'
else
return newvalue
end
end
end
newparam(:secret, :boolean => true) do
desc 'Whether to hide the value from Puppet logs. Defaults to `false`.'
newvalues(:true, :false)
defaultto false
end
end

View File

@ -0,0 +1,89 @@
# == Class: vxlan::compute
#
# Full description of class vxlan here.
#
# === Parameters
#
# Document parameters here.
#
# [*sample_parameter*]
# Explanation of what this parameter affects and what it defaults to.
# e.g. "Specify one or more upstream ntp servers as an array."
#
# === Variables
#
# Here you should define a list of variables that this module would require.
#
# [*sample_variable*]
# Explanation of how this variable affects the funtion of this class and if
# it has a default. e.g. "The parameter enc_ntp_servers must be set by the
# External Node Classifier as a comma separated list of hostnames." (Note,
# global variables should be avoided in favor of class parameters as
# of Puppet 2.6.)
#
# === Examples
#
# class { 'vxlan':
#
# }
#
# === Authors
#
# Author Name <samuel.bartel@orange.com>
#
# === Copyright
#
# Copyright 2014 Your name here, unless otherwise noted.
#
class vxlan::compute (
$vxlan_port = 4789,
){
include vxlan::params
#add vxlan tunnel type to ovs and agent
neutron_plugin_ml2 {
'ml2/type_drivers': value => 'vxlan,flat,vlan,gre';
}~> Service['neutron-plugin-openvswitch-agent']
neutron_plugin_ml2 {
'ml2/tenant_network_types': value => 'vxlan,flat,vlan,gre';
}~> Service['neutron-plugin-openvswitch-agent']
neutron_plugin_ml2 {
'ovs/tunnel_type': value => 'vxlan,gre';
}
neutron_plugin_ml2 {
'agent/tunnel_types': value => 'vxlan,gre';
}~> Service['neutron-plugin-openvswitch-agent']
class {'::firewall':}
firewall { '334 notrack vxlan':
port => $vxlan_port,
chain => 'PREROUTING',
table => 'raw',
proto => 'udp',
jump => 'NOTRACK',
}
firewall { '335 accept vxlan port 4789':
chain => 'INPUT',
table => 'filter',
port => $vxlan_port,
proto => 'udp',
action => 'accept',
}
service { 'neutron-plugin-openvswitch-agent':
ensure => running,
enable => true,
}
}

View File

@ -0,0 +1,77 @@
# == Class: vxlan
#
# Full description of class vxlan here.
#
# === Parameters
#
# Document parameters here.
#
# [*sample_parameter*]
# Explanation of what this parameter affects and what it defaults to.
# e.g. "Specify one or more upstream ntp servers as an array."
#
# === Variables
#
# Here you should define a list of variables that this module would require.
#
# [*sample_variable*]
# Explanation of how this variable affects the funtion of this class and if
# it has a default. e.g. "The parameter enc_ntp_servers must be set by the
# External Node Classifier as a comma separated list of hostnames." (Note,
# global variables should be avoided in favor of class parameters as
# of Puppet 2.6.)
#
# === Examples
#
# class { 'vxlan':
#
# }
#
# === Authors
#
# Author Name <samuel.bartel@orange.com>
#
# === Copyright
#
# Copyright 2014 Your name here, unless otherwise noted.
#
class vxlan::controller (
$vxlan_port = 4789,
){
include vxlan::params
#update ml2 configuration
neutron_plugin_ml2 {
'ml2/type_drivers': value => 'vxlan,flat,vlan,gre';
'ml2/tenant_network_types': value => 'vxlan,flat,vlan,gre';
'ovs/tunnel_type': value => 'vxlan,gre';
}~> Service['neutron-server']
class{'vxlan::neutron_services':}
#add vxlan port to firewall
class {'::firewall':}
firewall { '334 notrack vxlan':
port => $vxlan_port,
chain => 'PREROUTING',
table => 'raw',
proto => 'udp',
jump => 'NOTRACK',
}
firewall { '335 accept vxlan port 4789':
chain => 'INPUT',
table => 'filter',
port => $vxlan_port,
proto => 'udp',
action => 'accept',
}
service { 'neutron-server':
ensure => running,
enable => true,
}
}

View File

@ -0,0 +1,68 @@
# == Class: vxlan
#
# Full description of class vxlan here.
#
# === Parameters
#
# Document parameters here.
#
# [*sample_parameter*]
# Explanation of what this parameter affects and what it defaults to.
# e.g. "Specify one or more upstream ntp servers as an array."
#
# === Variables
#
# Here you should define a list of variables that this module would require.
#
# [*sample_variable*]
# Explanation of how this variable affects the funtion of this class and if
# it has a default. e.g. "The parameter enc_ntp_servers must be set by the
# External Node Classifier as a comma separated list of hostnames." (Note,
# global variables should be avoided in favor of class parameters as
# of Puppet 2.6.)
#
# === Examples
#
# class { 'vxlan':
#
# }
#
# === Authors
#
# Author Name <samuel.bartel@orange.com>
#
# === Copyright
#
# Copyright 2014 Your name here, unless otherwise noted.
#
class vxlan::neutron_services (){
if ($::fuel_settings['deployment_mode'] == 'multinode') {
neutron_plugin_ml2 {
'agent/tunnel_types': value => 'vxlan,gre';
}~> Service['neutron-plugin-openvswitch-agent']
service { 'neutron-plugin-openvswitch-agent':
ensure => running,
enable => true,
}
service { 'neutron-l3-agent':
ensure => running,
enable => true,
}
}
else {
neutron_plugin_ml2 {
'agent/tunnel_types': value => 'vxlan';
}->
exec { "neutron-pluign-openvswitch-agent_restart":
command => "/usr/sbin/crm resource restart p_neutron-plugin-openvswitch-agent",
}->
exec { "neutron-l3-agent_restart":
command => "/usr/sbin/crm resource restart p_neutron-l3-agent",
}
}
}

View File

@ -0,0 +1,3 @@
class vxlan::params {
$ml2_conf_file = '/etc/neutron/plugins/ml2/ml2_conf.ini'
}

View File

@ -0,0 +1,14 @@
{
"name": "vxlan",
"version": "0.1.0",
"author": "samuelbartel",
"summary": "Module to manage vxlan",
"license": "Apache 2.0",
"source": "",
"project_page": "samuel.bartel@orange.com",
"issues_url": "samuel.bartel@orange.com",
"dependencies": [
{"name":"puppetlabs-stdlib","version_requirement":">= 1.0.0"}
]
}

View File

@ -0,0 +1,7 @@
require 'spec_helper'
describe 'vxlan' do
context 'with defaults for all parameters' do
it { should contain_class('vxlan') }
end
end

View File

@ -0,0 +1 @@
require 'puppetlabs_spec_helper/module_spec_helper'

View File

@ -0,0 +1,12 @@
# The baseline for module testing used by Puppet Labs is that each manifest
# should have a corresponding test manifest that declares that class or defined
# type.
#
# Tests are then run by using puppet apply --noop (to check for compilation
# errors and view a log of events) or by fully applying the test in a virtual
# environment (to compare the resulting system state to the desired state).
#
# Learn more about module testing here:
# http://docs.puppetlabs.com/guides/tests_smoke.html
#
include vxlan

6
environment_config.yaml Normal file
View File

@ -0,0 +1,6 @@
attributes:
# Show vxlan only for neutron
metadata:
restrictions:
- condition: "cluster:net_provider != 'neutron'"
action: hide

25
metadata.yaml Normal file
View File

@ -0,0 +1,25 @@
# Plugin name
name: vxlan
title: Vxlan plugin for Neutron
# Plugin version
version: 1.0.0
# Description
description: Enables Vxlan for Neutron, works in multinode mode only
# Required fuel version
fuel_version: ['6.0']
# The plugin is compatible with releases in the list
releases:
- os: ubuntu
version: 2014.2-6.0
mode: ['ha', 'multinode']
deployment_scripts_path: deployment_scripts/
repository_path: repositories/ubuntu
- os: centos
version: 2014.2-6.0
mode: ['ha', 'multinode']
deployment_scripts_path: deployment_scripts/
repository_path: repositories/centos
# Version of plugin package
package_version: '1.0.0'

5
pre_build_hook Normal file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# Add here any the actions which are required before plugin build
# like packages building, packages downloading from mirrors and so on.
# The script should return 0 if there were no errors.

View File

View File

15
tasks.yaml Normal file
View File

@ -0,0 +1,15 @@
# Deployment is required for controllers
- role: ['primary-controller', 'controller']
stage: post_deployment
type: puppet
parameters:
puppet_manifest: puppet/manifests/site-controller-post.pp
puppet_modules: puppet/modules:/etc/puppet/modules/
timeout: 360
- role: ['compute']
stage: post_deployment
type: puppet
parameters:
puppet_manifest: puppet/manifests/site-compute-post.pp
puppet_modules: puppet/modules:/etc/puppet/modules/
timeout: 360