diff --git a/modules/apt/CHANGELOG.md b/modules/apt/CHANGELOG.md index bf8553d..f16b803 100644 --- a/modules/apt/CHANGELOG.md +++ b/modules/apt/CHANGELOG.md @@ -1,3 +1,8 @@ +##2014-07-10 - Supported Release 1.5.1 +###Summary + +This release has added tests to ensure graceful failure on OSX. + ##2014-06-04 - Release 1.5.0 ###Summary diff --git a/modules/apt/Gemfile b/modules/apt/Gemfile index 1af60fc..78cf9f4 100644 --- a/modules/apt/Gemfile +++ b/modules/apt/Gemfile @@ -2,6 +2,7 @@ source ENV['GEM_SOURCE'] || 'https://rubygems.org' group :development, :test do gem 'rake', '10.1.1', :require => false + gem 'rspec', '~> 2.11', :require => false gem 'pry', :require => false gem 'rspec-puppet', :require => false gem 'puppet-lint', :require => false diff --git a/modules/apt/Modulefile b/modules/apt/Modulefile index 45169b3..b06eba3 100644 --- a/modules/apt/Modulefile +++ b/modules/apt/Modulefile @@ -1,5 +1,5 @@ name 'puppetlabs-apt' -version '1.5.0' +version '1.5.1' source 'https://github.com/puppetlabs/puppetlabs-apt' author 'Evolving Web / Puppet Labs' license 'Apache License 2.0' diff --git a/modules/apt/checksums.json b/modules/apt/checksums.json index 92fea70..dfb2a3a 100644 --- a/modules/apt/checksums.json +++ b/modules/apt/checksums.json @@ -1,9 +1,8 @@ { - "CHANGELOG.md": "2edd771d2aa653812ac9f58ddb8a8dd6", - "Gemfile": "0d209c30c4451b1cdce50967b396a8cb", - "Gemfile.lock": "3cc280f2c4a11d8e1a3c010a5e503b24", + "CHANGELOG.md": "595b1e7dc9bc1b5eadbe3ffe58ab085e", + "Gemfile": "d707c4e5d933f198c606918143321739", "LICENSE": "c236a08bf88c5c8e480957038c869ed1", - "Modulefile": "e2ddc834592f3bd9dc229c3b7edda549", + "Modulefile": "b8c18422e33c5272099a436bd70d8c45", "README.md": "0047d76fa8c10a673d556f5c099a1113", "Rakefile": "de8eeacfe1fbbc6a6f4d89adfc98bcaf", "lib/puppet/provider/apt_key/apt_key.rb": "7c1517b4ec04c5574f078b1651ed3c5c", @@ -25,7 +24,7 @@ "manifests/source.pp": "9f36b03fde5b1148d01c89677eee6b0e", "manifests/unattended_upgrades.pp": "aaf5e4c350800d2d77faac0430c4c598", "manifests/update.pp": "2950af6e8e4a4b30ecdf2921d7f01fee", - "metadata.json": "9e77b5b32cd0c0379601b3c6f15c4f7c", + "metadata.json": "7e4a6bf01989519b65055a4faa9e03e5", "spec/acceptance/apt_builddep_spec.rb": "fb7ede528790507df42032d77eb74211", "spec/acceptance/apt_key_provider_spec.rb": "bfc96c53af3e7ea1e1f3694466a40e86", "spec/acceptance/apt_key_spec.rb": "5a5a6586db3010a0876dfa4ee29e3155", @@ -50,7 +49,7 @@ "spec/classes/backports_spec.rb": "fe8d8f03cacaed1134cd8b612f7ee139", "spec/classes/debian_testing_spec.rb": "939bc900d8d9bc82e6c378b21b56035a", "spec/classes/debian_unstable_spec.rb": "ac7e6a5bd599fd54ddc91a20c9777a90", - "spec/classes/init_spec.rb": "c08cdc9c025b1b343911d87ce4838bd2", + "spec/classes/init_spec.rb": "675019cee8af90adfdcfef49a8d9ef7c", "spec/classes/params_spec.rb": "d5e4ef728174328113c75fbf6de85692", "spec/classes/release_spec.rb": "bc3cce54aca2fef36a60fdfa0ae24488", "spec/classes/unattended_upgrades_spec.rb": "686780f08979420bfe34495227fe284b", diff --git a/modules/apt/metadata.json b/modules/apt/metadata.json index 66a6145..7dad03b 100644 --- a/modules/apt/metadata.json +++ b/modules/apt/metadata.json @@ -1,6 +1,6 @@ { "name": "puppetlabs-apt", - "version": "1.5.0", + "version": "1.5.1", "author": "Evolving Web / Puppet Labs", "summary": "Puppet Labs Apt Module", "license": "Apache License 2.0", diff --git a/modules/apt/spec/classes/init_spec.rb b/modules/apt/spec/classes/init_spec.rb index 120b7e8..c5e938a 100644 --- a/modules/apt/spec/classes/init_spec.rb +++ b/modules/apt/spec/classes/init_spec.rb @@ -54,4 +54,16 @@ describe 'apt' do it { should contain_file('puppetlabs.list').with_content(/^deb http:\/\/apt.puppetlabs.com precise main$/) } it { should contain_file('puppetlabs.list').with_content(/^deb-src http:\/\/apt.puppetlabs.com precise main$/) } end + + context 'with unsupported osfamily' do + let :facts do + { :osfamily => 'Darwin', } + end + + it do + expect { + should compile + }.to raise_error(Puppet::Error, /This module only works on Debian or derivatives like Ubuntu/) + end + end end diff --git a/modules/firewall/CHANGELOG.md b/modules/firewall/CHANGELOG.md new file mode 100644 index 0000000..8c6d848 --- /dev/null +++ b/modules/firewall/CHANGELOG.md @@ -0,0 +1,437 @@ +##2014-06-04 - Release 1.1.2 +###Summary + +This is a release of the code previously released as 1.1.1, with updated metadata. + +## 2014-05-16 Release 1.1.1 +###Summary + +This release reverts the alphabetical ordering of 1.1.0. We found this caused +a regression in the Openstack modules so in the interest of safety we have +removed this for now. + +## 2014-05-13 Release 1.1.0 +###Summary + +This release has a significant change from previous releases; we now apply the +firewall resources alphabetically by default, removing the need to create pre +and post classes just to enforce ordering. It only effects default ordering +and further information can be found in the README about this. Please test +this in development before rolling into production out of an abundance of +caution. + +We've also added `mask` which is required for --recent in recent (no pun +intended) versions of iptables, as well as connlimit and connmark. This +release has been validated against Ubuntu 14.04 and RHEL7 and should be fully +working on those platforms. + +####Features + +- Apply firewall resources alphabetically. +- Add support for connlimit and connmark. +- Add `mask` as a parameter. (Used exclusively with the recent parameter). + +####Bugfixes + +- Add systemd support for RHEL7. +- Replace &&'s with the correct and in manifests. +- Fix tests on Trusty and RHEL7 +- Fix for Fedora Rawhide. +- Fix boolean flag tests. +- Fix DNAT->SNAT typo in an error message. + +####Known Bugs + +* For Oracle, the `owner` and `socket` parameters require a workaround to function. Please see the Limitations section of the README. + + +## 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 ... diff --git a/modules/firewall/CONTRIBUTING.md b/modules/firewall/CONTRIBUTING.md new file mode 100644 index 0000000..630ba85 --- /dev/null +++ b/modules/firewall/CONTRIBUTING.md @@ -0,0 +1,87 @@ +# How to contribute + +Third-party patches are essential for keeping puppet great. We simply can't +access the huge number of platforms and myriad configurations for running +puppet. We want to keep it as easy as possible to contribute changes that +get things working 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. + +## Getting Started + +* Make sure you have a [Jira account](http://tickets.puppetlabs.com) +* Make sure you have a [GitHub account](https://github.com/signup/free) +* Submit a ticket for your issue, assuming one does not already exist. + * Clearly describe the issue including steps to reproduce when it is a bug. + * Make sure you fill in the earliest version that you know has the issue. +* Fork the repository on GitHub + +## Making Changes + +* Create a topic branch from where you want to base your work. + * This is usually the master branch. + * Only target release branches if you are certain your fix must be on that + branch. + * To quickly create a topic branch based on master; `git branch + fix/master/my_contribution master` then checkout the new branch with `git + checkout fix/master/my_contribution`. Please avoid working directly on the + `master` branch. +* Make commits of logical units. +* Check for unnecessary whitespace with `git diff --check` before committing. +* Make sure your commit messages are in the proper format. + +```` + (MODULES-1234) Make the example in CONTRIBUTING imperative and concrete + + Without this patch applied the example commit message in the CONTRIBUTING + document is not a concrete example. This is a problem because the + contributor is left to imagine what the commit message should look like + based on a description rather than an example. This patch fixes the + problem by making the example concrete and imperative. + + The first line is a real life imperative statement with a ticket number + from our issue tracker. The body describes the behavior without the patch, + why this is a problem, and how the patch fixes the problem when applied. +```` + +* Make sure you have added the necessary tests for your changes. +* Run _all_ the tests to assure nothing else was accidentally broken. + +## Making Trivial Changes + +### Documentation + +For changes of a trivial nature to comments and documentation, it is not +always necessary to create a new ticket in Jira. In this case, it is +appropriate to start the first line of a commit with '(doc)' instead of +a ticket number. + +```` + (doc) Add documentation commit example to CONTRIBUTING + + There is no example for contributing a documentation commit + to the Puppet repository. This is a problem because the contributor + is left to assume how a commit of this nature may appear. + + The first line is a real life imperative statement with '(doc)' in + place of what would have been the ticket number in a + non-documentation related commit. The body describes the nature of + the new documentation or comments added. +```` + +## Submitting Changes + +* Sign the [Contributor License Agreement](http://links.puppetlabs.com/cla). +* Push your changes to a topic branch in your fork of the repository. +* Submit a pull request to the repository in the puppetlabs organization. +* Update your Jira ticket to mark that you have submitted code and are ready for it to be reviewed (Status: Ready for Merge). + * Include a link to the pull request in the ticket. + +# Additional Resources + +* [More information on contributing](http://projects.puppetlabs.com/projects/module-site/wiki/Module_contributing) +* [Bug tracker (Jira)](http://tickets.puppetlabs.com) +* [Contributor License Agreement](http://links.puppetlabs.com/cla) +* [General GitHub documentation](http://help.github.com/) +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) +* #puppet-dev IRC channel on freenode.org diff --git a/modules/firewall/Gemfile b/modules/firewall/Gemfile new file mode 100644 index 0000000..9e6eaa5 --- /dev/null +++ b/modules/firewall/Gemfile @@ -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 diff --git a/modules/apt/Gemfile.lock b/modules/firewall/Gemfile.lock similarity index 74% rename from modules/apt/Gemfile.lock rename to modules/firewall/Gemfile.lock index e96f32d..1c2212b 100644 --- a/modules/apt/Gemfile.lock +++ b/modules/firewall/Gemfile.lock @@ -1,20 +1,22 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.2.7) + CFPropertyList (2.2.8) addressable (2.3.6) + archive-tar-minitar (0.5.2) autoparse (0.3.3) addressable (>= 2.3.1) extlib (>= 0.9.15) multi_json (>= 1.0.0) - aws-sdk (1.39.0) + aws-sdk (1.40.1) json (~> 1.4) nokogiri (>= 1.4.4) - beaker (1.10.0) + beaker (1.11.0) aws-sdk (~> 1.38) blimpy (~> 0.6) + docker-api fission (~> 0.4) - google-api-client (~> 0.6.4) + google-api-client (~> 0.7.1) inifile (~> 2.0) json (~> 1.8) mime-types (~> 1.25) @@ -23,11 +25,11 @@ GEM nokogiri (= 1.5.10) rbvmomi (= 1.8.1) unf (~> 0.1) - beaker-rspec (2.2.3) - beaker (~> 1.10.0) + beaker-rspec (2.2.4) + beaker (~> 1.10) rspec (~> 2.14) - serverspec (~> 1.0.0) - specinfra (~> 1.0.0) + serverspec (~> 1.0) + specinfra (~> 1.0) blimpy (0.6.7) fog minitar @@ -35,34 +37,39 @@ GEM builder (3.2.2) coderay (1.1.0) diff-lcs (1.2.5) - excon (0.31.0) + docker-api (1.10.10) + archive-tar-minitar + excon (>= 0.28) + json + excon (0.33.0) extlib (0.9.16) facter (2.0.1) CFPropertyList (~> 2.2.6) - faraday (0.8.9) - multipart-post (~> 1.2.0) + faraday (0.9.0) + multipart-post (>= 1.2, < 3) fission (0.5.0) CFPropertyList (~> 2.2) - fog (1.19.0) + fog (1.11.1) builder - excon (~> 0.31.0) + excon (~> 0.20) formatador (~> 0.2.0) + json (~> 1.7) mime-types - multi_json (~> 1.0) net-scp (~> 1.1) net-ssh (>= 2.1.3) - nokogiri (~> 1.5) + nokogiri (~> 1.5.0) ruby-hmac formatador (0.2.4) - google-api-client (0.6.4) + google-api-client (0.7.1) addressable (>= 2.3.2) autoparse (>= 0.3.3) extlib (>= 0.9.15) - faraday (~> 0.8.4) + faraday (>= 0.9.0) jwt (>= 0.1.5) launchy (>= 2.1.1) multi_json (>= 1.0.0) - signet (~> 0.4.5) + retriable (>= 1.4) + signet (>= 0.5.0) uuidtools (>= 2.1.0) hiera (1.3.2) json_pure @@ -70,8 +77,7 @@ GEM inifile (2.0.2) json (1.8.1) json_pure (1.8.1) - jwt (0.1.11) - multi_json (>= 1.5) + jwt (1.0.0) launchy (2.4.2) addressable (~> 2.3) metaclass (0.0.4) @@ -80,11 +86,11 @@ GEM minitar (0.5.4) mocha (1.0.0) metaclass (~> 0.0.1) - multi_json (1.9.3) - multipart-post (1.2.0) + multi_json (1.10.0) + multipart-post (2.0.0) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (2.9.0) + net-ssh (2.9.1) nokogiri (1.5.10) pry (0.9.12.6) coderay (~> 1.0) @@ -101,11 +107,12 @@ GEM rake rspec (>= 2.9.0) rspec-puppet (>= 0.1.1) - rake (10.1.1) + rake (10.3.1) rbvmomi (1.8.1) builder nokogiri (>= 1.4.1) trollop + retriable (1.4.1) rgen (0.6.6) rspec (2.14.1) rspec-core (~> 2.14.0) @@ -118,18 +125,18 @@ GEM rspec-puppet (1.0.1) rspec ruby-hmac (0.4.0) - serverspec (1.0.0) + serverspec (1.6.0) highline net-ssh rspec (~> 2.13) - specinfra (>= 1.0.0) - signet (0.4.5) + specinfra (~> 1.11) + signet (0.5.0) addressable (>= 2.2.3) - faraday (~> 0.8.1) + faraday (>= 0.9.0.rc5) jwt (>= 0.1.5) multi_json (>= 1.0.0) slop (3.5.0) - specinfra (1.0.5) + specinfra (1.12.0) thor (0.19.1) trollop (2.0) unf (0.1.4) @@ -141,12 +148,10 @@ PLATFORMS ruby DEPENDENCIES - beaker beaker-rspec pry puppet puppet-lint puppetlabs_spec_helper - rake (= 10.1.1) rspec-puppet serverspec diff --git a/modules/firewall/LICENSE b/modules/firewall/LICENSE new file mode 100644 index 0000000..1d196fc --- /dev/null +++ b/modules/firewall/LICENSE @@ -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. diff --git a/modules/firewall/Modulefile b/modules/firewall/Modulefile new file mode 100644 index 0000000..1d6bbe2 --- /dev/null +++ b/modules/firewall/Modulefile @@ -0,0 +1,8 @@ +name 'puppetlabs-firewall' +version '1.1.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' diff --git a/modules/firewall/README.markdown b/modules/firewall/README.markdown new file mode 100644 index 0000000..2470514 --- /dev/null +++ b/modules/firewall/README.markdown @@ -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', + state => ['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: + + + +###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: + + + +###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 JIRA issues + + + +##Development + +Puppet Labs modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can’t 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. diff --git a/modules/firewall/Rakefile b/modules/firewall/Rakefile new file mode 100644 index 0000000..8b12070 --- /dev/null +++ b/modules/firewall/Rakefile @@ -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, +] diff --git a/modules/firewall/checksums.json b/modules/firewall/checksums.json new file mode 100644 index 0000000..a183a2d --- /dev/null +++ b/modules/firewall/checksums.json @@ -0,0 +1,72 @@ +{ + "CHANGELOG.md": "74a76346f2d59dad54791724a3619b8c", + "CONTRIBUTING.md": "abdca3447afde207929d49c4c65eb7e7", + "Gemfile": "9c34a2c45945aa2601a98a521364b524", + "Gemfile.lock": "cbcf4134a7fa9e8d796f967739082543", + "LICENSE": "ade7f2bb88b5b4f034152822222ec314", + "Modulefile": "e5a482fea468afc196e17bd31aba1eb7", + "README.markdown": "9aa129c5afecefeaedc1e642f9d40dee", + "Rakefile": "f939a830f3e97bea4ebfa5773af890d2", + "lib/facter/ip6tables_version.rb": "091123ad703f1706686bca4398c5b06f", + "lib/facter/iptables_persistent_version.rb": "b7a47827cd3d3bb1acbd526a31da3acb", + "lib/facter/iptables_version.rb": "facbd760223f236538b731c1d1f6cf8f", + "lib/puppet/provider/firewall/ip6tables.rb": "06198524b5237bcf9222bce72535d817", + "lib/puppet/provider/firewall/iptables.rb": "470fdc741c7d60843573b23a2c9fc321", + "lib/puppet/provider/firewall.rb": "32d2f5e5dcc082986b82ef26a119038b", + "lib/puppet/provider/firewallchain/iptables_chain.rb": "194bde3d3992a37aa7d76e431d251178", + "lib/puppet/type/firewall.rb": "88739d191642568aab222bca3fb79b8a", + "lib/puppet/type/firewallchain.rb": "548676cc7da53598eb24268ebac38a0d", + "lib/puppet/util/firewall.rb": "6f7667742d9f6d192cd202be0014dd85", + "lib/puppet/util/ipcidr.rb": "e1160dfd6e73fc5ef2bb8abc291f6fd5", + "manifests/init.pp": "ba3e697f00fc3d4e7e5b9c7fdbc6a89d", + "manifests/linux/archlinux.pp": "1257fe335ecafa0629b285dc8621cf75", + "manifests/linux/debian.pp": "626f0fd23f2f451ca14e2b7f690675fe", + "manifests/linux/redhat.pp": "cd4316ac5df986a15fafaafbcbb99a74", + "manifests/linux.pp": "eb4a8970e7b90fa27b1b4968e2cb77b4", + "metadata.json": "2151a0c4602b2e4be575282d44989c21", + "spec/acceptance/change_source_spec.rb": "86279f2f4ad9e0532c9ffab00db8e009", + "spec/acceptance/class_spec.rb": "bf8fabb101db46d438fa3aedf35bba08", + "spec/acceptance/connlimit_spec.rb": "b0b6600381479e692d0f50215cc15121", + "spec/acceptance/connmark_spec.rb": "cf4233e7327af69f0d4431d63140edf1", + "spec/acceptance/firewall_spec.rb": "7ee0d49fa4f471d6190e84205941aab1", + "spec/acceptance/firewallchain_spec.rb": "1fb101c958bde31ac371e15052dfd6f9", + "spec/acceptance/ip6_fragment_spec.rb": "0fed30601238313a51dbc1115840e2bc", + "spec/acceptance/isfragment_spec.rb": "80d254c6c74e8dfc2a787115ec2b2ae5", + "spec/acceptance/nodesets/centos-59-x64-pe.yml": "255100fb63e3b7c204e24dc9464ae904", + "spec/acceptance/nodesets/centos-59-x64.yml": "879c50ab100b927bdbbc130f23baaff7", + "spec/acceptance/nodesets/centos-64-x64-fusion.yml": "f5da18ffa623c6a13d9f48521b1bfa39", + "spec/acceptance/nodesets/centos-64-x64-pe.yml": "ec075d95760df3d4702abea1ce0a829b", + "spec/acceptance/nodesets/centos-64-x64.yml": "092dd2c588a9f87fa1fb12997c0723ef", + "spec/acceptance/nodesets/debian-607-x64.yml": "d566bf76f534e2af7c9a4605316d232c", + "spec/acceptance/nodesets/debian-70rc1-x64.yml": "31ccca73af7b74e1cc2fb0035c230b2c", + "spec/acceptance/nodesets/default.yml": "092dd2c588a9f87fa1fb12997c0723ef", + "spec/acceptance/nodesets/fedora-18-x64.yml": "acc126fa764c39a3b1df36e9224a21d9", + "spec/acceptance/nodesets/sles-11sp1-x64.yml": "fa0046bd89c1ab4ba9521ad79db234cd", + "spec/acceptance/nodesets/ubuntu-server-10044-x64.yml": "dc0da2d2449f66c8fdae16593811504f", + "spec/acceptance/nodesets/ubuntu-server-12042-x64.yml": "d30d73e34cd50b043c7d14e305955269", + "spec/acceptance/nodesets/ubuntu-server-1404-x64.yml": "ea006afd1329a2d7a8e35d8287ec1658", + "spec/acceptance/params_spec.rb": "d86987bfc2156ed840c8b1ca33a92e0d", + "spec/acceptance/purge_spec.rb": "1a8327cc7203c6ebfd0ee1c7c8578367", + "spec/acceptance/resource_cmd_spec.rb": "9b03213492b13fbb309189246b69cadd", + "spec/acceptance/rules_spec.rb": "739538383e33dd711a66531189aa10d4", + "spec/acceptance/socket_spec.rb": "ee160ba60e3451f7a6a386e38f1f0de7", + "spec/acceptance/standard_usage_spec.rb": "71002dbb3939fc1bbec7f9045db0b30b", + "spec/acceptance/unsupported_spec.rb": "5540063193905d4e8235d6042e8633fc", + "spec/fixtures/ip6tables/conversion_hash.rb": "0d88a4226fdf28dd55e30f7e6d4a1cb7", + "spec/fixtures/iptables/conversion_hash.rb": "c63c630866e543e6f5e23dc30ec53212", + "spec/spec_helper.rb": "faae8467928b93bd251a1a66e1eedbe5", + "spec/spec_helper_acceptance.rb": "5a6eeb4ed87fa18a2a066fa03c5ec6df", + "spec/unit/classes/firewall_linux_archlinux_spec.rb": "1c600a9852ec328b14cb15b0630ed5ff", + "spec/unit/classes/firewall_linux_debian_spec.rb": "6334936fb16223cf15f637083c67850e", + "spec/unit/classes/firewall_linux_redhat_spec.rb": "f41b21caf6948f3ac08f42c1bc59ba1b", + "spec/unit/classes/firewall_linux_spec.rb": "5ea19b1d9b98f5d47d15f6c89c55a5c8", + "spec/unit/classes/firewall_spec.rb": "65e27f1c85066641d65d52c35c1d46cf", + "spec/unit/facter/iptables_persistent_version_spec.rb": "e08c68b6400c51735a3ac1cc4acf2d15", + "spec/unit/facter/iptables_spec.rb": "5265be1e6ec652184de8b314ae396d1a", + "spec/unit/puppet/provider/iptables_chain_spec.rb": "5b5dbc4cea409f6f40ef3f18e46fa270", + "spec/unit/puppet/provider/iptables_spec.rb": "2d0ed593e9e6996162b3abe4569dd9c6", + "spec/unit/puppet/type/firewall_spec.rb": "a57dc53289de01f80611292f5367b4ae", + "spec/unit/puppet/type/firewallchain_spec.rb": "ea82405b12b9c0069a6b38a48a6a544a", + "spec/unit/puppet/util/firewall_spec.rb": "7fe65c0ab8ea7438ec99a6fab0e2c619", + "spec/unit/puppet/util/ipcidr_spec.rb": "1a6eeb2dd7c9634fcfb60d8ead6e1d79" +} \ No newline at end of file diff --git a/modules/firewall/lib/facter/ip6tables_version.rb b/modules/firewall/lib/facter/ip6tables_version.rb new file mode 100644 index 0000000..3dce27f --- /dev/null +++ b/modules/firewall/lib/facter/ip6tables_version.rb @@ -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 diff --git a/modules/firewall/lib/facter/iptables_persistent_version.rb b/modules/firewall/lib/facter/iptables_persistent_version.rb new file mode 100644 index 0000000..80bf9de --- /dev/null +++ b/modules/firewall/lib/facter/iptables_persistent_version.rb @@ -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 diff --git a/modules/firewall/lib/facter/iptables_version.rb b/modules/firewall/lib/facter/iptables_version.rb new file mode 100644 index 0000000..6f7ae56 --- /dev/null +++ b/modules/firewall/lib/facter/iptables_version.rb @@ -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 diff --git a/modules/firewall/lib/puppet/provider/firewall.rb b/modules/firewall/lib/puppet/provider/firewall.rb new file mode 100644 index 0000000..c6b0b10 --- /dev/null +++ b/modules/firewall/lib/puppet/provider/firewall.rb @@ -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 diff --git a/modules/firewall/lib/puppet/provider/firewall/ip6tables.rb b/modules/firewall/lib/puppet/provider/firewall/ip6tables.rb new file mode 100644 index 0000000..e1ce01a --- /dev/null +++ b/modules/firewall/lib/puppet/provider/firewall/ip6tables.rb @@ -0,0 +1,136 @@ +Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source => :iptables do + @doc = "Ip6tables type provider" + + has_feature :iptables + has_feature :connection_limiting + 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", + :connlimit_above => "-m connlimit --connlimit-above", + :connlimit_mask => "--connlimit-mask", + :connmark => "-m connmark --mark", + :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, + :connlimit_above, :connlimit_mask, :connmark] + +end diff --git a/modules/firewall/lib/puppet/provider/firewall/iptables.rb b/modules/firewall/lib/puppet/provider/firewall/iptables.rb new file mode 100644 index 0000000..5ad1012 --- /dev/null +++ b/modules/firewall/lib/puppet/provider/firewall/iptables.rb @@ -0,0 +1,501 @@ +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 :connection_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 :isfragment + has_feature :socket + has_feature :address_type + has_feature :iprange + has_feature :ipsec_dir + has_feature :ipsec_policy + has_feature :mask + + 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", + :connlimit_above => "-m connlimit --connlimit-above", + :connlimit_mask => "--connlimit-mask", + :connmark => "-m connmark --mark", + :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", + :mask => '--mask', + } + + # 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, :mask, :rsource, :rdest, :jump, :todest, + :tosource, :toports, :random, :log_prefix, :log_level, :reject, :set_mark, + :connlimit_above, :connlimit_mask, :connmark + ] + + 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?(.*)/) + 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 diff --git a/modules/firewall/lib/puppet/provider/firewallchain/iptables_chain.rb b/modules/firewall/lib/puppet/provider/firewallchain/iptables_chain.rb new file mode 100644 index 0000000..29fbc1f --- /dev/null +++ b/modules/firewall/lib/puppet/provider/firewallchain/iptables_chain.rb @@ -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 diff --git a/modules/firewall/lib/puppet/type/firewall.rb b/modules/firewall/lib/puppet/type/firewall.rb new file mode 100644 index 0000000..22afbd2 --- /dev/null +++ b/modules/firewall/lib/puppet/type/firewall.rb @@ -0,0 +1,1077 @@ +# See: #10295 for more details. +# +# 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(:firewall) do + include Puppet::Util::Firewall + + @doc = <<-EOS + This type provides the capability to manage firewall rules within + puppet. + + **Autorequires:** + + If Puppet is managing the iptables or ip6tables chains specified in the + `chain` or `jump` parameters, the firewall resource will autorequire + those firewallchain resources. + + If Puppet is managing the iptables or iptables-persistent packages, and + the provider is iptables or ip6tables, the firewall resource will + autorequire those packages to ensure that any required binaries are + installed. + EOS + + feature :connection_limiting, "Connection limiting features." + feature :hop_limiting, "Hop limiting features." + feature :rate_limiting, "Rate limiting features." + feature :recent_limiting, "The netfilter recent module" + feature :snat, "Source NATing" + feature :dnat, "Destination NATing" + feature :interface_match, "Interface matching" + feature :icmp_match, "Matching ICMP types" + feature :owner, "Matching owners" + feature :state_match, "Matching stateful firewall states" + feature :reject_type, "The ability to control reject messages" + feature :log_level, "The ability to control the log level" + feature :log_prefix, "The ability to add prefixes to log messages" + feature :mark, "Match or Set the netfilter mark value associated with the packet" + feature :tcp_flags, "The ability to match on particular TCP flag settings" + feature :pkttype, "Match a packet type" + feature :socket, "Match open sockets" + feature :isfragment, "Match fragments" + feature :address_type, "The ability match on source or destination address type" + feature :iprange, "The ability match on source or destination IP range " + feature :ishasmorefrags, "Match a non-last fragment of a fragmented ipv6 packet - might be first" + feature :islastfrag, "Match the last fragment of an ipv6 packet" + feature :isfirstfrag, "Match the first fragment of a fragmented ipv6 packet" + feature :ipsec_policy, "Match IPsec policy" + feature :ipsec_dir, "Match IPsec policy direction" + + # provider specific features + feature :iptables, "The provider provides iptables features." + + ensurable do + desc <<-EOS + Manage the state of this rule. The default action is *present*. + EOS + + newvalue(:present) do + provider.insert + end + + newvalue(:absent) do + provider.delete + end + + defaultto :present + end + + newparam(:name) do + desc <<-EOS + The canonical name of the rule. This name is also used for ordering + so make sure you prefix the rule with a number: + + 000 this runs first + 999 this runs last + + Depending on the provider, the name of the rule can be stored using + the comment feature of the underlying firewall subsystem. + EOS + isnamevar + + # Keep rule names simple - they must start with a number + newvalues(/^\d+[[:alpha:][:digit:][:punct:][:space:]]+$/) + end + + newproperty(:action) do + desc <<-EOS + This is the action to perform on a match. Can be one of: + + * accept - the packet is accepted + * reject - the packet is rejected with a suitable ICMP response + * drop - the packet is dropped + + If you specify no value it will simply match the rule but perform no + action unless you provide a provider specific parameter (such as *jump*). + EOS + newvalues(:accept, :reject, :drop) + end + + # Generic matching properties + newproperty(:source) do + desc <<-EOS + The source address. For example: + + source => '192.168.2.0/24' + + You can also negate a mask by putting ! in front. For example: + + source => '! 192.168.2.0/24' + + The source can also be an IPv6 address if your provider supports it. + EOS + + munge do |value| + begin + @resource.host_to_mask(value) + rescue Exception => e + self.fail("host_to_ip failed for #{value}, exception #{e}") + end + end + end + + # Source IP range + newproperty(:src_range, :required_features => :iprange) do + desc <<-EOS + The source IP range. For example: + + src_range => '192.168.1.1-192.168.1.10' + + The source IP range is must in 'IP1-IP2' format. + EOS + + newvalues(/^((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)-((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)/) + end + + newproperty(:destination) do + desc <<-EOS + The destination address to match. For example: + + destination => '192.168.1.0/24' + + You can also negate a mask by putting ! in front. For example: + + destination => '! 192.168.2.0/24' + + The destination can also be an IPv6 address if your provider supports it. + EOS + + munge do |value| + begin + @resource.host_to_mask(value) + rescue Exception => e + self.fail("host_to_ip failed for #{value}, exception #{e}") + end + end + end + + # Destination IP range + newproperty(:dst_range, :required_features => :iprange) do + desc <<-EOS + The destination IP range. For example: + + dst_range => '192.168.1.1-192.168.1.10' + + The destination IP range is must in 'IP1-IP2' format. + EOS + + newvalues(/^((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)-((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)/) + end + + newproperty(:sport, :array_matching => :all) do + desc <<-EOS + The source port to match for this filter (if the protocol supports + ports). Will accept a single element or an array. + + For some firewall providers you can pass a range of ports in the format: + + - + + For example: + + 1-1024 + + This would cover ports 1 to 1024. + EOS + + munge do |value| + @resource.string_to_port(value, :proto) + end + + def is_to_s(value) + should_to_s(value) + end + + def should_to_s(value) + value = [value] unless value.is_a?(Array) + value.join(',') + end + end + + newproperty(:dport, :array_matching => :all) do + desc <<-EOS + The destination port to match for this filter (if the protocol supports + ports). Will accept a single element or an array. + + For some firewall providers you can pass a range of ports in the format: + + - + + For example: + + 1-1024 + + This would cover ports 1 to 1024. + EOS + + munge do |value| + @resource.string_to_port(value, :proto) + end + + def is_to_s(value) + should_to_s(value) + end + + def should_to_s(value) + value = [value] unless value.is_a?(Array) + value.join(',') + end + end + + newproperty(:port, :array_matching => :all) do + desc <<-EOS + The destination or source port to match for this filter (if the protocol + supports ports). Will accept a single element or an array. + + For some firewall providers you can pass a range of ports in the format: + + - + + For example: + + 1-1024 + + This would cover ports 1 to 1024. + EOS + + munge do |value| + @resource.string_to_port(value, :proto) + end + + def is_to_s(value) + should_to_s(value) + end + + def should_to_s(value) + value = [value] unless value.is_a?(Array) + value.join(',') + end + end + + newproperty(:dst_type, :required_features => :address_type) do + desc <<-EOS + The destination address type. For example: + + dst_type => 'LOCAL' + + Can be one of: + + * UNSPEC - an unspecified address + * UNICAST - a unicast address + * LOCAL - a local address + * BROADCAST - a broadcast address + * ANYCAST - an anycast packet + * MULTICAST - a multicast address + * BLACKHOLE - a blackhole address + * UNREACHABLE - an unreachable address + * PROHIBIT - a prohibited address + * THROW - undocumented + * NAT - undocumented + * XRESOLVE - undocumented + EOS + + newvalues(:UNSPEC, :UNICAST, :LOCAL, :BROADCAST, :ANYCAST, :MULTICAST, + :BLACKHOLE, :UNREACHABLE, :PROHIBIT, :THROW, :NAT, :XRESOLVE) + end + + newproperty(:src_type, :required_features => :address_type) do + desc <<-EOS + The source address type. For example: + + src_type => 'LOCAL' + + Can be one of: + + * UNSPEC - an unspecified address + * UNICAST - a unicast address + * LOCAL - a local address + * BROADCAST - a broadcast address + * ANYCAST - an anycast packet + * MULTICAST - a multicast address + * BLACKHOLE - a blackhole address + * UNREACHABLE - an unreachable address + * PROHIBIT - a prohibited address + * THROW - undocumented + * NAT - undocumented + * XRESOLVE - undocumented + EOS + + newvalues(:UNSPEC, :UNICAST, :LOCAL, :BROADCAST, :ANYCAST, :MULTICAST, + :BLACKHOLE, :UNREACHABLE, :PROHIBIT, :THROW, :NAT, :XRESOLVE) + end + + newproperty(:proto) do + desc <<-EOS + The specific protocol to match for this rule. By default this is + *tcp*. + EOS + + newvalues(:tcp, :udp, :icmp, :"ipv6-icmp", :esp, :ah, :vrrp, :igmp, :ipencap, :ospf, :gre, :all) + defaultto "tcp" + end + + # tcp-specific + newproperty(:tcp_flags, :required_features => :tcp_flags) do + desc <<-EOS + Match when the TCP flags are as specified. + Is a string with a list of comma-separated flag names for the mask, + then a space, then a comma-separated list of flags that should be set. + The flags are: SYN ACK FIN RST URG PSH ALL NONE + Note that you specify them in the order that iptables --list-rules + would list them to avoid having puppet think you changed the flags. + Example: FIN,SYN,RST,ACK SYN matches packets with the SYN bit set and the + ACK,RST and FIN bits cleared. Such packets are used to request + TCP connection initiation. + EOS + end + + + # Iptables specific + newproperty(:chain, :required_features => :iptables) do + desc <<-EOS + Name of the chain to use. Can be one of the built-ins: + + * INPUT + * FORWARD + * OUTPUT + * PREROUTING + * POSTROUTING + + Or you can provide a user-based chain. + + The default value is 'INPUT'. + EOS + + defaultto "INPUT" + newvalue(/^[a-zA-Z0-9\-_]+$/) + end + + newproperty(:table, :required_features => :iptables) do + desc <<-EOS + Table to use. Can be one of: + + * nat + * mangle + * filter + * raw + * rawpost + + By default the setting is 'filter'. + EOS + + newvalues(:nat, :mangle, :filter, :raw, :rawpost) + defaultto "filter" + end + + newproperty(:jump, :required_features => :iptables) do + desc <<-EOS + The value for the iptables --jump parameter. Normal values are: + + * QUEUE + * RETURN + * DNAT + * SNAT + * LOG + * MASQUERADE + * REDIRECT + * MARK + + But any valid chain name is allowed. + + For the values ACCEPT, DROP and REJECT you must use the generic + 'action' parameter. This is to enfore the use of generic parameters where + possible for maximum cross-platform modelling. + + If you set both 'accept' and 'jump' parameters, you will get an error as + only one of the options should be set. + EOS + + validate do |value| + unless value =~ /^[a-zA-Z0-9\-_]+$/ + raise ArgumentError, <<-EOS + Jump destination must consist of alphanumeric characters, an + underscore or a yphen. + EOS + end + + if ["accept","reject","drop"].include?(value.downcase) + raise ArgumentError, <<-EOS + Jump destination should not be one of ACCEPT, REJECT or DROP. Use + the action property instead. + EOS + end + + end + end + + # Interface specific matching properties + newproperty(:iniface, :required_features => :interface_match) do + desc <<-EOS + Input interface to filter on. + EOS + newvalues(/^[a-zA-Z0-9\-\._\+]+$/) + end + + newproperty(:outiface, :required_features => :interface_match) do + desc <<-EOS + Output interface to filter on. + EOS + newvalues(/^[a-zA-Z0-9\-\._\+]+$/) + end + + # NAT specific properties + newproperty(:tosource, :required_features => :snat) do + desc <<-EOS + When using jump => "SNAT" you can specify the new source address using + this parameter. + EOS + end + + newproperty(:todest, :required_features => :dnat) do + desc <<-EOS + When using jump => "DNAT" you can specify the new destination address + using this paramter. + EOS + end + + newproperty(:toports, :required_features => :dnat) do + desc <<-EOS + For DNAT this is the port that will replace the destination port. + EOS + end + + newproperty(:random, :required_features => :dnat) do + desc <<-EOS + When using a jump value of "MASQUERADE", "DNAT", "REDIRECT", or "SNAT" + this boolean will enable randomized port mapping. + EOS + + newvalues(:true, :false) + end + + # Reject ICMP type + newproperty(:reject, :required_features => :reject_type) do + desc <<-EOS + When combined with jump => "REJECT" you can specify a different icmp + response to be sent back to the packet sender. + EOS + end + + # Logging properties + newproperty(:log_level, :required_features => :log_level) do + desc <<-EOS + When combined with jump => "LOG" specifies the system log level to log + to. + EOS + + munge do |value| + if value.kind_of?(String) + value = @resource.log_level_name_to_number(value) + else + value + end + + if value == nil && value != "" + self.fail("Unable to determine log level") + end + value + end + end + + newproperty(:log_prefix, :required_features => :log_prefix) do + desc <<-EOS + When combined with jump => "LOG" specifies the log prefix to use when + logging. + EOS + end + + # ICMP matching property + newproperty(:icmp, :required_features => :icmp_match) do + desc <<-EOS + When matching ICMP packets, this is the type of ICMP packet to match. + + A value of "any" is not supported. To achieve this behaviour the + parameter should simply be omitted or undefined. + EOS + + validate do |value| + if value == "any" + raise ArgumentError, + "Value 'any' is not valid. This behaviour should be achieved " \ + "by omitting or undefining the ICMP parameter." + end + end + + munge do |value| + if value.kind_of?(String) + # ICMP codes differ between IPv4 and IPv6. + case @resource[:provider] + when :iptables + protocol = 'inet' + when :ip6tables + protocol = 'inet6' + else + self.fail("cannot work out protocol family") + end + + value = @resource.icmp_name_to_number(value, protocol) + else + value + end + + if value == nil && value != "" + self.fail("cannot work out icmp type") + end + value + end + end + + newproperty(:state, :array_matching => :all, :required_features => + :state_match) do + + desc <<-EOS + Matches a packet based on its state in the firewall stateful inspection + table. Values can be: + + * INVALID + * ESTABLISHED + * NEW + * RELATED + EOS + + newvalues(:INVALID,:ESTABLISHED,:NEW,:RELATED) + + # States should always be sorted. This normalizes the resource states to + # keep it consistent with the sorted result from iptables-save. + def should=(values) + @should = super(values).sort_by {|sym| sym.to_s} + end + + def is_to_s(value) + should_to_s(value) + end + + def should_to_s(value) + value = [value] unless value.is_a?(Array) + value.join(',') + end + end + + newproperty(:ctstate, :array_matching => :all, :required_features => + :state_match) do + + desc <<-EOS + Matches a packet based on its state in the firewall stateful inspection + table, using the conntrack module. Values can be: + + * INVALID + * ESTABLISHED + * NEW + * RELATED + EOS + + newvalues(:INVALID,:ESTABLISHED,:NEW,:RELATED) + + # States should always be sorted. This normalizes the resource states to + # keep it consistent with the sorted result from iptables-save. + def should=(values) + @should = super(values).sort_by {|sym| sym.to_s} + end + + def is_to_s(value) + should_to_s(value) + end + + def should_to_s(value) + value = [value] unless value.is_a?(Array) + value.join(',') + end + end + + + # Connection mark + newproperty(:connmark, :required_features => :mark) do + desc <<-EOS + Match the Netfilter mark value associated with the packet. Accepts either of: + mark/mask or mark. These will be converted to hex if they are not already. + EOS + munge do |value| + int_or_hex = '[a-fA-F0-9x]' + match = value.to_s.match("(#{int_or_hex}+)(/)?(#{int_or_hex}+)?") + mark = @resource.to_hex32(match[1]) + + # Values that can't be converted to hex. + # Or contain a trailing slash with no mask. + if mark.nil? or (mark and match[2] and match[3].nil?) + raise ArgumentError, "MARK value must be integer or hex between 0 and 0xffffffff" + end + + # There should not be a mask on connmark + unless match[3].nil? + raise ArgumentError, "iptables does not support masks on MARK match rules" + end + value = mark + + value + end + end + + # Connection limiting properties + newproperty(:connlimit_above, :required_features => :connection_limiting) do + desc <<-EOS + Connection limiting value for matched connections above n. + EOS + newvalue(/^\d+$/) + end + + newproperty(:connlimit_mask, :required_features => :connection_limiting) do + desc <<-EOS + Connection limiting by subnet mask for matched connections. + IPv4: 0-32 + IPv6: 0-128 + EOS + newvalue(/^\d+$/) + end + + # Hop limiting properties + newproperty(:hop_limit, :required_features => :hop_limiting) do + desc <<-EOS + Hop limiting value for matched packets. + EOS + newvalue(/^\d+$/) + end + + # Rate limiting properties + newproperty(:limit, :required_features => :rate_limiting) do + desc <<-EOS + Rate limiting value for matched packets. The format is: + rate/[/second/|/minute|/hour|/day]. + + Example values are: '50/sec', '40/min', '30/hour', '10/day'." + EOS + end + + newproperty(:burst, :required_features => :rate_limiting) do + desc <<-EOS + Rate limiting burst value (per second) before limit checks apply. + EOS + newvalue(/^\d+$/) + end + + newproperty(:uid, :required_features => :owner) do + desc <<-EOS + UID or Username owner matching rule. Accepts a string argument + only, as iptables does not accept multiple uid in a single + statement. + EOS + end + + newproperty(:gid, :required_features => :owner) do + desc <<-EOS + GID or Group owner matching rule. Accepts a string argument + only, as iptables does not accept multiple gid in a single + statement. + EOS + end + + newproperty(:set_mark, :required_features => :mark) do + desc <<-EOS + Set the Netfilter mark value associated with the packet. Accepts either of: + mark/mask or mark. These will be converted to hex if they are not already. + EOS + + munge do |value| + int_or_hex = '[a-fA-F0-9x]' + match = value.to_s.match("(#{int_or_hex}+)(/)?(#{int_or_hex}+)?") + mark = @resource.to_hex32(match[1]) + + # Values that can't be converted to hex. + # Or contain a trailing slash with no mask. + if mark.nil? or (mark and match[2] and match[3].nil?) + raise ArgumentError, "MARK value must be integer or hex between 0 and 0xffffffff" + end + + # Old iptables does not support a mask. New iptables will expect one. + iptables_version = Facter.fact('iptables_version').value + mask_required = (iptables_version and Puppet::Util::Package.versioncmp(iptables_version, '1.4.1') >= 0) + + if mask_required + if match[3].nil? + value = "#{mark}/0xffffffff" + else + mask = @resource.to_hex32(match[3]) + if mask.nil? + raise ArgumentError, "MARK mask must be integer or hex between 0 and 0xffffffff" + end + value = "#{mark}/#{mask}" + end + else + unless match[3].nil? + raise ArgumentError, "iptables version #{iptables_version} does not support masks on MARK rules" + end + value = mark + end + + value + end + end + + newproperty(:pkttype, :required_features => :pkttype) do + desc <<-EOS + Sets the packet type to match. + EOS + + newvalues(:unicast, :broadcast, :multicast) + end + + newproperty(:isfragment, :required_features => :isfragment) do + desc <<-EOS + Set to true to match tcp fragments (requires type to be set to tcp) + EOS + + newvalues(:true, :false) + end + + newproperty(:recent, :required_features => :recent_limiting) do + desc <<-EOS + Enable the recent module. Takes as an argument one of set, update, + rcheck or remove. For example: + + # If anyone's appeared on the 'badguy' blacklist within + # the last 60 seconds, drop their traffic, and update the timestamp. + firewall { '100 Drop badguy traffic': + recent => 'update', + rseconds => 60, + rsource => true, + rname => 'badguy', + action => 'DROP', + chain => 'FORWARD', + } + # No-one should be sending us traffic on eth0 from localhost + # Blacklist them + firewall { '101 blacklist strange traffic': + recent => 'set', + rsource => true, + rname => 'badguy', + destination => '127.0.0.0/8', + iniface => 'eth0', + action => 'DROP', + chain => 'FORWARD', + } + EOS + + newvalues(:set, :update, :rcheck, :remove) + munge do |value| + value = "--" + value + end + end + + newproperty(:rdest, :required_features => :recent_limiting) do + desc <<-EOS + Recent module; add the destination IP address to the list. + Must be boolean true. + EOS + + newvalues(:true, :false) + end + + newproperty(:rsource, :required_features => :recent_limiting) do + desc <<-EOS + Recent module; add the source IP address to the list. + Must be boolean true. + EOS + + newvalues(:true, :false) + end + + newproperty(:rname, :required_features => :recent_limiting) do + desc <<-EOS + Recent module; The name of the list. Takes a string argument. + EOS + end + + newproperty(:rseconds, :required_features => :recent_limiting) do + desc <<-EOS + Recent module; used in conjunction with one of `recent => 'rcheck'` or + `recent => 'update'`. When used, this will narrow the match to only + happen when the address is in the list and was seen within the last given + number of seconds. + EOS + end + + newproperty(:reap, :required_features => :recent_limiting) do + desc <<-EOS + Recent module; can only be used in conjunction with the `rseconds` + attribute. When used, this will cause entries older than 'seconds' to be + purged. Must be boolean true. + EOS + + newvalues(:true, :false) + end + + newproperty(:rhitcount, :required_features => :recent_limiting) do + desc <<-EOS + Recent module; used in conjunction with `recent => 'update'` or `recent + => 'rcheck'. When used, this will narrow the match to only happen when + the address is in the list and packets had been received greater than or + equal to the given value. + EOS + end + + newproperty(:rttl, :required_features => :recent_limiting) do + desc <<-EOS + Recent module; may only be used in conjunction with one of `recent => + 'rcheck'` or `recent => 'update'`. When used, this will narrow the match + to only happen when the address is in the list and the TTL of the current + packet matches that of the packet which hit the `recent => 'set'` rule. + This may be useful if you have problems with people faking their source + address in order to DoS you via this module by disallowing others access + to your site by sending bogus packets to you. Must be boolean true. + EOS + + newvalues(:true, :false) + end + + newproperty(:socket, :required_features => :socket) do + desc <<-EOS + If true, matches if an open socket can be found by doing a coket lookup + on the packet. + EOS + + newvalues(:true, :false) + end + + newproperty(:ishasmorefrags, :required_features => :ishasmorefrags) do + desc <<-EOS + If true, matches if the packet has it's 'more fragments' bit set. ipv6. + EOS + + newvalues(:true, :false) + end + + newproperty(:islastfrag, :required_features => :islastfrag) do + desc <<-EOS + If true, matches if the packet is the last fragment. ipv6. + EOS + + newvalues(:true, :false) + end + + newproperty(:isfirstfrag, :required_features => :isfirstfrag) do + desc <<-EOS + If true, matches if the packet is the first fragment. + Sadly cannot be negated. ipv6. + EOS + + newvalues(:true, :false) + end + + newproperty(:ipsec_policy, :required_features => :ipsec_policy) do + desc <<-EOS + Sets the ipsec policy type + EOS + + newvalues(:none, :ipsec) + end + + newproperty(:ipsec_dir, :required_features => :ipsec_dir) do + desc <<-EOS + Sets the ipsec policy direction + EOS + + newvalues(:in, :out) + end + + newproperty(:mask, :required_features => :mask) do + desc <<-EOS + Sets the mask to use when `recent` is enabled. + EOS + end + + newparam(:line) do + desc <<-EOS + Read-only property for caching the rule line. + EOS + end + + autorequire(:firewallchain) do + reqs = [] + protocol = nil + + case value(:provider) + when :iptables + protocol = "IPv4" + when :ip6tables + protocol = "IPv6" + end + + unless protocol.nil? + table = value(:table) + [value(:chain), value(:jump)].each do |chain| + reqs << "#{chain}:#{table}:#{protocol}" unless ( chain.nil? || (['INPUT', 'OUTPUT', 'FORWARD'].include?(chain) && table == :filter) ) + end + end + + reqs + end + + # Classes would be a better abstraction, pending: + # http://projects.puppetlabs.com/issues/19001 + autorequire(:package) do + case value(:provider) + when :iptables, :ip6tables + %w{iptables iptables-persistent} + else + [] + end + end + + validate do + debug("[validate]") + + # TODO: this is put here to skip validation if ensure is not set. This + # is because there is a revalidation stage called later where the values + # are not set correctly. I tried tracing it - but have put in this + # workaround instead to skip. Must get to the bottom of this. + if ! value(:ensure) + return + end + + # First we make sure the chains and tables are valid combinations + if value(:table).to_s == "filter" && + value(:chain) =~ /PREROUTING|POSTROUTING/ + + self.fail "PREROUTING and POSTROUTING cannot be used in table 'filter'" + end + + if value(:table).to_s == "nat" && value(:chain) =~ /INPUT|FORWARD/ + self.fail "INPUT and FORWARD cannot be used in table 'nat'" + end + + if value(:table).to_s == "raw" && + value(:chain) =~ /INPUT|FORWARD|POSTROUTING/ + + self.fail "INPUT, FORWARD and POSTROUTING cannot be used in table raw" + end + + # Now we analyse the individual properties to make sure they apply to + # the correct combinations. + if value(:iniface) + unless value(:chain).to_s =~ /INPUT|FORWARD|PREROUTING/ + self.fail "Parameter iniface only applies to chains " \ + "INPUT,FORWARD,PREROUTING" + end + end + + if value(:outiface) + unless value(:chain).to_s =~ /OUTPUT|FORWARD|POSTROUTING/ + self.fail "Parameter outiface only applies to chains " \ + "OUTPUT,FORWARD,POSTROUTING" + end + end + + if value(:uid) + unless value(:chain).to_s =~ /OUTPUT|POSTROUTING/ + self.fail "Parameter uid only applies to chains " \ + "OUTPUT,POSTROUTING" + end + end + + if value(:gid) + unless value(:chain).to_s =~ /OUTPUT|POSTROUTING/ + self.fail "Parameter gid only applies to chains " \ + "OUTPUT,POSTROUTING" + end + end + + if value(:set_mark) + unless value(:jump).to_s =~ /MARK/ && + value(:chain).to_s =~ /PREROUTING|OUTPUT/ && + value(:table).to_s =~ /mangle/ + self.fail "Parameter set_mark only applies to " \ + "the PREROUTING or OUTPUT chain of the mangle table and when jump => MARK" + end + end + + if value(:dport) + unless value(:proto).to_s =~ /tcp|udp|sctp/ + self.fail "[%s] Parameter dport only applies to sctp, tcp and udp " \ + "protocols. Current protocol is [%s] and dport is [%s]" % + [value(:name), should(:proto), should(:dport)] + end + end + + if value(:jump).to_s == "DNAT" + unless value(:table).to_s =~ /nat/ + self.fail "Parameter jump => DNAT only applies to table => nat" + end + + unless value(:todest) + self.fail "Parameter jump => DNAT must have todest parameter" + end + end + + if value(:jump).to_s == "SNAT" + unless value(:table).to_s =~ /nat/ + self.fail "Parameter jump => SNAT only applies to table => nat" + end + + unless value(:tosource) + self.fail "Parameter jump => SNAT must have tosource parameter" + end + end + + if value(:jump).to_s == "REDIRECT" + unless value(:toports) + self.fail "Parameter jump => REDIRECT missing mandatory toports " \ + "parameter" + end + end + + if value(:jump).to_s == "MASQUERADE" + unless value(:table).to_s =~ /nat/ + self.fail "Parameter jump => MASQUERADE only applies to table => nat" + end + end + + if value(:log_prefix) || value(:log_level) + unless value(:jump).to_s == "LOG" + self.fail "Parameter log_prefix and log_level require jump => LOG" + end + end + + if value(:burst) && ! value(:limit) + self.fail "burst makes no sense without limit" + end + + if value(:action) && value(:jump) + self.fail "Only one of the parameters 'action' and 'jump' can be set" + end + + if value(:connlimit_mask) && ! value(:connlimit_above) + self.fail "Parameter 'connlimit_mask' requires 'connlimit_above'" + end + + if value(:mask) && ! value(:recent) + self.fail "Mask can only be set if recent is enabled." + end + + end +end diff --git a/modules/firewall/lib/puppet/type/firewallchain.rb b/modules/firewall/lib/puppet/type/firewallchain.rb new file mode 100644 index 0000000..3e3c5d1 --- /dev/null +++ b/modules/firewall/lib/puppet/type/firewallchain.rb @@ -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 diff --git a/modules/firewall/lib/puppet/util/firewall.rb b/modules/firewall/lib/puppet/util/firewall.rb new file mode 100644 index 0000000..aa26d3b --- /dev/null +++ b/modules/firewall/lib/puppet/util/firewall.rb @@ -0,0 +1,225 @@ +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 to persist iptable rules + if os_key == 'RedHat' && Facter.value(:operatingsystem) == 'Fedora' && Facter.value(:operatingsystemrelease).to_i >= 15 + os_key = 'Fedora' + end + + # RHEL 7 and newer also use systemd to persist iptable rules + if os_key == 'RedHat' && Facter.value(:operatingsystem) == 'RedHat' && Facter.value(:operatingsystemrelease).to_i >= 7 + 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/iptables.init save} + when :IPv6 + %w{/usr/libexec/iptables/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 diff --git a/modules/firewall/lib/puppet/util/ipcidr.rb b/modules/firewall/lib/puppet/util/ipcidr.rb new file mode 100644 index 0000000..87e8d5e --- /dev/null +++ b/modules/firewall/lib/puppet/util/ipcidr.rb @@ -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 diff --git a/modules/firewall/manifests/init.pp b/modules/firewall/manifests/init.pp new file mode 100644 index 0000000..759f328 --- /dev/null +++ b/modules/firewall/manifests/init.pp @@ -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") + } + } +} diff --git a/modules/firewall/manifests/linux.pp b/modules/firewall/manifests/linux.pp new file mode 100644 index 0000000..7c4f3a8 --- /dev/null +++ b/modules/firewall/manifests/linux.pp @@ -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: {} + } +} diff --git a/modules/firewall/manifests/linux/archlinux.pp b/modules/firewall/manifests/linux/archlinux.pp new file mode 100644 index 0000000..546a5a8 --- /dev/null +++ b/modules/firewall/manifests/linux/archlinux.pp @@ -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'], + } +} diff --git a/modules/firewall/manifests/linux/debian.pp b/modules/firewall/manifests/linux/debian.pp new file mode 100644 index 0000000..4d28bc4 --- /dev/null +++ b/modules/firewall/manifests/linux/debian.pp @@ -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'], + } + } +} diff --git a/modules/firewall/manifests/linux/redhat.pp b/modules/firewall/manifests/linux/redhat.pp new file mode 100644 index 0000000..f697d21 --- /dev/null +++ b/modules/firewall/manifests/linux/redhat.pp @@ -0,0 +1,40 @@ +# = 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 +) { + + # RHEL 7 and later and Fedora 15 and later require the iptables-services + # package, which provides the /usr/libexec/iptables/iptables.init used by + # lib/puppet/util/firewall.rb. + if $::operatingsystem == RedHat and $::operatingsystemrelease >= 7 { + package { 'iptables-services': + ensure => present, + } + } + + if ($::operatingsystem == 'Fedora' and (( $::operatingsystemrelease =~ /^\d+/ and $::operatingsystemrelease >= 15 ) or $::operatingsystemrelease == "Rawhide")) { + package { 'iptables-services': + ensure => present, + } + } + + service { 'iptables': + ensure => $ensure, + enable => $enable, + hasstatus => true, + } +} diff --git a/modules/firewall/metadata.json b/modules/firewall/metadata.json new file mode 100644 index 0000000..8c26bd6 --- /dev/null +++ b/modules/firewall/metadata.json @@ -0,0 +1,79 @@ +{ + "name": "puppetlabs-firewall", + "version": "1.1.2", + "author": "puppetlabs", + "summary": "Firewall Module", + "license": "ASL 2.0", + "source": "git://github.com/puppetlabs/puppetlabs-firewall.git", + "project_page": "http://forge.puppetlabs.com/puppetlabs/firewall", + "issues_url": "https://github.com/puppetlabs/puppetlabs-firewall/issues", + "operatingsystem_support": [ + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "Scientific", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "SLES", + "operatingsystemrelease": [ + "11 SP1" + ] + }, + { + "operatingsystem": "Debian", + "operatingsystemrelease": [ + "6", + "7" + ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ + "10.04", + "12.04", + "14.04" + ] + } + ], + "requirements": [ + { + "name": "pe", + "version_requirement": ">= 3.2.0 < 3.4.0" + }, + { + "name": "puppet", + "version_requirement": "3.x" + } + ], + "description": "Manages Firewalls such as iptables", + "dependencies": [ + + ] +} diff --git a/modules/firewall/spec/acceptance/change_source_spec.rb b/modules/firewall/spec/acceptance/change_source_spec.rb new file mode 100644 index 0000000..f591108 --- /dev/null +++ b/modules/firewall/spec/acceptance/change_source_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper_acceptance' + +describe 'firewall type', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 diff --git a/modules/firewall/spec/acceptance/class_spec.rb b/modules/firewall/spec/acceptance/class_spec.rb new file mode 100644 index 0000000..4a9751a --- /dev/null +++ b/modules/firewall/spec/acceptance/class_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper_acceptance' + +describe "firewall class:", :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 diff --git a/modules/firewall/spec/acceptance/connlimit_spec.rb b/modules/firewall/spec/acceptance/connlimit_spec.rb new file mode 100644 index 0000000..bb049a9 --- /dev/null +++ b/modules/firewall/spec/acceptance/connlimit_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper_acceptance' + +describe 'firewall type', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do + + describe 'connlimit_above' do + context '10' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '500 - test': + proto => tcp, + dport => '22', + connlimit_above => '10', + action => reject, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + #connlimit-saddr is added in Ubuntu 14.04. + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --dports 22 -m comment --comment "500 - test" -m connlimit --connlimit-above 10 --connlimit-mask 32 (--connlimit-saddr )?-j REJECT --reject-with icmp-port-unreachable/) + end + end + end + end + + describe 'connlimit_mask' do + context '24' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '501 - test': + proto => tcp, + dport => '22', + connlimit_above => '10', + connlimit_mask => '24', + action => reject, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + #connlimit-saddr is added in Ubuntu 14.04. + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --dports 22 -m comment --comment "501 - test" -m connlimit --connlimit-above 10 --connlimit-mask 24 (--connlimit-saddr )?-j REJECT --reject-with icmp-port-unreachable/) + end + end + end + end +end diff --git a/modules/firewall/spec/acceptance/connmark_spec.rb b/modules/firewall/spec/acceptance/connmark_spec.rb new file mode 100644 index 0000000..b3409ab --- /dev/null +++ b/modules/firewall/spec/acceptance/connmark_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper_acceptance' + +describe 'firewall type', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do + + describe 'connmark' do + context '50' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '502 - test': + proto => 'all', + connmark => '0x1', + action => reject, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -m comment --comment "502 - test" -m connmark --mark 0x1 -j REJECT --reject-with icmp-port-unreachable/) + end + end + end + end +end diff --git a/modules/firewall/spec/acceptance/firewall_spec.rb b/modules/firewall/spec/acceptance/firewall_spec.rb new file mode 100644 index 0000000..b8a57ce --- /dev/null +++ b/modules/firewall/spec/acceptance/firewall_spec.rb @@ -0,0 +1,1618 @@ +require 'spec_helper_acceptance' + +describe 'firewall type', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do + + describe 'reset' do + it 'deletes all rules' do + shell('iptables --flush; iptables -t nat --flush; iptables -t mangle --flush') + end + end + + describe 'name' do + context 'valid' do + it 'applies cleanly' do + pp = <<-EOS + class { '::firewall': } + firewall { '001 - test': ensure => present } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + end + + context 'invalid' do + it 'fails' do + pp = <<-EOS + class { '::firewall': } + firewall { 'test': ensure => present } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Invalid value "test"./) + end + end + end + end + + describe 'ensure' do + context 'default' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '555 - test': + proto => tcp, + port => '555', + action => accept, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 555 -m comment --comment "555 - test" -j ACCEPT/) + end + end + end + + context 'present' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '555 - test': + ensure => present, + proto => tcp, + port => '555', + action => accept, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 555 -m comment --comment "555 - test" -j ACCEPT/) + end + end + end + + context 'absent' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '555 - test': + ensure => absent, + proto => tcp, + port => '555', + action => accept, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m multiport --ports 555 -m comment --comment "555 - test" -j ACCEPT/) + end + end + end + end + + describe 'source' do + context '192.168.2.0/24' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '556 - test': + proto => tcp, + port => '556', + action => accept, + source => '192.168.2.0/24', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -s 192.168.2.0\/(24|255\.255\.255\.0) -p tcp -m multiport --ports 556 -m comment --comment "556 - test" -j ACCEPT/) + end + end + end + + context '! 192.168.2.0/24' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '556 - test': + proto => tcp, + port => '556', + action => accept, + source => '! 192.168.2.0/24', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT (! -s|-s !) 192.168.2.0\/(24|255\.255\.255\.0) -p tcp -m multiport --ports 556 -m comment --comment "556 - test" -j ACCEPT/) + end + end + end + + # Invalid address + context '256.168.2.0/24' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '556 - test': + proto => tcp, + port => '556', + action => accept, + source => '256.168.2.0/24', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/host_to_ip failed for 256.168.2.0\/(24|255\.255\.255\.0)/) + end + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -s 256.168.2.0\/(24|255\.255\.255\.0) -p tcp -m multiport --ports 556 -m comment --comment "556 - test" -j ACCEPT/) + end + end + end + end + + describe 'src_range' do + context '192.168.1.1-192.168.1.10' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '557 - test': + proto => tcp, + port => '557', + action => accept, + src_range => '192.168.1.1-192.168.1.10', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m iprange --src-range 192.168.1.1-192.168.1.10 -m multiport --ports 557 -m comment --comment "557 - test" -j ACCEPT/) + end + end + end + + # Invalid IP + context '392.168.1.1-192.168.1.10' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '557 - test': + proto => tcp, + port => '557', + action => accept, + src_range => '392.168.1.1-192.168.1.10', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Invalid value "392.168.1.1-192.168.1.10"/) + end + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m iprange --src-range 392.168.1.1-192.168.1.10 -m multiport --ports 557 -m comment --comment "557 - test" -j ACCEPT/) + end + end + end + end + + describe 'destination' do + context '192.168.2.0/24' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '558 - test': + proto => tcp, + port => '558', + action => accept, + destination => '192.168.2.0/24', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -d 192.168.2.0\/(24|255\.255\.255\.0) -p tcp -m multiport --ports 558 -m comment --comment "558 - test" -j ACCEPT/) + end + end + end + + context '! 192.168.2.0/24' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '558 - test': + proto => tcp, + port => '558', + action => accept, + destination => '! 192.168.2.0/24', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT (! -d|-d !) 192.168.2.0\/(24|255\.255\.255\.0) -p tcp -m multiport --ports 558 -m comment --comment "558 - test" -j ACCEPT/) + end + end + end + + # Invalid address + context '256.168.2.0/24' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '558 - test': + proto => tcp, + port => '558', + action => accept, + destination => '256.168.2.0/24', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/host_to_ip failed for 256.168.2.0\/(24|255\.255\.255\.0)/) + end + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -d 256.168.2.0\/(24|255\.255\.255\.0) -p tcp -m multiport --ports 558 -m comment --comment "558 - test" -j ACCEPT/) + end + end + end + end + + describe 'dst_range' do + context '192.168.1.1-192.168.1.10' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '559 - test': + proto => tcp, + port => '559', + action => accept, + dst_range => '192.168.1.1-192.168.1.10', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m iprange --dst-range 192.168.1.1-192.168.1.10 -m multiport --ports 559 -m comment --comment "559 - test" -j ACCEPT/) + end + end + end + + # Invalid IP + context '392.168.1.1-192.168.1.10' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '559 - test': + proto => tcp, + port => '559', + action => accept, + dst_range => '392.168.1.1-192.168.1.10', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Invalid value "392.168.1.1-192.168.1.10"/) + end + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m iprange --dst-range 392.168.1.1-192.168.1.10 -m multiport --ports 559 -m comment --comment "559 - test" -j ACCEPT/) + end + end + end + end + + describe 'sport' do + context 'single port' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '560 - test': + proto => tcp, + sport => '560', + action => accept, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --sports 560 -m comment --comment "560 - test" -j ACCEPT/) + end + end + end + + context 'multiple ports' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '560 - test': + proto => tcp, + sport => '560-561', + action => accept, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --sports 560:561 -m comment --comment "560 - test" -j ACCEPT/) + end + end + end + + context 'invalid ports' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '560 - test': + proto => tcp, + sport => '9999560-561', + action => accept, + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/invalid port\/service `9999560' specified/) + end + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m multiport --sports 9999560-561 -m comment --comment "560 - test" -j ACCEPT/) + end + end + end + end + + describe 'dport' do + context 'single port' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '561 - test': + proto => tcp, + dport => '561', + action => accept, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --dports 561 -m comment --comment "561 - test" -j ACCEPT/) + end + end + end + + context 'multiple ports' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '561 - test': + proto => tcp, + dport => '561-562', + action => accept, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --dports 561:562 -m comment --comment "561 - test" -j ACCEPT/) + end + end + end + + context 'invalid ports' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '561 - test': + proto => tcp, + dport => '9999561-562', + action => accept, + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/invalid port\/service `9999561' specified/) + end + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m multiport --dports 9999561-562 -m comment --comment "560 - test" -j ACCEPT/) + end + end + end + end + + describe 'port' do + context 'single port' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '562 - test': + proto => tcp, + port => '562', + action => accept, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 562 -m comment --comment "562 - test" -j ACCEPT/) + end + end + end + + context 'multiple ports' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '562 - test': + proto => tcp, + port => '562-563', + action => accept, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 562:563 -m comment --comment "562 - test" -j ACCEPT/) + end + end + end + + context 'invalid ports' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '562 - test': + proto => tcp, + port => '9999562-563', + action => accept, + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/invalid port\/service `9999562' specified/) + end + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m multiport --ports 9999562-563 -m comment --comment "562 - test" -j ACCEPT/) + end + end + end + end + + ['dst_type', 'src_type'].each do |type| + describe "#{type}" do + context 'MULTICAST' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '563 - test': + proto => tcp, + action => accept, + #{type} => 'MULTICAST', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m addrtype\s.*\sMULTICAST -m comment --comment "563 - test" -j ACCEPT/) + end + end + end + + context 'BROKEN' do + it 'fails' do + pp = <<-EOS + class { '::firewall': } + firewall { '563 - test': + proto => tcp, + action => accept, + #{type} => 'BROKEN', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Invalid value "BROKEN"./) + end + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m addrtype\s.*\sBROKEN -m comment --comment "563 - test" -j ACCEPT/) + end + end + end + end + end + + describe 'tcp_flags' do + context 'FIN,SYN ACK' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '564 - test': + proto => tcp, + action => accept, + tcp_flags => 'FIN,SYN ACK', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN ACK -m comment --comment "564 - test" -j ACCEPT/) + end + end + end + end + + describe 'chain' do + context 'INPUT' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '565 - test': + proto => tcp, + action => accept, + chain => 'FORWARD', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A FORWARD -p tcp -m comment --comment "565 - test" -j ACCEPT/) + end + end + end + end + + describe 'table' do + context 'mangle' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '566 - test': + proto => tcp, + action => accept, + table => 'mangle', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save -t mangle') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m comment --comment "566 - test" -j ACCEPT/) + end + end + end + context 'nat' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '566 - test2': + proto => tcp, + action => accept, + table => 'nat', + chain => 'OUTPUT', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should not contain the rule' do + shell('iptables-save -t nat') do |r| + expect(r.stdout).to match(/-A OUTPUT -p tcp -m comment --comment "566 - test2" -j ACCEPT/) + end + end + end + end + + describe 'jump' do + after :all do + iptables_flush_all_tables + expect(shell('iptables -t filter -X TEST').stderr).to eq("") + end + + context 'MARK' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewallchain { 'TEST:filter:IPv4': + ensure => present, + } + firewall { '567 - test': + proto => tcp, + chain => 'INPUT', + jump => 'TEST', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m comment --comment "567 - test" -j TEST/) + end + end + end + + context 'jump and apply' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewallchain { 'TEST:filter:IPv4': + ensure => present, + } + firewall { '568 - test': + proto => tcp, + chain => 'INPUT', + action => 'accept', + jump => 'TEST', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Only one of the parameters 'action' and 'jump' can be set/) + end + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m comment --comment "568 - test" -j TEST/) + end + end + end + end + + describe 'tosource' do + context '192.168.1.1' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '568 - test': + proto => tcp, + table => 'nat', + chain => 'POSTROUTING', + jump => 'SNAT', + tosource => '192.168.1.1', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save -t nat') do |r| + expect(r.stdout).to match(/A POSTROUTING -p tcp -m comment --comment "568 - test" -j SNAT --to-source 192.168.1.1/) + end + end + end + end + + describe 'todest' do + context '192.168.1.1' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '569 - test': + proto => tcp, + table => 'nat', + chain => 'PREROUTING', + jump => 'DNAT', + source => '200.200.200.200', + todest => '192.168.1.1', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save -t nat') do |r| + expect(r.stdout).to match(/-A PREROUTING -s 200.200.200.200(\/32)? -p tcp -m comment --comment "569 - test" -j DNAT --to-destination 192.168.1.1/) + end + end + end + end + + describe 'toports' do + context '192.168.1.1' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '570 - test': + proto => icmp, + table => 'nat', + chain => 'PREROUTING', + jump => 'REDIRECT', + toports => '2222', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save -t nat') do |r| + expect(r.stdout).to match(/-A PREROUTING -p icmp -m comment --comment "570 - test" -j REDIRECT --to-ports 2222/) + end + end + end + end + + # RHEL5 does not support --random + if default['platform'] !~ /el-5/ + describe 'random' do + context '192.168.1.1' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '570 - test 2': + proto => all, + table => 'nat', + chain => 'POSTROUTING', + jump => 'MASQUERADE', + source => '172.30.0.0/16', + random => true + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should contain the rule' do + shell('iptables-save -t nat') do |r| + expect(r.stdout).to match(/-A POSTROUTING -s 172\.30\.0\.0\/16 -m comment --comment "570 - test 2" -j MASQUERADE --random/) + end + end + end + end + end + + describe 'icmp' do + context 'any' do + it 'fails' do + pp = <<-EOS + class { '::firewall': } + firewall { '571 - test': + proto => icmp, + icmp => 'any', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/This behaviour should be achieved by omitting or undefining the ICMP parameter/) + end + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p icmp -m comment --comment "570 - test" -m icmp --icmp-type 11/) + end + end + end + end + + #iptables version 1.3.5 is not suppored by the ip6tables provider + if default['platform'] !~ /el-5/ + describe 'hop_limit' do + context '5' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '571 - test': + ensure => present, + proto => tcp, + port => '571', + action => accept, + hop_limit => '5', + provider => 'ip6tables', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('ip6tables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 571 -m comment --comment "571 - test" -m hl --hl-eq 5 -j ACCEPT/) + end + end + end + + context 'invalid' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '571 - test': + ensure => present, + proto => tcp, + port => '571', + action => accept, + hop_limit => 'invalid', + provider => 'ip6tables', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Invalid value "invalid"./) + end + end + + it 'should not contain the rule' do + shell('ip6tables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m multiport --ports 571 -m comment --comment "571 - test" -m hl --hl-eq invalid -j ACCEPT/) + end + end + end + end + + describe 'ishasmorefrags' do + context 'true' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '587 - test': + ensure => present, + proto => tcp, + port => '587', + action => accept, + ishasmorefrags => true, + provider => 'ip6tables', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('ip6tables-save') do |r| + expect(r.stdout).to match(/A INPUT -p tcp -m frag --fragid 0 --fragmore -m multiport --ports 587 -m comment --comment "587 - test" -j ACCEPT/) + end + end + end + + context 'false' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '588 - test': + ensure => present, + proto => tcp, + port => '588', + action => accept, + ishasmorefrags => false, + provider => 'ip6tables', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('ip6tables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 588 -m comment --comment "588 - test" -j ACCEPT/) + end + end + end + end + + describe 'islastfrag' do + context 'true' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '589 - test': + ensure => present, + proto => tcp, + port => '589', + action => accept, + islastfrag => true, + provider => 'ip6tables', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('ip6tables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m frag --fragid 0 --fraglast -m multiport --ports 589 -m comment --comment "589 - test" -j ACCEPT/) + end + end + end + + context 'false' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '590 - test': + ensure => present, + proto => tcp, + port => '590', + action => accept, + islastfrag => false, + provider => 'ip6tables', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('ip6tables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 590 -m comment --comment "590 - test" -j ACCEPT/) + end + end + end + end + + describe 'isfirstfrag' do + context 'true' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '591 - test': + ensure => present, + proto => tcp, + port => '591', + action => accept, + isfirstfrag => true, + provider => 'ip6tables', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('ip6tables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m frag --fragid 0 --fragfirst -m multiport --ports 591 -m comment --comment "591 - test" -j ACCEPT/) + end + end + end + + context 'false' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '592 - test': + ensure => present, + proto => tcp, + port => '592', + action => accept, + isfirstfrag => false, + provider => 'ip6tables', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('ip6tables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 592 -m comment --comment "592 - test" -j ACCEPT/) + end + end + end + end + end + + describe 'limit' do + context '500/sec' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '572 - test': + ensure => present, + proto => tcp, + port => '572', + action => accept, + limit => '500/sec', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 572 -m comment --comment "572 - test" -m limit --limit 500\/sec -j ACCEPT/) + end + end + end + end + + describe 'burst' do + context '500' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '573 - test': + ensure => present, + proto => tcp, + port => '573', + action => accept, + limit => '500/sec', + burst => '1500', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 573 -m comment --comment "573 - test" -m limit --limit 500\/sec --limit-burst 1500 -j ACCEPT/) + end + end + end + + context 'invalid' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '571 - test': + ensure => present, + proto => tcp, + port => '571', + action => accept, + limit => '500/sec', + burst => '1500/sec', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Invalid value "1500\/sec"./) + end + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m multiport --ports 573 -m comment --comment "573 - test" -m limit --limit 500\/sec --limit-burst 1500\/sec -j ACCEPT/) + end + end + end + end + + describe 'uid' do + context 'nobody' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '574 - test': + ensure => present, + proto => tcp, + chain => 'OUTPUT', + port => '574', + action => accept, + uid => 'nobody', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A OUTPUT -p tcp -m owner --uid-owner (nobody|\d+) -m multiport --ports 574 -m comment --comment "574 - test" -j ACCEPT/) + end + end + end + end + + describe 'gid' do + context 'root' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '575 - test': + ensure => present, + proto => tcp, + chain => 'OUTPUT', + port => '575', + action => accept, + gid => 'root', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A OUTPUT -p tcp -m owner --gid-owner (root|\d+) -m multiport --ports 575 -m comment --comment "575 - test" -j ACCEPT/) + end + end + end + end + + #iptables version 1.3.5 does not support masks on MARK rules + if default['platform'] !~ /el-5/ + describe 'set_mark' do + context '0x3e8/0xffffffff' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '580 - test': + ensure => present, + chain => 'OUTPUT', + proto => tcp, + port => '580', + jump => 'MARK', + table => 'mangle', + set_mark => '0x3e8/0xffffffff', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save -t mangle') do |r| + expect(r.stdout).to match(/-A OUTPUT -p tcp -m multiport --ports 580 -m comment --comment "580 - test" -j MARK --set-xmark 0x3e8\/0xffffffff/) + end + end + end + end + end + + describe 'pkttype' do + context 'multicast' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '581 - test': + ensure => present, + proto => tcp, + port => '581', + action => accept, + pkttype => 'multicast', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 581 -m pkttype --pkt-type multicast -m comment --comment "581 - test" -j ACCEPT/) + end + end + end + + context 'test' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '582 - test': + ensure => present, + proto => tcp, + port => '582', + action => accept, + pkttype => 'test', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Invalid value "test"./) + end + end + + it 'should not contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to_not match(/-A INPUT -p tcp -m multiport --ports 582 -m pkttype --pkt-type multicast -m comment --comment "582 - test" -j ACCEPT/) + end + end + end + end + + describe 'isfragment' do + context 'true' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '583 - test': + ensure => present, + proto => tcp, + port => '583', + action => accept, + isfragment => true, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -f -m multiport --ports 583 -m comment --comment "583 - test" -j ACCEPT/) + end + end + end + + context 'false' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '584 - test': + ensure => present, + proto => tcp, + port => '584', + action => accept, + isfragment => false, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -p tcp -m multiport --ports 584 -m comment --comment "584 - test" -j ACCEPT/) + end + end + end + end + + # RHEL5/SLES does not support -m socket + describe 'socket', :unless => (default['platform'] =~ /el-5/ or fact('operatingsystem') == 'SLES') do + context 'true' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '585 - test': + ensure => present, + proto => tcp, + port => '585', + action => accept, + chain => 'PREROUTING', + table => 'nat', + socket => true, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save -t nat') do |r| + expect(r.stdout).to match(/-A PREROUTING -p tcp -m multiport --ports 585 -m socket -m comment --comment "585 - test" -j ACCEPT/) + end + end + end + + context 'false' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '586 - test': + ensure => present, + proto => tcp, + port => '586', + action => accept, + chain => 'PREROUTING', + table => 'nat', + socket => false, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save -t nat') do |r| + expect(r.stdout).to match(/-A PREROUTING -p tcp -m multiport --ports 586 -m comment --comment "586 - test" -j ACCEPT/) + end + end + end + end + + describe 'ipsec_policy' do + context 'ipsec' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '593 - test': + ensure => 'present', + action => 'reject', + chain => 'OUTPUT', + destination => '20.0.0.0/8', + ipsec_dir => 'out', + ipsec_policy => 'ipsec', + proto => 'all', + reject => 'icmp-net-unreachable', + table => 'filter', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A OUTPUT -d 20.0.0.0\/(8|255\.0\.0\.0) -m comment --comment "593 - test" -m policy --dir out --pol ipsec -j REJECT --reject-with icmp-net-unreachable/) + end + end + end + + context 'none' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '594 - test': + ensure => 'present', + action => 'reject', + chain => 'OUTPUT', + destination => '20.0.0.0/8', + ipsec_dir => 'out', + ipsec_policy => 'none', + proto => 'all', + reject => 'icmp-net-unreachable', + table => 'filter', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A OUTPUT -d 20.0.0.0\/(8|255\.0\.0\.0) -m comment --comment "594 - test" -m policy --dir out --pol none -j REJECT --reject-with icmp-net-unreachable/) + end + end + end + end + + describe 'ipsec_dir' do + context 'out' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '595 - test': + ensure => 'present', + action => 'reject', + chain => 'OUTPUT', + destination => '20.0.0.0/8', + ipsec_dir => 'out', + ipsec_policy => 'ipsec', + proto => 'all', + reject => 'icmp-net-unreachable', + table => 'filter', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A OUTPUT -d 20.0.0.0\/(8|255\.0\.0\.0) -m comment --comment "595 - test" -m policy --dir out --pol ipsec -j REJECT --reject-with icmp-net-unreachable/) + end + end + end + + context 'in' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '596 - test': + ensure => 'present', + action => 'reject', + chain => 'INPUT', + destination => '20.0.0.0/8', + ipsec_dir => 'in', + ipsec_policy => 'none', + proto => 'all', + reject => 'icmp-net-unreachable', + table => 'filter', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -d 20.0.0.0\/(8|255\.0\.0\.0) -m comment --comment "596 - test" -m policy --dir in --pol none -j REJECT --reject-with icmp-net-unreachable/) + end + end + end + end + + describe 'recent' do + context 'set' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '597 - test': + ensure => 'present', + chain => 'INPUT', + destination => '30.0.0.0/8', + proto => 'all', + table => 'filter', + recent => 'set', + rdest => true, + rname => 'list1', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + # Mask added as of Ubuntu 14.04. + expect(r.stdout).to match(/-A INPUT -d 30.0.0.0\/(8|255\.0\.0\.0) -m comment --comment "597 - test" -m recent --set --name list1 (--mask 255.255.255.255 )?--rdest/) + end + end + end + + context 'rcheck' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '598 - test': + ensure => 'present', + chain => 'INPUT', + destination => '30.0.0.0/8', + proto => 'all', + table => 'filter', + recent => 'rcheck', + rsource => true, + rname => 'list1', + rseconds => 60, + rhitcount => 5, + rttl => true, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -d 30.0.0.0\/(8|255\.0\.0\.0) -m comment --comment "598 - test" -m recent --rcheck --seconds 60 --hitcount 5 --rttl --name list1 (--mask 255.255.255.255 )?--rsource/) + end + end + end + + context 'update' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '599 - test': + ensure => 'present', + chain => 'INPUT', + destination => '30.0.0.0/8', + proto => 'all', + table => 'filter', + recent => 'update', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -d 30.0.0.0\/(8|255\.0\.0\.0) -m comment --comment "599 - test" -m recent --update/) + end + end + end + + context 'remove' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '600 - test': + ensure => 'present', + chain => 'INPUT', + destination => '30.0.0.0/8', + proto => 'all', + table => 'filter', + recent => 'remove', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + expect(r.stdout).to match(/-A INPUT -d 30.0.0.0\/(8|255\.0\.0\.0) -m comment --comment "600 - test" -m recent --remove/) + end + end + end + end + + describe 'reset' do + it 'deletes all rules' do + shell('ip6tables --flush') + shell('iptables --flush; iptables -t nat --flush; iptables -t mangle --flush') + end + end + +end diff --git a/modules/firewall/spec/acceptance/firewallchain_spec.rb b/modules/firewall/spec/acceptance/firewallchain_spec.rb new file mode 100644 index 0000000..f70d9ce --- /dev/null +++ b/modules/firewall/spec/acceptance/firewallchain_spec.rb @@ -0,0 +1,125 @@ +require 'spec_helper_acceptance' + +describe 'puppet resource firewallchain command:', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 diff --git a/modules/firewall/spec/acceptance/ip6_fragment_spec.rb b/modules/firewall/spec/acceptance/ip6_fragment_spec.rb new file mode 100644 index 0000000..3e44f87 --- /dev/null +++ b/modules/firewall/spec/acceptance/ip6_fragment_spec.rb @@ -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", :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 diff --git a/modules/firewall/spec/acceptance/isfragment_spec.rb b/modules/firewall/spec/acceptance/isfragment_spec.rb new file mode 100644 index 0000000..a4b65e7 --- /dev/null +++ b/modules/firewall/spec/acceptance/isfragment_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper_acceptance' + +describe 'firewall isfragment property', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 diff --git a/modules/firewall/spec/acceptance/nodesets/centos-59-x64-pe.yml b/modules/firewall/spec/acceptance/nodesets/centos-59-x64-pe.yml new file mode 100644 index 0000000..3a6470b --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/centos-59-x64-pe.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/centos-59-x64.yml b/modules/firewall/spec/acceptance/nodesets/centos-59-x64.yml new file mode 100644 index 0000000..b41a947 --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/centos-59-x64.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/centos-64-x64-fusion.yml b/modules/firewall/spec/acceptance/nodesets/centos-64-x64-fusion.yml new file mode 100644 index 0000000..d516673 --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/centos-64-x64-fusion.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/centos-64-x64-pe.yml b/modules/firewall/spec/acceptance/nodesets/centos-64-x64-pe.yml new file mode 100644 index 0000000..7d9242f --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/centos-64-x64-pe.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/centos-64-x64.yml b/modules/firewall/spec/acceptance/nodesets/centos-64-x64.yml new file mode 100644 index 0000000..05540ed --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/centos-64-x64.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/debian-607-x64.yml b/modules/firewall/spec/acceptance/nodesets/debian-607-x64.yml new file mode 100644 index 0000000..4c8be42 --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/debian-607-x64.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/debian-70rc1-x64.yml b/modules/firewall/spec/acceptance/nodesets/debian-70rc1-x64.yml new file mode 100644 index 0000000..19181c1 --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/debian-70rc1-x64.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/default.yml b/modules/firewall/spec/acceptance/nodesets/default.yml new file mode 100644 index 0000000..05540ed --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/default.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/fedora-18-x64.yml b/modules/firewall/spec/acceptance/nodesets/fedora-18-x64.yml new file mode 100644 index 0000000..624b537 --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/fedora-18-x64.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/sles-11sp1-x64.yml b/modules/firewall/spec/acceptance/nodesets/sles-11sp1-x64.yml new file mode 100644 index 0000000..554c37a --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/sles-11sp1-x64.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml b/modules/firewall/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml new file mode 100644 index 0000000..5047017 --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml b/modules/firewall/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml new file mode 100644 index 0000000..d065b30 --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml @@ -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 diff --git a/modules/firewall/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml b/modules/firewall/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml new file mode 100644 index 0000000..7e789c8 --- /dev/null +++ b/modules/firewall/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml @@ -0,0 +1,9 @@ +HOSTS: + ubuntu-server-1404-x64: + roles: + - master + platform: ubuntu-14.04-64 + box: puppetlabs/ubuntu-14.04-64-nocm + hypervisor : vagrant +CONFIG: + type: foss diff --git a/modules/firewall/spec/acceptance/params_spec.rb b/modules/firewall/spec/acceptance/params_spec.rb new file mode 100644 index 0000000..93b83ef --- /dev/null +++ b/modules/firewall/spec/acceptance/params_spec.rb @@ -0,0 +1,154 @@ +require 'spec_helper_acceptance' + +describe "param based tests:", :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 diff --git a/modules/firewall/spec/acceptance/purge_spec.rb b/modules/firewall/spec/acceptance/purge_spec.rb new file mode 100644 index 0000000..4de968a --- /dev/null +++ b/modules/firewall/spec/acceptance/purge_spec.rb @@ -0,0 +1,124 @@ +require 'spec_helper_acceptance' + +describe "purge tests:", :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 diff --git a/modules/firewall/spec/acceptance/resource_cmd_spec.rb b/modules/firewall/spec/acceptance/resource_cmd_spec.rb new file mode 100644 index 0000000..1a10fdd --- /dev/null +++ b/modules/firewall/spec/acceptance/resource_cmd_spec.rb @@ -0,0 +1,92 @@ +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:', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 + # don't check stderr, puppet throws deprecation warnings + 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.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 + # don't check stderr, puppet throws deprecation warnings + 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 + # don't check stderr, puppet throws deprecation warnings + 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 + # don't check stderr, puppet throws deprecation warnings + 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 + # don't check stderr, puppet throws deprecation warnings + end + end + end +end diff --git a/modules/firewall/spec/acceptance/rules_spec.rb b/modules/firewall/spec/acceptance/rules_spec.rb new file mode 100644 index 0000000..b7eb2df --- /dev/null +++ b/modules/firewall/spec/acceptance/rules_spec.rb @@ -0,0 +1,252 @@ +require 'spec_helper_acceptance' + +describe 'complex ruleset 1', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 diff --git a/modules/firewall/spec/acceptance/socket_spec.rb b/modules/firewall/spec/acceptance/socket_spec.rb new file mode 100644 index 0000000..5503a9a --- /dev/null +++ b/modules/firewall/spec/acceptance/socket_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper_acceptance' + +# RHEL5 does not support -m socket +describe 'firewall socket property', :unless => (UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) || 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 diff --git a/modules/firewall/spec/acceptance/standard_usage_spec.rb b/modules/firewall/spec/acceptance/standard_usage_spec.rb new file mode 100644 index 0000000..8dcbcef --- /dev/null +++ b/modules/firewall/spec/acceptance/standard_usage_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper_acceptance' + +# Some tests for the standard recommended usage +describe 'standard usage tests:', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) 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 diff --git a/modules/firewall/spec/acceptance/unsupported_spec.rb b/modules/firewall/spec/acceptance/unsupported_spec.rb new file mode 100644 index 0000000..dfb75e2 --- /dev/null +++ b/modules/firewall/spec/acceptance/unsupported_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper_acceptance' + +describe 'unsupported distributions and OSes', :if => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do + it 'should fail' do + pp = <<-EOS + class { 'firewall': } + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/not currently supported/i) + end +end diff --git a/modules/firewall/spec/fixtures/ip6tables/conversion_hash.rb b/modules/firewall/spec/fixtures/ip6tables/conversion_hash.rb new file mode 100644 index 0000000..7c507d7 --- /dev/null +++ b/modules/firewall/spec/fixtures/ip6tables/conversion_hash.rb @@ -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], + }, +} diff --git a/modules/firewall/spec/fixtures/iptables/conversion_hash.rb b/modules/firewall/spec/fixtures/iptables/conversion_hash.rb new file mode 100644 index 0000000..105d27f --- /dev/null +++ b/modules/firewall/spec/fixtures/iptables/conversion_hash.rb @@ -0,0 +1,934 @@ +# 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"], + }, + }, + 'connlimit_above' => { + :line => '-A INPUT -p tcp -m multiport --dports 22 -m comment --comment "061 REJECT connlimit_above 10" -m connlimit --connlimit-above 10 --connlimit-mask 32 -j REJECT --reject-with icmp-port-unreachable', + :table => 'filter', + :params => { + :proto => 'tcp', + :dport => ["22"], + :connlimit_above => '10', + :action => 'reject', + }, + }, + 'connlimit_above_with_connlimit_mask' => { + :line => '-A INPUT -p tcp -m multiport --dports 22 -m comment --comment "061 REJECT connlimit_above 10 with mask 24" -m connlimit --connlimit-above 10 --connlimit-mask 24 -j REJECT --reject-with icmp-port-unreachable', + :table => 'filter', + :params => { + :proto => 'tcp', + :dport => ["22"], + :connlimit_above => '10', + :connlimit_mask => '24', + :action => 'reject', + }, + }, + 'connmark' => { + :line => '-A INPUT -m comment --comment "062 REJECT connmark" -m connmark --mark 0x1 -j REJECT --reject-with icmp-port-unreachable', + :table => 'filter', + :params => { + :proto => 'all', + :connmark => '0x1', + :action => 'reject', + }, + }, +} + +# 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'], + }, + 'connlimit_above' => { + :params => { + :name => '061 REJECT connlimit_above 10', + :table => 'filter', + :proto => 'tcp', + :dport => ["22"], + :connlimit_above => '10', + :action => 'reject', + }, + :args => ["-t", :filter, "-p", :tcp, "-m", "multiport", "--dports", "22", "-m", "comment", "--comment", "061 REJECT connlimit_above 10", "-j", "REJECT", "-m", "connlimit", "--connlimit-above", "10"], + }, + 'connlimit_above_with_connlimit_mask' => { + :params => { + :name => '061 REJECT connlimit_above 10 with mask 24', + :table => 'filter', + :proto => 'tcp', + :dport => ["22"], + :connlimit_above => '10', + :connlimit_mask => '24', + :action => 'reject', + }, + :args => ["-t", :filter, "-p", :tcp, "-m", "multiport", "--dports", "22", "-m", "comment", "--comment", "061 REJECT connlimit_above 10 with mask 24", "-j", "REJECT", "-m", "connlimit", "--connlimit-above", "10", "--connlimit-mask", "24"], + }, + 'connmark' => { + :params => { + :name => '062 REJECT connmark', + :table => 'filter', + :proto => 'all', + :connmark => '0x1', + :action => 'reject', + }, + :args => ["-t", :filter, "-p", :all, "-m", "comment", "--comment", "062 REJECT connmark", "-j", "REJECT", "-m", "connmark", "--mark", "0x1"], + }, +} diff --git a/modules/firewall/spec/spec_helper.rb b/modules/firewall/spec/spec_helper.rb new file mode 100644 index 0000000..dc8bc39 --- /dev/null +++ b/modules/firewall/spec/spec_helper.rb @@ -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 diff --git a/modules/firewall/spec/spec_helper_acceptance.rb b/modules/firewall/spec/spec_helper_acceptance.rb new file mode 100644 index 0000000..4f0fc94 --- /dev/null +++ b/modules/firewall/spec/spec_helper_acceptance.rb @@ -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' or ENV['BEAKER_provision'] == 'no' + if hosts.first.is_pe? + install_pe + else + install_puppet + end + hosts.each do |host| + on host, "mkdir -p #{host['distmoduledir']}" + end +end + +UNSUPPORTED_PLATFORMS = ['windows','Solaris','Darwin'] + +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 diff --git a/modules/firewall/spec/unit/classes/firewall_linux_archlinux_spec.rb b/modules/firewall/spec/unit/classes/firewall_linux_archlinux_spec.rb new file mode 100644 index 0000000..954d9ee --- /dev/null +++ b/modules/firewall/spec/unit/classes/firewall_linux_archlinux_spec.rb @@ -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 diff --git a/modules/firewall/spec/unit/classes/firewall_linux_debian_spec.rb b/modules/firewall/spec/unit/classes/firewall_linux_debian_spec.rb new file mode 100644 index 0000000..98285b6 --- /dev/null +++ b/modules/firewall/spec/unit/classes/firewall_linux_debian_spec.rb @@ -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 diff --git a/modules/firewall/spec/unit/classes/firewall_linux_redhat_spec.rb b/modules/firewall/spec/unit/classes/firewall_linux_redhat_spec.rb new file mode 100644 index 0000000..ea49d2b --- /dev/null +++ b/modules/firewall/spec/unit/classes/firewall_linux_redhat_spec.rb @@ -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 diff --git a/modules/firewall/spec/unit/classes/firewall_linux_spec.rb b/modules/firewall/spec/unit/classes/firewall_linux_spec.rb new file mode 100644 index 0000000..42056c1 --- /dev/null +++ b/modules/firewall/spec/unit/classes/firewall_linux_spec.rb @@ -0,0 +1,30 @@ +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 + releases = (os == 'Fedora' ? [14,15,'Rawhide'] : [6,7]) + releases.each do |osrel| + context "operatingsystemrelease => #{osrel}" do + let(:facts) { facts_default.merge({ :operatingsystem => os, + :operatingsystemrelease => osrel}) } + it { should contain_class('firewall::linux::redhat').with_require('Package[iptables]') } + end + end + 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 diff --git a/modules/firewall/spec/unit/classes/firewall_spec.rb b/modules/firewall/spec/unit/classes/firewall_spec.rb new file mode 100644 index 0000000..efc153a --- /dev/null +++ b/modules/firewall/spec/unit/classes/firewall_spec.rb @@ -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 diff --git a/modules/firewall/spec/unit/facter/iptables_persistent_version_spec.rb b/modules/firewall/spec/unit/facter/iptables_persistent_version_spec.rb new file mode 100644 index 0000000..13a23a5 --- /dev/null +++ b/modules/firewall/spec/unit/facter/iptables_persistent_version_spec.rb @@ -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 diff --git a/modules/firewall/spec/unit/facter/iptables_spec.rb b/modules/firewall/spec/unit/facter/iptables_spec.rb new file mode 100644 index 0000000..5773fdc --- /dev/null +++ b/modules/firewall/spec/unit/facter/iptables_spec.rb @@ -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 diff --git a/modules/firewall/spec/unit/puppet/provider/iptables_chain_spec.rb b/modules/firewall/spec/unit/puppet/provider/iptables_chain_spec.rb new file mode 100755 index 0000000..f350c2e --- /dev/null +++ b/modules/firewall/spec/unit/puppet/provider/iptables_chain_spec.rb @@ -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 diff --git a/modules/firewall/spec/unit/puppet/provider/iptables_spec.rb b/modules/firewall/spec/unit/puppet/provider/iptables_spec.rb new file mode 100644 index 0000000..d6f5b64 --- /dev/null +++ b/modules/firewall/spec/unit/puppet/provider/iptables_spec.rb @@ -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 + diff --git a/modules/firewall/spec/unit/puppet/type/firewall_spec.rb b/modules/firewall/spec/unit/puppet/type/firewall_spec.rb new file mode 100755 index 0000000..afb6166 --- /dev/null +++ b/modules/firewall/spec/unit/puppet/type/firewall_spec.rb @@ -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 diff --git a/modules/firewall/spec/unit/puppet/type/firewallchain_spec.rb b/modules/firewall/spec/unit/puppet/type/firewallchain_spec.rb new file mode 100755 index 0000000..88ca99d --- /dev/null +++ b/modules/firewall/spec/unit/puppet/type/firewallchain_spec.rb @@ -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(< '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 diff --git a/modules/firewall/spec/unit/puppet/util/firewall_spec.rb b/modules/firewall/spec/unit/puppet/util/firewall_spec.rb new file mode 100644 index 0000000..e586487 --- /dev/null +++ b/modules/firewall/spec/unit/puppet/util/firewall_spec.rb @@ -0,0 +1,197 @@ +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 /sbin/service if running RHEL 6 or earlier' do + allow(Facter.fact(:osfamily)).to receive(:value).and_return('RedHat') + allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('RedHat') + allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return('6') + + expect(subject).to receive(:execute).with(%w{/sbin/service iptables save}) + subject.persist_iptables(proto) + end + + it 'should exec for systemd if running RHEL 7 or greater' do + allow(Facter.fact(:osfamily)).to receive(:value).and_return('RedHat') + allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('RedHat') + allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return('7') + + expect(subject).to receive(:execute).with(%w{/usr/libexec/iptables/iptables.init 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/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') + allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return('6') + + 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 diff --git a/modules/firewall/spec/unit/puppet/util/ipcidr_spec.rb b/modules/firewall/spec/unit/puppet/util/ipcidr_spec.rb new file mode 100644 index 0000000..916f74a --- /dev/null +++ b/modules/firewall/spec/unit/puppet/util/ipcidr_spec.rb @@ -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 diff --git a/modules/postgresql/CHANGELOG.md b/modules/postgresql/CHANGELOG.md index 5f70a34..a045b2e 100644 --- a/modules/postgresql/CHANGELOG.md +++ b/modules/postgresql/CHANGELOG.md @@ -1,3 +1,25 @@ +##2014-04-14 - Supported Release 3.4.0 +###Summary + +This feature rolls up several important features, the biggest being PostGIS +handling and allowing `port` to be set on postgresql::server in order to +change the port that Postgres listens on. We've added support for RHEL7 +and Ubuntu 14.04, as well as allowing you to manage the service via +`service_ensure` finally. + +####Features +- Added `perl_package_name` for installing bindings. +- Added `service_ensure` for allowing control of services. +- Added `postgis_version` and postgis class for installing postgis. +- Added `port` for selecting the port Postgres runs on. +- Add support for RHEL7 and Ubuntu 14.04. +- Add `default_db` to postgresql::server::database. +- Widen the selection of unquoted parameters in postgresql_conf{} +- Require the service within postgresql::server::reload for RHEL7. +- Add `inherit` to postgresql::server::role. + +####Bugfixes + ##2014-03-04 - Supported Release 3.3.3 ###Summary diff --git a/modules/postgresql/Gemfile b/modules/postgresql/Gemfile index cb5deec..d6c55ec 100644 --- a/modules/postgresql/Gemfile +++ b/modules/postgresql/Gemfile @@ -1,12 +1,11 @@ source ENV['GEM_SOURCE'] || "https://rubygems.org" group :development, :test do - gem 'rake' - gem 'pry', :require => false + gem 'rake', '10.1.1' gem 'puppetlabs_spec_helper', :require => false - gem 'rspec-puppet', '< 1.0' + gem 'rspec-puppet', '~> 1.0' + gem 'rspec', '~> 2.11', :require => false gem 'puppet-lint', '~> 0.3.2' - gem 'beaker', :require => false gem 'beaker-rspec', :require => false gem 'serverspec', :require => false end diff --git a/modules/postgresql/Gemfile.lock b/modules/postgresql/Gemfile.lock index 8214eac..cc3f0aa 100644 --- a/modules/postgresql/Gemfile.lock +++ b/modules/postgresql/Gemfile.lock @@ -1,23 +1,35 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.2.7) - beaker (1.7.0) + CFPropertyList (2.2.8) + addressable (2.3.6) + archive-tar-minitar (0.5.2) + autoparse (0.3.3) + addressable (>= 2.3.1) + extlib (>= 0.9.15) + multi_json (>= 1.0.0) + aws-sdk (1.42.0) + json (~> 1.4) + nokogiri (>= 1.4.4) + beaker (1.15.0) + aws-sdk (= 1.42.0) blimpy (~> 0.6) + docker-api fission (~> 0.4) + google-api-client (~> 0.7.1) inifile (~> 2.0) json (~> 1.8) mime-types (~> 1.25) net-scp (~> 1.1) net-ssh (~> 2.6) - nokogiri (= 1.5.10) + nokogiri (~> 1.5.10) rbvmomi (= 1.8.1) unf (~> 0.1) - beaker-rspec (2.1.1) - beaker (~> 1.3) - rspec (~> 2.14) - serverspec (~> 0.14) - specinfra (~> 0.3) + beaker-rspec (2.2.6) + beaker (~> 1.10) + rspec + serverspec (~> 1.0) + specinfra (~> 1.0) blimpy (0.6.7) fog minitar @@ -25,81 +37,133 @@ GEM builder (3.2.2) coderay (1.1.0) diff-lcs (1.2.5) - excon (0.31.0) - facter (1.7.5) + docile (1.1.5) + docker-api (1.13.0) + archive-tar-minitar + excon (>= 0.37.0) + json + excon (0.37.0) + extlib (0.9.16) + facter (2.1.0) + CFPropertyList (~> 2.2.6) + faraday (0.9.0) + multipart-post (>= 1.2, < 3) fission (0.5.0) CFPropertyList (~> 2.2) - fog (1.19.0) + fog (1.22.1) + fog-brightbox + fog-core (~> 1.22) + fog-json + ipaddress (~> 0.5) + nokogiri (~> 1.5, >= 1.5.11) + fog-brightbox (0.1.1) + fog-core (~> 1.22) + fog-json + inflecto + fog-core (1.22.0) builder - excon (~> 0.31.0) - formatador (~> 0.2.0) + excon (~> 0.33) + formatador (~> 0.2) mime-types - multi_json (~> 1.0) net-scp (~> 1.1) net-ssh (>= 2.1.3) - nokogiri (~> 1.5) - ruby-hmac - formatador (0.2.4) - hiera (1.3.2) + fog-json (1.0.0) + multi_json (~> 1.0) + formatador (0.2.5) + google-api-client (0.7.1) + addressable (>= 2.3.2) + autoparse (>= 0.3.3) + extlib (>= 0.9.15) + faraday (>= 0.9.0) + jwt (>= 0.1.5) + launchy (>= 2.1.1) + multi_json (>= 1.0.0) + retriable (>= 1.4) + signet (>= 0.5.0) + uuidtools (>= 2.1.0) + hiera (1.3.4) json_pure highline (1.6.21) + inflecto (0.0.2) inifile (2.0.2) + ipaddress (0.8.0) json (1.8.1) json_pure (1.8.1) + jwt (1.0.0) + launchy (2.4.2) + addressable (~> 2.3) metaclass (0.0.4) method_source (0.8.2) mime-types (1.25.1) minitar (0.5.4) - mocha (1.0.0) + mocha (1.1.0) metaclass (~> 0.0.1) - multi_json (1.8.4) - net-scp (1.1.2) + multi_json (1.10.1) + multipart-post (2.0.0) + net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (2.8.0) - nokogiri (1.5.10) - pry (0.9.12.6) - coderay (~> 1.0) - method_source (~> 0.8) + net-ssh (2.9.1) + nokogiri (1.5.11) + pry (0.10.0) + coderay (~> 1.1.0) + method_source (~> 0.8.1) slop (~> 3.4) - puppet (3.4.3) - facter (~> 1.6) + puppet (3.6.2) + facter (> 1.6, < 3) hiera (~> 1.0) + json_pure rgen (~> 0.6.5) puppet-lint (0.3.2) - puppetlabs_spec_helper (0.4.1) - mocha (>= 0.10.5) + puppetlabs_spec_helper (0.6.0) + mocha + puppet-lint rake - rspec (>= 2.9.0) - rspec-puppet (>= 0.1.1) - rake (10.1.1) + rspec + rspec-puppet + rake (10.3.2) rbvmomi (1.8.1) builder nokogiri (>= 1.4.1) trollop + retriable (1.4.1) rgen (0.6.6) - rspec (2.14.1) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) - rspec-core (2.14.8) - rspec-expectations (2.14.5) + rspec (2.99.0) + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rspec-core (2.99.1) + rspec-expectations (2.99.1) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.14.6) - rspec-puppet (0.1.6) + rspec-its (1.0.1) + rspec-core (>= 2.99.0.beta1) + rspec-expectations (>= 2.99.0.beta1) + rspec-mocks (2.99.1) + rspec-puppet (1.0.1) rspec - ruby-hmac (0.4.0) - serverspec (0.15.3) + serverspec (1.9.1) highline net-ssh - rspec (>= 2.13.0) - specinfra (>= 0.5.8) - slop (3.4.7) - specinfra (0.7.0) - thor (0.18.1) + rspec (~> 2.13) + rspec-its + specinfra (~> 1.18) + signet (0.5.1) + addressable (>= 2.2.3) + faraday (>= 0.9.0.rc5) + jwt (>= 0.1.5) + multi_json (>= 1.0.0) + simplecov (0.8.2) + docile (~> 1.1.0) + multi_json + simplecov-html (~> 0.8.0) + simplecov-html (0.8.0) + slop (3.5.0) + specinfra (1.20.0) + thor (0.19.1) trollop (2.0) - unf (0.1.3) + unf (0.1.4) unf_ext unf_ext (0.0.6) + uuidtools (2.1.4) PLATFORMS ruby @@ -107,10 +171,12 @@ PLATFORMS DEPENDENCIES beaker beaker-rspec + facter pry puppet - puppet-lint (~> 0.3.2) + puppet-lint puppetlabs_spec_helper rake - rspec-puppet (< 1.0) + rspec-puppet serverspec + simplecov diff --git a/modules/postgresql/Modulefile b/modules/postgresql/Modulefile deleted file mode 100644 index e0e9354..0000000 --- a/modules/postgresql/Modulefile +++ /dev/null @@ -1,13 +0,0 @@ -name 'puppetlabs-postgresql' -version '3.3.3' -source 'git://github.com/puppetlabs/puppet-postgresql.git' -author 'Inkling/Puppet Labs' -description 'PostgreSQL defined resource types' -summary 'PostgreSQL defined resource types' -license 'ASL 2.0' -project_page 'https://github.com/puppetlabs/puppet-postgresql' - -dependency 'puppetlabs/stdlib', '>=3.2.0 <5.0.0' -dependency 'puppetlabs/firewall', '>= 0.0.4' -dependency 'puppetlabs/apt', '>=1.1.0 <2.0.0' -dependency 'puppetlabs/concat', '>= 1.0.0 <2.0.0' diff --git a/modules/postgresql/README.md b/modules/postgresql/README.md index 7a26cb9..64006f9 100644 --- a/modules/postgresql/README.md +++ b/modules/postgresql/README.md @@ -226,10 +226,12 @@ Classes: * [postgresql::globals](#class-postgresqlglobals) * [postgresql::lib::devel](#class-postgresqllibdevel) * [postgresql::lib::java](#class-postgresqllibjava) +* [postgresql::lib::perl](#class-postgresqllibperl) * [postgresql::lib::python](#class-postgresqllibpython) * [postgresql::server](#class-postgresqlserver) * [postgresql::server::plperl](#class-postgresqlserverplperl) * [postgresql::server::contrib](#class-postgresqlservercontrib) +* [postgresql::server::postgis](#class-postgresqlserverpostgis) Resources: @@ -288,12 +290,18 @@ This setting can be used to override the default postgresql devel package name. ####`java_package_name` This setting can be used to override the default postgresql java package name. If not specified, the module will use whatever package name is the default for your OS distro. +####`perl_package_name` +This setting can be used to override the default postgresql Perl package name. If not specified, the module will use whatever package name is the default for your OS distro. + ####`plperl_package_name` This setting can be used to override the default postgresql PL/perl package name. If not specified, the module will use whatever package name is the default for your OS distro. ####`python_package_name` This setting can be used to override the default postgresql Python package name. If not specified, the module will use whatever package name is the default for your OS distro. +####`service_ensure` +This setting can be used to override the default postgresql service ensure status. If not specified, the module will use `ensure` instead. + ####`service_name` This setting can be used to override the default postgresql service name. If not specified, the module will use whatever service name is the default for your OS distro. @@ -347,6 +355,9 @@ The version of PostgreSQL to install/manage. This is a simple way of providing a Defaults to your operating system default. +####`postgis_version` +The version of PostGIS to install if you install PostGIS. Defaults to the lowest available with the version of PostgreSQL to be installed. + ####`needs_initdb` This setting can be used to explicitly call the initdb operation after server package is installed and before the postgresql service is started. If not specified, the module will decide whether to call initdb or not depending on your OS distro. @@ -372,9 +383,6 @@ The following list are options that you can set in the `config_hash` parameter o ####`ensure` This value default to `present`. When set to `absent` it will remove all packages, configuration and data so use this with extreme caution. -####`version` -This will set the version of the PostgreSQL software to install. Defaults to your operating systems default. - ####`postgres_password` This value defaults to `undef`, meaning the super user account in the postgres database is a user called `postgres` and this account does not have a password. If you provide this setting, the module will set the password for the `postgres` user to your specified value. @@ -402,6 +410,9 @@ This setting is used to specify the name of the default database to connect with ####`listen_addresses` This value defaults to `localhost`, meaning the postgres server will only accept connections from localhost. If you'd like to be able to connect to postgres from remote machines, you can override this setting. A value of `*` will tell postgres to accept connections from any remote machine. Alternately, you can specify a comma-separated list of hostnames or IP addresses. (For more info, have a look at the `postgresql.conf` file from your system's postgres package). +####`port` +This value defaults to `5432`, meaning the postgres server will listen on TCP port 5432. Note that the same port number is used for all IP addresses the server listens on. + ####`ip_mask_deny_postgres_user` This value defaults to `0.0.0.0/0`. Sometimes it can be useful to block the superuser account from remote connections if you are allowing other database users to connect remotely. Set this to an IP and mask for which you want to deny connections by the postgres superuser account. So, e.g., the default value of `0.0.0.0/0` will match any remote IP and deny access, so the postgres user won't be able to connect remotely at all. Conversely, a value of `0.0.0.0/32` would not match any remote IP, and thus the deny rule will not be applied and the postgres user will be allowed to connect. @@ -481,6 +492,8 @@ The name of the postgresql contrib package. ####`package_ensure` The ensure parameter passed on to postgresql contrib package resource. +###Class: postgresql::server::postgis +Installs the postgresql postgis packages. ###Class: postgresql::lib::devel Installs the packages containing the development libraries for PostgreSQL. @@ -502,6 +515,16 @@ The name of the postgresql java package. The ensure parameter passed on to postgresql java package resource. +###Class: postgresql::lib::perl +This class installs the postgresql Perl libraries. For customer requirements you can customise the following parameters: + +####`package_name` +The name of the postgresql perl package. + +####`package_ensure` +The ensure parameter passed on to postgresql perl package resource. + + ###Class: postgresql::lib::python This class installs the postgresql Python libraries. For customer requirements you can customise the following parameters: @@ -554,6 +577,9 @@ For example, to create a database called `test1` with a corresponding user of th ####`namevar` The namevar for the resource designates the name of the database. +####`dbname` +The name of the database to be created. Defaults to `namevar`. + ####`user` User to create and assign access to the database upon creation. Mandatory. @@ -697,6 +723,9 @@ Whether to grant the ability to create new roles with this role. Defaults to `fa ####`login` Whether to grant login capability for the new role. Defaults to `false`. +####`inherit` +Whether to grant inherit capability for the new role. Defaults to `true`. + ####`superuser` Whether to grant super user capability for the new role. Defaults to `false`. @@ -822,11 +851,24 @@ Works with versions of PostgreSQL from 8.1 through 9.2. Current it is only actively tested with the following operating systems: * Debian 6.x and 7.x -* Centos 5.x and 6.x -* Ubuntu 10.04 and 12.04 +* Centos 5.x, 6.x, and 7.x. +* Ubuntu 10.04 and 12.04, 14.04 Although patches are welcome for making it work with other OS distros, it is considered best effort. +### Postgis support + +Postgis is currently considered an unsupported feature as it doesn't work on +all platforms correctly. + +### All versions of RHEL/Centos + +If you have selinux enabled you must add any custom ports you use to the postgresql_port_t context. You can do this as follows: + +``` +# semanage port -a -t postgresql_port_t -p tcp $customport +``` + Development ------------ @@ -862,11 +904,11 @@ If you want to run the system tests, make sure you also have: Then run the tests using: - bundle exec rake spec:system + bundle exec rspec spec/acceptance To run the tests on different operating systems, see the sets available in .nodeset.yml and run the specific set with the following syntax: - RSPEC_SET=debian-607-x64 bundle exec rake spec:system + RSPEC_SET=debian-607-x64 bundle exec rspec spec/acceptance Transfer Notice ---------------- diff --git a/modules/postgresql/checksums.json b/modules/postgresql/checksums.json new file mode 100644 index 0000000..fb85e9f --- /dev/null +++ b/modules/postgresql/checksums.json @@ -0,0 +1,115 @@ +{ + "CHANGELOG.md": "0988b5bdb4105331ab66e5cb1903af20", + "Gemfile": "e9062e0fb5e3006b5f45f6083992cbdc", + "Gemfile.lock": "04ba92c1e89b28bb0d2b66585413a00f", + "LICENSE": "746fe83ebbf8970af0a9ea13962293e9", + "NOTICE": "d8ffc52f00e00877b45d2b77e709f69e", + "README.md": "a9ecd5d8104851cd25a93d325b2d3d4f", + "Rakefile": "c4f5c8ac6adc0eb4530c4b3d10cb7da0", + "files/RPM-GPG-KEY-PGDG": "78b5db170d33f80ad5a47863a7476b22", + "files/validate_postgresql_connection.sh": "20301932819f035492a30880f5bf335a", + "lib/puppet/parser/functions/postgresql_acls_to_resources_hash.rb": "d518a7959b950874820a3b0a7a324488", + "lib/puppet/parser/functions/postgresql_escape.rb": "2e136fcd653ab38d831c5b40806d47d1", + "lib/puppet/parser/functions/postgresql_password.rb": "820da02a888ab42357fe9bc2352b1c37", + "lib/puppet/provider/postgresql_conf/parsed.rb": "53cacac5630209bd4a4ea62433fba764", + "lib/puppet/provider/postgresql_psql/ruby.rb": "7c84a41473c41edba2c2526843c00fbf", + "lib/puppet/type/postgresql_conf.rb": "4f333138a3689f9768e7fe4bc3cde9fd", + "lib/puppet/type/postgresql_psql.rb": "9bdcc5809a2afd9c0d67d7f66f406504", + "manifests/client.pp": "f9bc3a578017fe8eb881de2255bdc023", + "manifests/globals.pp": "f4ecf66904a776116251c9c77c6582cc", + "manifests/lib/devel.pp": "94ae7eac3acf1dd3072d481eca4d2d7f", + "manifests/lib/java.pp": "6e4a2187c2b4caecad8098b46e99c8e0", + "manifests/lib/perl.pp": "5213fb4284f041fe28a22d68119f646d", + "manifests/lib/python.pp": "90736f86301c4c6401ec1180c176b616", + "manifests/params.pp": "44185cc010c3ec4c9499c5f78c214ed5", + "manifests/repo/apt_postgresql_org.pp": "5499d3c362a5297a2a63386c0157fb91", + "manifests/repo/yum_postgresql_org.pp": "e0c445f877cdb39774b735417c967d1d", + "manifests/repo.pp": "a18a5cb760dbb1e10bdd83730300c1fe", + "manifests/server/config.pp": "584bbe930bf30631bdf5c62bea2e3aea", + "manifests/server/config_entry.pp": "d7d6e532ddca64f876085721b4bf4dbb", + "manifests/server/contrib.pp": "3112bd1edbed51b68e1402027f9d53b1", + "manifests/server/database.pp": "66639c9579ae5572d1d39a03256370c1", + "manifests/server/database_grant.pp": "66e5470bb932b087b540c444ee49941b", + "manifests/server/db.pp": "796d0ca0b28c87175e42d0671f32c3ea", + "manifests/server/firewall.pp": "524f43b8d50160a6434bee347836c166", + "manifests/server/grant.pp": "b2ac20bf9fecb640c7fe6eba603d6e1b", + "manifests/server/initdb.pp": "215f84f3d9931372190d9928832e315a", + "manifests/server/install.pp": "3b67ca28ca49dc54081d859b8bad3050", + "manifests/server/passwd.pp": "197af62ecfcbd0982aa6563f2579d8bc", + "manifests/server/pg_hba_rule.pp": "f8e203ac4ea5a18ba065a47c8410f165", + "manifests/server/plperl.pp": "d6a2e2f0c93c7b543e9db64202c2e48d", + "manifests/server/postgis.pp": "4c2de08074b473a6fc6d240a3def1d03", + "manifests/server/reload.pp": "a5310d0d598c7e4c90926524bed19133", + "manifests/server/role.pp": "4e549d109b433e398a91fff402f0bff3", + "manifests/server/service.pp": "100d9026722ee7e1a1f97c3139125b6e", + "manifests/server/table_grant.pp": "7fbf5eafa4e5191b93195b07ef839bf9", + "manifests/server/tablespace.pp": "a7aca8e596fd05817957b8bb73e29336", + "manifests/server.pp": "2cbebd09a1a607fe93794534c65a49af", + "manifests/validate_db_connection.pp": "b0056a7dd2fa7a8fad3d7b7aab3a77fd", + "metadata.json": "0b22f02f59d02cb514a5222555161254", + "spec/acceptance/client_spec.rb": "5fc4174c010624f52d57ee9cefb44da7", + "spec/acceptance/common_patterns_spec.rb": "994e6117a54cbe96402a4c98686c3929", + "spec/acceptance/contrib_spec.rb": "efeaff22f5119067e0d0150edd9630e8", + "spec/acceptance/lib/devel_spec.rb": "8ff9ed46602e54b173dcd144394dddd4", + "spec/acceptance/lib/java_spec.rb": "46cec8b3fbc4a14172a4445ba351f0f8", + "spec/acceptance/lib/perl_spec.rb": "7ddfba081b4edfc68a2e72e04872578e", + "spec/acceptance/lib/python_spec.rb": "2b311428d59918c8a0bd11b0188e6af5", + "spec/acceptance/nodesets/centos-510-x64.yml": "5698f7e61292730c603e03f64fe19359", + "spec/acceptance/nodesets/centos-59-x64.yml": "57eb3e471b9042a8ea40978c467f8151", + "spec/acceptance/nodesets/centos-64-x64-pe.yml": "ec075d95760df3d4702abea1ce0a829b", + "spec/acceptance/nodesets/centos-64-x64.yml": "9cde7b5d2ab6a42366d2344c264d6bdc", + "spec/acceptance/nodesets/debian-607-x64.yml": "d566bf76f534e2af7c9a4605316d232c", + "spec/acceptance/nodesets/debian-73-x64.yml": "ba5a75e27644c48ad8f3564fb6d372a7", + "spec/acceptance/nodesets/default.yml": "9cde7b5d2ab6a42366d2344c264d6bdc", + "spec/acceptance/nodesets/ubuntu-server-10044-x64.yml": "dc0da2d2449f66c8fdae16593811504f", + "spec/acceptance/nodesets/ubuntu-server-12042-x64.yml": "78a3ee42652e26119d90aa62586565b2", + "spec/acceptance/nodesets/ubuntu-server-1404-x64.yml": "ea006afd1329a2d7a8e35d8287ec1658", + "spec/acceptance/postgresql_psql_spec.rb": "2726a4198506325845281f9540a45711", + "spec/acceptance/server/config_entry_spec.rb": "3b90a260d01ff028131d4f6eabdb354a", + "spec/acceptance/server/database_grant_spec.rb": "57fa17960e79b2d6641e148b0ad416de", + "spec/acceptance/server/database_spec.rb": "1eef2cd0b81e01b21a1cc5a715b67a14", + "spec/acceptance/server/db_spec.rb": "80b9bff843a7b5bc8b904583b6c6e99e", + "spec/acceptance/server/grant_spec.rb": "e920c215c4936f2a70ee6bcb6fed5220", + "spec/acceptance/server/pg_hba_rule_spec.rb": "12809f3d42e6c86e2de9583fab908ede", + "spec/acceptance/server/plperl_spec.rb": "c8d175b8235d0c61377866746d2d0186", + "spec/acceptance/server/role_spec.rb": "a3cd31e4245670286c1811f85ca6b266", + "spec/acceptance/server/table_grant_spec.rb": "c7f824d83745f2bcc865e69726f1db92", + "spec/acceptance/server/tablespace_spec.rb": "b367d810e2fbc219adb8bdc46dd8b99b", + "spec/acceptance/server_spec.rb": "9120d47137bccb2cd9aafa266374ed45", + "spec/acceptance/unsupported_spec.rb": "fc8f86f0843fabe29f880514db5be44d", + "spec/acceptance/validate_db_connection_spec.rb": "de172901777cb6973225348fb26a4f51", + "spec/spec_helper.rb": "6f3771a469c9ca3b050d12c19839f442", + "spec/spec_helper_acceptance.rb": "7bca3db19a6bb47a06f537914059f2ae", + "spec/unit/classes/client_spec.rb": "b26438da8906e68d17e568252c1e43b5", + "spec/unit/classes/globals_spec.rb": "acaa1c14215ce4f0b76222b9d344c379", + "spec/unit/classes/lib/devel_spec.rb": "11a2a75953d63a34b3e9ab7b6be2cc69", + "spec/unit/classes/lib/java_spec.rb": "bdb60c3b379a3788b3bf1f6c29b31c0a", + "spec/unit/classes/lib/perl_spec.rb": "efb82d3c12bbaf772a37704cb571ada5", + "spec/unit/classes/lib/python_spec.rb": "677c763c1a43a0e33ef7e6e819ec9f0a", + "spec/unit/classes/params_spec.rb": "2db946aa96446a1e38991c843b765323", + "spec/unit/classes/repo_spec.rb": "bc7e37bf56d82b0bd080391aa46c7f1d", + "spec/unit/classes/server/contrib_spec.rb": "97d0e01330e19b9657d51efb182a299f", + "spec/unit/classes/server/initdb_spec.rb": "a13e8ab43144ba4387f5fa1e14fc6552", + "spec/unit/classes/server/plperl_spec.rb": "48426f8a532690ce6ff758526b428085", + "spec/unit/classes/server/postgis_spec.rb": "b8041550a224a771c6f1c93e34f1bf87", + "spec/unit/classes/server_spec.rb": "1fef0fa2b0ee6e5b203a224dc7cc0526", + "spec/unit/defines/server/config_entry_spec.rb": "e797f48e9774031e86fdfc9687d39040", + "spec/unit/defines/server/database_grant_spec.rb": "2418cfbdb1e913f96a5ec7997526f9bf", + "spec/unit/defines/server/database_spec.rb": "cd8318b25bf775d3540fec1198efa73c", + "spec/unit/defines/server/db_spec.rb": "53ff276aca01971b0357c3cabb2e0525", + "spec/unit/defines/server/grant_spec.rb": "6d3926ed814834268defedf671d4ac96", + "spec/unit/defines/server/pg_hba_rule_spec.rb": "fd5926d6218998d96051179ae31e7d68", + "spec/unit/defines/server/role_spec.rb": "1a8d25c409b210333a50c6cedd2409c7", + "spec/unit/defines/server/table_grant_spec.rb": "ea304aedc93d7801329181f926d8106a", + "spec/unit/defines/server/tablespace_spec.rb": "e087c175bbd7ac0c2ff0ff2076db99d5", + "spec/unit/defines/validate_db_connection_spec.rb": "88e57a8f780d381d75fe062f1178e1ce", + "spec/unit/functions/postgresql_acls_to_resources_hash_spec.rb": "e7740c3cd2110e2fcebab8356012267c", + "spec/unit/functions/postgresql_escape_spec.rb": "6e52e4f3ca56491f8ba2d1490a5fd1ad", + "spec/unit/functions/postgresql_password_spec.rb": "76034569a5ff627073c5e6ff69176ac3", + "spec/unit/provider/postgresql_conf/parsed_spec.rb": "45cbf8783706d85005bd69ebfc013c09", + "spec/unit/puppet/provider/postgresql_psql/ruby_spec.rb": "c0ef725e4e7a5def13c29fad0c42478c", + "spec/unit/puppet/type/postgresql_psql_spec.rb": "2af5b74f7f4b89ff246818cd79488b3e", + "spec/unit/type/postgresql_conf_spec.rb": "5a05106ce1fe78a40d6ff620a75c08e9", + "templates/pg_hba_rule.conf": "13b46eecdfd359eddff71fa485ef2f54", + "templates/systemd-port-override.erb": "9af958cfc89251d8b32dd27050f58826" +} \ No newline at end of file diff --git a/modules/postgresql/lib/puppet/provider/postgresql_conf/parsed.rb b/modules/postgresql/lib/puppet/provider/postgresql_conf/parsed.rb index c9366b5..d53e6d2 100644 --- a/modules/postgresql/lib/puppet/provider/postgresql_conf/parsed.rb +++ b/modules/postgresql/lib/puppet/provider/postgresql_conf/parsed.rb @@ -18,7 +18,7 @@ Puppet::Type.type(:postgresql_conf).provide( :to_line => proc { |h| # simple string and numeric values don't need to be enclosed in quotes - dontneedquote = h[:value].match(/^(\w+|[0-9.-]+)$/) + dontneedquote = h[:value].match(/^(\w+)$/) dontneedequal = h[:name].match(/^(include|include_if_exists)$/i) str = h[:name].downcase # normalize case diff --git a/modules/postgresql/lib/puppet/provider/postgresql_psql/ruby.rb b/modules/postgresql/lib/puppet/provider/postgresql_psql/ruby.rb index b86fb62..d79f8f6 100644 --- a/modules/postgresql/lib/puppet/provider/postgresql_psql/ruby.rb +++ b/modules/postgresql/lib/puppet/provider/postgresql_psql/ruby.rb @@ -63,6 +63,7 @@ Puppet::Type.type(:postgresql_psql).provide(:ruby) do command = [resource[:psql_path]] command.push("-d", resource[:db]) if resource[:db] + command.push("-p", resource[:port]) if resource[:port] command.push("-t", "-c", sql) if resource[:cwd] diff --git a/modules/postgresql/lib/puppet/type/postgresql_psql.rb b/modules/postgresql/lib/puppet/type/postgresql_psql.rb index 8646914..70a056f 100644 --- a/modules/postgresql/lib/puppet/type/postgresql_psql.rb +++ b/modules/postgresql/lib/puppet/type/postgresql_psql.rb @@ -49,6 +49,10 @@ Puppet::Type.newtype(:postgresql_psql) do desc "The name of the database to execute the SQL command against." end + newparam(:port) do + desc "The port of the database server to execute the SQL command against." + end + newparam(:search_path) do desc "The schema search path to use when executing the SQL command" end diff --git a/modules/postgresql/manifests/globals.pp b/modules/postgresql/manifests/globals.pp index ebc117d..21e18a1 100644 --- a/modules/postgresql/manifests/globals.pp +++ b/modules/postgresql/manifests/globals.pp @@ -8,8 +8,10 @@ class postgresql::globals ( $contrib_package_name = undef, $devel_package_name = undef, $java_package_name = undef, + $perl_package_name = undef, $plperl_package_name = undef, $python_package_name = undef, + $postgis_package_name = undef, $service_name = undef, $service_provider = undef, @@ -33,6 +35,7 @@ class postgresql::globals ( $group = undef, $version = undef, + $postgis_version = undef, $needs_initdb = undef, @@ -56,6 +59,7 @@ class postgresql::globals ( }, 'Amazon' => '9.2', default => $::operatingsystemrelease ? { + /^7\./ => '9.2', /^6\./ => '8.4', /^5\./ => '8.1', default => undef, @@ -83,12 +87,28 @@ class postgresql::globals ( default => undef, } $globals_version = pick($version, $default_version, 'unknown') + if($globals_version == 'unknown') { + fail('No preferred version defined or automatically detected.') + } + + $default_postgis_version = $globals_version ? { + '8.1' => '1.3.6', + '8.4' => '1.5', + '9.0' => '1.5', + '9.1' => '1.5', + '9.2' => '2.0', + '9.3' => '2.1', + } + $globals_postgis_version = pick($postgis_version, $default_postgis_version) # Setup of the repo only makes sense globally, so we are doing this here. if($manage_package_repo) { - class { 'postgresql::repo': - ensure => $ensure, - version => $globals_version + # Workaround the lack of RHEL7 repositories for now. + if ! ($::operatingsystem == 'RedHat' and $::operatingsystemrelease =~ /^7/) { + class { 'postgresql::repo': + ensure => $ensure, + version => $globals_version + } } } } diff --git a/modules/postgresql/manifests/lib/perl.pp b/modules/postgresql/manifests/lib/perl.pp new file mode 100644 index 0000000..6ed2853 --- /dev/null +++ b/modules/postgresql/manifests/lib/perl.pp @@ -0,0 +1,13 @@ +# This class installs the perl libs for postgresql. See README.md for more +# details. +class postgresql::lib::perl( + $package_name = $postgresql::params::perl_package_name, + $package_ensure = 'present' +) inherits postgresql::params { + + package { 'perl-DBD-Pg': + ensure => $package_ensure, + name => $package_name, + } + +} diff --git a/modules/postgresql/manifests/params.pp b/modules/postgresql/manifests/params.pp index 4f5ae97..36e3027 100644 --- a/modules/postgresql/manifests/params.pp +++ b/modules/postgresql/manifests/params.pp @@ -1,16 +1,17 @@ # PRIVATE CLASS: do not use directly class postgresql::params inherits postgresql::globals { - $ensure = true + $ensure = present $version = $globals_version + $postgis_version = $globals_postgis_version $listen_addresses = 'localhost' + $port = 5432 $ip_mask_deny_postgres_user = '0.0.0.0/0' $ip_mask_allow_all_users = '127.0.0.1/32' $ipv4acls = [] $ipv6acls = [] - $user = pick($user, 'postgres') - $group = pick($group, 'postgres') $encoding = $encoding $locale = $locale + $service_ensure = undef $service_provider = $service_provider $manage_firewall = $manage_firewall $manage_pg_hba_conf = pick($manage_pg_hba_conf, true) @@ -18,8 +19,12 @@ class postgresql::params inherits postgresql::globals { # Amazon Linux's OS Family is 'Linux', operating system 'Amazon'. case $::osfamily { 'RedHat', 'Linux': { + $user = pick($user, 'postgres') + $group = pick($group, 'postgres') $needs_initdb = pick($needs_initdb, true) $firewall_supported = pick($firewall_supported, true) + $version_parts = split($version, '[.]') + $package_version = "${version_parts[0]}${version_parts[1]}" if $version == $default_version { $client_package_name = pick($client_package_name, 'postgresql') @@ -36,8 +41,6 @@ class postgresql::params inherits postgresql::globals { } $confdir = pick($confdir, $datadir) } else { - $version_parts = split($version, '[.]') - $package_version = "${version_parts[0]}${version_parts[1]}" $client_package_name = pick($client_package_name, "postgresql${package_version}") $server_package_name = pick($server_package_name, "postgresql${package_version}-server") $contrib_package_name = pick($contrib_package_name,"postgresql${package_version}-contrib") @@ -55,7 +58,18 @@ class postgresql::params inherits postgresql::globals { $psql_path = pick($psql_path, "${bindir}/psql") $service_status = $service_status + $perl_package_name = pick($perl_package_name, 'perl-DBD-Pg') $python_package_name = pick($python_package_name, 'python-psycopg2') + + $postgis_package_name = pick( + $postgis_package_name, + $::operatingsystemrelease ? { + /5/ => 'postgis', + default => versioncmp($postgis_version, '2') ? { + '-1' => "postgis${package_version}", + default => "postgis2_${package_version}",} + } + ) } 'Archlinux': { @@ -64,6 +78,8 @@ class postgresql::params inherits postgresql::globals { # so they can set it themself $firewall_supported = pick($firewall_supported, true) $needs_initdb = pick($needs_initdb, true) + $user = pick($user, 'postgres') + $group = pick($group, 'postgres') # Archlinux doesn't have a client-package but has a libs package which # pulls in postgresql server @@ -84,9 +100,13 @@ class postgresql::params inherits postgresql::globals { $service_status = $service_status $python_package_name = pick($python_package_name, 'python-psycopg2') + # Archlinux does not have a perl::DBD::Pg package + $perl_package_name = pick($perl_package_name, 'undef') } 'Debian': { + $user = pick($user, 'postgres') + $group = pick($group, 'postgres') if $manage_package_repo == true { $needs_initdb = pick($needs_initdb, true) @@ -106,8 +126,16 @@ class postgresql::params inherits postgresql::globals { $client_package_name = pick($client_package_name, "postgresql-client-${version}") $server_package_name = pick($server_package_name, "postgresql-${version}") $contrib_package_name = pick($contrib_package_name, "postgresql-contrib-${version}") + $postgis_package_name = pick( + $postgis_package_name, + versioncmp($postgis_version, '2') ? { + '-1' => "postgresql-${version}-postgis", + default => "postgresql-${version}-postgis-${postgis_version}", + } + ) $devel_package_name = pick($devel_package_name, 'libpq-dev') $java_package_name = pick($java_package_name, 'libpostgresql-jdbc-java') + $perl_package_name = pick($perl_package_name, 'libdbd-pg-perl') $plperl_package_name = pick($plperl_package_name, "postgresql-plperl-${version}") $python_package_name = pick($python_package_name, 'python-psycopg2') @@ -121,19 +149,22 @@ class postgresql::params inherits postgresql::globals { } 'FreeBSD': { + $user = pick($user, 'pgsql') + $group = pick($group, 'pgsql') $client_package_name = pick($client_package_name, "databases/postgresql${version}-client") $server_package_name = pick($server_package_name, "databases/postgresql${version}-server") $contrib_package_name = pick($contrib_package_name, "databases/postgresql${version}-contrib") $devel_package_name = pick($devel_package_name, 'databases/postgresql-libpqxx3') $java_package_name = pick($java_package_name, 'databases/postgresql-jdbc') + $perl_package_name = pick($plperl_package_name, 'databases/p5-DBD-Pg') $plperl_package_name = pick($plperl_package_name, "databases/postgresql${version}-plperl") $python_package_name = pick($python_package_name, 'databases/py-psycopg2') $service_name = pick($service_name, 'postgresql') $bindir = pick($bindir, '/usr/local/bin') $datadir = pick($datadir, '/usr/local/pgsql/data') - $confdir = pick($confdir, '/usr/local/share/postgresql') + $confdir = pick($confdir, $datadir) $service_status = pick($service_status, "/usr/local/etc/rc.d/${service_name} status") $psql_path = pick($psql_path, "${bindir}/psql") @@ -168,8 +199,4 @@ class postgresql::params inherits postgresql::globals { $pg_hba_conf_defaults = pick($pg_hba_conf_defaults, true) $postgresql_conf_path = pick($postgresql_conf_path, "${confdir}/postgresql.conf") $default_database = pick($default_database, 'postgres') - - if($version == 'unknown') { - fail('No preferred version defined or automatically detected.') - } } diff --git a/modules/postgresql/manifests/repo/apt_postgresql_org.pp b/modules/postgresql/manifests/repo/apt_postgresql_org.pp index 610eb47..25cb148 100644 --- a/modules/postgresql/manifests/repo/apt_postgresql_org.pp +++ b/modules/postgresql/manifests/repo/apt_postgresql_org.pp @@ -1,5 +1,6 @@ # PRIVATE CLASS: do not use directly class postgresql::repo::apt_postgresql_org inherits postgresql::repo { +include ::apt if($ensure == 'present' or $ensure == true) { # Here we have tried to replicate the instructions on the PostgreSQL site: # diff --git a/modules/postgresql/manifests/server.pp b/modules/postgresql/manifests/server.pp index 4d5baab..53c0885 100644 --- a/modules/postgresql/manifests/server.pp +++ b/modules/postgresql/manifests/server.pp @@ -2,8 +2,6 @@ class postgresql::server ( $ensure = $postgresql::params::ensure, - $version = $postgresql::params::version, - $postgres_password = undef, $package_name = $postgresql::params::server_package_name, @@ -12,12 +10,14 @@ class postgresql::server ( $plperl_package_name = $postgresql::params::plperl_package_name, + $service_ensure = $postgresql::params::service_ensure, $service_name = $postgresql::params::service_name, $service_provider = $postgresql::params::service_provider, $service_status = $postgresql::params::service_status, $default_database = $postgresql::params::default_database, $listen_addresses = $postgresql::params::listen_addresses, + $port = $postgresql::params::port, $ip_mask_deny_postgres_user = $postgresql::params::ip_mask_deny_postgres_user, $ip_mask_allow_all_users = $postgresql::params::ip_mask_allow_all_users, $ipv4acls = $postgresql::params::ipv4acls, @@ -44,10 +44,20 @@ class postgresql::server ( $manage_firewall = $postgresql::params::manage_firewall, $manage_pg_hba_conf = $postgresql::params::manage_pg_hba_conf, - $firewall_supported = $postgresql::params::firewall_supported + $firewall_supported = $postgresql::params::firewall_supported, + + #Deprecated + $version = $postgresql::params::version, ) inherits postgresql::params { $pg = 'postgresql::server' + if $version != undef { + warning('Passing "version" to postgresql::server is deprecated; please use postgresql::globals instead.') + $_version = $postgresql::params::version + } else { + $_version = $version + } + if ($ensure == 'present' or $ensure == true) { # Reload has its own ordering, specified by other defines class { "${pg}::reload": require => Class["${pg}::install"] } diff --git a/modules/postgresql/manifests/server/config.pp b/modules/postgresql/manifests/server/config.pp index a59f179..eaf599f 100644 --- a/modules/postgresql/manifests/server/config.pp +++ b/modules/postgresql/manifests/server/config.pp @@ -4,6 +4,7 @@ class postgresql::server::config { $ip_mask_deny_postgres_user = $postgresql::server::ip_mask_deny_postgres_user $ip_mask_allow_all_users = $postgresql::server::ip_mask_allow_all_users $listen_addresses = $postgresql::server::listen_addresses + $port = $postgresql::server::port $ipv4acls = $postgresql::server::ipv4acls $ipv6acls = $postgresql::server::ipv6acls $pg_hba_conf_path = $postgresql::server::pg_hba_conf_path @@ -19,7 +20,7 @@ class postgresql::server::config { if ($manage_pg_hba_conf == true) { # Prepare the main pg_hba file concat { $pg_hba_conf_path: - owner => 0, + owner => $user, group => $group, mode => '0640', warn => true, @@ -97,6 +98,18 @@ class postgresql::server::config { postgresql::server::config_entry { 'listen_addresses': value => $listen_addresses, } + postgresql::server::config_entry { 'port': + value => "${port}", + } + + # RedHat-based systems hardcode some PG* variables in the init script, and need to be overriden + # in /etc/sysconfig/pgsql/postgresql. Create a blank file so we can manage it with augeas later. + if ($::osfamily == 'RedHat') and ($::operatingsystemrelease !~ /^7/) { + file { '/etc/sysconfig/pgsql/postgresql': + ensure => present, + replace => false, + } + } } else { file { $pg_hba_conf_path: ensure => absent, diff --git a/modules/postgresql/manifests/server/config_entry.pp b/modules/postgresql/manifests/server/config_entry.pp index c6cd1a7..a730f60 100644 --- a/modules/postgresql/manifests/server/config_entry.pp +++ b/modules/postgresql/manifests/server/config_entry.pp @@ -11,6 +11,10 @@ define postgresql::server::config_entry ( default => $path, } + Exec { + logoutput => 'on_failure', + } + case $name { /data_directory|hba_file|ident_file|include|listen_addresses|port|max_connections|superuser_reserved_connections|unix_socket_directory|unix_socket_group|unix_socket_permissions|bonjour|bonjour_name|ssl|ssl_ciphers|shared_buffers|max_prepared_transactions|max_files_per_process|shared_preload_libraries|wal_level|wal_buffers|archive_mode|max_wal_senders|hot_standby|logging_collector|silent_mode|track_activity_query_size|autovacuum_max_workers|autovacuum_freeze_max_age|max_locks_per_transaction|max_pred_locks_per_transaction|restart_after_crash|lc_messages|lc_monetary|lc_numeric|lc_time/: { Postgresql_conf { @@ -26,6 +30,52 @@ define postgresql::server::config_entry ( } } + # We have to handle ports in a weird and special way. On Redhat we either + # have to create a systemd override for the port or update the sysconfig + # file. + if $::osfamily == 'RedHat' { + if $::operatingsystemrelease =~ /^7/ { + if $name == 'port' { + file { 'systemd-port-override': + ensure => present, + path => '/etc/systemd/system/postgresql.service', + owner => root, + group => root, + content => template('postgresql/systemd-port-override.erb'), + notify => [ Exec['restart-systemd'], Class['postgresql::server::service'] ], + before => Class['postgresql::server::reload'], + } + exec { 'restart-systemd': + command => 'systemctl daemon-reload', + refreshonly => true, + path => '/bin:/usr/bin:/usr/local/bin' + } + } + } else { + if $name == 'port' { + # We need to force postgresql to stop before updating the port + # because puppet becomes confused and is unable to manage the + # service appropriately. + exec { 'postgresql_stop': + command => "service ${::postgresql::server::service_name} stop", + onlyif => "service ${::postgresql::server::service_name} status", + unless => "grep 'PGPORT=${value}' /etc/sysconfig/pgsql/postgresql", + path => '/sbin:/bin:/usr/bin:/usr/local/bin', + require => File['/etc/sysconfig/pgsql/postgresql'], + } -> + augeas { 'override PGPORT in /etc/sysconfig/pgsql/postgresql': + lens => 'Shellvars.lns', + incl => '/etc/sysconfig/pgsql/*', + context => '/files/etc/sysconfig/pgsql/postgresql', + changes => "set PGPORT ${value}", + require => File['/etc/sysconfig/pgsql/postgresql'], + notify => Class['postgresql::server::service'], + before => Class['postgresql::server::reload'], + } + } + } + } + case $ensure { /present|absent/: { postgresql_conf { $name: diff --git a/modules/postgresql/manifests/server/database.pp b/modules/postgresql/manifests/server/database.pp index adeabde..1a5e620 100644 --- a/modules/postgresql/manifests/server/database.pp +++ b/modules/postgresql/manifests/server/database.pp @@ -12,13 +12,16 @@ define postgresql::server::database( $user = $postgresql::server::user $group = $postgresql::server::group $psql_path = $postgresql::server::psql_path + $port = $postgresql::server::port $version = $postgresql::server::version + $default_db = $postgresql::server::default_database # Set the defaults for the postgresql_psql resource Postgresql_psql { psql_user => $user, psql_group => $group, psql_path => $psql_path, + port => $port, } # Optionally set the locale switch. Older versions of createdb may not accept @@ -44,11 +47,13 @@ define postgresql::server::database( default => "--tablespace='${tablespace}' ", } - $createdb_command = "${createdb_path} --owner='${owner}' --template=${template} ${encoding_option}${locale_option}${tablespace_option} '${dbname}'" + $createdb_command = "${createdb_path} --port='${port}' --owner='${owner}' --template=${template} ${encoding_option}${locale_option}${tablespace_option} '${dbname}'" postgresql_psql { "Check for existence of db '${dbname}'": command => 'SELECT 1', unless => "SELECT datname FROM pg_database WHERE datname='${dbname}'", + db => $default_db, + port => $port, require => Class['postgresql::server::service'] }~> exec { $createdb_command : @@ -60,13 +65,15 @@ define postgresql::server::database( # This will prevent users from connecting to the database unless they've been # granted privileges. postgresql_psql {"REVOKE ${public_revoke_privilege} ON DATABASE \"${dbname}\" FROM public": - db => $user, + db => $default_db, + port => $port, refreshonly => true, } Exec [ $createdb_command ]-> postgresql_psql {"UPDATE pg_database SET datistemplate = ${istemplate} WHERE datname = '${dbname}'": unless => "SELECT datname FROM pg_database WHERE datname = '${dbname}' AND datistemplate = ${istemplate}", + db => $default_db, } # Build up dependencies on tablespace diff --git a/modules/postgresql/manifests/server/db.pp b/modules/postgresql/manifests/server/db.pp index 5cc7954..afafc41 100644 --- a/modules/postgresql/manifests/server/db.pp +++ b/modules/postgresql/manifests/server/db.pp @@ -3,6 +3,7 @@ define postgresql::server::db ( $user, $password, + $dbname = $title, $encoding = $postgresql::server::encoding, $locale = $postgresql::server::locale, $grant = 'ALL', @@ -11,13 +12,16 @@ define postgresql::server::db ( $istemplate = false, $owner = undef ) { - postgresql::server::database { $name: - encoding => $encoding, - tablespace => $tablespace, - template => $template, - locale => $locale, - istemplate => $istemplate, - owner => $owner, + + if ! defined(Postgresql::Server::Database[$dbname]) { + postgresql::server::database { $dbname: + encoding => $encoding, + tablespace => $tablespace, + template => $template, + locale => $locale, + istemplate => $istemplate, + owner => $owner, + } } if ! defined(Postgresql::Server::Role[$user]) { @@ -26,10 +30,12 @@ define postgresql::server::db ( } } - postgresql::server::database_grant { "GRANT ${user} - ${grant} - ${name}": - privilege => $grant, - db => $name, - role => $user, + if ! defined(Postgresql::Server::Database_grant["GRANT ${user} - ${grant} - ${dbname}"]) { + postgresql::server::database_grant { "GRANT ${user} - ${grant} - ${dbname}": + privilege => $grant, + db => $dbname, + role => $user, + } } if($tablespace != undef and defined(Postgresql::Server::Tablespace[$tablespace])) { diff --git a/modules/postgresql/manifests/server/firewall.pp b/modules/postgresql/manifests/server/firewall.pp index afe3797..4564048 100644 --- a/modules/postgresql/manifests/server/firewall.pp +++ b/modules/postgresql/manifests/server/firewall.pp @@ -3,17 +3,17 @@ class postgresql::server::firewall { $ensure = $postgresql::server::ensure $manage_firewall = $postgresql::server::manage_firewall $firewall_supported = $postgresql::server::firewall_supported + $port = $postgresql::server::port if ($manage_firewall and $firewall_supported) { if ($ensure == 'present' or $ensure == true) { - # TODO: get rid of hard-coded port - firewall { '5432 accept - postgres': - port => '5432', + firewall { "$port accept - postgres": + port => $port, proto => 'tcp', action => 'accept', } } else { - firewall { '5432 accept - postgres': + firewall { "$port accept - postgres": ensure => absent, } } diff --git a/modules/postgresql/manifests/server/grant.pp b/modules/postgresql/manifests/server/grant.pp index d24130c..54bf8c7 100644 --- a/modules/postgresql/manifests/server/grant.pp +++ b/modules/postgresql/manifests/server/grant.pp @@ -5,8 +5,9 @@ define postgresql::server::grant ( $privilege = undef, $object_type = 'database', $object_name = $db, - $psql_db = $postgresql::server::user, - $psql_user = $postgresql::server::user + $psql_db = $postgresql::server::default_database, + $psql_user = $postgresql::server::user, + $port = $postgresql::server::port ) { $group = $postgresql::server::group $psql_path = $postgresql::server::psql_path @@ -68,6 +69,7 @@ define postgresql::server::grant ( $grant_cmd = "GRANT ${_privilege} ON ${_object_type} \"${object_name}\" TO \"${role}\"" postgresql_psql { $grant_cmd: db => $on_db, + port => $port, psql_user => $psql_user, psql_group => $group, psql_path => $psql_path, diff --git a/modules/postgresql/manifests/server/install.pp b/modules/postgresql/manifests/server/install.pp index 1e912a8..1ac55f4 100644 --- a/modules/postgresql/manifests/server/install.pp +++ b/modules/postgresql/manifests/server/install.pp @@ -23,7 +23,7 @@ class postgresql::server::install { # This will clean up anything we miss exec { 'apt-get-autoremove-postgresql-client-brute': - command => "dpkg -P postgresql*", + command => 'dpkg -P postgresql*', onlyif => "dpkg -l postgresql* | grep -e '^ii'", logoutput => on_failure, path => '/usr/bin:/bin:/usr/sbin/:/sbin', diff --git a/modules/postgresql/manifests/server/postgis.pp b/modules/postgresql/manifests/server/postgis.pp new file mode 100644 index 0000000..cb0c2ac --- /dev/null +++ b/modules/postgresql/manifests/server/postgis.pp @@ -0,0 +1,32 @@ +# Install the postgis postgresql packaging. See README.md for more details. +class postgresql::server::postgis ( + $package_name = $postgresql::params::postgis_package_name, + $package_ensure = 'present' +) inherits postgresql::params { + validate_string($package_name) + + package { 'postgresql-postgis': + ensure => $package_ensure, + name => $package_name, + tag => 'postgresql', + } + + if($package_ensure == 'present' or $package_ensure == true) { + anchor { 'postgresql::server::postgis::start': }-> + Class['postgresql::server::install']-> + Package['postgresql-postgis']-> + Class['postgresql::server::service']-> + anchor { 'postgresql::server::postgis::end': } + + if $postgresql::globals::manage_package_repo { + Class['postgresql::repo'] -> + Package['postgresql-postgis'] + } + } else { + anchor { 'postgresql::server::postgis::start': }-> + Class['postgresql::server::service']-> + Package['postgresql-postgis']-> + Class['postgresql::server::install']-> + anchor { 'postgresql::server::postgis::end': } + } +} diff --git a/modules/postgresql/manifests/server/reload.pp b/modules/postgresql/manifests/server/reload.pp index 6b11ebf..8da5d20 100644 --- a/modules/postgresql/manifests/server/reload.pp +++ b/modules/postgresql/manifests/server/reload.pp @@ -10,6 +10,7 @@ class postgresql::server::reload { command => "service ${service_name} reload", onlyif => $service_status, refreshonly => true, + require => Class['postgresql::server::service'], } } } diff --git a/modules/postgresql/manifests/server/role.pp b/modules/postgresql/manifests/server/role.pp index 971191f..8150413 100644 --- a/modules/postgresql/manifests/server/role.pp +++ b/modules/postgresql/manifests/server/role.pp @@ -3,8 +3,10 @@ define postgresql::server::role( $password_hash = false, $createdb = false, $createrole = false, - $db = $postgresql::server::user, + $db = $postgresql::server::default_database, + $port = $postgresql::server::port, $login = true, + $inherit = true, $superuser = false, $replication = false, $connection_limit = '-1', @@ -16,6 +18,7 @@ define postgresql::server::role( $version = $postgresql::server::version $login_sql = $login ? { true => 'LOGIN', default => 'NOLOGIN' } + $inherit_sql = $inherit ? { true => 'INHERIT', default => 'NOINHERIT' } $createrole_sql = $createrole ? { true => 'CREATEROLE', default => 'NOCREATEROLE' } $createdb_sql = $createdb ? { true => 'CREATEDB', default => 'NOCREATEDB' } $superuser_sql = $superuser ? { true => 'SUPERUSER', default => 'NOSUPERUSER' } @@ -28,6 +31,7 @@ define postgresql::server::role( Postgresql_psql { db => $db, + port => $port, psql_user => $psql_user, psql_group => $psql_group, psql_path => $psql_path, @@ -55,6 +59,10 @@ define postgresql::server::role( unless => "SELECT rolname FROM pg_roles WHERE rolname='${username}' and rolcanlogin=${login}", } + postgresql_psql {"ALTER ROLE \"${username}\" ${inherit_sql}": + unless => "SELECT rolname FROM pg_roles WHERE rolname='${username}' and rolinherit=${inherit}", + } + if(versioncmp($version, '9.1') >= 0) { if $replication_sql == '' { postgresql_psql {"ALTER ROLE \"${username}\" NOREPLICATION": diff --git a/modules/postgresql/manifests/server/service.pp b/modules/postgresql/manifests/server/service.pp index 598acd8..d4c81db 100644 --- a/modules/postgresql/manifests/server/service.pp +++ b/modules/postgresql/manifests/server/service.pp @@ -1,13 +1,25 @@ # PRIVATE CLASS: do not call directly class postgresql::server::service { $ensure = $postgresql::server::ensure + $service_ensure = $postgresql::server::service_ensure $service_name = $postgresql::server::service_name $service_provider = $postgresql::server::service_provider $service_status = $postgresql::server::service_status $user = $postgresql::server::user + $port = $postgresql::server::port $default_database = $postgresql::server::default_database - $service_ensure = $ensure ? { + if $service_ensure { + $real_service_ensure = $service_ensure + } else { + $real_service_ensure = $ensure ? { + present => 'running', + absent => 'stopped', + default => $ensure + } + } + + $service_enable = $ensure ? { present => true, absent => false, default => $ensure @@ -16,15 +28,15 @@ class postgresql::server::service { anchor { 'postgresql::server::service::begin': } service { 'postgresqld': - ensure => $service_ensure, + ensure => $real_service_ensure, name => $service_name, - enable => $service_ensure, + enable => $service_enable, provider => $service_provider, hasstatus => true, status => $service_status, } - if($service_ensure) { + if $real_service_ensure == 'running' { # This blocks the class before continuing if chained correctly, making # sure the service really is 'up' before continuing. # @@ -33,6 +45,7 @@ class postgresql::server::service { postgresql::validate_db_connection { 'validate_service_is_running': run_as => $user, database_name => $default_database, + database_port => $port, sleep => 1, tries => 60, create_db_first => false, diff --git a/modules/postgresql/manifests/server/table_grant.pp b/modules/postgresql/manifests/server/table_grant.pp index 643416e..6a50f93 100644 --- a/modules/postgresql/manifests/server/table_grant.pp +++ b/modules/postgresql/manifests/server/table_grant.pp @@ -5,12 +5,14 @@ define postgresql::server::table_grant( $table, $db, $role, + $port = $postgresql::server::port, $psql_db = undef, $psql_user = undef ) { postgresql::server::grant { "table:${name}": role => $role, db => $db, + port => $port, privilege => $privilege, object_type => 'TABLE', object_name => $table, diff --git a/modules/postgresql/manifests/server/tablespace.pp b/modules/postgresql/manifests/server/tablespace.pp index 2252377..26ff4d5 100644 --- a/modules/postgresql/manifests/server/tablespace.pp +++ b/modules/postgresql/manifests/server/tablespace.pp @@ -6,12 +6,14 @@ define postgresql::server::tablespace( ) { $user = $postgresql::server::user $group = $postgresql::server::group + $port = $postgresql::server::port $psql_path = $postgresql::server::psql_path Postgresql_psql { psql_user => $user, psql_group => $group, psql_path => $psql_path, + port => $port, } if ($owner == undef) { diff --git a/modules/postgresql/manifests/validate_db_connection.pp b/modules/postgresql/manifests/validate_db_connection.pp index f70af1e..2514b44 100644 --- a/modules/postgresql/manifests/validate_db_connection.pp +++ b/modules/postgresql/manifests/validate_db_connection.pp @@ -55,7 +55,7 @@ define postgresql::validate_db_connection( environment => $env, logoutput => 'on_failure', user => $run_as, - path => '/bin', + path => '/bin:/usr/bin:/usr/local/bin', timeout => $timeout, require => Package['postgresql-client'], } diff --git a/modules/postgresql/metadata.json b/modules/postgresql/metadata.json index acea407..4321f7e 100644 --- a/modules/postgresql/metadata.json +++ b/modules/postgresql/metadata.json @@ -1,31 +1,43 @@ { + "name": "puppetlabs-postgresql", + "version": "3.4.0", + "author": "Inkling/Puppet Labs", + "summary": "PostgreSQL defined resource types", + "license": "ASL 2.0", + "source": "git://github.com/puppetlabs/puppet-postgresql.git", + "project_page": "https://github.com/puppetlabs/puppet-postgresql", + "issues_url": "https://github.com/puppetlabs/puppet-postgresql/issues", "operatingsystem_support": [ { "operatingsystem": "RedHat", "operatingsystemrelease": [ "5", - "6" + "6", + "7" ] }, { "operatingsystem": "CentOS", "operatingsystemrelease": [ "5", - "6" + "6", + "7" ] }, { "operatingsystem": "OracleLinux", "operatingsystemrelease": [ "5", - "6" + "6", + "7" ] }, { "operatingsystem": "Scientific", "operatingsystemrelease": [ "5", - "6" + "6", + "7" ] }, { @@ -39,28 +51,22 @@ "operatingsystem": "Ubuntu", "operatingsystemrelease": [ "10.04", - "12.04" + "12.04", + "14.04" ] } ], "requirements": [ { "name": "pe", - "version_requirement": "3.2.x" + "version_requirement": ">= 3.2.0 < 3.4.0" }, { "name": "puppet", "version_requirement": "3.x" } ], - "name": "puppetlabs-postgresql", - "version": "3.3.3", - "source": "git://github.com/puppetlabs/puppet-postgresql.git", - "author": "Inkling/Puppet Labs", - "license": "ASL 2.0", - "summary": "PostgreSQL defined resource types", "description": "PostgreSQL defined resource types", - "project_page": "https://github.com/puppetlabs/puppet-postgresql", "dependencies": [ { "name": "puppetlabs/stdlib", @@ -78,199 +84,5 @@ "name": "puppetlabs/concat", "version_requirement": ">= 1.0.0 <2.0.0" } - ], - "types": [ - { - "name": "postgresql_conf", - "doc": "This type allows puppet to manage postgresql.conf parameters.", - "properties": [ - { - "name": "ensure", - "doc": "The basic property that the resource should be in.\n\nValid values are `present`, `absent`. " - }, - { - "name": "value", - "doc": "The value to set for this parameter.\n\n" - }, - { - "name": "target", - "doc": "The path to postgresql.conf\n\n" - } - ], - "parameters": [ - { - "name": "name", - "doc": "The postgresql parameter name to manage.\n\nValues can match `/^[\\w\\.]+$/`." - } - ], - "providers": [ - { - "name": "parsed", - "doc": "Set key/values in postgresql.conf." - } - ] - }, - { - "name": "postgresql_psql", - "doc": "", - "properties": [ - { - "name": "command", - "doc": "The SQL command to execute via psql.\n\n" - } - ], - "parameters": [ - { - "name": "name", - "doc": "An arbitrary tag for your own reference; the name of the message.\n\n" - }, - { - "name": "unless", - "doc": "An optional SQL command to execute prior to the main :command; this is generally intended to be used for idempotency, to check for the existence of an object in the database to determine whether or not the main SQL command needs to be executed at all.\n\n" - }, - { - "name": "db", - "doc": "The name of the database to execute the SQL command against.\n\n" - }, - { - "name": "search_path", - "doc": "The schema search path to use when executing the SQL command\n\n" - }, - { - "name": "psql_path", - "doc": "The path to psql executable.\n\n" - }, - { - "name": "psql_user", - "doc": "The system user account under which the psql command should be executed.\n\n" - }, - { - "name": "psql_group", - "doc": "The system user group account under which the psql command should be executed.\n\n" - }, - { - "name": "cwd", - "doc": "The working directory under which the psql command should be executed.\n\n" - }, - { - "name": "refreshonly", - "doc": "If 'true', then the SQL will only be executed via a notify/subscribe event.\n\nValid values are `true`, `false`. " - } - ], - "providers": [ - { - "name": "ruby", - "doc": "" - } - ] - } - ], - "checksums": { - "CHANGELOG.md": "a6b64297ce9a85952614f7bdb57578fd", - "Gemfile": "1ef583c607af3c85f8a230989b1c41d9", - "Gemfile.lock": "cb6ae23a3082f56401a05db733102ef6", - "LICENSE": "746fe83ebbf8970af0a9ea13962293e9", - "Modulefile": "886e51cf26f9cd2e252b60772effff81", - "NOTICE": "d8ffc52f00e00877b45d2b77e709f69e", - "README.md": "1d43f087642b1b5252602fe73e10dc66", - "Rakefile": "c4f5c8ac6adc0eb4530c4b3d10cb7da0", - "files/RPM-GPG-KEY-PGDG": "78b5db170d33f80ad5a47863a7476b22", - "files/validate_postgresql_connection.sh": "20301932819f035492a30880f5bf335a", - "lib/puppet/parser/functions/postgresql_acls_to_resources_hash.rb": "d518a7959b950874820a3b0a7a324488", - "lib/puppet/parser/functions/postgresql_escape.rb": "2e136fcd653ab38d831c5b40806d47d1", - "lib/puppet/parser/functions/postgresql_password.rb": "820da02a888ab42357fe9bc2352b1c37", - "lib/puppet/provider/postgresql_conf/parsed.rb": "9c198422c0558faab1bedc00b68cfe45", - "lib/puppet/provider/postgresql_psql/ruby.rb": "8b651764452d8f410f72bd4bbc37d27d", - "lib/puppet/type/postgresql_conf.rb": "4f333138a3689f9768e7fe4bc3cde9fd", - "lib/puppet/type/postgresql_psql.rb": "2c1270cfed4ffb2971059846a57cf983", - "manifests/client.pp": "f9bc3a578017fe8eb881de2255bdc023", - "manifests/globals.pp": "f6840d296342f17b9e4717a8391d79f6", - "manifests/lib/devel.pp": "94ae7eac3acf1dd3072d481eca4d2d7f", - "manifests/lib/java.pp": "6e4a2187c2b4caecad8098b46e99c8e0", - "manifests/lib/python.pp": "90736f86301c4c6401ec1180c176b616", - "manifests/params.pp": "8a34fe2ef5cca6d0684bb304fa52776b", - "manifests/repo/apt_postgresql_org.pp": "ef7012ea3c5429bea11b1114183d32c3", - "manifests/repo/yum_postgresql_org.pp": "e0c445f877cdb39774b735417c967d1d", - "manifests/repo.pp": "a18a5cb760dbb1e10bdd83730300c1fe", - "manifests/server/config.pp": "ad4e6a3b034bf2975e47ac8df156eab4", - "manifests/server/config_entry.pp": "81ddbb8fe161d94394c7277a92cc6c1c", - "manifests/server/contrib.pp": "3112bd1edbed51b68e1402027f9d53b1", - "manifests/server/database.pp": "a6eacc4bf05c8ac12047dbf8aace7bc4", - "manifests/server/database_grant.pp": "66e5470bb932b087b540c444ee49941b", - "manifests/server/db.pp": "368ce3c55cb1685a7e9598a4760f0d1f", - "manifests/server/firewall.pp": "98632b073511a00926908c6951851052", - "manifests/server/grant.pp": "7f7fdf96611bfdd599dc382755c61c00", - "manifests/server/initdb.pp": "215f84f3d9931372190d9928832e315a", - "manifests/server/install.pp": "8520e3a86c74e0587a46c4548097bab3", - "manifests/server/passwd.pp": "197af62ecfcbd0982aa6563f2579d8bc", - "manifests/server/pg_hba_rule.pp": "f8e203ac4ea5a18ba065a47c8410f165", - "manifests/server/plperl.pp": "d6a2e2f0c93c7b543e9db64202c2e48d", - "manifests/server/reload.pp": "d62c048c8f25c167d266e99e36c0f227", - "manifests/server/role.pp": "a99508ee4e84034064c2cb1a121bdc62", - "manifests/server/service.pp": "8db858d1ea5b778cfef00e6ade81abb8", - "manifests/server/table_grant.pp": "bbc864f0ad8545837cf7782d1f7a1755", - "manifests/server/tablespace.pp": "beda12859757f7f677a711304dfd5185", - "manifests/server.pp": "aef6102d75512fab876195b4601e6fb3", - "manifests/validate_db_connection.pp": "be61434836f7d25cc184126a91b2e3e6", - "spec/acceptance/client_spec.rb": "5fc4174c010624f52d57ee9cefb44da7", - "spec/acceptance/common_patterns_spec.rb": "994e6117a54cbe96402a4c98686c3929", - "spec/acceptance/contrib_spec.rb": "efeaff22f5119067e0d0150edd9630e8", - "spec/acceptance/lib/devel_spec.rb": "8ff9ed46602e54b173dcd144394dddd4", - "spec/acceptance/lib/java_spec.rb": "46cec8b3fbc4a14172a4445ba351f0f8", - "spec/acceptance/lib/python_spec.rb": "2b311428d59918c8a0bd11b0188e6af5", - "spec/acceptance/nodesets/centos-510-x64.yml": "5698f7e61292730c603e03f64fe19359", - "spec/acceptance/nodesets/centos-59-x64.yml": "57eb3e471b9042a8ea40978c467f8151", - "spec/acceptance/nodesets/centos-64-x64-pe.yml": "ec075d95760df3d4702abea1ce0a829b", - "spec/acceptance/nodesets/centos-64-x64.yml": "9cde7b5d2ab6a42366d2344c264d6bdc", - "spec/acceptance/nodesets/debian-607-x64.yml": "d566bf76f534e2af7c9a4605316d232c", - "spec/acceptance/nodesets/debian-73-x64.yml": "ba5a75e27644c48ad8f3564fb6d372a7", - "spec/acceptance/nodesets/default.yml": "9cde7b5d2ab6a42366d2344c264d6bdc", - "spec/acceptance/nodesets/ubuntu-server-10044-x64.yml": "dc0da2d2449f66c8fdae16593811504f", - "spec/acceptance/nodesets/ubuntu-server-12042-x64.yml": "78a3ee42652e26119d90aa62586565b2", - "spec/acceptance/postgresql_psql_spec.rb": "2726a4198506325845281f9540a45711", - "spec/acceptance/server/config_entry_spec.rb": "acde0cb5edfb56b276273fd7b8c9e54c", - "spec/acceptance/server/database_grant_spec.rb": "57fa17960e79b2d6641e148b0ad416de", - "spec/acceptance/server/database_spec.rb": "d52e1743f93933e2b3ccb0536fa06ae3", - "spec/acceptance/server/db_spec.rb": "9389f1688296d38b2be1b3f82f3c47ef", - "spec/acceptance/server/grant_spec.rb": "e920c215c4936f2a70ee6bcb6fed5220", - "spec/acceptance/server/pg_hba_rule_spec.rb": "12809f3d42e6c86e2de9583fab908ede", - "spec/acceptance/server/plperl_spec.rb": "c8d175b8235d0c61377866746d2d0186", - "spec/acceptance/server/role_spec.rb": "a24f1c009013b55a1994eb713526f845", - "spec/acceptance/server/table_grant_spec.rb": "c7f824d83745f2bcc865e69726f1db92", - "spec/acceptance/server/tablespace_spec.rb": "b367d810e2fbc219adb8bdc46dd8b99b", - "spec/acceptance/server_spec.rb": "b49f58a9bce516f93479b5cd59fdf755", - "spec/acceptance/unsupported_spec.rb": "dc8c697053f34c0d1f33e31fb66adf5d", - "spec/acceptance/validate_db_connection_spec.rb": "19e271901a3d782e851805cee03b2b15", - "spec/spec_helper.rb": "d4e4a9a154ada34e7f13b0d8ece0f5db", - "spec/spec_helper_acceptance.rb": "a913cd3be2a7616f42b10eb403aee79b", - "spec/unit/classes/client_spec.rb": "b26438da8906e68d17e568252c1e43b5", - "spec/unit/classes/globals_spec.rb": "ca7fab47214c69fcf4fdbb1320cc4abf", - "spec/unit/classes/lib/devel_spec.rb": "11a2a75953d63a34b3e9ab7b6be2cc69", - "spec/unit/classes/lib/java_spec.rb": "bdb60c3b379a3788b3bf1f6c29b31c0a", - "spec/unit/classes/lib/python_spec.rb": "677c763c1a43a0e33ef7e6e819ec9f0a", - "spec/unit/classes/params_spec.rb": "2db946aa96446a1e38991c843b765323", - "spec/unit/classes/repo_spec.rb": "437ec024ca8d8cbd344671ccbb879a98", - "spec/unit/classes/server/contrib_spec.rb": "16528171ee3e058c06c5fea454dc9dbc", - "spec/unit/classes/server/initdb_spec.rb": "ece63fe900c3bcdb746e03a234b550c2", - "spec/unit/classes/server/plperl_spec.rb": "d00b94c70241848babaeee45143870ae", - "spec/unit/classes/server_spec.rb": "babaac49a4e5ae918f17328d86b72249", - "spec/unit/defines/server/config_entry_spec.rb": "cc2d9d0e4508d745f85c3446ccf76eb4", - "spec/unit/defines/server/database_grant_spec.rb": "e09254037c042efa5a29ba8d777c882f", - "spec/unit/defines/server/database_spec.rb": "090e9cf334843a4dc8b3f4eadce0109b", - "spec/unit/defines/server/db_spec.rb": "9f2181b0df771f4c6adf089b788adf42", - "spec/unit/defines/server/grant_spec.rb": "b8d8f46c7c4539747ee0b797a3a1834f", - "spec/unit/defines/server/pg_hba_rule_spec.rb": "3ed69d689bf28b56a030c543e7ce6775", - "spec/unit/defines/server/role_spec.rb": "fdb53fa637ccd79f8231e15383099137", - "spec/unit/defines/server/table_grant_spec.rb": "bb794a0b15dc74e8c8fa5d4878fd3c79", - "spec/unit/defines/server/tablespace_spec.rb": "68e7b9a193475491c58485debf1be220", - "spec/unit/defines/validate_db_connection_spec.rb": "88e57a8f780d381d75fe062f1178e1ce", - "spec/unit/functions/postgresql_acls_to_resources_hash_spec.rb": "e7740c3cd2110e2fcebab8356012267c", - "spec/unit/functions/postgresql_escape_spec.rb": "6e52e4f3ca56491f8ba2d1490a5fd1ad", - "spec/unit/functions/postgresql_password_spec.rb": "76034569a5ff627073c5e6ff69176ac3", - "spec/unit/provider/postgresql_conf/parsed_spec.rb": "7295501a413d8cf99df6f40ea50a36fc", - "spec/unit/puppet/provider/postgresql_psql/ruby_spec.rb": "c0ef725e4e7a5def13c29fad0c42478c", - "spec/unit/puppet/type/postgresql_psql_spec.rb": "2af5b74f7f4b89ff246818cd79488b3e", - "spec/unit/type/postgresql_conf_spec.rb": "76f460e0dfc90a1f38c407e5a0d4f463", - "templates/pg_hba_rule.conf": "13b46eecdfd359eddff71fa485ef2f54" - } -} \ No newline at end of file + ] +} diff --git a/modules/postgresql/spec/acceptance/lib/perl_spec.rb b/modules/postgresql/spec/acceptance/lib/perl_spec.rb new file mode 100644 index 0000000..f05b358 --- /dev/null +++ b/modules/postgresql/spec/acceptance/lib/perl_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper_acceptance' + +describe 'postgresql::lib::perl:', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do + after :all do + # Cleanup after tests have ran + apply_manifest("class { 'postgresql::lib::perl': package_ensure => purged }", :catch_failures => true) + end + + it 'test loading class with no parameters' do + pp = <<-EOS.unindent + class { 'postgresql::lib::perl': } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end +end diff --git a/modules/postgresql/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml b/modules/postgresql/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml new file mode 100644 index 0000000..7e789c8 --- /dev/null +++ b/modules/postgresql/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml @@ -0,0 +1,9 @@ +HOSTS: + ubuntu-server-1404-x64: + roles: + - master + platform: ubuntu-14.04-64 + box: puppetlabs/ubuntu-14.04-64-nocm + hypervisor : vagrant +CONFIG: + type: foss diff --git a/modules/postgresql/spec/acceptance/server/config_entry_spec.rb b/modules/postgresql/spec/acceptance/server/config_entry_spec.rb index a58903a..6f97f64 100644 --- a/modules/postgresql/spec/acceptance/server/config_entry_spec.rb +++ b/modules/postgresql/spec/acceptance/server/config_entry_spec.rb @@ -23,4 +23,22 @@ describe 'postgresql::server::config_entry:', :unless => UNSUPPORTED_PLATFORMS.i expect(r.stderr).to eq('') end end + + it 'should correctly set a quotes-required string' do + pp = <<-EOS.unindent + class { 'postgresql::server': } + + postgresql::server::config_entry { 'log_directory': + value => '/tmp/testfile', + } + EOS + + apply_manifest(pp, :catch_failures => true) + + psql('--command="show all" postgres') do |r| + r.stdout.should =~ /log_directory.+\/tmp\/testfile/ + r.stderr.should be_empty + r.exit_code.should == 0 + end + end end diff --git a/modules/postgresql/spec/acceptance/server/database_spec.rb b/modules/postgresql/spec/acceptance/server/database_spec.rb index 7f227d1..4087b08 100644 --- a/modules/postgresql/spec/acceptance/server/database_spec.rb +++ b/modules/postgresql/spec/acceptance/server/database_spec.rb @@ -27,3 +27,28 @@ describe 'postgresql::server::database:', :unless => UNSUPPORTED_PLATFORMS.inclu end end end + +describe 'postgresql::server::database: alternate port', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do + it 'should idempotently create a db on a non-default port that we can connect to' do + begin + pp = <<-EOS.unindent + $db = 'postgresql_test_db' + class { 'postgresql::server': + port => 5433, + } + + postgresql::server::database { $db: } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + + psql('--command="select datname from pg_database" --port=5433 postgresql_test_db') do |r| + expect(r.stdout).to match(/postgresql_test_db/) + expect(r.stderr).to eq('') + end + ensure + psql('--command="drop database postgresql_test_db" --port=5433 postgres') + end + end +end diff --git a/modules/postgresql/spec/acceptance/server/db_spec.rb b/modules/postgresql/spec/acceptance/server/db_spec.rb index 0287976..f14578c 100644 --- a/modules/postgresql/spec/acceptance/server/db_spec.rb +++ b/modules/postgresql/spec/acceptance/server/db_spec.rb @@ -135,4 +135,30 @@ describe 'postgresql::server::db', :unless => UNSUPPORTED_PLATFORMS.include?(fac psql('--command="drop database postgresql_test_db" postgres') end end + + it 'should take a dbname parameter' do + begin + pp = <<-EOS.unindent + $db = 'postgresql_test_db' + $dbname = 'postgresql_testtest_db' + class { 'postgresql::server': } + + postgresql::server::db { $db: + dbname => $dbname, + user => $db, + password => postgresql_password($db, $db), + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + + psql('--command="select datname from pg_database" postgresql_testtest_db') do |r| + expect(r.stdout).to match(/postgresql_testtest_db/) + expect(r.stderr).to eq('') + end + ensure + psql('--command="drop database postgresql_testtest_db" postgres') + end + end end diff --git a/modules/postgresql/spec/acceptance/server/role_spec.rb b/modules/postgresql/spec/acceptance/server/role_spec.rb index 2bd2b70..c2bd452 100644 --- a/modules/postgresql/spec/acceptance/server/role_spec.rb +++ b/modules/postgresql/spec/acceptance/server/role_spec.rb @@ -85,4 +85,32 @@ describe 'postgresql::server::role:', :unless => UNSUPPORTED_PLATFORMS.include?( expect(r.stderr).to eq('') end end + + it 'should idempotently create a user with noinherit' do + pp = <<-EOS.unindent + $user = "postgresql_test_noinherit" + $password = "postgresql_test_noinherit" + + class { 'postgresql::server': } + + # Since we are not testing pg_hba or any of that, make a local user for ident auth + user { $user: + ensure => present, + } + + postgresql::server::role { $user: + password_hash => $password, + inherit => false, + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + + # Check that the user has noinherit set + psql('--command="select rolname from pg_roles where not rolinherit" postgres', 'postgresql_test_noinherit') do |r| + expect(r.stdout).to match(/postgresql_test_noinherit/) + expect(r.stderr).to eq('') + end + end end diff --git a/modules/postgresql/spec/acceptance/server_spec.rb b/modules/postgresql/spec/acceptance/server_spec.rb index b8456ee..1b3086c 100644 --- a/modules/postgresql/spec/acceptance/server_spec.rb +++ b/modules/postgresql/spec/acceptance/server_spec.rb @@ -1,9 +1,38 @@ require 'spec_helper_acceptance' +# Hack around the fact that so far only Ubuntu 14.04 seems to have moved this +# file. Can revisit if everyone else gets clever. + +case fact('operatingsystem') +when 'Ubuntu' + case fact('operatingsystemrelease') + when '14.04' + pghba_file = '/etc/postgresql/9.3/main/pg_hba.conf' + when '12.04' + pghba_file = '/etc/postgresql/9.1/main/pg_hba.conf' + end +when 'Debian' + case fact('operatingsystemmajrelease') + when '7' + pghba_file = '/etc/postgresql/9.1/main/pg_hba.conf' + when '6' + pghba_file = '/etc/postgresql/8.4/main/pg_hba.conf' + end +else + pghba_file = '/var/lib/pgsql/data/pg_hba.conf' +end + describe 'server:', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do after :all do # Cleanup after tests have ran - apply_manifest("class { 'postgresql::server': ensure => absent }", :catch_failures => true) + pp = <<-EOS.unindent + class { 'postgresql::server': ensure => absent } -> + class { 'postgresql::client': package_ensure => absent } + EOS + apply_manifest(pp, :catch_failures => true) + if fact('osfamily') == 'RedHat' + shell('rpm -qa | grep postgres | xargs rpm -e') + end end it 'test loading class with no parameters' do @@ -19,6 +48,13 @@ describe 'server:', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) it { should be_listening } end + describe file(pghba_file) do + it { should be_file } + it { should be_owned_by 'postgres' } + it { should be_grouped_into 'postgres' } + it { should be_mode 640 } + end + describe 'setting postgres password' do it 'should install and successfully adjust the password' do pp = <<-EOS.unindent @@ -64,15 +100,20 @@ describe 'server without defaults:', :unless => UNSUPPORTED_PLATFORMS.include?(f } } class { 'postgresql::globals': - ensure => absent, manage_package_repo => true, version => '9.3', } class { 'postgresql::server': ensure => absent, + } -> + class { 'postgresql::client': + package_ensure => absent, } EOS - expect(apply_manifest(pp, :catch_failures => true).stderr).to eq('') + apply_manifest(pp, :catch_failures => true) + if fact('osfamily') == 'RedHat' + shell('rpm -qa | grep postgres | xargs rpm -e') + end end it 'perform installation and create a db' do @@ -92,12 +133,11 @@ describe 'server without defaults:', :unless => UNSUPPORTED_PLATFORMS.include?(f user => "foo1", password => postgresql_password('foo1', 'foo1'), } - postgresql::server::config_entry { 'port': - value => '5432', - } EOS - expect(apply_manifest(pp, :catch_failures => true).stderr).to eq('') + # Yum cache for yum.postgresql.org is outdated + shell('yum clean all') if fact('osfamily') == 'RedHat' + apply_manifest(pp, :catch_failures => true) apply_manifest(pp, :catch_changes => true) shell('test -d /tmp/pg_xlogs') do |r| @@ -113,11 +153,38 @@ describe 'server without defaults:', :unless => UNSUPPORTED_PLATFORMS.include?(f end end + context 'test deprecating non-default version of postgresql to postgresql::server' do + after :all do + pp = <<-EOS.unindent + class { 'postgresql::globals': + version => '9.3', + } + class { 'postgresql::server': + ensure => absent, + } -> + class { 'postgresql::client': + package_ensure => absent, + } + EOS + apply_manifest(pp, :catch_failures => true) + end + + it 'raises a warning' do + pp = <<-EOS.unindent + class { 'postgresql::server': + ensure => absent, + version => '9.3', + } + EOS + expect(apply_manifest(pp, :catch_failures => false).stderr).to match(/Passing "version" to postgresql::server is deprecated/i) + end + end + unless ((fact('osfamily') == 'RedHat' and fact('lsbmajdistrelease') == '5') || fact('osfamily') == 'Debian') context 'override locale and encoding' do - after :each do + before :each do apply_manifest("class { 'postgresql::server': ensure => absent }", :catch_failures => true) end @@ -162,6 +229,9 @@ describe 'server with firewall:', :unless => UNSUPPORTED_PLATFORMS.include?(fact } EOS + if fact('osfamily') == 'RedHat' and fact('operatingsystemmajrelease') == '5' + shell('iptables -F') + end apply_manifest(pp, :catch_failures => true) apply_manifest(pp, :catch_changes => true) end @@ -186,3 +256,48 @@ describe 'server without pg_hba.conf:', :unless => UNSUPPORTED_PLATFORMS.include end end end + +describe 'server on alternate port:', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do + after :all do + apply_manifest("class { 'postgresql::server': ensure => absent }", :catch_failures => true) + end + + it 'sets up selinux' do + pp = <<-EOS + if $::osfamily == 'RedHat' and $::selinux == 'true' { + $semanage_package = $::operatingsystemmajrelease ? { + '5' => 'policycoreutils', + default => 'policycoreutils-python', + } + + package { $semanage_package: ensure => installed } + exec { 'set_postgres': + command => 'semanage port -a -t postgresql_port_t -p tcp 5433', + path => '/bin:/usr/bin/:/sbin:/usr/sbin', + subscribe => Package[$semanage_package], + refreshonly => true, + } + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + + context 'test installing postgresql with alternate port' do + it 'perform installation and make sure it is idempotent' do + pp = <<-EOS.unindent + class { "postgresql::server": + port => 5433, + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + describe port(5433) do + it { should be_listening } + end + end +end diff --git a/modules/postgresql/spec/acceptance/unsupported_spec.rb b/modules/postgresql/spec/acceptance/unsupported_spec.rb index 1f64a1f..64964d8 100644 --- a/modules/postgresql/spec/acceptance/unsupported_spec.rb +++ b/modules/postgresql/spec/acceptance/unsupported_spec.rb @@ -5,12 +5,12 @@ describe 'unsupported distributions and OSes', :if => UNSUPPORTED_PLATFORMS.incl pp = <<-EOS class { 'postgresql::client': } EOS - expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/does not provide defaults for osfamily/i) + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/No preferred version defined or automatically detected/i) end it 'should fail for server' do pp = <<-EOS class { 'postgresql::server': } EOS - expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/does not provide defaults for osfamily/i) + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/No preferred version defined or automatically detected/i) end end diff --git a/modules/postgresql/spec/acceptance/validate_db_connection_spec.rb b/modules/postgresql/spec/acceptance/validate_db_connection_spec.rb index 0bfe509..5a7245f 100644 --- a/modules/postgresql/spec/acceptance/validate_db_connection_spec.rb +++ b/modules/postgresql/spec/acceptance/validate_db_connection_spec.rb @@ -32,12 +32,22 @@ describe 'postgresql::validate_db_connection:', :unless => UNSUPPORTED_PLATFORMS apply_manifest(pp, :catch_failures => true) end + it 'stops postgresql' do + # First we stop postgresql. + pp = <<-EOS + class { 'postgresql::server': + service_ensure => 'stopped', + } + EOS + apply_manifest(pp, :catch_failures => true) + end + it 'should keep retrying if database is down' do - # So first we shut the db down, then background a startup routine with a - # sleep 10 in front of it. That way the tests should continue while - # the pause and db startup happens in the background. - shell("/etc/init.d/postgresql* stop") - shell('nohup bash -c "sleep 10; /etc/init.d/postgresql* start" > /dev/null 2>&1 &') + if fact('operatingsystem') == 'RedHat' && fact('operatingsystemrelease') =~ /^7/ + shell('nohup bash -c "sleep 10; systemctl start `basename /usr/lib/systemd/system/postgres*`" > /dev/null 2>&1 &') + else + shell('nohup bash -c "sleep 10; /etc/init.d/postgresql* start" > /dev/null 2>&1 &') + end pp = <<-EOS.unindent postgresql::validate_db_connection { 'foo': @@ -47,7 +57,6 @@ describe 'postgresql::validate_db_connection:', :unless => UNSUPPORTED_PLATFORMS run_as => 'postgres', } EOS - apply_manifest(pp, :catch_failures => true) end @@ -76,4 +85,13 @@ describe 'postgresql::validate_db_connection:', :unless => UNSUPPORTED_PLATFORMS apply_manifest(pp, :expect_failures => true) end + + it 'starts postgresql' do + pp = <<-EOS + class { 'postgresql::server': + service_ensure => 'running', + } + EOS + apply_manifest(pp, :catch_failures => true) + end end diff --git a/modules/postgresql/spec/spec_helper.rb b/modules/postgresql/spec/spec_helper.rb index b4c44aa..f4852aa 100644 --- a/modules/postgresql/spec/spec_helper.rb +++ b/modules/postgresql/spec/spec_helper.rb @@ -7,15 +7,13 @@ RSpec.configure do |c| c.include PuppetlabsSpec::Files c.before :each do - # Ensure that we don't accidentally cache facts and environment - # between test cases. - Facter::Util::Loader.any_instance.stubs(:load_all) - Facter.clear - Facter.clear_messages - # Store any environment variables away to be restored later @old_env = {} ENV.each_key {|k| @old_env[k] = ENV[k]} + + if ENV['STRICT_VARIABLES'] == 'yes' + Puppet.settings[:strict_variables]=true + end end c.after :each do diff --git a/modules/postgresql/spec/spec_helper_acceptance.rb b/modules/postgresql/spec/spec_helper_acceptance.rb index 4984da6..93ddacc 100644 --- a/modules/postgresql/spec/spec_helper_acceptance.rb +++ b/modules/postgresql/spec/spec_helper_acceptance.rb @@ -29,18 +29,26 @@ def shellescape(str) return str end -def psql(psql_cmd, user = 'postgres', exit_codes = [0], &block) +def psql(psql_cmd, user = 'postgres', exit_codes = [0,1], &block) psql = "psql #{psql_cmd}" shell("su #{shellescape(user)} -c #{shellescape(psql)}", :acceptable_exit_codes => exit_codes, &block) end -unless ENV['RS_PROVISION'] == 'no' +unless ENV['RS_PROVISION'] == 'no' or ENV['BEAKER_provision'] == 'no' + if hosts.first.is_pe? + install_pe + else + install_puppet + end hosts.each do |host| - if host.is_pe? - install_pe - else - install_puppet - on host, "mkdir -p #{host['distmoduledir']}" + shell("mkdir -p #{host['distmoduledir']}") + if ! host.is_pe? + # Augeas is only used in one place, for Redhat. + if fact('osfamily') == 'RedHat' + install_package host, 'ruby-devel' + install_package host, 'augeas-devel' + install_package host, 'ruby-augeas' + end end end end @@ -66,6 +74,10 @@ RSpec.configure do |c| on host, '/usr/sbin/locale-gen' on host, '/usr/sbin/update-locale' end + if fact('osfamily') == 'RedHat' + shell('yum -y install policycoreutils-python') + shell('semanage port -a -t postgresql_port_t -p tcp 5433') + end on host, puppet('module','install','puppetlabs-stdlib'), { :acceptable_exit_codes => [0,1] } on host, puppet('module','install','puppetlabs-firewall'), { :acceptable_exit_codes => [0,1] } on host, puppet('module','install','puppetlabs-apt'), { :acceptable_exit_codes => [0,1] } diff --git a/modules/postgresql/spec/unit/classes/globals_spec.rb b/modules/postgresql/spec/unit/classes/globals_spec.rb index f8feb2f..315a349 100644 --- a/modules/postgresql/spec/unit/classes/globals_spec.rb +++ b/modules/postgresql/spec/unit/classes/globals_spec.rb @@ -7,6 +7,7 @@ describe 'postgresql::globals', :type => :class do :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', :lsbdistid => 'Debian', + :lsbdistcodename => 'squeeze', } end diff --git a/modules/postgresql/spec/unit/classes/lib/perl_spec.rb b/modules/postgresql/spec/unit/classes/lib/perl_spec.rb new file mode 100644 index 0000000..e107bf2 --- /dev/null +++ b/modules/postgresql/spec/unit/classes/lib/perl_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe 'postgresql::lib::perl', :type => :class do + + describe 'on a redhat based os' do + let :facts do { + :osfamily => 'RedHat', + :operatingsystem => 'RedHat', + :operatingsystemrelease => '6.4', + } + end + it { should contain_package('perl-DBD-Pg').with( + :name => 'perl-DBD-Pg', + :ensure => 'present' + )} + end + + describe 'on a debian based os' do + let :facts do { + :osfamily => 'Debian', + :operatingsystem => 'Debian', + :operatingsystemrelease => '6.0', + } + end + it { should contain_package('perl-DBD-Pg').with( + :name => 'libdbd-pg-perl', + :ensure => 'present' + )} + end + +end diff --git a/modules/postgresql/spec/unit/classes/repo_spec.rb b/modules/postgresql/spec/unit/classes/repo_spec.rb index e7e286f..f31d468 100644 --- a/modules/postgresql/spec/unit/classes/repo_spec.rb +++ b/modules/postgresql/spec/unit/classes/repo_spec.rb @@ -7,6 +7,7 @@ describe 'postgresql::repo', :type => :class do :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', :lsbdistid => 'Debian', + :lsbdistcodename => 'squeeze', } end diff --git a/modules/postgresql/spec/unit/classes/server/contrib_spec.rb b/modules/postgresql/spec/unit/classes/server/contrib_spec.rb index 9fbab06..a22a3d0 100644 --- a/modules/postgresql/spec/unit/classes/server/contrib_spec.rb +++ b/modules/postgresql/spec/unit/classes/server/contrib_spec.rb @@ -12,6 +12,8 @@ describe 'postgresql::server::contrib', :type => :class do :operatingsystemrelease => '6.0', :kernel => 'Linux', :concat_basedir => tmpfilename('contrib'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end diff --git a/modules/postgresql/spec/unit/classes/server/initdb_spec.rb b/modules/postgresql/spec/unit/classes/server/initdb_spec.rb index bcf2dbe..24ebfa8 100644 --- a/modules/postgresql/spec/unit/classes/server/initdb_spec.rb +++ b/modules/postgresql/spec/unit/classes/server/initdb_spec.rb @@ -11,6 +11,9 @@ describe 'postgresql::server::initdb', :type => :class do :operatingsystem => 'CentOS', :operatingsystemrelease => '6.0', :concat_basedir => tmpfilename('server'), + :kernel => 'Linux', + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end it { should contain_file('/var/lib/pgsql/data').with_ensure('directory') } @@ -20,7 +23,11 @@ describe 'postgresql::server::initdb', :type => :class do { :osfamily => 'RedHat', :operatingsystem => 'Amazon', + :operatingsystemrelease => '1.0', :concat_basedir => tmpfilename('server'), + :kernel => 'Linux', + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end it { should contain_file('/var/lib/pgsql9/data').with_ensure('directory') } diff --git a/modules/postgresql/spec/unit/classes/server/plperl_spec.rb b/modules/postgresql/spec/unit/classes/server/plperl_spec.rb index 785ed9a..a0c914e 100644 --- a/modules/postgresql/spec/unit/classes/server/plperl_spec.rb +++ b/modules/postgresql/spec/unit/classes/server/plperl_spec.rb @@ -8,6 +8,8 @@ describe 'postgresql::server::plperl', :type => :class do :operatingsystemrelease => '6.0', :kernel => 'Linux', :concat_basedir => tmpfilename('plperl'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end diff --git a/modules/postgresql/spec/unit/classes/server/postgis_spec.rb b/modules/postgresql/spec/unit/classes/server/postgis_spec.rb new file mode 100644 index 0000000..6636bf2 --- /dev/null +++ b/modules/postgresql/spec/unit/classes/server/postgis_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe 'postgresql::server::postgis', :type => :class do + let :pre_condition do + "class { 'postgresql::server': }" + end + + let :facts do + { + :osfamily => 'Debian', + :operatingsystem => 'Debian', + :operatingsystemrelease => '6.0', + :kernel => 'Linux', + :concat_basedir => tmpfilename('postgis'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + } + end + + describe 'with parameters' do + let(:params) do + { + :package_name => 'mypackage', + :package_ensure => 'absent', + } + end + + it 'should create package with correct params' do + should contain_package('postgresql-postgis').with({ + :ensure => 'absent', + :name => 'mypackage', + :tag => 'postgresql', + }) + end + end + + describe 'with no parameters' do + it 'should create package with postgresql tag' do + should contain_package('postgresql-postgis').with({ + :tag => 'postgresql', + }) + end + end +end diff --git a/modules/postgresql/spec/unit/classes/server_spec.rb b/modules/postgresql/spec/unit/classes/server_spec.rb index 203eecb..8fcf93f 100644 --- a/modules/postgresql/spec/unit/classes/server_spec.rb +++ b/modules/postgresql/spec/unit/classes/server_spec.rb @@ -8,6 +8,8 @@ describe 'postgresql::server', :type => :class do :operatingsystemrelease => '6.0', :concat_basedir => tmpfilename('server'), :kernel => 'Linux', + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end @@ -19,6 +21,24 @@ describe 'postgresql::server', :type => :class do end end + describe 'service_ensure => running' do + let(:params) {{ :service_ensure => 'running' }} + it { should contain_class("postgresql::params") } + it { should contain_class("postgresql::server") } + it 'should validate connection' do + should contain_postgresql__validate_db_connection('validate_service_is_running') + end + end + + describe 'service_ensure => stopped' do + let(:params) {{ :service_ensure => 'stopped' }} + it { should contain_class("postgresql::params") } + it { should contain_class("postgresql::server") } + it 'shouldnt validate connection' do + should_not contain_postgresql__validate_db_connection('validate_service_is_running') + end + end + describe 'manage_firewall => true' do let(:params) do { @@ -49,7 +69,7 @@ describe 'postgresql::server', :type => :class do it 'stop the service' do should contain_service('postgresqld').with({ - :ensure => false, + :ensure => 'stopped', }) end @@ -81,7 +101,7 @@ describe 'postgresql::server', :type => :class do it 'should still enable the service' do should contain_service('postgresqld').with({ - :ensure => true, + :ensure => 'running', }) end end diff --git a/modules/postgresql/spec/unit/defines/server/config_entry_spec.rb b/modules/postgresql/spec/unit/defines/server/config_entry_spec.rb index 4c25c67..e7c59fd 100644 --- a/modules/postgresql/spec/unit/defines/server/config_entry_spec.rb +++ b/modules/postgresql/spec/unit/defines/server/config_entry_spec.rb @@ -6,6 +6,10 @@ describe 'postgresql::server::config_entry', :type => :define do :osfamily => 'RedHat', :operatingsystem => 'RedHat', :operatingsystemrelease => '6.4', + :kernel => 'Linux', + :concat_basedir => tmpfilename('contrib'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end @@ -16,6 +20,10 @@ describe 'postgresql::server::config_entry', :type => :define do end context "syntax check" do + let :pre_condition do + "class {'postgresql::server':}" + end + let(:params) { { :ensure => 'present'} } it { should contain_postgresql__server__config_entry('config_entry') } end diff --git a/modules/postgresql/spec/unit/defines/server/database_grant_spec.rb b/modules/postgresql/spec/unit/defines/server/database_grant_spec.rb index 2e481df..cf18469 100644 --- a/modules/postgresql/spec/unit/defines/server/database_grant_spec.rb +++ b/modules/postgresql/spec/unit/defines/server/database_grant_spec.rb @@ -6,6 +6,10 @@ describe 'postgresql::server::database_grant', :type => :define do :osfamily => 'Debian', :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', + :kernel => 'Linux', + :concat_basedir => tmpfilename('contrib'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end @@ -21,6 +25,10 @@ describe 'postgresql::server::database_grant', :type => :define do } end + let :pre_condition do + "class {'postgresql::server':}" + end + it { should contain_postgresql__server__database_grant('test') } it { should contain_postgresql__server__grant('database:test') } end diff --git a/modules/postgresql/spec/unit/defines/server/database_spec.rb b/modules/postgresql/spec/unit/defines/server/database_spec.rb index a703827..a3d1ace 100644 --- a/modules/postgresql/spec/unit/defines/server/database_spec.rb +++ b/modules/postgresql/spec/unit/defines/server/database_spec.rb @@ -6,11 +6,20 @@ describe 'postgresql::server::database', :type => :define do :osfamily => 'Debian', :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', + :kernel => 'Linux', + :concat_basedir => tmpfilename('contrib'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end let :title do 'test' end + + let :pre_condition do + "class {'postgresql::server':}" + end + it { should contain_postgresql__server__database('test') } it { should contain_postgresql_psql("Check for existence of db 'test'") } end diff --git a/modules/postgresql/spec/unit/defines/server/db_spec.rb b/modules/postgresql/spec/unit/defines/server/db_spec.rb index 60fa9a9..157de10 100644 --- a/modules/postgresql/spec/unit/defines/server/db_spec.rb +++ b/modules/postgresql/spec/unit/defines/server/db_spec.rb @@ -6,6 +6,10 @@ describe 'postgresql::server::db', :type => :define do :osfamily => 'Debian', :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', + :kernel => 'Linux', + :concat_basedir => tmpfilename('contrib'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end @@ -13,16 +17,42 @@ describe 'postgresql::server::db', :type => :define do 'test' end - let :params do - { - :user => 'test', - :password => 'test', - :owner => 'tester', - } + context 'without dbname param' do + + let :params do + { + :user => 'test', + :password => 'test', + :owner => 'tester', + } + end + + let :pre_condition do + "class {'postgresql::server':}" + end + + it { should contain_postgresql__server__db('test') } + it { should contain_postgresql__server__database('test').with_owner('tester') } + it { should contain_postgresql__server__role('test') } + it { should contain_postgresql__server__database_grant('GRANT test - ALL - test') } + end - it { should contain_postgresql__server__db('test') } - it { should contain_postgresql__server__database('test').with_owner('tester') } - it { should contain_postgresql__server__role('test') } - it { should contain_postgresql__server__database_grant('GRANT test - ALL - test') } + context 'dbname' do + + let :params do + { + :dbname => 'testtest', + :user => 'test', + :password => 'test', + :owner => 'tester', + } + end + + let :pre_condition do + "class {'postgresql::server':}" + end + + it { should contain_postgresql__server__database('testtest') } + end end diff --git a/modules/postgresql/spec/unit/defines/server/grant_spec.rb b/modules/postgresql/spec/unit/defines/server/grant_spec.rb index 43eeb8c..c020a69 100644 --- a/modules/postgresql/spec/unit/defines/server/grant_spec.rb +++ b/modules/postgresql/spec/unit/defines/server/grant_spec.rb @@ -6,6 +6,10 @@ describe 'postgresql::server::grant', :type => :define do :osfamily => 'Debian', :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', + :kernel => 'Linux', + :concat_basedir => tmpfilename('contrib'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end @@ -20,5 +24,9 @@ describe 'postgresql::server::grant', :type => :define do } end + let :pre_condition do + "class {'postgresql::server':}" + end + it { should contain_postgresql__server__grant('test') } end diff --git a/modules/postgresql/spec/unit/defines/server/pg_hba_rule_spec.rb b/modules/postgresql/spec/unit/defines/server/pg_hba_rule_spec.rb index b01e338..abe4de0 100644 --- a/modules/postgresql/spec/unit/defines/server/pg_hba_rule_spec.rb +++ b/modules/postgresql/spec/unit/defines/server/pg_hba_rule_spec.rb @@ -6,7 +6,10 @@ describe 'postgresql::server::pg_hba_rule', :type => :define do :osfamily => 'Debian', :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', + :kernel => 'Linux', :concat_basedir => tmpfilename('pg_hba'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end let :title do @@ -17,6 +20,12 @@ describe 'postgresql::server::pg_hba_rule', :type => :define do end context 'test template 1' do + let :pre_condition do + <<-EOS + class { 'postgresql::server': } + EOS + end + let :params do { :type => 'host', @@ -28,12 +37,19 @@ describe 'postgresql::server::pg_hba_rule', :type => :define do } end it do - content = param('concat::fragment', 'pg_hba_rule_test', 'content') - content.should =~ /host\s+all\s+all\s+1\.1\.1\.1\/24\s+md5/ + should contain_concat__fragment('pg_hba_rule_test').with({ + :content => /host\s+all\s+all\s+1\.1\.1\.1\/24\s+md5/ + }) end end context 'test template 2' do + let :pre_condition do + <<-EOS + class { 'postgresql::server': } + EOS + end + let :params do { :type => 'local', @@ -44,12 +60,19 @@ describe 'postgresql::server::pg_hba_rule', :type => :define do } end it do - content = param('concat::fragment', 'pg_hba_rule_test', 'content') - content.should =~ /local\s+all\s+all\s+ident/ + should contain_concat__fragment('pg_hba_rule_test').with({ + :content => /local\s+all\s+all\s+ident/ + }) end end context 'test template 3' do + let :pre_condition do + <<-EOS + class { 'postgresql::server': } + EOS + end + let :params do { :type => 'host', @@ -62,13 +85,20 @@ describe 'postgresql::server::pg_hba_rule', :type => :define do } end it do - content = param('concat::fragment', 'pg_hba_rule_test', 'content') - content.should =~ /host\s+all\s+all\s+0\.0\.0\.0\/0\s+ldap\s+foo=bar/ + should contain_concat__fragment('pg_hba_rule_test').with({ + :content => /host\s+all\s+all\s+0\.0\.0\.0\/0\s+ldap\s+foo=bar/ + }) end end context 'validation' do context 'validate type test 1' do + let :pre_condition do + <<-EOS + class { 'postgresql::server': } + EOS + end + let :params do { :type => 'invalid', @@ -86,6 +116,12 @@ describe 'postgresql::server::pg_hba_rule', :type => :define do end context 'validate auth_method' do + let :pre_condition do + <<-EOS + class { 'postgresql::server': } + EOS + end + let :params do { :type => 'local', @@ -152,8 +188,9 @@ describe 'postgresql::server::pg_hba_rule', :type => :define do end it do - content = param('concat::fragment', 'pg_hba_rule_test', 'content') - content.should =~ /local\s+all\s+all\s+0\.0\.0\.0\/0\s+peer/ + should contain_concat__fragment('pg_hba_rule_test').with({ + :content => /local\s+all\s+all\s+0\.0\.0\.0\/0\s+peer/ + }) end end diff --git a/modules/postgresql/spec/unit/defines/server/role_spec.rb b/modules/postgresql/spec/unit/defines/server/role_spec.rb index a50ca14..18ebb84 100644 --- a/modules/postgresql/spec/unit/defines/server/role_spec.rb +++ b/modules/postgresql/spec/unit/defines/server/role_spec.rb @@ -6,6 +6,10 @@ describe 'postgresql::server::role', :type => :define do :osfamily => 'Debian', :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', + :kernel => 'Linux', + :concat_basedir => tmpfilename('contrib'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end @@ -19,5 +23,9 @@ describe 'postgresql::server::role', :type => :define do } end + let :pre_condition do + "class {'postgresql::server':}" + end + it { should contain_postgresql__server__role('test') } end diff --git a/modules/postgresql/spec/unit/defines/server/table_grant_spec.rb b/modules/postgresql/spec/unit/defines/server/table_grant_spec.rb index 15136a7..0ba0277 100644 --- a/modules/postgresql/spec/unit/defines/server/table_grant_spec.rb +++ b/modules/postgresql/spec/unit/defines/server/table_grant_spec.rb @@ -6,6 +6,10 @@ describe 'postgresql::server::table_grant', :type => :define do :osfamily => 'Debian', :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', + :kernel => 'Linux', + :concat_basedir => tmpfilename('table_grant'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end @@ -22,6 +26,10 @@ describe 'postgresql::server::table_grant', :type => :define do } end + let :pre_condition do + "class {'postgresql::server':}" + end + it { should contain_postgresql__server__table_grant('test') } it { should contain_postgresql__server__grant('table:test') } end diff --git a/modules/postgresql/spec/unit/defines/server/tablespace_spec.rb b/modules/postgresql/spec/unit/defines/server/tablespace_spec.rb index eb29f1e..848f461 100644 --- a/modules/postgresql/spec/unit/defines/server/tablespace_spec.rb +++ b/modules/postgresql/spec/unit/defines/server/tablespace_spec.rb @@ -6,6 +6,10 @@ describe 'postgresql::server::tablespace', :type => :define do :osfamily => 'Debian', :operatingsystem => 'Debian', :operatingsystemrelease => '6.0', + :kernel => 'Linux', + :concat_basedir => tmpfilename('tablespace'), + :id => 'root', + :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', } end @@ -19,5 +23,9 @@ describe 'postgresql::server::tablespace', :type => :define do } end + let :pre_condition do + "class {'postgresql::server':}" + end + it { should contain_postgresql__server__tablespace('test') } end diff --git a/modules/postgresql/spec/unit/provider/postgresql_conf/parsed_spec.rb b/modules/postgresql/spec/unit/provider/postgresql_conf/parsed_spec.rb index 2e2bfbe..5eb638c 100644 --- a/modules/postgresql/spec/unit/provider/postgresql_conf/parsed_spec.rb +++ b/modules/postgresql/spec/unit/provider/postgresql_conf/parsed_spec.rb @@ -9,7 +9,7 @@ describe provider_class do conf_class = Puppet::Type.type(:postgresql_conf) provider = conf_class.provider(:parsed) conffile = tmpfilename('postgresql.conf') - provider.any_instance.stubs(:target).returns conffile + provider.any_instance.stub(:target).and_return conffile provider } diff --git a/modules/postgresql/spec/unit/type/postgresql_conf_spec.rb b/modules/postgresql/spec/unit/type/postgresql_conf_spec.rb index 4524203..43b4c51 100644 --- a/modules/postgresql/spec/unit/type/postgresql_conf_spec.rb +++ b/modules/postgresql/spec/unit/type/postgresql_conf_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' describe Puppet::Type.type(:postgresql_conf) do before do @provider_class = described_class.provide(:simple) { mk_resource_methods } - @provider_class.stubs(:suitable?).returns true - described_class.stubs(:defaultprovider).returns @provider_class + @provider_class.stub(:suitable?).and_return true + described_class.stub(:defaultprovider).and_return @provider_class end describe "namevar validation" do diff --git a/modules/postgresql/templates/systemd-port-override.erb b/modules/postgresql/templates/systemd-port-override.erb new file mode 100644 index 0000000..c3f24a6 --- /dev/null +++ b/modules/postgresql/templates/systemd-port-override.erb @@ -0,0 +1,3 @@ +.include /lib/systemd/system/postgresql.service +[Service] +Environment=PGPORT=<%= @value %>