From 107dbf079e8d284d43a1928bd50e3e20c3cb2ef9 Mon Sep 17 00:00:00 2001 From: Kevin Horst Date: Mon, 22 Dec 2014 15:18:05 +0100 Subject: [PATCH 1/4] fix some startup errors --- manifests/init.pp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/manifests/init.pp b/manifests/init.pp index 8e32a93..b8c8369 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -15,7 +15,7 @@ error /(?i)^error / # match line containing '[error]', case-insensitive -error /(?i)\[error\]/ +error /(?i)[error]/ # match line beginning with '[Fail]', case-insensitive error /(?i)^fail/ @@ -43,8 +43,8 @@ include imagemagick class { 'apache': notify => [ - exec['clean_urls_for_drupal'], - exec['allow_jenkins_virtual_hosts'], + Exec['clean_urls_for_drupal'], + Exec['allow_jenkins_virtual_hosts'], ], } @@ -84,7 +84,11 @@ } # don't use a firewall, see http://stackoverflow.com/questions/5984217 -service { iptables: ensure => stopped } +service { iptables: + ensure => stopped, + hasstatus => "true", + status => "true" +} # Install git and dependencies, see # https://github.com/jenkinsci/puppet-jenkins/issues/78 From aff058a2aecc5804d3507e5cb0ec9210da2f1680 Mon Sep 17 00:00:00 2001 From: Kevin Horst Date: Mon, 22 Dec 2014 15:19:16 +0100 Subject: [PATCH 2/4] add module puppetlabs/firewall to module directory --- manifests/Puppetfile | 4 +- manifests/modules/firewall/.fixtures.yml | 3 + manifests/modules/firewall/.nodeset.yml | 31 + manifests/modules/firewall/.sync.yml | 3 + manifests/modules/firewall/.travis.yml | 17 + manifests/modules/firewall/CHANGELOG.md | 482 +++++ manifests/modules/firewall/CONTRIBUTING.md | 220 +++ manifests/modules/firewall/Gemfile | 30 + manifests/modules/firewall/LICENSE | 25 + manifests/modules/firewall/README.markdown | 759 ++++++++ manifests/modules/firewall/Rakefile | 10 + .../firewall/lib/facter/ip6tables_version.rb | 11 + .../lib/facter/iptables_persistent_version.rb | 15 + .../firewall/lib/facter/iptables_version.rb | 11 + .../firewall/lib/puppet/provider/firewall.rb | 34 + .../lib/puppet/provider/firewall/ip6tables.rb | 143 ++ .../lib/puppet/provider/firewall/iptables.rb | 581 ++++++ .../provider/firewallchain/iptables_chain.rb | 179 ++ .../firewall/lib/puppet/type/firewall.rb | 1165 +++++++++++ .../firewall/lib/puppet/type/firewallchain.rb | 222 +++ .../firewall/lib/puppet/util/firewall.rb | 226 +++ .../firewall/lib/puppet/util/ipcidr.rb | 42 + manifests/modules/firewall/manifests/init.pp | 40 + manifests/modules/firewall/manifests/linux.pp | 59 + .../firewall/manifests/linux/archlinux.pp | 43 + .../firewall/manifests/linux/debian.pp | 49 + .../firewall/manifests/linux/redhat.pp | 54 + .../modules/firewall/manifests/params.pp | 41 + manifests/modules/firewall/metadata.json | 78 + .../spec/acceptance/change_source_spec.rb | 77 + .../firewall/spec/acceptance/class_spec.rb | 27 + .../spec/acceptance/connlimit_spec.rb | 55 + .../firewall/spec/acceptance/connmark_spec.rb | 27 + .../firewall/spec/acceptance/firewall_spec.rb | 1734 +++++++++++++++++ .../spec/acceptance/firewallchain_spec.rb | 131 ++ .../firewall/spec/acceptance/invert_spec.rb | 62 + .../spec/acceptance/ip6_fragment_spec.rb | 120 ++ .../spec/acceptance/isfragment_spec.rb | 98 + .../acceptance/nodesets/centos-59-x64-pe.yml | 12 + .../acceptance/nodesets/centos-59-x64.yml | 10 + .../nodesets/centos-64-x64-fusion.yml | 10 + .../acceptance/nodesets/centos-64-x64-pe.yml | 12 + .../acceptance/nodesets/centos-64-x64.yml | 10 + .../acceptance/nodesets/centos-65-x64.yml | 10 + .../acceptance/nodesets/debian-607-x64.yml | 10 + .../acceptance/nodesets/debian-70rc1-x64.yml | 10 + .../spec/acceptance/nodesets/default.yml | 10 + .../acceptance/nodesets/fedora-18-x64.yml | 10 + .../acceptance/nodesets/sles-11sp1-x64.yml | 10 + .../nodesets/ubuntu-server-10044-x64.yml | 10 + .../nodesets/ubuntu-server-12042-x64.yml | 10 + .../nodesets/ubuntu-server-1404-x64.yml | 11 + .../firewall/spec/acceptance/params_spec.rb | 147 ++ .../firewall/spec/acceptance/purge_spec.rb | 239 +++ .../spec/acceptance/resource_cmd_spec.rb | 129 ++ .../firewall/spec/acceptance/rules_spec.rb | 276 +++ .../firewall/spec/acceptance/socket_spec.rb | 103 + .../spec/acceptance/standard_usage_spec.rb | 62 + .../spec/acceptance/unsupported_spec.rb | 10 + .../fixtures/ip6tables/conversion_hash.rb | 107 + .../spec/fixtures/iptables/conversion_hash.rb | 1049 ++++++++++ manifests/modules/firewall/spec/spec.opts | 6 + .../modules/firewall/spec/spec_helper.rb | 29 + .../firewall/spec/spec_helper_acceptance.rb | 45 + .../classes/firewall_linux_archlinux_spec.rb | 38 + .../classes/firewall_linux_debian_spec.rb | 87 + .../classes/firewall_linux_redhat_spec.rb | 63 + .../spec/unit/classes/firewall_linux_spec.rb | 30 + .../spec/unit/classes/firewall_spec.rb | 35 + .../iptables_persistent_version_spec.rb | 35 + .../spec/unit/facter/iptables_spec.rb | 23 + .../puppet/provider/iptables_chain_spec.rb | 231 +++ .../unit/puppet/provider/iptables_spec.rb | 435 +++++ .../spec/unit/puppet/type/firewall_spec.rb | 680 +++++++ .../unit/puppet/type/firewallchain_spec.rb | 207 ++ .../spec/unit/puppet/util/firewall_spec.rb | 206 ++ .../spec/unit/puppet/util/ipcidr_spec.rb | 67 + 77 files changed, 11391 insertions(+), 1 deletion(-) create mode 100644 manifests/modules/firewall/.fixtures.yml create mode 100644 manifests/modules/firewall/.nodeset.yml create mode 100644 manifests/modules/firewall/.sync.yml create mode 100644 manifests/modules/firewall/.travis.yml create mode 100644 manifests/modules/firewall/CHANGELOG.md create mode 100644 manifests/modules/firewall/CONTRIBUTING.md create mode 100644 manifests/modules/firewall/Gemfile create mode 100644 manifests/modules/firewall/LICENSE create mode 100644 manifests/modules/firewall/README.markdown create mode 100644 manifests/modules/firewall/Rakefile create mode 100644 manifests/modules/firewall/lib/facter/ip6tables_version.rb create mode 100644 manifests/modules/firewall/lib/facter/iptables_persistent_version.rb create mode 100644 manifests/modules/firewall/lib/facter/iptables_version.rb create mode 100644 manifests/modules/firewall/lib/puppet/provider/firewall.rb create mode 100644 manifests/modules/firewall/lib/puppet/provider/firewall/ip6tables.rb create mode 100644 manifests/modules/firewall/lib/puppet/provider/firewall/iptables.rb create mode 100644 manifests/modules/firewall/lib/puppet/provider/firewallchain/iptables_chain.rb create mode 100644 manifests/modules/firewall/lib/puppet/type/firewall.rb create mode 100644 manifests/modules/firewall/lib/puppet/type/firewallchain.rb create mode 100644 manifests/modules/firewall/lib/puppet/util/firewall.rb create mode 100644 manifests/modules/firewall/lib/puppet/util/ipcidr.rb create mode 100644 manifests/modules/firewall/manifests/init.pp create mode 100644 manifests/modules/firewall/manifests/linux.pp create mode 100644 manifests/modules/firewall/manifests/linux/archlinux.pp create mode 100644 manifests/modules/firewall/manifests/linux/debian.pp create mode 100644 manifests/modules/firewall/manifests/linux/redhat.pp create mode 100644 manifests/modules/firewall/manifests/params.pp create mode 100644 manifests/modules/firewall/metadata.json create mode 100644 manifests/modules/firewall/spec/acceptance/change_source_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/class_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/connlimit_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/connmark_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/firewall_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/firewallchain_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/invert_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/ip6_fragment_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/isfragment_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/centos-59-x64-pe.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/centos-59-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/centos-64-x64-fusion.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/centos-64-x64-pe.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/centos-64-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/centos-65-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/debian-607-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/debian-70rc1-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/default.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/fedora-18-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/sles-11sp1-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml create mode 100644 manifests/modules/firewall/spec/acceptance/params_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/purge_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/resource_cmd_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/rules_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/socket_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/standard_usage_spec.rb create mode 100644 manifests/modules/firewall/spec/acceptance/unsupported_spec.rb create mode 100644 manifests/modules/firewall/spec/fixtures/ip6tables/conversion_hash.rb create mode 100644 manifests/modules/firewall/spec/fixtures/iptables/conversion_hash.rb create mode 100644 manifests/modules/firewall/spec/spec.opts create mode 100644 manifests/modules/firewall/spec/spec_helper.rb create mode 100644 manifests/modules/firewall/spec/spec_helper_acceptance.rb create mode 100644 manifests/modules/firewall/spec/unit/classes/firewall_linux_archlinux_spec.rb create mode 100644 manifests/modules/firewall/spec/unit/classes/firewall_linux_debian_spec.rb create mode 100644 manifests/modules/firewall/spec/unit/classes/firewall_linux_redhat_spec.rb create mode 100644 manifests/modules/firewall/spec/unit/classes/firewall_linux_spec.rb create mode 100644 manifests/modules/firewall/spec/unit/classes/firewall_spec.rb create mode 100644 manifests/modules/firewall/spec/unit/facter/iptables_persistent_version_spec.rb create mode 100644 manifests/modules/firewall/spec/unit/facter/iptables_spec.rb create mode 100755 manifests/modules/firewall/spec/unit/puppet/provider/iptables_chain_spec.rb create mode 100644 manifests/modules/firewall/spec/unit/puppet/provider/iptables_spec.rb create mode 100755 manifests/modules/firewall/spec/unit/puppet/type/firewall_spec.rb create mode 100755 manifests/modules/firewall/spec/unit/puppet/type/firewallchain_spec.rb create mode 100644 manifests/modules/firewall/spec/unit/puppet/util/firewall_spec.rb create mode 100644 manifests/modules/firewall/spec/unit/puppet/util/ipcidr_spec.rb diff --git a/manifests/Puppetfile b/manifests/Puppetfile index 73f6046..837898f 100644 --- a/manifests/Puppetfile +++ b/manifests/Puppetfile @@ -15,6 +15,8 @@ mod "jenkins", mod "apt", :git => "git://github.com/puppetlabs/puppetlabs-apt.git" +mod "puppetlabs/firewall" + mod 'puppetlabs/stdlib' mod 'puppetlabs/git' @@ -23,4 +25,4 @@ mod 'erikwebb/drush' mod 'puppetlabs/apache' mod 'puppetlabs/mysql' mod 'thias/php' -mod 'ScarceMedia/imagemagick' \ No newline at end of file +mod 'ScarceMedia/imagemagick' diff --git a/manifests/modules/firewall/.fixtures.yml b/manifests/modules/firewall/.fixtures.yml new file mode 100644 index 0000000..0d10d5c --- /dev/null +++ b/manifests/modules/firewall/.fixtures.yml @@ -0,0 +1,3 @@ +fixtures: + symlinks: + "firewall": "#{source_dir}" diff --git a/manifests/modules/firewall/.nodeset.yml b/manifests/modules/firewall/.nodeset.yml new file mode 100644 index 0000000..767f9cd --- /dev/null +++ b/manifests/modules/firewall/.nodeset.yml @@ -0,0 +1,31 @@ +--- +default_set: 'centos-64-x64' +sets: + 'centos-59-x64': + nodes: + "main.foo.vm": + prefab: 'centos-59-x64' + 'centos-64-x64': + nodes: + "main.foo.vm": + prefab: 'centos-64-x64' + 'fedora-18-x64': + nodes: + "main.foo.vm": + prefab: 'fedora-18-x64' + 'debian-607-x64': + nodes: + "main.foo.vm": + prefab: 'debian-607-x64' + 'debian-70rc1-x64': + nodes: + "main.foo.vm": + prefab: 'debian-70rc1-x64' + 'ubuntu-server-10044-x64': + nodes: + "main.foo.vm": + prefab: 'ubuntu-server-10044-x64' + 'ubuntu-server-12042-x64': + nodes: + "main.foo.vm": + prefab: 'ubuntu-server-12042-x64' diff --git a/manifests/modules/firewall/.sync.yml b/manifests/modules/firewall/.sync.yml new file mode 100644 index 0000000..66a03c6 --- /dev/null +++ b/manifests/modules/firewall/.sync.yml @@ -0,0 +1,3 @@ +--- +spec/spec_helper.rb: + unmanaged: true diff --git a/manifests/modules/firewall/.travis.yml b/manifests/modules/firewall/.travis.yml new file mode 100644 index 0000000..6cf8b00 --- /dev/null +++ b/manifests/modules/firewall/.travis.yml @@ -0,0 +1,17 @@ +--- +language: ruby +bundler_args: --without system_tests +script: "bundle exec rake validate && bundle exec rake lint && bundle exec rake spec SPEC_OPTS='--format documentation'" +matrix: + fast_finish: true + include: + - rvm: 1.8.7 + env: PUPPET_GEM_VERSION="~> 2.7.0" FACTER_GEM_VERSION="~> 1.6.0" + - rvm: 1.8.7 + env: PUPPET_GEM_VERSION="~> 2.7.0" FACTER_GEM_VERSION="~> 1.7.0" + - rvm: 1.9.3 + env: PUPPET_GEM_VERSION="~> 3.0" + - rvm: 2.0.0 + env: PUPPET_GEM_VERSION="~> 3.0" +notifications: + email: false diff --git a/manifests/modules/firewall/CHANGELOG.md b/manifests/modules/firewall/CHANGELOG.md new file mode 100644 index 0000000..7ff2a27 --- /dev/null +++ b/manifests/modules/firewall/CHANGELOG.md @@ -0,0 +1,482 @@ +##2014-12-16 - Supported Release 1.3.0 +###Summary + +This release includes a number of bugfixes and features, including fixing `tcp_flags` support, and added support for interface aliases, negation for iniface and outiface, and extra configurability for packages and service names. + +####Features +- Add support for interface aliases (eth0:0) (MODULES-1469) +- Add negation for iniface, outiface (MODULES-1470) +- Make package and service names configurable (MODULES-1309) + +####Bugfixes +- Fix test regexes for EL5 (MODULES-1565) +- Fix `tcp_flags` support for ip6tables (MODULES-556) +- Don't arbitrarily limit `set_mark` for certain chains + +##2014-11-04 - Supported Release 1.2.0 +###Summary + +This release has a number of new features and bugfixes, including rule inversion, future parser support, improved EL7 support, and the ability to purge ip6tables rules. + +####Features +- Documentation updates! +- Test updates! +- Add ipset support +- Enable rule inversion +- Future parser support +- Improved support for EL7 +- Support netfilter-persistent +- Add support for statistics module +- Add support for mac address source rules +- Add cbt protocol + +####Bugfixes +- Incorrect use of `source => :iptables` in the ip6tables provider was making it impossible to purge ip6tables rules (MODULES-41) +- Don't require `toports` when `jump => 'REDIRECT'` (MODULES-1086) +- Don't limit which chains iniface and outiface parameters can be used in +- Don't fail on rules added with ipsec/strongswan (MODULES-796) + +##2014-07-08 - Supported Release 1.1.3 +###Summary +This is a supported release with test coverage enhancements. + +####Bugfixes +- Confine to supported kernels + +##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/manifests/modules/firewall/CONTRIBUTING.md b/manifests/modules/firewall/CONTRIBUTING.md new file mode 100644 index 0000000..f1cbde4 --- /dev/null +++ b/manifests/modules/firewall/CONTRIBUTING.md @@ -0,0 +1,220 @@ +Checklist (and a short version for the impatient) +================================================= + + * Commits: + + - Make commits of logical units. + + - Check for unnecessary whitespace with "git diff --check" before + committing. + + - Commit using Unix line endings (check the settings around "crlf" in + git-config(1)). + + - Do not check in commented out code or unneeded files. + + - The first line of the commit message should be a short + description (50 characters is the soft limit, excluding ticket + number(s)), and should skip the full stop. + + - Associate the issue in the message. The first line should include + the issue number in the form "(#XXXX) Rest of message". + + - The body should provide a meaningful commit message, which: + + - uses the imperative, present tense: "change", not "changed" or + "changes". + + - includes motivation for the change, and contrasts its + implementation with the previous behavior. + + - Make sure that you have tests for the bug you are fixing, or + feature you are adding. + + - Make sure the test suites passes after your commit: + `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below + + - When introducing a new feature, make sure it is properly + documented in the README.md + + * Submission: + + * Pre-requisites: + + - Make sure you have a [GitHub account](https://github.com/join) + + - [Create a ticket](https://tickets.puppetlabs.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppetlabs.com/browse/) you are patching for. + + * Preferred method: + + - Fork the repository on GitHub. + + - Push your changes to a topic branch in your fork of the + repository. (the format ticket/1234-short_description_of_change is + usually preferred for this project). + + - Submit a pull request to the repository in the puppetlabs + organization. + +The long version +================ + + 1. Make separate commits for logically separate changes. + + Please break your commits down into logically consistent units + which include new or changed tests relevant to the rest of the + change. The goal of doing this is to make the diff easier to + read for whoever is reviewing your code. In general, the easier + your diff is to read, the more likely someone will be happy to + review it and get it into the code base. + + If you are going to refactor a piece of code, please do so as a + separate commit from your feature or bug fix changes. + + We also really appreciate changes that include tests to make + sure the bug is not re-introduced, and that the feature is not + accidentally broken. + + Describe the technical detail of the change(s). If your + description starts to get too long, that is a good sign that you + probably need to split up your commit into more finely grained + pieces. + + Commits which plainly describe the things which help + reviewers check the patch and future developers understand the + code are much more likely to be merged in with a minimum of + bike-shedding or requested changes. Ideally, the commit message + would include information, and be in a form suitable for + inclusion in the release notes for the version of Puppet that + includes them. + + Please also check that you are not introducing any trailing + whitespace or other "whitespace errors". You can do this by + running "git diff --check" on your changes before you commit. + + 2. Sending your patches + + To submit your changes via a GitHub pull request, we _highly_ + recommend that you have them on a topic branch, instead of + directly on "master". + It makes things much easier to keep track of, especially if + you decide to work on another thing before your first change + is merged in. + + GitHub has some pretty good + [general documentation](http://help.github.com/) on using + their site. They also have documentation on + [creating pull requests](http://help.github.com/send-pull-requests/). + + In general, after pushing your topic branch up to your + repository on GitHub, you can switch to the branch in the + GitHub UI and click "Pull Request" towards the top of the page + in order to open a pull request. + + + 3. Update the related GitHub issue. + + If there is a GitHub issue associated with the change you + submitted, then you should update the ticket to include the + location of your branch, along with any other commentary you + may wish to make. + +Testing +======= + +Getting Started +--------------- + +Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby +package manager such as [bundler](http://bundler.io/) what Ruby packages, +or Gems, are required to build, develop, and test this software. + +Please make sure you have [bundler installed](http://bundler.io/#getting-started) +on your system, then use it to install all dependencies needed for this project, +by running + +```shell +% bundle install +Fetching gem metadata from https://rubygems.org/........ +Fetching gem metadata from https://rubygems.org/.. +Using rake (10.1.0) +Using builder (3.2.2) +-- 8><-- many more --><8 -- +Using rspec-system-puppet (2.2.0) +Using serverspec (0.6.3) +Using rspec-system-serverspec (1.0.0) +Using bundler (1.3.5) +Your bundle is complete! +Use `bundle show [gemname]` to see where a bundled gem is installed. +``` + +NOTE some systems may require you to run this command with sudo. + +If you already have those gems installed, make sure they are up-to-date: + +```shell +% bundle update +``` + +With all dependencies in place and up-to-date we can now run the tests: + +```shell +% rake spec +``` + +This will execute all the [rspec tests](http://rspec-puppet.com/) tests +under [spec/defines](./spec/defines), [spec/classes](./spec/classes), +and so on. rspec tests may have the same kind of dependencies as the +module they are testing. While the module defines in its [Modulefile](./Modulefile), +rspec tests define them in [.fixtures.yml](./fixtures.yml). + +Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker) +tests. These tests spin up a virtual machine under +[VirtualBox](https://www.virtualbox.org/)) with, controlling it with +[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test +scenarios. In order to run these, you will need both of those tools +installed on your system. + +You can run them by issuing the following command + +```shell +% rake spec_clean +% rspec spec/acceptance +``` + +This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml), +install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb) +and then run all the tests under [spec/acceptance](./spec/acceptance). + +Writing Tests +------------- + +XXX getting started writing tests. + +If you have commit access to the repository +=========================================== + +Even if you have commit access to the repository, you will still need to +go through the process above, and have someone else review and merge +in your changes. The rule is that all changes must be reviewed by a +developer on the project (that did not write the code) to ensure that +all changes go through a code review process. + +Having someone other than the author of the topic branch recorded as +performing the merge is the record that they performed the code +review. + + +Additional Resources +==================== + +* [Getting additional help](http://puppetlabs.com/community/get-help) + +* [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests) + +* [Patchwork](https://patchwork.puppetlabs.com) + +* [General GitHub documentation](http://help.github.com/) + +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) + diff --git a/manifests/modules/firewall/Gemfile b/manifests/modules/firewall/Gemfile new file mode 100644 index 0000000..12fd363 --- /dev/null +++ b/manifests/modules/firewall/Gemfile @@ -0,0 +1,30 @@ +source ENV['GEM_SOURCE'] || "https://rubygems.org" + +group :development, :unit_tests do + gem 'rake', :require => false + gem 'rspec-puppet', :require => false + gem 'puppetlabs_spec_helper', :require => false + gem 'puppet-lint', :require => false + gem 'simplecov', :require => false + gem 'puppet_facts', :require => false + gem 'json', :require => false +end + +group :system_tests do + gem 'beaker-rspec', :require => false + gem 'serverspec', :require => false +end + +if facterversion = ENV['FACTER_GEM_VERSION'] + gem 'facter', facterversion, :require => false +else + gem 'facter', :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/manifests/modules/firewall/LICENSE b/manifests/modules/firewall/LICENSE new file mode 100644 index 0000000..1d196fc --- /dev/null +++ b/manifests/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/manifests/modules/firewall/README.markdown b/manifests/modules/firewall/README.markdown new file mode 100644 index 0000000..abd85b4 --- /dev/null +++ b/manifests/modules/firewall/README.markdown @@ -0,0 +1,759 @@ +#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) + * [Additional Uses for the Firewall Module](#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 module introduces the `firewall` resource, which is used to manage and configure firewall rules from within the Puppet DSL. This module offers support for iptables and ip6tables. The module also introduces the `firewallchain` resource, which allows you to manage chains or firewall lists and ebtables for bridging support. At the moment, only iptables and ip6tables chains are supported. + +The firewall module acts on your running firewall, making immediate changes as the catalog executes. Defining default pre and post rules allows you to provide global defaults for your hosts before and after any custom rules. Defining `pre` and `post` rules is also necessary to help you avoid locking yourself out of your own boxes when Puppet runs. + +##Setup + +###What firewall Affects + +* Every node running a firewall +* Firewall settings in your system +* Connection settings for managed nodes +* Unmanaged resources (get purged) + + +###Setup Requirements + +Firewall uses Ruby-based providers, so you must enable [pluginsync](http://docs.puppetlabs.com/guides/plugins_in_modules.html#enabling-pluginsync). + +###Beginning with firewall + +In the following two sections, you create new classes and then create firewall rules related to those classes. These steps are optional but provide a framework for firewall rules, which is helpful if you’re just starting to create them. + +If you already have rules in place, then you don’t need to do these two sections. However, be aware of the ordering of your firewall rules. The module will dynamically apply rules in the order they appear in the catalog, meaning a deny rule could be applied before the allow rules. This might mean the module hasn’t established some of the important connections, such as the connection to the Puppet master. + +The following steps are designed to ensure that you keep your SSH and other connections, primarily your connection to your Puppet master. If you create the `pre` and `post` classes described in the first section, then you also need to create the rules described in the second section. + +####Create the `my_fw::pre` and `my_fw::post` Classes + +This approach employs a whitelist setup, so you can define what rules you want and everything else is ignored rather than removed. + +The code in this section does the following: + +* The 'require' parameter in `firewall {}` ensures `my_fw::pre` is run before any other rules. +* In the `my_fw::post` class declaration, the 'before' parameter ensures `my_fw::post` is run after any other rules. + +Therefore, the run order is: + +* The rules in `my_fw::pre` +* Your rules (defined in code) +* The rules in `my_fw::post` + +The rules in the `pre` and `post` classes are fairly general. These two classes ensure that you retain connectivity and that you drop unmatched packets appropriately. The rules you define in your manifests are likely specific to the applications you run. + +1.) Add the `pre` class to my_fw/manifests/pre.pp. Your pre.pp file should contain any default rules to be applied first. The rules in this class should be added in the order you want them to run.2. + ```puppet + 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 reject local traffic not on loopback interface": + iniface => '! lo', + proto => 'all', + destination => '127.0.0.1/8', + action => 'reject', + }-> + firewall { '003 accept related established rules': + proto => 'all', + state => ['RELATED', 'ESTABLISHED'], + action => 'accept', + } + } + ``` + + The rules in `pre` should allow basic networking (such as ICMP and TCP) and ensure that existing connections are not closed. + +2.) Add the `post` class to my_fw/manifests/post.pp and include any default rules to be applied last. + ```puppet + class my_fw::post { + firewall { '999 drop all': + proto => 'all', + action => 'drop', + before => undef, + } + } + ``` + +####Create Firewall Rules + +The rules you create here are helpful if you don’t have any existing rules; they help you order your firewall configurations so you don’t lock yourself out of your box. + +Rules are persisted automatically between reboots, although there are known issues with ip6tables on older Debian/Ubuntu distributions. There are also known issues with ebtables. + +1.) In site.pp or another top-scope file, add the following code to 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. + + **Note** - This only purges IPv4 rules. + ```puppet + resources { "firewall": + purge => true + } + ``` +2.) Use the following code to set up the default parameters for all of the firewall rules you will establish later. These defaults will ensure that the `pre` and `post` classes are run in the correct order to avoid locking you out of your box during the first Puppet run. + + ```puppet + Firewall { + before => Class['my_fw::post'], + require => Class['my_fw::pre'], + } + ``` + +3.) Then, declare the `my_fw::pre` and `my_fw::post` classes to satisfy dependencies. You can declare these classes using an External Node Classifier or the following code: + + ```puppet + class { ['my_fw::pre', 'my_fw::post']: } + ``` + +4.) Include the `firewall` class to ensure the correct packages are installed. + + ```puppet + class { 'firewall': } + ``` + +###Upgrading + +Use these steps if you already have a version of the firewall module installed. + +####From version 0.2.0 and more recent + +Upgrade the module with the puppet module tool as normal: + + puppet module upgrade puppetlabs/firewall + +##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 for 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, for example, '000 accept all icmp requests'. _000_ runs first, _999_ 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, and rules in the `post` class, last. + +In iptables, the title of the rule is stored using the comment feature of the underlying firewall subsystem. Values must match '/^\d+[[:alpha:][:digit:][:punct:][:space:]]+$/'. + +####Examples of Default Rules + +Basic accept ICMP request example: + +```puppet +firewall { "000 accept all icmp requests": + proto => "icmp", + action => "accept", +} +``` +Drop all: + +```puppet +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 +makes sense to create your firewall rules in the profiles, so they +remain close to the services managed by the profile. + +This is an example of firewall rules in a profile: + +```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, + } +} +``` + +###Rule inversion +Firewall rules may be inverted by prefixing the value of a parameter by "! ". If the value is an array, then every item in the array must be prefixed as iptables does not understand inverting a single value. + +Parameters that understand inversion are: connmark, ctstate, destination, dport, dst\_range, dst\_type, iniface, outiface, port, proto, source, sport, src\_range, src\_type, and state. + +Examples: + +```puppet +firewall { '001 disallow esp protocol': + action => 'accept', + proto => '! esp', +} +firewall { '002 drop NEW external website packets with FIN/RST/ACK set and SYN unset': + chain => 'INPUT', + state => 'NEW', + action => 'drop', + proto => 'tcp', + sport => ['! http', '! 443'], + source => '! 10.0.0.0/8', + tcp_flags => '! FIN,SYN,RST,ACK SYN', +} +``` + +###Additional Uses for the Firewall Module + +You can 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. Apply a rule to a node as follows: + +```puppet +node 'some.node.com' { + firewall { '111 open port 111': + dport => 111 + } +} +``` + +You can also do more complex things with the `firewall` resource. This example sets up static NAT for the source network 10.1.2.0/24: +```puppet +firewall { '100 snat for network foo2': + chain => 'POSTROUTING', + jump => 'MASQUERADE', + proto => 'all', + outiface => "eth0", + source => '10.1.2.0/24', + table => 'nat', +} +``` + +The following example creates a new chain and forwards any port 5000 access to it. +```puppet +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 + +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 + +Performs the basic setup tasks required for using the firewall resources. + +At the moment this takes care of: + +* iptables-persistent package installation + +Include the `firewall` class for nodes that need to use the resources in this module: + + class { 'firewall': } + +####ensure + +Parameter that controls the state of the iptables service on your system, allowing you to disable iptables if you want. + +`ensure` can either be 'running' or 'stopped'. Default to 'running'. + +####package + +Specify the platform-specific package(s) to install. Defaults defined in `firewall::params`. + +####service + +Specify the platform-specific service(s) to start or stop. Defaults defined in `firewall::params`. + +###Type: firewall + +This type enables you to manage firewall rules within Puppet. + +####Providers +**Note:** Not all features are available with all providers. + + * `ip6tables`: Ip6tables type provider + * Required binaries: `ip6tables-save`, `ip6tables`. + * Supported features: `connection_limiting`, `dnat`, `hop_limiting`, `icmp_match`, `interface_match`, `iptables`, `isfirstfrag`, `ishasmorefrags`, `islastfrag`, `log_level`, `log_prefix`, `mark`, `owner`, `pkttype`, `rate_limiting`, `recent_limiting`, `reject_type`, `snat`, `state_match`, `tcp_flags`. + +* `iptables`: Iptables type provider + * Required binaries: `iptables-save`, `iptables`. + * Default for `kernel` == `linux`. + * Supported features: `address_type`, `connection_limiting`, `dnat`, `icmp_match`, `interface_match`, `iprange`, `ipsec_dir`, `ipsec_policy`, `iptables`, `isfragment`, `log_level`, `log_prefix`, `mark`, `owner`, `pkttype`, `rate_limiting`, `recent_limiting`, `reject_type`, `snat`, `socket`, `state_match`, `tcp_flags`, `netmap`. + +**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. + +#### Features + +* `address_type`: The ability to match on source or destination address type. + +* `connection_limiting`: Connection limiting features. + +* `dnat`: Destination NATing. + +* `hop_limiting`: Hop limiting features. + +* `icmp_match`: The ability to match ICMP types. + +* `interface_match`: Interface matching. + +* `iprange`: The ability to match on source or destination IP range. + +* `ipsec_dir`: The ability to match IPsec policy direction. + +* `ipsec_policy`: The ability to match IPsec policy. + +* `iptables`: The provider provides iptables features. + +* `isfirstfrag`: The ability to match the first fragment of a fragmented ipv6 packet. + +* `isfragment`: The ability to match fragments. + +* `ishasmorefrags`: The ability to match a non-last fragment of a fragmented ipv6 packet. + +* `islastfrag`: The ability to match the last fragment of an ipv6 packet. + +* `log_level`: The ability to control the log level. + +* `log_prefix`: The ability to add prefixes to log messages. + +* `mark`: The ability to match or set the netfilter mark value associated with the packet. + +* `mask`: The ability to match recent rules based on the ipv4 mask. + +* `owner`: The ability to match owners. + +* `pkttype`: The ability to match a packet type. + +* `rate_limiting`: Rate limiting features. + +* `recent_limiting`: The netfilter recent module. + +* `reject_type`: The ability to control reject messages. + +* `snat`: Source NATing. + +* `socket`: The ability to match open sockets. + +* `state_match`: The ability to match stateful firewall states. + +* `tcp_flags`: The ability to match on particular TCP flag settings. + +* `netmap`: The ability to map entire subnets via source or destination nat rules. + +#### Parameters + +* `action`: This is the action to perform on a match. Valid values for this action are: + * '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`). + +* `burst`: Rate limiting burst value (per second) before limit checks apply. Values must match '/^\d+$/'. Requires the `rate_limiting` feature. + +* `chain`: Name of the chain to use. You can provide a user-based chain or use one of the following built-in chains:'INPUT','FORWARD','OUTPUT','PREROUTING', or 'POSTROUTING'. The default value is 'INPUT'. Values must match '/^[a-zA-Z0-9\-_]+$/'. Requires the `iptables` feature. + +* `connlimit_above`: Connection limiting value for matched connections above n. Values must match '/^\d+$/'. Requires the `connection_limiting` feature. + +* `connlimit_mask`: Connection limiting by subnet mask for matched connections. Apply a subnet mask of /0 to /32 for IPv4, and a subnet mask of /0 to /128 for IPv6. Values must match '/^\d+$/'. Requires the `connection_limiting` feature. + +* `connmark`: Match the Netfilter mark value associated with the packet. Accepts values `mark/mask` or `mark`. These will be converted to hex if they are not hex already. Requires the `mark` feature. + +* `ctstate`: Matches a packet based on its state in the firewall stateful inspection table, using the conntrack module. Valid values are: 'INVALID', 'ESTABLISHED', 'NEW', 'RELATED'. Requires the `state_match` feature. + +* `destination`: 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. + + For some firewall providers you can pass a range of ports in the format: 'start number-end number'. For example, '1-1024' would cover ports 1 to 1024. + +* `dport`: 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: 'start number-end number'. For example, '1-1024' would cover ports 1 to 1024. + +* `dst_range`: 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. Values must match '0.0.0.0-0.0.0.0' through '255.255.255.255-255.255.255.255'. Requires the `iprange` feature. + +* `dst_type`: The destination address type. For example: `dst_type => 'LOCAL'`. + + Valid values are: + + * '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': an unroutable address + * 'XRESOLVE: an unresolvable address + + Requires the `address_type` feature. + +* `ensure`: Ensures that the resource is present. Valid values are 'present', 'absent'. The default is 'present'. + +* `gid`: GID or Group owner matching rule. Accepts a string argument only, as iptables does not accept multiple gid in a single statement. Requires the `owner` feature. + +* `hop_limit`: Hop limiting value for matched packets. Values must match '/^\d+$/'. Requires the `hop_limiting` feature. + +* `icmp`: When matching ICMP packets, this indicates the type of ICMP packet to match. A value of 'any' is not supported. To match any type of ICMP packet, the parameter should be omitted or undefined. Requires the `icmp_match` feature. + +* `iniface`: Input interface to filter on. Values must match '/^!?\s?[a-zA-Z0-9\-\._\+\:]+$/'. Requires the `interface_match` feature. Supports interface alias (eth0:0) and negation. + +* `ipsec_dir`: Sets the ipsec policy direction. Valid values are 'in', 'out'. Requires the `ipsec_dir` feature. + +* `ipsec_policy`: Sets the ipsec policy type. Valid values are 'none', 'ipsec'. Requires the `ipsec_policy` feature. + +* `ipset`: Matches IP sets. Value must be 'ipset_name (src|dst|src,dst)' and can be negated by putting ! in front. Requires ipset kernel module. + +* `isfirstfrag`: If true, matches when the packet is the first fragment of a fragmented ipv6 packet. Cannot be negated. Supported by ipv6 only. Valid values are 'true', 'false'. Requires the `isfirstfrag` feature. + +* `isfragment`: If 'true', matches when the packet is a tcp fragment of a fragmented packet. Supported by iptables only. Valid values are 'true', 'false'. Requires features `isfragment`. + +* `ishasmorefrags`: If 'true', matches when the packet has the 'more fragments' bit set. Supported by ipv6 only. Valid values are 'true', 'false'. Requires the `ishasmorefrags` feature. + +* `islastfrag`: If true, matches when the packet is the last fragment of a fragmented ipv6 packet. Supported by ipv6 only. Valid values are 'true', 'false'. Requires the `islastfrag`. + +* `jump`: The value for the iptables `--jump` parameter. Any valid chain name is allowed, but normal values are: 'QUEUE', 'RETURN', 'DNAT', 'SNAT', 'LOG', 'MASQUERADE', 'REDIRECT', 'MARK'. + + For the values 'ACCEPT', 'DROP', and 'REJECT', you must use the generic `action` parameter. This is to enforce the use of generic parameters where possible for maximum cross-platform modeling. + + If you set both `accept` and `jump` parameters, you will get an error, because only one of the options should be set. Requires the `iptables` feature. + +* `limit`: 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'. Requires the `rate_limiting` feature. + +* `line`: Read-only property for caching the rule line. + +* `log_level`: When combined with `jump => 'LOG'` specifies the system log level to log to. Requires the `log_level` feature. + +* `log_prefix`: When combined with `jump => 'LOG'` specifies the log prefix to use when logging. Requires the `log_prefix` feature. + +* `mask`: Sets the mask to use when `recent` is enabled. Requires the `mask` feature. + +* `name`: The canonical name of the rule. This name is also used for ordering, so make sure you prefix the rule with a number. For example: + +```puppet +firewall { '000 this runs first': + # this rule will run first +} +firewall { '999 this runs last': + # this rule will run last +} + ``` + + Depending on the provider, the name of the rule can be stored using the comment feature of the underlying firewall subsystem. Values must match '/^\d+[[:alpha:][:digit:][:punct:][:space:]]+$/'. + +* `outiface`: Output interface to filter on. Values must match '/^!?\s?[a-zA-Z0-9\-\._\+\:]+$/'. Requires the `interface_match` feature. Supports interface alias (eth0:0) and negation. + +* `pkttype`: Sets the packet type to match. Valid values are: 'unicast', 'broadcast', and'multicast'. Requires the `pkttype` feature. + +* `port`: 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: 'start number-end number'. For example, '1-1024' would cover ports 1 to 1024. + +* `proto`: The specific protocol to match for this rule. This is 'tcp' by default. Valid values are: + * 'tcp' + * 'udp' + * 'icmp' + * 'ipv6-icmp' + * 'esp' + * 'ah' + * 'vrrp' + * 'igmp' + * 'ipencap' + * 'ospf' + * 'gre' + * 'all' + +* `provider`: The specific backend to use for this firewall resource. You will seldom need to specify this --- Puppet will usually discover the appropriate provider for your platform. Available providers are ip6tables and iptables. See the [Providers](#providers) section above for details about these providers. + + * `random`: When using a `jump` value of 'MASQUERADE', 'DNAT', 'REDIRECT', or 'SNAT', this boolean will enable randomized port mapping. Valid values are true or false. Requires the `dnat` feature. + +* `rdest`: If boolean 'true', adds the destination IP address to the list. Valid values are true or false. Requires the `recent_limiting` feature and the `recent` parameter. + +* `reap`: Can only be used in conjunction with the `rseconds` parameter. If boolean 'true', this will purge entries older than 'seconds' as specified in `rseconds`. Valid values are true or false. Requires the `recent_limiting` feature and the `recent` parameter. + +* `recent`: Enable the recent module. Valid values are: 'set', 'update', 'rcheck', or 'remove'. For example: + +```puppet +# 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', +} +``` + + Requires the `recent_limiting` feature. + +* `reject`: When combined with `jump => 'REJECT'`, you can specify a different ICMP response to be sent back to the packet sender. Requires the `reject_type` feature. + +* `rhitcount`: Used in conjunction with `recent => 'update'` or `recent => 'rcheck'`. When used, this will narrow the match to happen only when the address is in the list and packets greater than or equal to the given value have been received. Requires the `recent_limiting` feature and the `recent` parameter. + +* `rname`: Specify the name of the list. Takes a string argument. Requires the `recent_limiting` feature and the `recent` parameter. + +* `rseconds`: Used in conjunction with `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. Requires the `recent_limiting` feature and the `recent` parameter. + +* `rsource`: If boolean 'true', adds the source IP address to the list. Valid values are 'true', 'false'. Requires the `recent_limiting` feature and the `recent` parameter. + +* `rttl`: May only be used in conjunction with `recent => 'rcheck'` or `recent => 'update'`. If boolean 'true', this will narrow the match to happen only when the address is in the list and the TTL of the current packet matches that of the packet that hit the `recent => 'set'` rule. If you have problems with DoS attacks via bogus packets from fake source addresses, this parameter may help. Valid values are 'true', 'false'. Requires the `recent_limiting` feature and the `recent` parameter. + +* `set_mark`: Set the Netfilter mark value associated with the packet. Accepts either 'mark/mask' or 'mark'. These will be converted to hex if they are not already. Requires the `mark` feature. + +* `socket`: If 'true', matches if an open socket can be found by doing a socket lookup on the packet. Valid values are 'true', 'false'. Requires the `socket` feature. + +* `source`: 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. + +* `sport`: 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:'start number-end number'. For example, '1-1024' would cover ports 1 to 1024. + +* `src_range`: 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. Values must match '0.0.0.0-0.0.0.0' through '255.255.255.255-255.255.255.255'. Requires the `iprange` feature. + +* `src_type`: Specify the source address type. For example: `src_type => 'LOCAL'`. + + Valid values are: + + * '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': an unroutable address. + * 'XRESOLVE': an unresolvable address. + + Requires the `address_type` feature. + +* `stat_every`: Match one packet every nth packet. Requires `stat_mode => 'nth'` + +* `stat_mode`: Set the matching mode for statistic matching. Supported modes are `random` and `nth`. + +* `stat_packet`: Set the initial counter value for the nth mode. Must be between 0 and the value of `stat_every`. Defaults to 0. Requires `stat_mode => 'nth'` + +* `stat_probability`: Set the probability from 0 to 1 for a packet to be randomly matched. It works only with `stat_mode => 'random'`. + +* `state`: Matches a packet based on its state in the firewall stateful inspection table. Valid values are: 'INVALID', 'ESTABLISHED', 'NEW', 'RELATED'. Requires the `state_match` feature. + +* `table`: Table to use. Valid values are: 'nat', 'mangle', 'filter', 'raw', 'rawpost'. By default the setting is 'filter'. Requires the `iptables` feature. + +* `tcp_flags`: Match when the TCP flags are as specified. Set as 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 flags in the order that iptables `--list` rules would list them to avoid having Puppet think you changed the flags. For 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. Requires the `tcp_flags` feature. + +* `todest`: When using `jump => 'DNAT'`, you can specify the new destination address using this parameter. Requires the `dnat` feature. + +* `toports`: For DNAT this is the port that will replace the destination port. Requires the `dnat` feature. + +* `tosource`: When using `jump => 'SNAT'`, you can specify the new source address using this parameter. Requires the `snat` feature. + +* `to`: When using `jump => 'NETMAP'`, you can specify a source or destination subnet to nat to. Requires the `netmap` feature`. + +* `uid`: UID or Username owner matching rule. Accepts a string argument only, as iptables does not accept multiple uid in a single statement. Requires the `owner` feature. + +###Type: firewallchain + +Enables you to manage rule chains for firewalls. + +Currently this type supports only iptables, ip6tables, and ebtables on Linux. It also 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. + +####Providers + +`iptables_chain` is the only provider that supports firewallchain. + +####Features + +* `iptables_chain`: The provider provides iptables chain features. +* `policy`: Default policy (inbuilt chains only). + +####Parameters + +* `ensure`: Ensures that the resource is present. Valid values are 'present', 'absent'. + +* `ignore`: 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: a regex such as '/foo/i' can be written as '(?i)foo' or '(?i:foo)'. Only when purge is 'true'. + + Full example: +```puppet +firewallchain { 'INPUT:filter:IPv4': + purge => true, + ignore => [ + # ignore the fail2ban jump rule + '-j fail2ban-ssh', + # ignore any rules with "ignore" (case insensitive) in the comment in the rule + '--comment "[^"](?i:ignore)[^"]"', + ], +} +``` + +* `name`: Specify the canonical name of the chain. For iptables the format must be {chain}:{table}:{protocol}. + +* `policy`: Set the action the packet will perform when the end of the chain is reached. It can only be set on inbuilt chains ('INPUT', 'FORWARD', 'OUTPUT', 'PREROUTING', 'POSTROUTING'). Valid values are: + + * '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 to the default of inbuilt chains. + +* `provider`: The specific backend to use for this firewallchain resource. You will seldom need to specify this --- Puppet will usually discover the appropriate provider for your platform. The only available provider is: + + `iptables_chain`: iptables chain provider + + * Required binaries: `ebtables-save`, `ebtables`, `ip6tables-save`, `ip6tables`, `iptables-save`, `iptables`. + * Default for `kernel` == `linux`. + * Supported features: `iptables_chain`, `policy`. + +* `purge`: Purge unmanaged firewall rules in this chain. Valid values are 'false', 'true'. + +###Fact: ip6tables_version + +A Facter fact that can be used to determine what the default version of ip6tables is for your operating system/distribution. + +###Fact: iptables_version + +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) + +###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: + + RS_SET=ubuntu-1404-x64 rspec spec/acceptance + RS_SET=centos-64-x64 rspec spec/acceptance diff --git a/manifests/modules/firewall/Rakefile b/manifests/modules/firewall/Rakefile new file mode 100644 index 0000000..e3be95b --- /dev/null +++ b/manifests/modules/firewall/Rakefile @@ -0,0 +1,10 @@ +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' + +PuppetLint.configuration.fail_on_warnings +PuppetLint.configuration.send('relative') +PuppetLint.configuration.send('disable_80chars') +PuppetLint.configuration.send('disable_class_inherits_from_params_class') +PuppetLint.configuration.send('disable_documentation') +PuppetLint.configuration.send('disable_single_quote_string_with_variables') +PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"] diff --git a/manifests/modules/firewall/lib/facter/ip6tables_version.rb b/manifests/modules/firewall/lib/facter/ip6tables_version.rb new file mode 100644 index 0000000..3dce27f --- /dev/null +++ b/manifests/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/manifests/modules/firewall/lib/facter/iptables_persistent_version.rb b/manifests/modules/firewall/lib/facter/iptables_persistent_version.rb new file mode 100644 index 0000000..80bf9de --- /dev/null +++ b/manifests/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/manifests/modules/firewall/lib/facter/iptables_version.rb b/manifests/modules/firewall/lib/facter/iptables_version.rb new file mode 100644 index 0000000..6f7ae56 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/lib/puppet/provider/firewall.rb b/manifests/modules/firewall/lib/puppet/provider/firewall.rb new file mode 100644 index 0000000..c6b0b10 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/lib/puppet/provider/firewall/ip6tables.rb b/manifests/modules/firewall/lib/puppet/provider/firewall/ip6tables.rb new file mode 100644 index 0000000..e517519 --- /dev/null +++ b/manifests/modules/firewall/lib/puppet/provider/firewall/ip6tables.rb @@ -0,0 +1,143 @@ +Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source => :ip6tables 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', + }) + + confine :kernel => :linux + + 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", + :hop_limit => "-m hl --hl-eq", + :icmp => "-m icmp6 --icmpv6-type", + :iniface => "-i", + :isfirstfrag => "-m frag --fragid 0 --fragfirst", + :ishasmorefrags => "-m frag --fragid 0 --fragmore", + :islastfrag => "-m frag --fragid 0 --fraglast", + :jump => "-j", + :limit => "-m limit --limit", + :log_level => "--log-level", + :log_prefix => "--log-prefix", + :name => "-m comment --comment", + :outiface => "-o", + :pkttype => "-m pkttype --pkt-type", + :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", + :sport => "-m multiport --sports", + :stat_every => '--every', + :stat_mode => "-m statistic --mode", + :stat_packet => '--packet', + :stat_probability => '--probability', + :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", + } + + # 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, :tcp_flags, :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/manifests/modules/firewall/lib/puppet/provider/firewall/iptables.rb b/manifests/modules/firewall/lib/puppet/provider/firewall/iptables.rb new file mode 100644 index 0000000..233d960 --- /dev/null +++ b/manifests/modules/firewall/lib/puppet/provider/firewall/iptables.rb @@ -0,0 +1,581 @@ +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 :netmap + 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 + has_feature :ipset + + optional_commands({ + :iptables => 'iptables', + :iptables_save => 'iptables-save', + }) + + defaultfor :kernel => :linux + confine :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", + :dport => ["-m multiport --dports", "--dport"], + :dst_range => "-m iprange --dst-range", + :dst_type => "-m addrtype --dst-type", + :gid => "-m owner --gid-owner", + :icmp => "-m icmp --icmp-type", + :iniface => "-i", + :ipsec_dir => "-m policy --dir", + :ipsec_policy => "--pol", + :ipset => "-m set --match-set", + :isfragment => "-f", + :jump => "-j", + :limit => "-m limit --limit", + :log_level => "--log-level", + :log_prefix => "--log-prefix", + :mac_source => ["-m mac --mac-source", "--mac-source"], + :mask => '--mask', + :name => "-m comment --comment", + :outiface => "-o", + :pkttype => "-m pkttype --pkt-type", + :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", + :sport => ["-m multiport --sports", "--sport"], + :src_range => "-m iprange --src-range", + :src_type => "-m addrtype --src-type", + :stat_every => '--every', + :stat_mode => "-m statistic --mode", + :stat_packet => '--packet', + :stat_probability => '--probability', + :state => "-m state --state", + :table => "-t", + :tcp_flags => "-m tcp --tcp-flags", + :todest => "--to-destination", + :toports => "--to-ports", + :tosource => "--to-source", + :to => "--to", + :uid => "-m owner --uid-owner", + } + + # 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, + :stat_mode, :stat_every, :stat_packet, :stat_probability, + :src_range, :dst_range, :tcp_flags, :gid, :uid, :mac_source, :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, :ipset, :jump, :todest, + :tosource, :toports, :to, :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.gsub(/(!\s+)?--tcp-flags (\S*) (\S*)/, '--tcp-flags "\1\2 \3"') + # ditto for --match-set + values = values.sub(/(!\s+)?--match-set (\S*) (\S*)/, '--match-set "\1\2 \3"') + # we do a similar thing for negated address masks (source and destination). + values = values.gsub(/(-\S+) (!)\s?(\S*)/,'\1 "\2 \3"') + # the actual rule will have the ! mark before the option. + values = values.gsub(/(!)\s*(-\S+)\s*(\S*)/, '\2 "\1 \3"') + # The match extension for tcp & udp are optional and throws off the @resource_map. + values = values.gsub(/-m (tcp|udp) (--(s|d)port|-m multiport)/, '\2') + # '--pol ipsec' takes many optional arguments; we cheat again by adding " around them + values = values.sub(/ + --pol\sipsec + (\s--strict)? + (\s--reqid\s\S+)? + (\s--spi\s\S+)? + (\s--proto\s\S+)? + (\s--mode\s\S+)? + (\s--tunnel-dst\s\S+)? + (\s--tunnel-src\s\S+)? + (\s--next)?/x, + '--pol "ipsec\1\2\3\4\5\6\7\8" ' + ) + + # 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 + ##################### + + [: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 + + # Invert any rules that are prefixed with a '!' + [ + :connmark, + :ctstate, + :destination, + :dport, + :dst_range, + :dst_type, + :ipset, + :port, + :proto, + :source, + :sport, + :src_range, + :src_type, + :state, + ].each do |prop| + if hash[prop] and hash[prop].is_a?(Array) + # find if any are negated, then negate all if so + should_negate = hash[prop].index do |value| + value.match(/^(!)\s+/) + end + hash[prop] = hash[prop].collect { |v| + "! #{v.sub(/^!\s+/,'')}" + } if should_negate + elsif hash[prop] + m = hash[prop].match(/^(!?)\s?(.*)/) + neg = "! " if m[1] == "!" + if [:source,:destination].include?(prop) + # Normalise all rules to CIDR notation. + hash[prop] = "#{neg}#{Puppet::Util::IPCidr.new(m[2]).cidr}" + else + hash[prop] = "#{neg}#{m[2]}" + end + 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(' ') + args = args.flatten + + # 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, '!') + elsif resource_value.is_a?(Symbol) and resource_value.to_s.match(/^!/) then + #ruby 1.8.7 can't .match Symbols ------------------ ^ + resource_value = resource_value.to_s.sub!(/^!\s*/, '').to_sym + args.insert(-2, '!') + elsif resource_value.is_a?(Array) + should_negate = resource_value.index do |value| + #ruby 1.8.7 can't .match symbols + value.to_s.match(/^(!)\s+/) + end + if should_negate + resource_value, wrong_values = resource_value.collect do |value| + if value.is_a?(String) + wrong = value if ! value.match(/^!\s+/) + [value.sub(/^!\s*/, ''),wrong] + else + [value,nil] + end + end.transpose + wrong_values = wrong_values.compact + if ! wrong_values.empty? + fail "All values of the '#{res}' property must be prefixed with a '!' when inverting, but '#{wrong_values.join("', '")}' #{wrong_values.length>1?"are":"is"} not prefixed; aborting" + end + args.insert(-2, '!') + end + 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 or res == :ipset + 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/manifests/modules/firewall/lib/puppet/provider/firewallchain/iptables_chain.rb b/manifests/modules/firewall/lib/puppet/provider/firewallchain/iptables_chain.rb new file mode 100644 index 0000000..df166f6 --- /dev/null +++ b/manifests/modules/firewall/lib/puppet/provider/firewallchain/iptables_chain.rb @@ -0,0 +1,179 @@ +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 + confine :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/manifests/modules/firewall/lib/puppet/type/firewall.rb b/manifests/modules/firewall/lib/puppet/type/firewall.rb new file mode 100644 index 0000000..85c6464 --- /dev/null +++ b/manifests/modules/firewall/lib/puppet/type/firewall.rb @@ -0,0 +1,1165 @@ +# 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, iptables-persistent, or iptables-services 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 :netmap, "NET MAPping" + 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" + feature :mask, "Ability to match recent rules based on the ipv4 mask" + feature :ipset, "Match against specified ipset list" + + # 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, :cbt, :all].collect do |proto| + [proto, "! #{proto}".to_sym] + end.flatten) + 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. Supports interface alias like eth0:0. + To negate the match try this: + + iniface => '! lo', + + EOS + newvalues(/^!?\s?[a-zA-Z0-9\-\._\+\:]+$/) + end + + newproperty(:outiface, :required_features => :interface_match) do + desc <<-EOS + Output interface to filter on. Supports interface alias like eth0:0. + To negate the match try this: + + outiface => '! lo', + + EOS + newvalues(/^!?\s?[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(:to, :required_features => :netmap) do + desc <<-EOS + For NETMAP this will replace the destination IP + 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. May take a combination of arguments for any flags that can be passed to `--pol ipsec` such as: `--strict`, `--reqid 100`, `--next`, `--proto esp`, etc. + 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(:stat_mode) do + desc <<-EOS + Set the matching mode for statistic matching. Supported modes are `random` and `nth`. + EOS + + newvalues(:nth, :random) + end + + newproperty(:stat_every) do + desc <<-EOS + Match one packet every nth packet. Requires `stat_mode => 'nth'` + EOS + + validate do |value| + unless value =~ /^\d+$/ + raise ArgumentError, <<-EOS + stat_every value must be a digit + EOS + end + + unless value.to_i > 0 + raise ArgumentError, <<-EOS + stat_every value must be larger than 0 + EOS + end + end + end + + newproperty(:stat_packet) do + desc <<-EOS + Set the initial counter value for the nth mode. Must be between 0 and the value of `stat_every`. Defaults to 0. Requires `stat_mode => 'nth'` + EOS + + newvalues(/^\d+$/) + end + + newproperty(:stat_probability) do + desc <<-EOS + Set the probability from 0 to 1 for a packet to be randomly matched. It works only with `stat_mode => 'random'`. + EOS + + validate do |value| + unless value =~ /^([01])\.(\d+)$/ + raise ArgumentError, <<-EOS + stat_probability must be between 0.0 and 1.0 + EOS + end + + if $1.to_i == 1 && $2.to_i != 0 + raise ArgumentError, <<-EOS + start_probability must be between 0.0 and 1.0 + EOS + end + end + end + + newproperty(:mask, :required_features => :mask) do + desc <<-EOS + Sets the mask to use when `recent` is enabled. + EOS + end + + newproperty(:ipset, :required_features => :ipset) do + desc <<-EOS + Matches against the specified ipset list. + Requires ipset kernel module. + The value is the name of the blacklist, followed by a space, and then + 'src' and/or 'dst' separated by a comma. + For example: 'blacklist src,dst' + EOS + end + + newparam(:line) do + desc <<-EOS + Read-only property for caching the rule line. + EOS + end + + newproperty(:mac_source) do + desc <<-EOS + MAC Source + EOS + newvalues(/^([0-9a-f]{2}[:]){5}([0-9a-f]{2})$/i) + 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 iptables-services} + 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(: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(:table).to_s =~ /mangle/ + self.fail "Parameter set_mark only applies to " \ + "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 == "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 + + [:stat_packet, :stat_every, :stat_probability].each do |param| + if value(param) && ! value(:stat_mode) + self.fail "Parameter '#{param.to_s}' requires 'stat_mode' to be set" + end + end + + if value(:stat_packet) && value(:stat_mode) != :nth + self.fail "Parameter 'stat_packet' requires 'stat_mode' to be set to 'nth'" + end + + if value(:stat_every) && value(:stat_mode) != :nth + self.fail "Parameter 'stat_every' requires 'stat_mode' to be set to 'nth'" + end + + if value(:stat_probability) && value(:stat_mode) != :random + self.fail "Parameter 'stat_probability' requires 'stat_mode' to be set to 'random'" + end + + end +end diff --git a/manifests/modules/firewall/lib/puppet/type/firewallchain.rb b/manifests/modules/firewall/lib/puppet/type/firewallchain.rb new file mode 100644 index 0000000..b962a0a --- /dev/null +++ b/manifests/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, iptables-persistent, or iptables-services 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 iptables-services} + 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/manifests/modules/firewall/lib/puppet/util/firewall.rb b/manifests/modules/firewall/lib/puppet/util/firewall.rb new file mode 100644 index 0000000..c5a78b8 --- /dev/null +++ b/manifests/modules/firewall/lib/puppet/util/firewall.rb @@ -0,0 +1,226 @@ +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 + + m = value.to_s.match(/^(!\s+)?(\S+)/) + if m[2].match(/^\d+(-\d+)?$/) + return "#{m[1]}#{m[2]}" + else + return "#{m[1]}#{Socket.getservbyname(m[2], 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' && ['RedHat','CentOS','Scientific','SL','SLC','Ascendos','CloudLinux','PSBM','OracleLinux','OVS','OEL','Amazon','XenServer'].include?(Facter.value(:operatingsystem)) && 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 + if Puppet::Util::Package.versioncmp(persist_ver, '1.0') > 0 + %w{/usr/sbin/service netfilter-persistent save} + else + %w{/usr/sbin/service iptables-persistent save} + end + 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/manifests/modules/firewall/lib/puppet/util/ipcidr.rb b/manifests/modules/firewall/lib/puppet/util/ipcidr.rb new file mode 100644 index 0000000..87e8d5e --- /dev/null +++ b/manifests/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/manifests/modules/firewall/manifests/init.pp b/manifests/modules/firewall/manifests/init.pp new file mode 100644 index 0000000..97ed273 --- /dev/null +++ b/manifests/modules/firewall/manifests/init.pp @@ -0,0 +1,40 @@ +# = 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, + $service_name = $::firewall::params::service_name, + $package_name = $::firewall::params::package_name, +) inherits ::firewall::params { + case $ensure { + /^(running|stopped)$/: { + # Do nothing. + } + default: { + fail("${title}: Ensure value '${ensure}' is not supported") + } + } + + case $::kernel { + 'Linux': { + class { "${title}::linux": + ensure => $ensure, + service_name => $service_name, + package_name => $package_name, + } + } + default: { + fail("${title}: Kernel '${::kernel}' is not currently supported") + } + } +} diff --git a/manifests/modules/firewall/manifests/linux.pp b/manifests/modules/firewall/manifests/linux.pp new file mode 100644 index 0000000..21ec784 --- /dev/null +++ b/manifests/modules/firewall/manifests/linux.pp @@ -0,0 +1,59 @@ +# = 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, + $service_name = $::firewall::params::service_name, + $package_name = $::firewall::params::package_name, +) inherits ::firewall::params { + $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, + package_name => $package_name, + service_name => $service_name, + require => Package['iptables'], + } + } + 'Debian', 'Ubuntu': { + class { "${title}::debian": + ensure => $ensure, + enable => $enable, + package_name => $package_name, + service_name => $service_name, + require => Package['iptables'], + } + } + 'Archlinux': { + class { "${title}::archlinux": + ensure => $ensure, + enable => $enable, + package_name => $package_name, + service_name => $service_name, + require => Package['iptables'], + } + } + default: {} + } +} diff --git a/manifests/modules/firewall/manifests/linux/archlinux.pp b/manifests/modules/firewall/manifests/linux/archlinux.pp new file mode 100644 index 0000000..cfe1a69 --- /dev/null +++ b/manifests/modules/firewall/manifests/linux/archlinux.pp @@ -0,0 +1,43 @@ +# = 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_name = $::firewall::params::service_name, + $package_name = $::firewall::params::package_name, +) inherits ::firewall::params { + if $package_name { + package { $package_name: + ensure => $ensure, + } + } + + service { $service_name: + ensure => $ensure, + enable => $enable, + hasstatus => true, + } + + file { '/etc/iptables/iptables.rules': + ensure => present, + before => Service[$service_name], + } + + file { '/etc/iptables/ip6tables.rules': + ensure => present, + before => Service[$service_name], + } +} diff --git a/manifests/modules/firewall/manifests/linux/debian.pp b/manifests/modules/firewall/manifests/linux/debian.pp new file mode 100644 index 0000000..87ec123 --- /dev/null +++ b/manifests/modules/firewall/manifests/linux/debian.pp @@ -0,0 +1,49 @@ +# = 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, + $service_name = $::firewall::params::service_name, + $package_name = $::firewall::params::package_name, +) inherits ::firewall::params { + + if $package_name { + package { $package_name: + 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[$package_name], + } + } else { + # This isn't a real service/daemon. The start action loads rules, so just + # needs to be called on system boot. + service { $service_name: + ensure => undef, + enable => $enable, + hasstatus => true, + require => Package[$package_name], + } + } +} diff --git a/manifests/modules/firewall/manifests/linux/redhat.pp b/manifests/modules/firewall/manifests/linux/redhat.pp new file mode 100644 index 0000000..b3db4b7 --- /dev/null +++ b/manifests/modules/firewall/manifests/linux/redhat.pp @@ -0,0 +1,54 @@ +# = Class: firewall::linux::redhat +# +# Manages the `iptables` service on RedHat-alike systems. +# +# == Parameters: +# +# [*ensure*] +# Ensure parameter passed onto Service[] resources. +# Default: running +# +# [*enable*] +# Enable parameter passed onto Service[] resources. +# Default: true +# +class firewall::linux::redhat ( + $ensure = running, + $enable = true, + $service_name = $::firewall::params::service_name, + $package_name = $::firewall::params::package_name, +) inherits ::firewall::params { + + # 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 != 'Fedora' and versioncmp($::operatingsystemrelease, '7.0') >= 0) + or ($::operatingsystem == 'Fedora' and versioncmp($::operatingsystemrelease, '15') >= 0) { + service { 'firewalld': + ensure => stopped, + enable => false, + before => Package[$package_name], + } + } + + if $package_name { + package { $package_name: + ensure => present, + before => Service[$service_name], + } + } + + service { $service_name: + ensure => $ensure, + enable => $enable, + hasstatus => true, + require => File['/etc/sysconfig/iptables'], + } + + file { '/etc/sysconfig/iptables': + ensure => present, + owner => 'root', + group => 'root', + mode => '0600', + } +} diff --git a/manifests/modules/firewall/manifests/params.pp b/manifests/modules/firewall/manifests/params.pp new file mode 100644 index 0000000..d990a1a --- /dev/null +++ b/manifests/modules/firewall/manifests/params.pp @@ -0,0 +1,41 @@ +class firewall::params { + case $::osfamily { + 'RedHat': { + case $::operatingsystem { + 'Archlinux': { + $service_name = ['iptables','ip6tables'] + $package_name = undef + } + 'Fedora': { + if versioncmp($::operatingsystemrelease, '15') >= 0 { + $package_name = 'iptables-services' + } else { + $package_name = undef + } + $service_name = 'iptables' + } + default: { + if versioncmp($::operatingsystemrelease, '7.0') >= 0 { + $package_name = 'iptables-services' + } else { + $package_name = undef + } + $service_name = 'iptables' + } + } + } + 'Debian': { + if $::operatingsystem == 'Debian' and versioncmp($::operatingsystemrelease, '8.0') >= 0 { + $service_name = 'netfilter-persistent' + $package_name = 'netfilter-persistent' + } else { + $service_name = 'iptables-persistent' + $package_name = 'iptables-persistent' + } + } + default: { + $package_name = undef + $service_name = 'iptables' + } + } +} diff --git a/manifests/modules/firewall/metadata.json b/manifests/modules/firewall/metadata.json new file mode 100644 index 0000000..ebc11a3 --- /dev/null +++ b/manifests/modules/firewall/metadata.json @@ -0,0 +1,78 @@ +{ + "name": "puppetlabs-firewall", + "version": "1.3.0", + "author": "Puppet Labs", + "summary": "Manages Firewalls such as iptable", + "license": "Apache-2.0", + "source": "https://github.com/puppetlabs/puppetlabs-firewall", + "project_page": "http://github.com/puppetlabs/puppetlabs-firewall", + "issues_url": "https://tickets.puppetlabs.com/browse/MODULES", + "operatingsystem_support": [ + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "6", + "7" + ] + }, + { + "operatingsystem": "Scientific", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "SLES", + "operatingsystemrelease": [ + "11 SP1", + "12" + ] + }, + { + "operatingsystem": "Debian", + "operatingsystemrelease": [ + "6", + "7" + ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ + "10.04", + "12.04", + "14.04" + ] + } + ], + "requirements": [ + { + "name": "pe", + "version_requirement": "3.x" + }, + { + "name": "puppet", + "version_requirement": "3.x" + } + ], + "dependencies": [ + + ] +} diff --git a/manifests/modules/firewall/spec/acceptance/change_source_spec.rb b/manifests/modules/firewall/spec/acceptance/change_source_spec.rb new file mode 100644 index 0000000..f591108 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/class_spec.rb b/manifests/modules/firewall/spec/acceptance/class_spec.rb new file mode 100644 index 0000000..4a9751a --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/connlimit_spec.rb b/manifests/modules/firewall/spec/acceptance/connlimit_spec.rb new file mode 100644 index 0000000..bb049a9 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/connmark_spec.rb b/manifests/modules/firewall/spec/acceptance/connmark_spec.rb new file mode 100644 index 0000000..b3409ab --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/firewall_spec.rb b/manifests/modules/firewall/spec/acceptance/firewall_spec.rb new file mode 100644 index 0000000..ac172b0 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/firewall_spec.rb @@ -0,0 +1,1734 @@ +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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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 + + describe 'tcp_flags' do + context 'FIN,SYN ACK' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '593 - test': + proto => tcp, + action => accept, + tcp_flags => 'FIN,SYN ACK', + 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 tcp --tcp-flags FIN,SYN ACK -m comment --comment "593 - 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 'mac_source' do + context '0A:1B:3C:4D:5E:6F' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '610 - test': + ensure => present, + source => '10.1.5.28/32', + mac_source => '0A:1B:3C:4D:5E:6F', + chain => 'INPUT', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should contain the rule' do + shell('iptables-save') do |r| + if (fact('osfamily') == 'RedHat' and fact('operatingsystemmajrelease') == '5') + expect(r.stdout).to match(/-A INPUT -s 10.1.5.28 -p tcp -m mac --mac-source 0A:1B:3C:4D:5E:6F -m comment --comment "610 - test"/) + else + expect(r.stdout).to match(/-A INPUT -s 10.1.5.28\/(32|255\.255\.255\.255) -p tcp -m mac --mac-source 0A:1B:3C:4D:5E:6F -m comment --comment "610 - test"/) + end + 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 + + describe 'to' do + context 'Destination netmap 192.168.1.1' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '569 - test': + proto => tcp, + table => 'nat', + chain => 'PREROUTING', + jump => 'NETMAP', + source => '200.200.200.200', + to => '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 "611 - test" -j NETMAP --to 192.168.1.1/) + end + end + end + + context 'Source netmap 192.168.1.1' do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '569 - test': + proto => tcp, + table => 'nat', + chain => 'POSTROUTING', + jump => 'NETMAP', + destination => '200.200.200.200', + to => '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 -d 200.200.200.200(\/32)? -p tcp -m comment --comment "611 - test" -j NETMAP --to 192.168.1.1/) + end + end + end + end + +end diff --git a/manifests/modules/firewall/spec/acceptance/firewallchain_spec.rb b/manifests/modules/firewall/spec/acceptance/firewallchain_spec.rb new file mode 100644 index 0000000..fab20b3 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/firewallchain_spec.rb @@ -0,0 +1,131 @@ +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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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/manifests/modules/firewall/spec/acceptance/invert_spec.rb b/manifests/modules/firewall/spec/acceptance/invert_spec.rb new file mode 100644 index 0000000..16af9b8 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/invert_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper_acceptance' + +describe 'firewall type', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do + before(:all) do + iptables_flush_all_tables + end + + context "inverting rules" do + it 'applies' do + pp = <<-EOS + class { '::firewall': } + firewall { '601 disallow esp protocol': + action => 'accept', + proto => '! esp', + } + firewall { '602 drop NEW external website packets with FIN/RST/ACK set and SYN unset': + chain => 'INPUT', + state => 'NEW', + action => 'drop', + proto => 'tcp', + sport => ['! http', '! 443'], + source => '! 10.0.0.0/8', + tcp_flags => '! FIN,SYN,RST,ACK SYN', + } + EOS + + apply_manifest(pp, :catch_failures => true) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + end + + it 'should contain the rules' do + shell('iptables-save') do |r| + if (fact('osfamily') == 'RedHat' and fact('operatingsystemmajrelease') == '5') + expect(r.stdout).to match(/-A INPUT -p ! esp -m comment --comment "601 disallow esp protocol" -j ACCEPT/) + expect(r.stdout).to match(/-A INPUT -s ! 10\.0\.0\.0\/255\.0\.0\.0 -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m multiport --sports ! 80,443 -m comment --comment "602 drop NEW external website packets with FIN\/RST\/ACK set and SYN unset" -m state --state NEW -j DROP/) + else + expect(r.stdout).to match(/-A INPUT ! -p esp -m comment --comment "601 disallow esp protocol" -j ACCEPT/) + expect(r.stdout).to match(/-A INPUT ! -s 10\.0\.0\.0\/8 -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m multiport ! --sports 80,443 -m comment --comment "602 drop NEW external website packets with FIN\/RST\/ACK set and SYN unset" -m state --state NEW -j DROP/) + end + end + end + end + context "inverting partial array rules" do + it 'raises a failure' do + pp = <<-EOS + class { '::firewall': } + firewall { '603 drop 80,443 traffic': + chain => 'INPUT', + action => 'drop', + proto => 'tcp', + sport => ['! http', '443'], + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/is not prefixed/) + end + end + end +end diff --git a/manifests/modules/firewall/spec/acceptance/ip6_fragment_spec.rb b/manifests/modules/firewall/spec/acceptance/ip6_fragment_spec.rb new file mode 100644 index 0000000..61e79ce --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/ip6_fragment_spec.rb @@ -0,0 +1,120 @@ +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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + + 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 + + if fact('selinux') == 'true' + apply_manifest(pp, :catch_failures => true) + else + apply_manifest(pp, :catch_changes => true) + end + + 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/manifests/modules/firewall/spec/acceptance/isfragment_spec.rb b/manifests/modules/firewall/spec/acceptance/isfragment_spec.rb new file mode 100644 index 0000000..772f949 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/isfragment_spec.rb @@ -0,0 +1,98 @@ +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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + + 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 + + if fact('selinux') == 'true' + apply_manifest(pp, :catch_failures => true) + else + apply_manifest(pp, :catch_changes => true) + end + + 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/manifests/modules/firewall/spec/acceptance/nodesets/centos-59-x64-pe.yml b/manifests/modules/firewall/spec/acceptance/nodesets/centos-59-x64-pe.yml new file mode 100644 index 0000000..3a6470b --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/centos-59-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/centos-59-x64.yml new file mode 100644 index 0000000..2ad90b8 --- /dev/null +++ b/manifests/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: git diff --git a/manifests/modules/firewall/spec/acceptance/nodesets/centos-64-x64-fusion.yml b/manifests/modules/firewall/spec/acceptance/nodesets/centos-64-x64-fusion.yml new file mode 100644 index 0000000..d516673 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/centos-64-x64-pe.yml b/manifests/modules/firewall/spec/acceptance/nodesets/centos-64-x64-pe.yml new file mode 100644 index 0000000..7d9242f --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/centos-64-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/centos-64-x64.yml new file mode 100644 index 0000000..05540ed --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/centos-65-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/centos-65-x64.yml new file mode 100644 index 0000000..4e2cb80 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/nodesets/centos-65-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + centos-65-x64: + roles: + - master + platform: el-6-x86_64 + box : centos-65-x64-vbox436-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/manifests/modules/firewall/spec/acceptance/nodesets/debian-607-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/debian-607-x64.yml new file mode 100644 index 0000000..4c8be42 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/debian-70rc1-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/debian-70rc1-x64.yml new file mode 100644 index 0000000..19181c1 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/default.yml b/manifests/modules/firewall/spec/acceptance/nodesets/default.yml new file mode 100644 index 0000000..05540ed --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/fedora-18-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/fedora-18-x64.yml new file mode 100644 index 0000000..624b537 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/sles-11sp1-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/sles-11sp1-x64.yml new file mode 100644 index 0000000..554c37a --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml new file mode 100644 index 0000000..5ca1514 --- /dev/null +++ b/manifests/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: foss diff --git a/manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml new file mode 100644 index 0000000..d065b30 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml b/manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml new file mode 100644 index 0000000..cba1cd0 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml @@ -0,0 +1,11 @@ +HOSTS: + ubuntu-server-1404-x64: + roles: + - master + platform: ubuntu-14.04-amd64 + box : puppetlabs/ubuntu-14.04-64-nocm + box_url : https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm + hypervisor : vagrant +CONFIG: + log_level : debug + type: git diff --git a/manifests/modules/firewall/spec/acceptance/params_spec.rb b/manifests/modules/firewall/spec/acceptance/params_spec.rb new file mode 100644 index 0000000..37fe980 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/params_spec.rb @@ -0,0 +1,147 @@ +require 'spec_helper_acceptance' + +describe "param based tests:", :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do + it 'test various params', :unless => (default['platform'].match(/el-5/) || fact('operatingsystem') == 'SLES') do + iptables_flush_all_tables + + ppm = <<-EOS + firewall { '100 test': + table => 'raw', + socket => 'true', + chain => 'PREROUTING', + jump => 'LOG', + log_level => 'debug', + } + EOS + + 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 = <<-EOS + firewall { '998 log all': + proto => 'all', + jump => 'LOG', + log_level => 'debug', + } + EOS + 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 = <<-EOS + firewall { '004 log all INVALID packets': + chain => 'INPUT', + proto => 'all', + ctstate => 'INVALID', + jump => 'LOG', + log_level => '3', + log_prefix => 'IPTABLES dropped invalid: ', + } + EOS + + ppm2 = <<-EOS + firewall { '003 log all INVALID packets': + chain => 'INPUT', + proto => 'all', + ctstate => 'INVALID', + jump => 'LOG', + log_level => '3', + log_prefix => 'IPTABLES dropped invalid: ', + } + EOS + + 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 = <<-EOS + firewall { '004 with a chain': + chain => 'INPUT', + proto => 'all', + } + EOS + + ppm2 = <<-EOS + firewall { '004 with a chain': + chain => 'OUTPUT', + proto => 'all', + } + EOS + + 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 = <<-EOS + firewall { '004 log all INVALID packets': + chain => 'INPUT', + proto => 'all', + ctstate => 'INVALID', + jump => 'LOG', + log_level => '3', + log_prefix => 'IPTABLES dropped invalid: ', + } + EOS + + 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 = <<-EOS + firewall { '997 block src ip range': + chain => 'INPUT', + proto => 'all', + action => 'drop', + src_range => '10.0.0.1-10.0.0.10', + } + EOS + + 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 = <<-EOS + firewall { '998 block dst ip range': + chain => 'INPUT', + proto => 'all', + action => 'drop', + dst_range => '10.0.0.2-10.0.0.20', + } + EOS + + 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/manifests/modules/firewall/spec/acceptance/purge_spec.rb b/manifests/modules/firewall/spec/acceptance/purge_spec.rb new file mode 100644 index 0000000..73582b8 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/purge_spec.rb @@ -0,0 +1,239 @@ +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('ipv4 chain purge') do + after(:all) do + iptables_flush_all_tables + end + 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 + + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + end + + it 'ignores specified rules' do + pp = <<-EOS + class { 'firewall': } + firewallchain { 'INPUT:filter:IPv4': + purge => true, + ignore => [ + '-s 1\.2\.1\.1', + ], + } + EOS + + if fact('selinux') == 'true' + apply_manifest(pp, :catch_failures => true) + else + apply_manifest(pp, :catch_changes => true) + end + 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 + context 'ipv6 chain purge', :unless => (fact('osfamily') == 'RedHat' and fact('operatingsystemmajrelease') == '5') do + after(:all) do + ip6tables_flush_all_tables + end + before(:each) do + ip6tables_flush_all_tables + + shell('ip6tables -A INPUT -p tcp -s 1::42') + shell('ip6tables -A INPUT -p udp -s 1::42') + shell('ip6tables -A OUTPUT -s 1::50 -m comment --comment "010 output-1::50"') + end + + it 'purges only the specified chain' do + pp = <<-EOS + class { 'firewall': } + firewallchain { 'INPUT:filter:IPv6': + purge => true, + } + EOS + + apply_manifest(pp, :expect_changes => true) + + shell('ip6tables-save') do |r| + expect(r.stdout).to match(/010 output-1::50/) + expect(r.stdout).to_not match(/1::42/) + expect(r.stderr).to eq("") + end + end + + it 'ignores managed rules' do + pp = <<-EOS + class { 'firewall': } + firewallchain { 'OUTPUT:filter:IPv6': + purge => true, + } + firewall { '010 output-1::50': + chain => 'OUTPUT', + proto => 'all', + source => '1::50', + provider => 'ip6tables', + } + EOS + + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + end + + it 'ignores specified rules' do + pp = <<-EOS + class { 'firewall': } + firewallchain { 'INPUT:filter:IPv6': + purge => true, + ignore => [ + '-s 1::42', + ], + } + EOS + + if fact('selinux') == 'true' + apply_manifest(pp, :catch_failures => true) + else + apply_manifest(pp, :catch_changes => true) + end + end + + it 'adds managed rules with ignored rules' do + pp = <<-EOS + class { 'firewall': } + firewallchain { 'INPUT:filter:IPv6': + purge => true, + ignore => [ + '-s 1::42', + ], + } + firewall { '014 input-1::46': + chain => 'INPUT', + proto => 'all', + source => '1::46', + provider => 'ip6tables', + } + -> firewall { '013 input-1::45': + chain => 'INPUT', + proto => 'all', + source => '1::45', + provider => 'ip6tables', + } + -> firewall { '012 input-1::44': + chain => 'INPUT', + proto => 'all', + source => '1::44', + provider => 'ip6tables', + } + -> firewall { '011 input-1::43': + chain => 'INPUT', + proto => 'all', + source => '1::43', + provider => 'ip6tables', + } + EOS + + apply_manifest(pp, :catch_failures => true) + + expect(shell('ip6tables-save').stdout).to match(/-A INPUT -s 1::42(\/128)? -p tcp\s?\n-A INPUT -s 1::42(\/128)? -p udp/) + end + end +end diff --git a/manifests/modules/firewall/spec/acceptance/resource_cmd_spec.rb b/manifests/modules/firewall/spec/acceptance/resource_cmd_spec.rb new file mode 100644 index 0000000..ca4837a --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/resource_cmd_spec.rb @@ -0,0 +1,129 @@ +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 + + context 'accepts rules utilizing the statistic module' do + before :all do + iptables_flush_all_tables + # This command doesn't work with all versions/oses, so let it fail + shell('iptables -t nat -A POSTROUTING -d 1.2.3.4/32 -o eth0 -m statistic --mode nth --every 2 -j SNAT --to-source 2.3.4.5', :acceptable_exit_codes => [0,1,2] ) + shell('iptables -t nat -A POSTROUTING -d 1.2.3.4/32 -o eth0 -m statistic --mode nth --every 1 --packet 0 -j SNAT --to-source 2.3.4.6') + shell('iptables -t nat -A POSTROUTING -d 1.2.3.4/32 -o eth0 -m statistic --mode random --probability 0.99 -j SNAT --to-source 2.3.4.7') + 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 -m policy --dir out --pol ipsec -j ACCEPT') + shell('iptables -t filter -A FORWARD -s 192.168.1.0/24 -d 192.168.122.0/24 -i eth0 -m policy --dir in --pol ipsec --reqid 108 --proto esp -j ACCEPT') + shell('iptables -t filter -A FORWARD -s 192.168.122.0/24 -d 192.168.1.0/24 -o eth0 -m policy --dir out --pol ipsec --reqid 108 --proto esp -j ACCEPT') + shell('iptables -t filter -A FORWARD -s 192.168.201.1/32 -d 192.168.122.0/24 -i eth0 -m policy --dir in --pol ipsec --reqid 107 --proto esp -j ACCEPT') + shell('iptables -t filter -A FORWARD -s 192.168.122.0/24 -d 192.168.201.1/32 -o eth0 -m policy --dir out --pol ipsec --reqid 107 --proto esp -j ACCEPT') + 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/manifests/modules/firewall/spec/acceptance/rules_spec.rb b/manifests/modules/firewall/spec/acceptance/rules_spec.rb new file mode 100644 index 0000000..c2acb8b --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/rules_spec.rb @@ -0,0 +1,276 @@ +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 { "011 reject local traffic not on loopback interface": + iniface => '! lo', + proto => 'all', + destination => '127.0.0.1/8', + action => 'reject', + } + 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 { '025 smtp': + outiface => '! eth0:2', + chain => 'OUTPUT', + proto => 'tcp', + dport => '25', + state => 'NEW', + action => 'accept', + } + 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 { '443 ssl on aliased interface': + proto => 'tcp', + dport => '443', + state => 'NEW', + action => 'accept', + iniface => 'eth0:3', + } + 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + 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 -d 127.0.0.0\/(8|255\.0\.0\.0) (! -i|-i !) lo -m comment --comment \"011 reject local traffic not on loopback interface\" -j REJECT --reject-with icmp-port-unreachable/, + /-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 OUTPUT (! -o|-o !) eth0:2 -p tcp -m multiport --dports 25 -m comment --comment \"025 smtp\" -m state --state NEW -j ACCEPT/, + /-A INPUT -i eth0:3 -p tcp -m multiport --dports 443 -m comment --comment \"443 ssl on aliased interface\" -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/manifests/modules/firewall/spec/acceptance/socket_spec.rb b/manifests/modules/firewall/spec/acceptance/socket_spec.rb new file mode 100644 index 0000000..2a21066 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/socket_spec.rb @@ -0,0 +1,103 @@ +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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + + 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 + + if fact('selinux') == 'true' + apply_manifest(pp, :catch_failures => true) + else + apply_manifest(pp, :catch_changes => true) + end + + 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/manifests/modules/firewall/spec/acceptance/standard_usage_spec.rb b/manifests/modules/firewall/spec/acceptance/standard_usage_spec.rb new file mode 100644 index 0000000..753f6e1 --- /dev/null +++ b/manifests/modules/firewall/spec/acceptance/standard_usage_spec.rb @@ -0,0 +1,62 @@ +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 { "0002 reject local traffic not on loopback interface": + iniface => '! lo', + destination => '127.0.0.1/8', + action => 'reject', + }-> + firewall { '003 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) + unless fact('selinux') == 'true' + apply_manifest(pp, :catch_changes => true) + end + end +end diff --git a/manifests/modules/firewall/spec/acceptance/unsupported_spec.rb b/manifests/modules/firewall/spec/acceptance/unsupported_spec.rb new file mode 100644 index 0000000..dfb75e2 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/fixtures/ip6tables/conversion_hash.rb b/manifests/modules/firewall/spec/fixtures/ip6tables/conversion_hash.rb new file mode 100644 index 0000000..7c507d7 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/fixtures/iptables/conversion_hash.rb b/manifests/modules/firewall/spec/fixtures/iptables/conversion_hash.rb new file mode 100644 index 0000000..e33a2e1 --- /dev/null +++ b/manifests/modules/firewall/spec/fixtures/iptables/conversion_hash.rb @@ -0,0 +1,1049 @@ +# 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 = { + 'mac_source_1' => { + :line => '-A neutron-openvswi-FORWARD -s 1.2.3.4/32 -m mac --mac-source FA:16:00:00:00:00 -j ACCEPT', + :table => 'filter', + :params => { + :chain => 'neutron-openvswi-FORWARD', + :source => '1.2.3.4/32', + :mac_source => 'FA:16:00:00:00:00', + }, + }, + '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_1_negated' => { + :line => '-A INPUT ! -i eth0 -m comment --comment "060 iniface" -j DROP', + :table => 'filter', + :params => { + :action => 'drop', + :chain => 'INPUT', + :iniface => '! eth0', + }, + }, + 'iniface_1_aliased' => { + :line => '-A INPUT -i eth0:1 -m comment --comment "060 iniface" -j DROP', + :table => 'filter', + :params => { + :action => 'drop', + :chain => 'INPUT', + :iniface => 'eth0:1', + }, + }, + '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_1_negated' => { + :line => '-A OUTPUT ! -o eth0 -m comment --comment "060 outiface" -j DROP', + :table => 'filter', + :params => { + :action => 'drop', + :chain => 'OUTPUT', + :outiface => '! eth0', + }, + }, + 'outiface_1_aliased' => { + :line => '-A OUTPUT -o eth0:2 -m comment --comment "060 outiface" -j DROP', + :table => 'filter', + :params => { + :action => 'drop', + :chain => 'OUTPUT', + :outiface => 'eth0:2', + }, + }, + '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', + }, + }, + 'disallow_esp_protocol' => { + :line => '-t filter ! -p esp -m comment --comment "063 disallow esp protocol" -j ACCEPT', + :table => 'filter', + :params => { + :name => '063 disallow esp protocol', + :action => 'accept', + :proto => '! esp', + }, + }, + 'drop_new_packets_without_syn' => { + :line => '-t filter ! -s 10.0.0.0/8 ! -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "064 drop NEW non-tcp external packets with FIN/RST/ACK set and SYN unset" -m state --state NEW -j DROP', + :table => 'filter', + :params => { + :name => '064 drop NEW non-tcp external packets with FIN/RST/ACK set and SYN unset', + :state => ['NEW'], + :action => 'drop', + :proto => '! tcp', + :source => '! 10.0.0.0/8', + :tcp_flags => '! FIN,SYN,RST,ACK SYN', + }, + }, + 'negate_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,69 ! --dport 67,66 -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','! 69'], + :dport => ['! 67','! 66'], + :proto => 'udp', + }, + }, +} + +# 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"], + }, + 'disallow_esp_protocol' => { + :params => { + :name => '063 disallow esp protocol', + :table => 'filter', + :action => 'accept', + :proto => '! esp', + }, + :args => ["-t", :filter, "!", "-p", :esp, "-m", "comment", "--comment", "063 disallow esp protocol", "-j", "ACCEPT"], + }, + 'drop_new_packets_without_syn' => { + :params => { + :name => '064 drop NEW non-tcp external packets with FIN/RST/ACK set and SYN unset', + :table => 'filter', + :chain => 'INPUT', + :state => ['NEW'], + :action => 'drop', + :proto => '! tcp', + :source => '! 10.0.0.0/8', + :tcp_flags => '! FIN,SYN,RST,ACK SYN', + }, + :args => ["-t", :filter, "!", "-s", "10.0.0.0/8", "!", "-p", :tcp, "-m", "tcp", "!", "--tcp-flags", "FIN,SYN,RST,ACK", "SYN", "-m", "comment", "--comment", "064 drop NEW non-tcp external packets with FIN/RST/ACK set and SYN unset", "-m", "state", "--state", "NEW", "-j", "DROP"] + }, + 'negate_dport_and_sport' => { + :params => { + :name => '065 negate dport and sport', + :table => 'filter', + :action => 'accept', + :chain => 'nova-compute-FORWARD', + :source => '0.0.0.0/32', + :destination => '255.255.255.255/32', + :sport => ['! 68','! 69'], + :dport => ['! 67','! 66'], + :proto => 'udp', + }, + :args => ["-t", :filter, "-s", "0.0.0.0/32", "-d", "255.255.255.255/32", "-p", :udp, "-m", "multiport", "!", "--sports", "68,69", "-m", "multiport", "!", "--dports", "67,66", "-m", "comment", "--comment", "065 negate dport and sport", "-j", "ACCEPT"], + }, +} diff --git a/manifests/modules/firewall/spec/spec.opts b/manifests/modules/firewall/spec/spec.opts new file mode 100644 index 0000000..91cd642 --- /dev/null +++ b/manifests/modules/firewall/spec/spec.opts @@ -0,0 +1,6 @@ +--format +s +--colour +--loadby +mtime +--backtrace diff --git a/manifests/modules/firewall/spec/spec_helper.rb b/manifests/modules/firewall/spec/spec_helper.rb new file mode 100644 index 0000000..dc8bc39 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/spec_helper_acceptance.rb b/manifests/modules/firewall/spec/spec_helper_acceptance.rb new file mode 100644 index 0000000..b9af876 --- /dev/null +++ b/manifests/modules/firewall/spec/spec_helper_acceptance.rb @@ -0,0 +1,45 @@ +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' + # This will install the latest available package on el and deb based + # systems fail on windows and osx, and install via gem on other *nixes + foss_opts = { :default_action => 'gem_install' } + + if default.is_pe?; then install_pe; else install_puppet( foss_opts ); 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 + hosts.each do |host| + copy_module_to(host, :source => proj_root, :module_name => 'firewall') + on(host, "/bin/touch #{host['hieraconf']}") + on host, puppet('module install puppetlabs-stdlib --version 3.2.0'), { :acceptable_exit_codes => [0,1] } + end + end +end diff --git a/manifests/modules/firewall/spec/unit/classes/firewall_linux_archlinux_spec.rb b/manifests/modules/firewall/spec/unit/classes/firewall_linux_archlinux_spec.rb new file mode 100644 index 0000000..cf5b19b --- /dev/null +++ b/manifests/modules/firewall/spec/unit/classes/firewall_linux_archlinux_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'firewall::linux::archlinux', :type => :class do + let(:facts) do + { + :osfamily => 'RedHat', + :operatingsystem => 'Archlinux' + } + end + 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/manifests/modules/firewall/spec/unit/classes/firewall_linux_debian_spec.rb b/manifests/modules/firewall/spec/unit/classes/firewall_linux_debian_spec.rb new file mode 100644 index 0000000..f781743 --- /dev/null +++ b/manifests/modules/firewall/spec/unit/classes/firewall_linux_debian_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe 'firewall::linux::debian', :type => :class do + context "Debian 7" do + let(:facts) {{ + :osfamily => 'Debian', + :operatingsystem => 'Debian', + :operatingsystemrelease => '7.0' + }} + it { should contain_package('iptables-persistent').with( + :ensure => 'present' + )} + it { should contain_service('iptables-persistent').with( + :ensure => nil, + :enable => 'true', + :require => 'Package[iptables-persistent]' + )} + end + + context 'deb7 enable => false' do + let(:facts) {{ + :osfamily => 'Debian', + :operatingsystem => 'Debian', + :operatingsystemrelease => '7.0' + }} + let(:params) {{ :enable => 'false' }} + it { should contain_service('iptables-persistent').with( + :enable => 'false' + )} + end + + context "Debian 8" do + let(:facts) {{ + :osfamily => 'Debian', + :operatingsystem => 'Debian', + :operatingsystemrelease => 'jessie/sid' + }} + it { should contain_package('netfilter-persistent').with( + :ensure => 'present' + )} + it { should contain_service('netfilter-persistent').with( + :ensure => nil, + :enable => 'true', + :require => 'Package[netfilter-persistent]' + )} + end + + context 'deb8 enable => false' do + let(:facts) {{ + :osfamily => 'Debian', + :operatingsystem => 'Debian', + :operatingsystemrelease => 'jessie/sid' + }} + let(:params) {{ :enable => 'false' }} + it { should contain_service('netfilter-persistent').with( + :enable => 'false' + )} + end + + context "Debian 8, alt operatingsystem" do + let(:facts) {{ + :osfamily => 'Debian', + :operatingsystem => 'Debian', + :operatingsystemrelease => '8.0' + }} + it { should contain_package('netfilter-persistent').with( + :ensure => 'present' + )} + it { should contain_service('netfilter-persistent').with( + :ensure => nil, + :enable => 'true', + :require => 'Package[netfilter-persistent]' + )} + end + + context 'deb8, alt operatingsystem, enable => false' do + let(:facts) {{ + :osfamily => 'Debian', + :operatingsystem => 'Debian', + :operatingsystemrelease => '8.0' + }} + let(:params) {{ :enable => 'false' }} + it { should contain_service('netfilter-persistent').with( + :enable => 'false' + )} + end +end diff --git a/manifests/modules/firewall/spec/unit/classes/firewall_linux_redhat_spec.rb b/manifests/modules/firewall/spec/unit/classes/firewall_linux_redhat_spec.rb new file mode 100644 index 0000000..8feecf4 --- /dev/null +++ b/manifests/modules/firewall/spec/unit/classes/firewall_linux_redhat_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe 'firewall::linux::redhat', :type => :class do + %w{RedHat CentOS Fedora}.each do |os| + oldreleases = (os == 'Fedora' ? ['14'] : ['6.5']) + newreleases = (os == 'Fedora' ? ['15','Rawhide'] : ['7.0.1406']) + + oldreleases.each do |osrel| + context "os #{os} and osrel #{osrel}" do + let(:facts) {{ + :osfamily => 'RedHat', + :operatingsystem => os, + :operatingsystemrelease => osrel + }} + + it { should_not contain_service('firewalld') } + it { should_not contain_package('iptables-services') } + end + end + + newreleases.each do |osrel| + context "os #{os} and osrel #{osrel}" do + let(:facts) {{ + :osfamily => 'RedHat', + :operatingsystem => os, + :operatingsystemrelease => osrel + }} + + it { should contain_service('firewalld').with( + :ensure => 'stopped', + :enable => false, + :before => 'Package[iptables-services]' + )} + + it { should contain_package('iptables-services').with( + :ensure => 'present', + :before => 'Service[iptables]' + )} + end + end + + describe 'ensure' do + context 'default' do + it { should contain_service('iptables').with( + :ensure => 'running', + :enable => 'true' + )} + end + 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 + end +end diff --git a/manifests/modules/firewall/spec/unit/classes/firewall_linux_spec.rb b/manifests/modules/firewall/spec/unit/classes/firewall_linux_spec.rb new file mode 100644 index 0000000..e43c1e9 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/unit/classes/firewall_spec.rb b/manifests/modules/firewall/spec/unit/classes/firewall_spec.rb new file mode 100644 index 0000000..cbfb48c --- /dev/null +++ b/manifests/modules/firewall/spec/unit/classes/firewall_spec.rb @@ -0,0 +1,35 @@ +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 'kernel => SunOS' do + let(:facts) {{ :kernel => 'SunOS' }} + it { expect { should contain_class('firewall::linux') }.to raise_error(Puppet::Error) } + end + + context 'kernel => Darwin' do + let(:facts) {{ :kernel => 'Darwin' }} + 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/manifests/modules/firewall/spec/unit/facter/iptables_persistent_version_spec.rb b/manifests/modules/firewall/spec/unit/facter/iptables_persistent_version_spec.rb new file mode 100644 index 0000000..13a23a5 --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/unit/facter/iptables_spec.rb b/manifests/modules/firewall/spec/unit/facter/iptables_spec.rb new file mode 100644 index 0000000..5773fdc --- /dev/null +++ b/manifests/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/manifests/modules/firewall/spec/unit/puppet/provider/iptables_chain_spec.rb b/manifests/modules/firewall/spec/unit/puppet/provider/iptables_chain_spec.rb new file mode 100755 index 0000000..e2c0fd3 --- /dev/null +++ b/manifests/modules/firewall/spec/unit/puppet/provider/iptables_chain_spec.rb @@ -0,0 +1,231 @@ +#!/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 + + # 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 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) { |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/manifests/modules/firewall/spec/unit/puppet/provider/iptables_spec.rb b/manifests/modules/firewall/spec/unit/puppet/provider/iptables_spec.rb new file mode 100644 index 0000000..e73bf84 --- /dev/null +++ b/manifests/modules/firewall/spec/unit/puppet/provider/iptables_spec.rb @@ -0,0 +1,435 @@ +#!/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 + + # 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 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) { |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_truthy + 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 inverting rules' do + let(:resource) { + Puppet::Type.type(:firewall).new({ + :name => '040 partial invert', + :table => 'filter', + :action => 'accept', + :chain => 'nova-compute-FORWARD', + :source => '0.0.0.0/32', + :destination => '255.255.255.255/32', + :sport => ['! 78','79','http'], + :dport => ['77','! 76'], + :proto => 'udp', + }) + } + let(:instance) { provider.new(resource) } + + it 'fails when not all array items are inverted' do + expect { instance.insert }.to raise_error Puppet::Error, /but '79', '80' are not prefixed/ + 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/manifests/modules/firewall/spec/unit/puppet/type/firewall_spec.rb b/manifests/modules/firewall/spec/unit/puppet/type/firewall_spec.rb new file mode 100755 index 0000000..19c1219 --- /dev/null +++ b/manifests/modules/firewall/spec/unit/puppet/type/firewall_spec.rb @@ -0,0 +1,680 @@ +#!/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 + it "should accept a negated #{iface} value as a string" do + @resource[iface] = '! eth1' + @resource[iface].should == '! eth1' + end + it "should accept an interface alias for the #{iface} value as a string" do + @resource[iface] = 'eth1:2' + @resource[iface].should == 'eth1:2' + end + end + end + + [:tosource, :todest, :to].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, iptables-persistent, and iptables-services" do + @resource[:provider] = provider + @resource[:provider].should == provider + packages = [ + Puppet::Type.type(:package).new(:name => 'iptables'), + Puppet::Type.type(:package).new(:name => 'iptables-persistent'), + Puppet::Type.type(:package).new(:name => 'iptables-services') + ] + 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 + it 'is suitable' do + expect(@resource.suitable?).to be_truthy + end +end + +describe 'firewall on unsupported platforms' do + it 'is not suitable' do + # Stub iptables version + allow(Facter.fact(:iptables_version)).to receive(:value).and_return(nil) + allow(Facter.fact(:ip6tables_version)).to receive(:value).and_return(nil) + + # Stub confine facts + allow(Facter.fact(:kernel)).to receive(:value).and_return('Darwin') + allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Darwin') + resource = firewall.new(:name => "000 test foo", :ensure => :present) + + # If our provider list is nil, then the Puppet::Transaction#evaluate will + # say 'Error: Could not find a suitable provider for firewall' but there + # isn't a unit testable way to get this. + expect(resource.suitable?).to be_falsey + end +end diff --git a/manifests/modules/firewall/spec/unit/puppet/type/firewallchain_spec.rb b/manifests/modules/firewall/spec/unit/puppet/type/firewallchain_spec.rb new file mode 100755 index 0000000..bd3095e --- /dev/null +++ b/manifests/modules/firewall/spec/unit/puppet/type/firewallchain_spec.rb @@ -0,0 +1,207 @@ +#!/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, iptables-persistent, and iptables-services" do + resource[:provider].should == :iptables_chain + packages = [ + Puppet::Type.type(:package).new(:name => 'iptables'), + Puppet::Type.type(:package).new(:name => 'iptables-persistent'), + Puppet::Type.type(:package).new(:name => 'iptables-services') + ] + 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 + it 'is suitable' do + expect(resource.suitable?).to be_truthy + end +end + +describe 'firewall on unsupported platforms' do + it 'is not suitable' do + # Stub iptables version + allow(Facter.fact(:iptables_version)).to receive(:value).and_return(nil) + allow(Facter.fact(:ip6tables_version)).to receive(:value).and_return(nil) + + # Stub confine facts + allow(Facter.fact(:kernel)).to receive(:value).and_return('Darwin') + allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Darwin') + resource = firewallchain.new(:name => "INPUT:filter:IPv4", :ensure => :present) + + # If our provider list is nil, then the Puppet::Transaction#evaluate will + # say 'Error: Could not find a suitable provider for firewall' but there + # isn't a unit testable way to get this. + expect(resource.suitable?).to be_falsey + end +end diff --git a/manifests/modules/firewall/spec/unit/puppet/util/firewall_spec.rb b/manifests/modules/firewall/spec/unit/puppet/util/firewall_spec.rb new file mode 100644 index 0000000..4d6f92c --- /dev/null +++ b/manifests/modules/firewall/spec/unit/puppet/util/firewall_spec.rb @@ -0,0 +1,206 @@ +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 6 identified from operatingsystem and operatingsystemrelease' do + allow(Facter.fact(:osfamily)).to receive(:value).and_return(nil) + allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('CentOS') + allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return('6.5') + expect(subject).to receive(:execute).with(%w{/sbin/service iptables save}) + subject.persist_iptables(proto) + end + + it 'should exec for CentOS 7 identified from operatingsystem and operatingsystemrelease' do + allow(Facter.fact(:osfamily)).to receive(:value).and_return(nil) + allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('CentOS') + allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return('7.0.1406') + expect(subject).to receive(:execute).with(%w{/usr/libexec/iptables/iptables.init 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/manifests/modules/firewall/spec/unit/puppet/util/ipcidr_spec.rb b/manifests/modules/firewall/spec/unit/puppet/util/ipcidr_spec.rb new file mode 100644 index 0000000..916f74a --- /dev/null +++ b/manifests/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 From 2d4d66af2e17e9b625db92bf59aea76e7503de12 Mon Sep 17 00:00:00 2001 From: Kevin Horst Date: Mon, 22 Dec 2014 15:20:20 +0100 Subject: [PATCH 3/4] upgrade module jenkinsci/puppet-jenkins to version 1.3.0 --- manifests/modules/jenkins/.fixtures.yml | 0 manifests/modules/jenkins/.gitignore | 5 +- manifests/modules/jenkins/.rspec | 2 +- manifests/modules/jenkins/.travis.yml | 26 +- manifests/modules/jenkins/Blimpfile | 0 manifests/modules/jenkins/CHANGELOG.md | 47 ++ manifests/modules/jenkins/Gemfile | 30 +- manifests/modules/jenkins/Gemfile.lock | 79 --- manifests/modules/jenkins/HACKING.md | 4 + manifests/modules/jenkins/LICENSE | 0 manifests/modules/jenkins/Modulefile | 14 - manifests/modules/jenkins/README.md | 123 ++++ manifests/modules/jenkins/Rakefile | 21 +- manifests/modules/jenkins/Vagrantfile | 65 ++ manifests/modules/jenkins/_git/FETCH_HEAD | 7 - manifests/modules/jenkins/_git/HEAD | 1 - manifests/modules/jenkins/_git/ORIG_HEAD | 1 - manifests/modules/jenkins/_git/config | 12 - manifests/modules/jenkins/_git/description | 1 - .../jenkins/_git/hooks/applypatch-msg.sample | 15 - .../jenkins/_git/hooks/commit-msg.sample | 24 - .../jenkins/_git/hooks/post-update.sample | 8 - .../jenkins/_git/hooks/pre-applypatch.sample | 14 - .../jenkins/_git/hooks/pre-commit.sample | 50 -- .../jenkins/_git/hooks/pre-rebase.sample | 169 ----- .../_git/hooks/prepare-commit-msg.sample | 36 -- .../modules/jenkins/_git/hooks/update.sample | 128 ---- manifests/modules/jenkins/_git/index | Bin 8028 -> 0 bytes manifests/modules/jenkins/_git/info/exclude | 6 - manifests/modules/jenkins/_git/logs/HEAD | 1 - .../jenkins/_git/logs/refs/heads/master | 1 - ...0b49b8c7838a36b9613416fb9cb1e4d6828eda.idx | Bin 50800 -> 0 bytes ...b49b8c7838a36b9613416fb9cb1e4d6828eda.pack | Bin 466248 -> 0 bytes manifests/modules/jenkins/_git/packed-refs | 17 - .../modules/jenkins/_git/refs/heads/master | 1 - .../jenkins/_git/refs/remotes/origin/HEAD | 1 - .../examples/job-configuration/build.pp | 20 + .../job-configuration/templates/build.xml.erb | 17 + .../examples/plugin-configuration/git.pp | 0 .../templates/git.config.xml.erb | 0 .../jenkins/features/deb_support.feature | 0 .../step_definitions/deb_support_steps.rb | 0 .../features/support/boxes/deb/Vagrantfile | 0 .../support/boxes/deb/verify-plugin-install | 0 .../modules/jenkins/features/support/env.rb | 0 .../jenkins/features/support/vagrant.rb | 0 .../{jenkins-slave => jenkins-slave.Debian} | 1 - .../jenkins-slave.RedHat} | 32 +- .../jenkins/files/puppet_helper.groovy | 271 ++++++++ .../modules/jenkins/lib/facter/jenkins.rb | 0 .../jenkins/lib/puppet/features/jpm.rb | 0 .../modules/jenkins/lib/puppet/jenkins.rb | 0 .../jenkins/lib/puppet/jenkins/facts.rb | 0 .../jenkins/lib/puppet/jenkins/okjson.rb | 600 ++++++++++++++++++ .../jenkins/lib/puppet/jenkins/plugins.rb | 19 +- .../puppet/parser/functions/jenkins_port.rb | 21 + .../lib/puppet/provider/package/jpm.rb | 0 manifests/modules/jenkins/manifests/cli.pp | 33 +- .../modules/jenkins/manifests/cli_helper.pp | 36 ++ manifests/modules/jenkins/manifests/config.pp | 0 .../modules/jenkins/manifests/credentials.pp | 60 ++ .../modules/jenkins/manifests/firewall.pp | 2 +- manifests/modules/jenkins/manifests/init.pp | 74 ++- manifests/modules/jenkins/manifests/job.pp | 38 ++ .../modules/jenkins/manifests/job/absent.pp | 39 ++ .../modules/jenkins/manifests/job/present.pp | 100 +++ manifests/modules/jenkins/manifests/jobs.pp | 11 + manifests/modules/jenkins/manifests/master.pp | 0 .../modules/jenkins/manifests/package.pp | 0 manifests/modules/jenkins/manifests/params.pp | 25 +- manifests/modules/jenkins/manifests/plugin.pp | 58 +- .../modules/jenkins/manifests/plugins.pp | 0 manifests/modules/jenkins/manifests/proxy.pp | 5 + manifests/modules/jenkins/manifests/repo.pp | 0 .../modules/jenkins/manifests/repo/debian.pp | 1 + .../modules/jenkins/manifests/repo/el.pp | 2 + .../modules/jenkins/manifests/repo/suse.pp | 0 .../modules/jenkins/manifests/security.pp | 34 + .../modules/jenkins/manifests/service.pp | 0 manifests/modules/jenkins/manifests/slave.pp | 99 +-- .../modules/jenkins/manifests/sysconfig.pp | 16 +- manifests/modules/jenkins/manifests/user.pp | 63 ++ manifests/modules/jenkins/metadata.json | 27 + .../jenkins/spec/classes/jenkins_cli_spec.rb | 7 +- .../spec/classes/jenkins_config_spec.rb | 2 +- .../spec/classes/jenkins_firewall_spec.rb | 1 - .../jenkins/spec/classes/jenkins_jobs_spec.rb | 25 + .../spec/classes/jenkins_master_spec.rb | 0 .../spec/classes/jenkins_package_spec.rb | 0 .../spec/classes/jenkins_plugins_spec.rb | 0 .../spec/classes/jenkins_proxy_spec.rb | 12 +- .../spec/classes/jenkins_repo_debian_spec.rb | 6 + .../spec/classes/jenkins_repo_el_spec.rb | 0 .../jenkins/spec/classes/jenkins_repo_spec.rb | 0 .../spec/classes/jenkins_repo_suse_spec.rb | 0 .../spec/classes/jenkins_service_spec.rb | 0 .../spec/classes/jenkins_slave_spec.rb | 28 +- .../jenkins/spec/defines/jenkins_job_spec.rb | 96 +++ .../spec/defines/jenkins_plugin_spec.rb | 79 ++- .../spec/defines/jenkins_sysconfig_spec.rb | 0 .../spec/functions/jenkins_port_spec.rb | 30 + .../jenkins/spec/helpers/rspechelpers.rb | 0 .../jenkins/spec/serverspec/spec_helper.rb | 12 + .../spec/serverspec/ubuntu-precise/config.yml | 3 + .../ubuntu-precise/manifests/default.pp | 9 + .../serverspec/ubuntu-precise/precise_spec.rb | 29 + manifests/modules/jenkins/spec/spec_helper.rb | 1 + .../jenkins/spec/unit/facter/plugins_spec.rb | 0 .../jenkins/spec/unit/jenkins_plugins_spec.rb | 9 + .../spec/unit/jenkins_provider_spec.rb | 0 .../modules/jenkins/spec/unit/jenkins_spec.rb | 0 ...ults.Debian => jenkins-slave-defaults.erb} | 38 +- .../modules/jenkins/templates/proxy.xml.erb | 7 +- .../jenkins/tests/RedHatEnterpriseServer.pp | 21 + manifests/modules/jenkins/tests/Ubuntu.pp | 21 + 115 files changed, 2298 insertions(+), 751 deletions(-) mode change 100644 => 100755 manifests/modules/jenkins/.fixtures.yml mode change 100644 => 100755 manifests/modules/jenkins/.gitignore mode change 100644 => 100755 manifests/modules/jenkins/.rspec mode change 100644 => 100755 manifests/modules/jenkins/.travis.yml mode change 100644 => 100755 manifests/modules/jenkins/Blimpfile mode change 100644 => 100755 manifests/modules/jenkins/CHANGELOG.md mode change 100644 => 100755 manifests/modules/jenkins/Gemfile delete mode 100644 manifests/modules/jenkins/Gemfile.lock mode change 100644 => 100755 manifests/modules/jenkins/HACKING.md mode change 100644 => 100755 manifests/modules/jenkins/LICENSE delete mode 100644 manifests/modules/jenkins/Modulefile mode change 100644 => 100755 manifests/modules/jenkins/README.md mode change 100644 => 100755 manifests/modules/jenkins/Rakefile create mode 100755 manifests/modules/jenkins/Vagrantfile delete mode 100644 manifests/modules/jenkins/_git/FETCH_HEAD delete mode 100644 manifests/modules/jenkins/_git/HEAD delete mode 100644 manifests/modules/jenkins/_git/ORIG_HEAD delete mode 100644 manifests/modules/jenkins/_git/config delete mode 100644 manifests/modules/jenkins/_git/description delete mode 100755 manifests/modules/jenkins/_git/hooks/applypatch-msg.sample delete mode 100755 manifests/modules/jenkins/_git/hooks/commit-msg.sample delete mode 100755 manifests/modules/jenkins/_git/hooks/post-update.sample delete mode 100755 manifests/modules/jenkins/_git/hooks/pre-applypatch.sample delete mode 100755 manifests/modules/jenkins/_git/hooks/pre-commit.sample delete mode 100755 manifests/modules/jenkins/_git/hooks/pre-rebase.sample delete mode 100755 manifests/modules/jenkins/_git/hooks/prepare-commit-msg.sample delete mode 100755 manifests/modules/jenkins/_git/hooks/update.sample delete mode 100644 manifests/modules/jenkins/_git/index delete mode 100644 manifests/modules/jenkins/_git/info/exclude delete mode 100644 manifests/modules/jenkins/_git/logs/HEAD delete mode 100644 manifests/modules/jenkins/_git/logs/refs/heads/master delete mode 100644 manifests/modules/jenkins/_git/objects/pack/pack-e20b49b8c7838a36b9613416fb9cb1e4d6828eda.idx delete mode 100644 manifests/modules/jenkins/_git/objects/pack/pack-e20b49b8c7838a36b9613416fb9cb1e4d6828eda.pack delete mode 100644 manifests/modules/jenkins/_git/packed-refs delete mode 100644 manifests/modules/jenkins/_git/refs/heads/master delete mode 100644 manifests/modules/jenkins/_git/refs/remotes/origin/HEAD create mode 100755 manifests/modules/jenkins/contrib/examples/job-configuration/build.pp create mode 100755 manifests/modules/jenkins/contrib/examples/job-configuration/templates/build.xml.erb mode change 100644 => 100755 manifests/modules/jenkins/contrib/examples/plugin-configuration/git.pp mode change 100644 => 100755 manifests/modules/jenkins/contrib/examples/plugin-configuration/templates/git.config.xml.erb mode change 100644 => 100755 manifests/modules/jenkins/features/deb_support.feature mode change 100644 => 100755 manifests/modules/jenkins/features/step_definitions/deb_support_steps.rb mode change 100644 => 100755 manifests/modules/jenkins/features/support/boxes/deb/Vagrantfile mode change 100644 => 100755 manifests/modules/jenkins/features/support/boxes/deb/verify-plugin-install mode change 100644 => 100755 manifests/modules/jenkins/features/support/env.rb mode change 100644 => 100755 manifests/modules/jenkins/features/support/vagrant.rb rename manifests/modules/jenkins/files/{jenkins-slave => jenkins-slave.Debian} (97%) rename manifests/modules/jenkins/{templates/jenkins-slave.erb => files/jenkins-slave.RedHat} (50%) mode change 100644 => 100755 create mode 100755 manifests/modules/jenkins/files/puppet_helper.groovy mode change 100644 => 100755 manifests/modules/jenkins/lib/facter/jenkins.rb mode change 100644 => 100755 manifests/modules/jenkins/lib/puppet/features/jpm.rb mode change 100644 => 100755 manifests/modules/jenkins/lib/puppet/jenkins.rb mode change 100644 => 100755 manifests/modules/jenkins/lib/puppet/jenkins/facts.rb create mode 100755 manifests/modules/jenkins/lib/puppet/jenkins/okjson.rb mode change 100644 => 100755 manifests/modules/jenkins/lib/puppet/jenkins/plugins.rb create mode 100755 manifests/modules/jenkins/lib/puppet/parser/functions/jenkins_port.rb mode change 100644 => 100755 manifests/modules/jenkins/lib/puppet/provider/package/jpm.rb mode change 100644 => 100755 manifests/modules/jenkins/manifests/cli.pp create mode 100755 manifests/modules/jenkins/manifests/cli_helper.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/config.pp create mode 100755 manifests/modules/jenkins/manifests/credentials.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/firewall.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/init.pp create mode 100755 manifests/modules/jenkins/manifests/job.pp create mode 100755 manifests/modules/jenkins/manifests/job/absent.pp create mode 100755 manifests/modules/jenkins/manifests/job/present.pp create mode 100755 manifests/modules/jenkins/manifests/jobs.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/master.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/package.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/params.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/plugin.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/plugins.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/proxy.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/repo.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/repo/debian.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/repo/el.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/repo/suse.pp create mode 100755 manifests/modules/jenkins/manifests/security.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/service.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/slave.pp mode change 100644 => 100755 manifests/modules/jenkins/manifests/sysconfig.pp create mode 100755 manifests/modules/jenkins/manifests/user.pp create mode 100755 manifests/modules/jenkins/metadata.json mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_cli_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_config_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_firewall_spec.rb create mode 100755 manifests/modules/jenkins/spec/classes/jenkins_jobs_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_master_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_package_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_plugins_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_proxy_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_repo_debian_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_repo_el_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_repo_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_repo_suse_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_service_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/classes/jenkins_slave_spec.rb create mode 100755 manifests/modules/jenkins/spec/defines/jenkins_job_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/defines/jenkins_plugin_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/defines/jenkins_sysconfig_spec.rb create mode 100755 manifests/modules/jenkins/spec/functions/jenkins_port_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/helpers/rspechelpers.rb create mode 100755 manifests/modules/jenkins/spec/serverspec/spec_helper.rb create mode 100755 manifests/modules/jenkins/spec/serverspec/ubuntu-precise/config.yml create mode 100755 manifests/modules/jenkins/spec/serverspec/ubuntu-precise/manifests/default.pp create mode 100755 manifests/modules/jenkins/spec/serverspec/ubuntu-precise/precise_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/spec_helper.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/unit/facter/plugins_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/unit/jenkins_plugins_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/unit/jenkins_provider_spec.rb mode change 100644 => 100755 manifests/modules/jenkins/spec/unit/jenkins_spec.rb rename manifests/modules/jenkins/templates/{jenkins-slave-defaults.Debian => jenkins-slave-defaults.erb} (56%) mode change 100644 => 100755 mode change 100644 => 100755 manifests/modules/jenkins/templates/proxy.xml.erb mode change 100644 => 100755 manifests/modules/jenkins/tests/RedHatEnterpriseServer.pp mode change 100644 => 100755 manifests/modules/jenkins/tests/Ubuntu.pp diff --git a/manifests/modules/jenkins/.fixtures.yml b/manifests/modules/jenkins/.fixtures.yml old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/.gitignore b/manifests/modules/jenkins/.gitignore old mode 100644 new mode 100755 index 194ee64..d4df444 --- a/manifests/modules/jenkins/.gitignore +++ b/manifests/modules/jenkins/.gitignore @@ -7,6 +7,7 @@ README.pdf /test_reports .rvmrc .ruby-* -*.swp +*.sw* spec/fixtures -*.swo +Gemfile.lock +.vagrant* diff --git a/manifests/modules/jenkins/.rspec b/manifests/modules/jenkins/.rspec old mode 100644 new mode 100755 index ed074e8..38972dd --- a/manifests/modules/jenkins/.rspec +++ b/manifests/modules/jenkins/.rspec @@ -1 +1 @@ ---color --order random --format progress --fail-fast +--color --order random --fail-fast diff --git a/manifests/modules/jenkins/.travis.yml b/manifests/modules/jenkins/.travis.yml old mode 100644 new mode 100755 index b6b4540..a9ae941 --- a/manifests/modules/jenkins/.travis.yml +++ b/manifests/modules/jenkins/.travis.yml @@ -1,29 +1,37 @@ language: ruby -bundler_args: --without development +before_install: gem install bundler -v '~> 1.6.0' --no-ri --no-rdoc +bundler_args: "--without development plugins" script: "bundle exec rake spec SPEC_OPTS='--format documentation'" rvm: - - 1.8.7 - 1.9.3 - - 2.0.0 -before_install: - - gem update --system 2.1.11 + - 2.1.0 script: - - "rake lint" - - "rake spec SPEC_OPTS='--format documentation'" + - "bundle exec rake lint" + - "bundle exec rake spec SPEC_OPTS='--format documentation'" env: - PUPPET_VERSION="~> 2.7.0" - PUPPET_VERSION="~> 3.1.0" - PUPPET_VERSION="~> 3.2.0" - PUPPET_VERSION="~> 3.3.0" - PUPPET_VERSION="~> 3.4.0" + - PUPPET_VERSION="~> 3.5.0" + - PUPPET_VERSION="~> 3.6.0" matrix: exclude: - rvm: 1.9.3 env: PUPPET_VERSION="~> 2.7.0" - - rvm: 2.0.0 + - rvm: 2.1.0 env: PUPPET_VERSION="~> 2.7.0" - - rvm: 2.0.0 + - rvm: 2.1.0 + env: PUPPET_VERSION="~> 3.0.0" + - rvm: 2.1.0 env: PUPPET_VERSION="~> 3.1.0" + - rvm: 2.1.0 + env: PUPPET_VERSION="~> 3.2.0" + - rvm: 2.1.0 + env: PUPPET_VERSION="~> 3.3.0" + - rvm: 2.1.0 + env: PUPPET_VERSION="~> 3.4.0" notifications: email: false diff --git a/manifests/modules/jenkins/Blimpfile b/manifests/modules/jenkins/Blimpfile old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/CHANGELOG.md b/manifests/modules/jenkins/CHANGELOG.md old mode 100644 new mode 100755 index d6f5b8b..048d1b8 --- a/manifests/modules/jenkins/CHANGELOG.md +++ b/manifests/modules/jenkins/CHANGELOG.md @@ -2,6 +2,53 @@ This is a manually kept file, and may not entirely reflect reality +## v1.3.0 - Barnard + +* [#134](https://github.com/jenkinsci/puppet-jenkins/pull/134) - Added in ability for user to redefine update center plugin URL +* [#139](https://github.com/jenkinsci/puppet-jenkins/pull/139) - document additional class params +* [#169](https://github.com/jenkinsci/puppet-jenkins/pull/169) - Allow build jobs to be configured and managed by puppet. Includes #163 a... +* [#174](https://github.com/jenkinsci/puppet-jenkins/issues/174) - setting configure_firewall true returns error, port is undefined +* [#177](https://github.com/jenkinsci/puppet-jenkins/issues/177) - switch to metadata.json +* [#188](https://github.com/jenkinsci/puppet-jenkins/pull/188) - Fix installation of core plugins +* [#189](https://github.com/jenkinsci/puppet-jenkins/pull/189) - Fix test. +* [#191](https://github.com/jenkinsci/puppet-jenkins/pull/191) - set default port for firewall +* [#195](https://github.com/jenkinsci/puppet-jenkins/pull/195) - Bump up swarm version to 1.17 +* [#198](https://github.com/jenkinsci/puppet-jenkins/issues/198) - Relationship error when testing Jenkins::jobs +* [#199](https://github.com/jenkinsci/puppet-jenkins/pull/199) - missing include causes issuse #198 +* [#202](https://github.com/jenkinsci/puppet-jenkins/pull/202) - Proxy work +* [#203](https://github.com/jenkinsci/puppet-jenkins/pull/203) - Fix typo in job/present.pp +* [#204](https://github.com/jenkinsci/puppet-jenkins/pull/204) - Fix for #174 allows setting $jenkins::port +* [#206](https://github.com/jenkinsci/puppet-jenkins/issues/206) - Refactor some of the firewall port configuration +* [#207](https://github.com/jenkinsci/puppet-jenkins/pull/207) - Introduce the jenkins_port function + + +## v1.2.0 - Nestro + +* [#117](https://github.com/jenkinsci/puppet-jenkins/pull/117) - Add feature to disable SSL verification on Swarm clients +* [#131](https://github.com/jenkinsci/puppet-jenkins/pull/131) - Support updates for core jenkins modules +* [#135](https://github.com/jenkinsci/puppet-jenkins/issues/135) - cli option broken w/ jenkins 1.563 on ubuntu precise +* [#137](https://github.com/jenkinsci/puppet-jenkins/pull/137) - repos should be enabled if repo=true on RedHat +* [#140](https://github.com/jenkinsci/puppet-jenkins/issues/140) - Packaging Cruft in 1.1.0 +* [#144](https://github.com/jenkinsci/puppet-jenkins/pull/144) - Update init.pp - correct plugins example syntax +* [#149](https://github.com/jenkinsci/puppet-jenkins/pull/149) - Do not ensure plugin_parent_dir to be a directory (#148) +* [#150](https://github.com/jenkinsci/puppet-jenkins/pull/150) - Add ensure parameter to jenkins::slave +* [#151](https://github.com/jenkinsci/puppet-jenkins/issues/151) - Unsupported OSFamily RedHat on node +* [#152](https://github.com/jenkinsci/puppet-jenkins/issues/152) - Jenkins-slave on Centos: killproc and checkpid commands not found +* [#153](https://github.com/jenkinsci/puppet-jenkins/pull/153) - Fixes to Jenkins slave init and class +* [#154](https://github.com/jenkinsci/puppet-jenkins/issues/154) - slave_mode doesn't apply on debian distros. +* [#155](https://github.com/jenkinsci/puppet-jenkins/pull/155) - Add defined check for plugin_parent_dir resource +* [#157](https://github.com/jenkinsci/puppet-jenkins/pull/157) - Add missing slave mode to Debian defaults file +* [#160](https://github.com/jenkinsci/puppet-jenkins/pull/160) - User and credentials creation, simple security management +* [#166](https://github.com/jenkinsci/puppet-jenkins/issues/166) - Error loading fact /var/lib/puppet/lib/facter/jenkins.rb no such file to load -- json +* [#171](https://github.com/jenkinsci/puppet-jenkins/pull/171) - A bit of RedHat and Debian slave initd script merging +* [#176](https://github.com/jenkinsci/puppet-jenkins/issues/176) - no such file to load -- json +* [#180](https://github.com/jenkinsci/puppet-jenkins/issues/180) - Replace use of unzip with `jar` for unpacking jenkins CLI +* [#182](https://github.com/jenkinsci/puppet-jenkins/pull/182) - Include the apt module when installing an apt repository +* [#183](https://github.com/jenkinsci/puppet-jenkins/pull/183) - Rely on the `jar` command instead of `unzip` to unpack the cli.jar +* [#185](https://github.com/jenkinsci/puppet-jenkins/pull/185) - Allow setting the slave name, default to the fqdn at runtime +* [#186](https://github.com/jenkinsci/puppet-jenkins/issues/186) - Puppet Forge module +* [#187](https://github.com/jenkinsci/puppet-jenkins/issues/187) - Jenkins slave on RedHat - jenkins-slave.erb + ## v1.1.0 - Duckworth diff --git a/manifests/modules/jenkins/Gemfile b/manifests/modules/jenkins/Gemfile old mode 100644 new mode 100755 index d8dc9a1..e09ebc3 --- a/manifests/modules/jenkins/Gemfile +++ b/manifests/modules/jenkins/Gemfile @@ -1,16 +1,32 @@ source 'https://rubygems.org' -gem 'rake' -gem 'puppet-lint' -gem 'rspec-puppet' -gem 'puppetlabs_spec_helper' -gem 'puppet-syntax' -gem 'puppet', ENV['PUPPET_VERSION'] || '~> 3.5.1' +gem 'rake', '>= 10.1.1' +gem 'rspec', '~> 2.99.0' +gem 'rspec-its' +gem 'puppet-lint', '>= 0.3.2' +gem 'rspec-puppet', '>= 1.0.1' +gem 'puppetlabs_spec_helper', :github => 'jenkins-infra/puppetlabs_spec_helper' +gem 'puppet-syntax', '>= 1.1.0' +gem 'json' +gem 'puppet', ENV['PUPPET_VERSION'] || '~> 3.5' group :development do - gem 'rcov' + gem 'simplecov' gem 'parallel_tests' gem 'ci_reporter' gem 'debugger', :platform => :mri gem 'debugger-pry', :platform => :mri + + gem 'serverspec' + gem 'vagrant', :github => 'mitchellh/vagrant', + :ref => 'v1.6.5', + :platform => [:mri_19, :mri_21] +end + +# Vagrant plugins +group :plugins do + gem 'vagrant-aws', :github => 'mitchellh/vagrant-aws', + :platform => [:mri_19, :mri_21] + gem 'vagrant-serverspec', :github => 'jvoorhis/vagrant-serverspec', + :platform => [:mri_19, :mri_21] end diff --git a/manifests/modules/jenkins/Gemfile.lock b/manifests/modules/jenkins/Gemfile.lock deleted file mode 100644 index ef07877..0000000 --- a/manifests/modules/jenkins/Gemfile.lock +++ /dev/null @@ -1,79 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (2.2.7) - builder (3.2.2) - ci_reporter (1.9.1) - builder (>= 2.1.2) - coderay (1.1.0) - columnize (0.3.6) - debugger (1.6.5) - columnize (>= 0.3.1) - debugger-linecache (~> 1.2.0) - debugger-ruby_core_source (~> 1.3.1) - debugger-linecache (1.2.0) - debugger-pry (0.1.1) - debugger (~> 1) - pry (>= 0.9.9) - debugger-ruby_core_source (1.3.1) - diff-lcs (1.2.5) - facter (2.0.1) - CFPropertyList (~> 2.2.6) - hiera (1.3.2) - json_pure - json_pure (1.8.1) - metaclass (0.0.4) - method_source (0.8.2) - mocha (1.0.0) - metaclass (~> 0.0.1) - parallel (0.9.2) - parallel_tests (0.16.6) - parallel - pry (0.9.12.6) - coderay (~> 1.0) - method_source (~> 0.8) - slop (~> 3.4) - puppet (3.5.1) - facter (> 1.6, < 3) - hiera (~> 1.0) - json_pure - rgen (~> 0.6.5) - puppet-lint (0.3.2) - puppet-syntax (1.1.0) - puppet (>= 2.7.0) - rake - puppetlabs_spec_helper (0.4.1) - mocha (>= 0.10.5) - rake - rspec (>= 2.9.0) - rspec-puppet (>= 0.1.1) - rake (10.1.1) - rcov (0.9.11) - 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.7) - rspec-expectations (2.14.5) - diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.14.6) - rspec-puppet (1.0.1) - rspec - slop (3.4.7) - -PLATFORMS - ruby - -DEPENDENCIES - ci_reporter - debugger - debugger-pry - parallel_tests - puppet (~> 3.5.1) - puppet-lint - puppet-syntax - puppetlabs_spec_helper - rake - rcov - rspec-puppet diff --git a/manifests/modules/jenkins/HACKING.md b/manifests/modules/jenkins/HACKING.md old mode 100644 new mode 100755 index 46f5432..ce778d8 --- a/manifests/modules/jenkins/HACKING.md +++ b/manifests/modules/jenkins/HACKING.md @@ -69,3 +69,7 @@ Resources may be validated in the catalog using: * `contain_package('puppet')` * And so forth for other Puppet resources. +## Acceptance testings + + * `bundle exec rake spec_prep` + * `bundle exec vagrant up` diff --git a/manifests/modules/jenkins/LICENSE b/manifests/modules/jenkins/LICENSE old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/Modulefile b/manifests/modules/jenkins/Modulefile deleted file mode 100644 index c03b181..0000000 --- a/manifests/modules/jenkins/Modulefile +++ /dev/null @@ -1,14 +0,0 @@ -name 'rtyler-jenkins' -version '1.1.0' -source 'git://github.com/jenkinsci/puppet-jenkins.git' -author 'R. Tyler Croy ' -license 'Apache 2.0' -summary 'Manage the Jenkins continuous integration service with Puppet' -description 'Manage the Jenkins continuous integration service with Puppet' -project_page 'https://github.com/jenkinsci/puppet-jenkins' - -## Add dependencies, if any: -dependency 'puppetlabs/stdlib', '>= 2.0.0' -dependency 'puppetlabs/apt', '>= 0.0.3' -dependency 'puppetlabs/java', '>= 1.0.1' -dependency 'darin/zypprepo', '>= 1.0.1' diff --git a/manifests/modules/jenkins/README.md b/manifests/modules/jenkins/README.md old mode 100644 new mode 100755 index 62df873..475c646 --- a/manifests/modules/jenkins/README.md +++ b/manifests/modules/jenkins/README.md @@ -20,6 +20,33 @@ puppet module install rtyler/jenkins ``` Then the service should be running at [http://hostname.example.com:8080/](http://hostname.example.com:8080/). +### Managing Jenkins jobs + + +Build jobs can be managed using the `jenkins::job` define + +#### Creating or updating a build job +```puppet + jenkins::job { 'test-build-job': + config => template("${templates}/test-build-job.xml.erb"), + } +``` + +#### Disabling a build job +```puppet + jenkins::job { 'test-build-job': + enabled => 0, + config => template("${templates}/test-build-job.xml.erb"), + } +``` + +#### Removing an existing build job +```puppet + jenkins::job { 'test-build-job': + ensure => 'absent', + } +``` + ### Installing Jenkins plugins @@ -103,7 +130,103 @@ the following `require` statement: 2. Config Hash - jennkins::config 3. Configure Firewall - jenkins (init.pp) 4. Outbound Jenkins Proxy Config - jenkins (init.pp) +5. Jenkins Users +6. Credentials +7. Simple security model configuration + +### API-based Resources and Settings (Users, Credentials, security) + +This module includes a groovy-based helper script that uses the +[Jenkins CLI](https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+CLI) to +interact with the Jenkins API. Users, Credentials, and security model +configuration are all driven through this script. + +When an API-based resource is defined, the Jenkins CLI is installed and run +against the local system (127.0.0.1). Jenkins is assumed to be listening on +port 8080, but the module is smart enough to notice if you've configured an +alternate port using jenkins::config_hash['HTTP_PORT']. + +Users and credentials are Puppet-managed, meaning that changes made to them +from outside Puppet will be reset at the next puppet run. In this way, you can +ensure that certain accounts are present and have the appropriate login +credentials. + +### CLI Helper + +The CLI helper assumes unauthenticated access unless configured otherwise. +You can configure jenkins::cli_helper to use an SSH key on the managed system: + + class {'jenkins::cli_helper': + ssh_keyfile => '/path/to/id_rsa', + } + +There's an open bug in Jenkins (JENKINS-22346) that causes authentication to +fail when a key is used but authentication is disabled. Until the bug is fixed, +you may need to bootstrap jenkins out-of-band to ensure that resources and +security policy are configured in the correct order. For example: + + # In puppet: + anchor {'jenkins-bootstrap-start': } -> + Class['jenkins::cli_helper'] -> + Exec[$bootstrap_script] -> + anchor {'jenkins-bootstrap-complete': } + + # Code for $bootstrap_script + #!/bin/bash -e + # Generate an SSH key for the admin user + ADMIN_USER='<%= admin_user_name %>' + ADMIN_EMAIL='<%= admin_user_email %>' + ADMIN_PASSWORD='<%= admin_user_password %>' + ADMIN_FULLNAME='<%= admin_user_full_name %>' + ADMIN_SSH_KEY='<%= admin_ssh_keyfile %>' + JENKINS_CLI='<%= jenkins_libdir %>/jenkins-cli.jar' + PUPPET_HELPER='<%= jenkins_libdir %>/puppet_helper.groovy' + HELPER="java -jar $JENKINS_CLI -s http://127.0.0.1:8080 groovy $PUPPET_HELPER" + DONEFILE='<%= jenkins_libdir %>/jenkins-bootstrap.done' + + ADMIN_PUBKEY="$(cat ${ADMIN_SSH_KEY}.pub)" + + # Create the admin user, passing no credentials + $HELPER create_or_update_user "$ADMIN_USER" "$ADMIN_EMAIL" "$ADMIN_PASSWORD" "$ADMIN_FULLNAME" "$ADMIN_PUBKEY" + # Enable security. After this, credentials will be required. + $HELPER set_security full_control + + touch $DONEFILE + +#### Users + +Email and password are required. + +Create a `johndoe` user account whose full name is "Managed by Puppet": + + jenkins::user {'johndoe': + email => 'jdoe@example.com', + password => 'changeme', + } + +### Credentials + +Password is required. For ssh credentials, `password` is the key passphrase (or +'' if there is none). `private_key_or_path` is the text of key itself or an +absolute path to a key file on the managed system. + +Create ssh credentials named 'github-deploy-key', providing an unencrypted +private key: + + jenkins::credentials {'github-deploy-key': + password => '', + private_key_or_path => hiera('::github_deploy_key'), + } + +### Configuring Security + +The Jenkins security model can be set to one of two modes: + +* `full_control` - Users have full control after login. Authentication uses + Jenkins' built-in user database. +* `unsecured` - Authentication is not required. +Jenkins security is not managed by puppet unless jenkins::security is defined. ## Using from Github / source diff --git a/manifests/modules/jenkins/Rakefile b/manifests/modules/jenkins/Rakefile old mode 100644 new mode 100755 index ad5f2fb..b91296e --- a/manifests/modules/jenkins/Rakefile +++ b/manifests/modules/jenkins/Rakefile @@ -1,10 +1,9 @@ require 'rubygems' +require 'rake' require 'puppetlabs_spec_helper/rake_tasks' require 'puppet-lint/tasks/puppet-lint' require 'puppet-syntax/tasks/puppet-syntax' -task :default => [:lint, :spec, :syntax] - exclude_paths = [ "pkg/**/*", "vendor/**/*", @@ -12,9 +11,17 @@ exclude_paths = [ "contrib/**/*" ] -PuppetLint.configuration.log_format = "%{path}:%{linenumber}:%{check}:%{KIND}:%{message}" -PuppetLint.configuration.fail_on_warnings = true -PuppetLint.configuration.send("disable_80chars") -PuppetLint.configuration.send('disable_class_inherits_from_params_class') -PuppetLint.configuration.ignore_paths = exclude_paths +# Make sure we don't have the default rake task floating around +Rake::Task['lint'].clear + +PuppetLint.configuration.relative = true +PuppetLint::RakeTask.new(:lint) do |l| + l.disable_checks = %w(80chars class_inherits_from_params_class) + l.ignore_paths = exclude_paths + l.fail_on_warnings = true + l.log_format = "FUK %{path}:%{linenumber}:%{check}:%{KIND}:%{message}" +end + PuppetSyntax.exclude_paths = exclude_paths + +task :default => [:lint, :spec, :syntax] diff --git a/manifests/modules/jenkins/Vagrantfile b/manifests/modules/jenkins/Vagrantfile new file mode 100755 index 0000000..0436a07 --- /dev/null +++ b/manifests/modules/jenkins/Vagrantfile @@ -0,0 +1,65 @@ +require 'yaml' +ENV['VAGRANT_DEFAULT_PROVIDER'] = 'aws' + +Vagrant.configure("2") do |config| + access_key_id = File.read('.vagrant_key_id').chomp + secret_access_key = File.read('.vagrant_secret_access_key').chomp + keypair = File.read('.vagrant_keypair_name').chomp + + config.vm.box = 'dummy' + + Dir['spec/serverspec/*'].each do |dname| + next unless File.directory?(dname) + # Convert spec/serverspec/ubuntu-precise into 'ubuntu-precise' + name = File.basename(dname) + spec_config = YAML.load_file(File.join(dname + '/config.yml')) + + config.vm.synced_folder ".", "/vagrant/jenkins", type: "rsync" + + config.vm.define(name) do |node| + # This is a Vagrant-local hack to make sure we have properly udpated apt + # caches since AWS machines are definitely going to have stale ones + node.vm.provision 'shell', + :inline => 'if [ ! -f "/apt-cached" ]; then apt-get update && touch /apt-cached; fi' + node.vm.provision 'shell', + :inline => 'ln -sf /tmp/vagrant-puppet-2/modules-0 /tmp/vagrant-puppet-2/modules-0/jenkins' + + node.vm.provision 'puppet' do |pp| + pp.module_path = [ + '.', + 'spec/fixtures/modules', + ] + pp.manifests_path = "spec/serverspec/#{name}/manifests" + end + + + node.vm.provision :serverspec do |spec| + spec.pattern = "spec/serverspec/#{name}/*_spec.rb" + end + + node.vm.provider :aws do |aws, override| + aws.access_key_id = access_key_id + aws.secret_access_key = secret_access_key + aws.keypair_name = keypair + + hostname = "vagrant-jenkins-#{name}" + # Ensuring that our machines hostname is "correct" so Puppet will apply + # the right resources to it + aws.user_data = "#!/bin/sh + echo '#{hostname}' > /etc/hostname; + hostname '#{hostname}';" + + aws.tags = {:Name => hostname} + + # Ubuntu LTS 12.04 in us-west-2 with Puppet installed from the Puppet + # Labs apt repository + aws.ami = spec_config['ami'] + aws.region = spec_config['region'] + override.ssh.username = spec_config['username'] + override.ssh.private_key_path = File.expand_path('~/.ssh/id_rsa') + end + end + end +end + +# vim: ft=ruby diff --git a/manifests/modules/jenkins/_git/FETCH_HEAD b/manifests/modules/jenkins/_git/FETCH_HEAD deleted file mode 100644 index d16b9ea..0000000 --- a/manifests/modules/jenkins/_git/FETCH_HEAD +++ /dev/null @@ -1,7 +0,0 @@ -4aaf9784d1514f3f7ebf26b3e0b746681f95f4d4 not-for-merge tag 'release-0.1.0' of git://github.com/jenkinsci/puppet-jenkins -81398b61444401ab366c937f7bef36281e6f5520 not-for-merge tag 'release-0.2.3' of git://github.com/jenkinsci/puppet-jenkins -a1f1b2447aa7e8fbbe33d375556746a7ab599e2e not-for-merge tag 'release-0.2.4' of git://github.com/jenkinsci/puppet-jenkins -8b3f026e731406f7488d36bf7ae9307cdf11a53f not-for-merge tag 'v0.3.0' of git://github.com/jenkinsci/puppet-jenkins -37aab57bfcbf77a8d813f5d877639a940245b35c not-for-merge tag 'v1.0.0' of git://github.com/jenkinsci/puppet-jenkins -9e6a2eb55b8ae36a901c98124555fb21ac2b7899 not-for-merge tag 'v1.0.1' of git://github.com/jenkinsci/puppet-jenkins -cd449fa68cda5d3b61df611fd28422702491fcce not-for-merge tag 'v1.1.0' of git://github.com/jenkinsci/puppet-jenkins diff --git a/manifests/modules/jenkins/_git/HEAD b/manifests/modules/jenkins/_git/HEAD deleted file mode 100644 index cb089cd..0000000 --- a/manifests/modules/jenkins/_git/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/master diff --git a/manifests/modules/jenkins/_git/ORIG_HEAD b/manifests/modules/jenkins/_git/ORIG_HEAD deleted file mode 100644 index 0ac87b0..0000000 --- a/manifests/modules/jenkins/_git/ORIG_HEAD +++ /dev/null @@ -1 +0,0 @@ -ad341c3068cc713898ee58c5c0a6a7bc1715c1c9 diff --git a/manifests/modules/jenkins/_git/config b/manifests/modules/jenkins/_git/config deleted file mode 100644 index 0b4a53a..0000000 --- a/manifests/modules/jenkins/_git/config +++ /dev/null @@ -1,12 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = false - logallrefupdates = true - ignorecase = true -[remote "origin"] - fetch = +refs/heads/*:refs/remotes/origin/* - url = git://github.com/jenkinsci/puppet-jenkins.git -[branch "master"] - remote = origin - merge = refs/heads/master diff --git a/manifests/modules/jenkins/_git/description b/manifests/modules/jenkins/_git/description deleted file mode 100644 index 498b267..0000000 --- a/manifests/modules/jenkins/_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/manifests/modules/jenkins/_git/hooks/applypatch-msg.sample b/manifests/modules/jenkins/_git/hooks/applypatch-msg.sample deleted file mode 100755 index 8b2a2fe..0000000 --- a/manifests/modules/jenkins/_git/hooks/applypatch-msg.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message taken by -# applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. The hook is -# allowed to edit the commit message file. -# -# To enable this hook, rename this file to "applypatch-msg". - -. git-sh-setup -test -x "$GIT_DIR/hooks/commit-msg" && - exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} -: diff --git a/manifests/modules/jenkins/_git/hooks/commit-msg.sample b/manifests/modules/jenkins/_git/hooks/commit-msg.sample deleted file mode 100755 index b58d118..0000000 --- a/manifests/modules/jenkins/_git/hooks/commit-msg.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message. -# Called by "git commit" with one argument, the name of the file -# that has the commit message. The hook should exit with non-zero -# status after issuing an appropriate message if it wants to stop the -# commit. The hook is allowed to edit the commit message file. -# -# To enable this hook, rename this file to "commit-msg". - -# Uncomment the below to add a Signed-off-by line to the message. -# Doing this in a hook is a bad idea in general, but the prepare-commit-msg -# hook is more suited to it. -# -# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" - -# This example catches duplicate Signed-off-by lines. - -test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { - echo >&2 Duplicate Signed-off-by lines. - exit 1 -} diff --git a/manifests/modules/jenkins/_git/hooks/post-update.sample b/manifests/modules/jenkins/_git/hooks/post-update.sample deleted file mode 100755 index ec17ec1..0000000 --- a/manifests/modules/jenkins/_git/hooks/post-update.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare a packed repository for use over -# dumb transports. -# -# To enable this hook, rename this file to "post-update". - -exec git update-server-info diff --git a/manifests/modules/jenkins/_git/hooks/pre-applypatch.sample b/manifests/modules/jenkins/_git/hooks/pre-applypatch.sample deleted file mode 100755 index b1f187c..0000000 --- a/manifests/modules/jenkins/_git/hooks/pre-applypatch.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -. git-sh-setup -test -x "$GIT_DIR/hooks/pre-commit" && - exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} -: diff --git a/manifests/modules/jenkins/_git/hooks/pre-commit.sample b/manifests/modules/jenkins/_git/hooks/pre-commit.sample deleted file mode 100755 index 18c4829..0000000 --- a/manifests/modules/jenkins/_git/hooks/pre-commit.sample +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-commit". - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 -fi - -# If you want to allow non-ascii filenames set this variable to true. -allownonascii=$(git config hooks.allownonascii) - -# Redirect output to stderr. -exec 1>&2 - -# Cross platform projects tend to avoid non-ascii filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test $(git diff --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 -then - echo "Error: Attempt to add a non-ascii file name." - echo - echo "This can cause problems if you want to work" - echo "with people on other platforms." - echo - echo "To be portable it is advisable to rename the file ..." - echo - echo "If you know what you are doing you can disable this" - echo "check using:" - echo - echo " git config hooks.allownonascii true" - echo - exit 1 -fi - -# If there are whitespace errors, print the offending file names and fail. -exec git diff-index --check --cached $against -- diff --git a/manifests/modules/jenkins/_git/hooks/pre-rebase.sample b/manifests/modules/jenkins/_git/hooks/pre-rebase.sample deleted file mode 100755 index 9773ed4..0000000 --- a/manifests/modules/jenkins/_git/hooks/pre-rebase.sample +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006, 2008 Junio C Hamano -# -# The "pre-rebase" hook is run just before "git rebase" starts doing -# its job, and can prevent the command from running by exiting with -# non-zero status. -# -# The hook is called with the following parameters: -# -# $1 -- the upstream the series was forked from. -# $2 -- the branch being rebased (or empty when rebasing the current branch). -# -# This sample shows how to prevent topic branches that are already -# merged to 'next' branch from getting rebased, because allowing it -# would result in rebasing already published history. - -publish=next -basebranch="$1" -if test "$#" = 2 -then - topic="refs/heads/$2" -else - topic=`git symbolic-ref HEAD` || - exit 0 ;# we do not interrupt rebasing detached HEAD -fi - -case "$topic" in -refs/heads/??/*) - ;; -*) - exit 0 ;# we do not interrupt others. - ;; -esac - -# Now we are dealing with a topic branch being rebased -# on top of master. Is it OK to rebase it? - -# Does the topic really exist? -git show-ref -q "$topic" || { - echo >&2 "No such branch $topic" - exit 1 -} - -# Is topic fully merged to master? -not_in_master=`git rev-list --pretty=oneline ^master "$topic"` -if test -z "$not_in_master" -then - echo >&2 "$topic is fully merged to master; better remove it." - exit 1 ;# we could allow it, but there is no point. -fi - -# Is topic ever merged to next? If so you should not be rebasing it. -only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` -only_next_2=`git rev-list ^master ${publish} | sort` -if test "$only_next_1" = "$only_next_2" -then - not_in_topic=`git rev-list "^$topic" master` - if test -z "$not_in_topic" - then - echo >&2 "$topic is already up-to-date with master" - exit 1 ;# we could allow it, but there is no point. - else - exit 0 - fi -else - not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` - /usr/bin/perl -e ' - my $topic = $ARGV[0]; - my $msg = "* $topic has commits already merged to public branch:\n"; - my (%not_in_next) = map { - /^([0-9a-f]+) /; - ($1 => 1); - } split(/\n/, $ARGV[1]); - for my $elem (map { - /^([0-9a-f]+) (.*)$/; - [$1 => $2]; - } split(/\n/, $ARGV[2])) { - if (!exists $not_in_next{$elem->[0]}) { - if ($msg) { - print STDERR $msg; - undef $msg; - } - print STDERR " $elem->[1]\n"; - } - } - ' "$topic" "$not_in_next" "$not_in_master" - exit 1 -fi - -exit 0 - -################################################################ - -This sample hook safeguards topic branches that have been -published from being rewound. - -The workflow assumed here is: - - * Once a topic branch forks from "master", "master" is never - merged into it again (either directly or indirectly). - - * Once a topic branch is fully cooked and merged into "master", - it is deleted. If you need to build on top of it to correct - earlier mistakes, a new topic branch is created by forking at - the tip of the "master". This is not strictly necessary, but - it makes it easier to keep your history simple. - - * Whenever you need to test or publish your changes to topic - branches, merge them into "next" branch. - -The script, being an example, hardcodes the publish branch name -to be "next", but it is trivial to make it configurable via -$GIT_DIR/config mechanism. - -With this workflow, you would want to know: - -(1) ... if a topic branch has ever been merged to "next". Young - topic branches can have stupid mistakes you would rather - clean up before publishing, and things that have not been - merged into other branches can be easily rebased without - affecting other people. But once it is published, you would - not want to rewind it. - -(2) ... if a topic branch has been fully merged to "master". - Then you can delete it. More importantly, you should not - build on top of it -- other people may already want to - change things related to the topic as patches against your - "master", so if you need further changes, it is better to - fork the topic (perhaps with the same name) afresh from the - tip of "master". - -Let's look at this example: - - o---o---o---o---o---o---o---o---o---o "next" - / / / / - / a---a---b A / / - / / / / - / / c---c---c---c B / - / / / \ / - / / / b---b C \ / - / / / / \ / - ---o---o---o---o---o---o---o---o---o---o---o "master" - - -A, B and C are topic branches. - - * A has one fix since it was merged up to "next". - - * B has finished. It has been fully merged up to "master" and "next", - and is ready to be deleted. - - * C has not merged to "next" at all. - -We would want to allow C to be rebased, refuse A, and encourage -B to be deleted. - -To compute (1): - - git rev-list ^master ^topic next - git rev-list ^master next - - if these match, topic has not merged in next at all. - -To compute (2): - - git rev-list master..topic - - if this is empty, it is fully merged to "master". diff --git a/manifests/modules/jenkins/_git/hooks/prepare-commit-msg.sample b/manifests/modules/jenkins/_git/hooks/prepare-commit-msg.sample deleted file mode 100755 index f093a02..0000000 --- a/manifests/modules/jenkins/_git/hooks/prepare-commit-msg.sample +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare the commit log message. -# Called by "git commit" with the name of the file that has the -# commit message, followed by the description of the commit -# message's source. The hook's purpose is to edit the commit -# message file. If the hook fails with a non-zero status, -# the commit is aborted. -# -# To enable this hook, rename this file to "prepare-commit-msg". - -# This hook includes three examples. The first comments out the -# "Conflicts:" part of a merge commit. -# -# The second includes the output of "git diff --name-status -r" -# into the message, just before the "git status" output. It is -# commented because it doesn't cope with --amend or with squashed -# commits. -# -# The third example adds a Signed-off-by line to the message, that can -# still be edited. This is rarely a good idea. - -case "$2,$3" in - merge,) - /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; - -# ,|template,) -# /usr/bin/perl -i.bak -pe ' -# print "\n" . `git diff --cached --name-status -r` -# if /^#/ && $first++ == 0' "$1" ;; - - *) ;; -esac - -# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/manifests/modules/jenkins/_git/hooks/update.sample b/manifests/modules/jenkins/_git/hooks/update.sample deleted file mode 100755 index 71ab04e..0000000 --- a/manifests/modules/jenkins/_git/hooks/update.sample +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/sh -# -# An example hook script to blocks unannotated tags from entering. -# Called by "git receive-pack" with arguments: refname sha1-old sha1-new -# -# To enable this hook, rename this file to "update". -# -# Config -# ------ -# hooks.allowunannotated -# This boolean sets whether unannotated tags will be allowed into the -# repository. By default they won't be. -# hooks.allowdeletetag -# This boolean sets whether deleting tags will be allowed in the -# repository. By default they won't be. -# hooks.allowmodifytag -# This boolean sets whether a tag may be modified after creation. By default -# it won't be. -# hooks.allowdeletebranch -# This boolean sets whether deleting branches will be allowed in the -# repository. By default they won't be. -# hooks.denycreatebranch -# This boolean sets whether remotely creating branches will be denied -# in the repository. By default this is allowed. -# - -# --- Command line -refname="$1" -oldrev="$2" -newrev="$3" - -# --- Safety check -if [ -z "$GIT_DIR" ]; then - echo "Don't run this script from the command line." >&2 - echo " (if you want, you could supply GIT_DIR then run" >&2 - echo " $0 )" >&2 - exit 1 -fi - -if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then - echo "Usage: $0 " >&2 - exit 1 -fi - -# --- Config -allowunannotated=$(git config --bool hooks.allowunannotated) -allowdeletebranch=$(git config --bool hooks.allowdeletebranch) -denycreatebranch=$(git config --bool hooks.denycreatebranch) -allowdeletetag=$(git config --bool hooks.allowdeletetag) -allowmodifytag=$(git config --bool hooks.allowmodifytag) - -# check for no description -projectdesc=$(sed -e '1q' "$GIT_DIR/description") -case "$projectdesc" in -"Unnamed repository"* | "") - echo "*** Project description file hasn't been set" >&2 - exit 1 - ;; -esac - -# --- Check types -# if $newrev is 0000...0000, it's a commit to delete a ref. -zero="0000000000000000000000000000000000000000" -if [ "$newrev" = "$zero" ]; then - newrev_type=delete -else - newrev_type=$(git cat-file -t $newrev) -fi - -case "$refname","$newrev_type" in - refs/tags/*,commit) - # un-annotated tag - short_refname=${refname##refs/tags/} - if [ "$allowunannotated" != "true" ]; then - echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 - echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 - exit 1 - fi - ;; - refs/tags/*,delete) - # delete tag - if [ "$allowdeletetag" != "true" ]; then - echo "*** Deleting a tag is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/tags/*,tag) - # annotated tag - if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 - then - echo "*** Tag '$refname' already exists." >&2 - echo "*** Modifying a tag is not allowed in this repository." >&2 - exit 1 - fi - ;; - refs/heads/*,commit) - # branch - if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then - echo "*** Creating a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/heads/*,delete) - # delete branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/remotes/*,commit) - # tracking branch - ;; - refs/remotes/*,delete) - # delete tracking branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a tracking branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - *) - # Anything else (is there anything else?) - echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 - exit 1 - ;; -esac - -# --- Finished -exit 0 diff --git a/manifests/modules/jenkins/_git/index b/manifests/modules/jenkins/_git/index deleted file mode 100644 index 19db83f7684783c57816c248ef885b7da1d9679f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8028 zcma)>c|6qlAICq&JzEq>u3VMcG7=q1B=?=8Y&K~y7|ms7OhhFWl2j|DlC(v1xymg> z2O^ayyQvTft!hhb@%zsBewgq48oPhg`(eH_&)56?{=7f$&+)aebu|Y7;E;a~Zqdsk z5euLd0YD5?K0-d*dl3MU&&Z<<}i2 zMF^u(xR?o!0B(DvW=wPtG?sTk8{-GR-+xD(}nIa$_L9oU3jpNop_ylZtnwp~QKN#`Oisgm(0m}YKc=45T@;A~Bx`i*A{<{eseJfpEZ z5tnCwo}O{Nn<1l9CfB~`mq+z4vr6UwNox|7=ata-Vz3;E$iF0GLRs;;#VUQ~$(%P= zZlAD8D*@9`4uL}V-9DvXu~;5{k=1AOMZ?6d!lEzJ*0Jmz;*;ry!2oY#W^QlmG#RB( z{|;h#IEzh3KZT{9JfiL6Wq-ND^wBSuS9}}=k`A`!mQHS#!s{zefMZa(W{!$m@^a}# zXBsN5>#4SKX6`fuct^Tl2!({Ej29@(JG5y24`F$7Mm{fNjJ=M3PLgOJl%?+7 zmv2hP*ERLi|2eZmap$bX(Me3O#FtKEGs!-Bq);N2K_Rj97?h9zGEEox+@Bl}!X&cE zbebO01PBbqlzNF+J&_&SQLb^rLm6()4H>~Dva#i}KFF1U_5V$eO+uU@vJrQ<%HzHe zLa7u2iRm-$h0uCS!ulafx2THQ9Y5B)?3)Ss>YVv@XtI^+awx?2^7w#(F#Iay4GWX5+$7XGfwk!Gei+&{*_*=%GmZ zkm&R_5d)Y+8XL|D$cGlyNkMe@>JQL|O=h2aZqSifXtd|vinBYU9^IaG0T@i7L#DCV zL<&Whfn<}!)}``h;zXGWn?Gj-boVwvSIJlGCmg z6OCP;DNgmj2vq)$%Nef*wCIphhg@h;{_ogvQSh_q+xR)^l?vItEim15=f1i2n$`<| zJh!S?dNB7a3Xuah3Xlga%1y`mEu!83A;16aVfU2}gEX8%_t!pqsx5i~C{U1|WVytqVh0Q>j%N;LJy@}KWF{ltOJReyC z{5pk}-|qhHpIdZ2|J8+#bk3$>J8=bo+^obmi|vaii-m+K0bp<=aBu7fn^YVw-W>c=Sv~;)-32Rw5Z>C z0=yO8LE%Xy=kC@huOnWoedqMTqD(du$n)`dE8@g5C~V&3W4R)i@@hEF!#}OqD&u?k zyR#j!HIZ}qEl`-``jeTY9Z27VWauZfsJ}O`T-gjQ@$Cg!O@9v8q_i&?yP>%2?vhYE zm^sPiE;w8!yGCiGHh-zdOx zMG|*v>UZKsf9<1aHpsOYmZ}*47&+ z+|K^yrymia%bN+nmo&5F1 zBr)h*Ec6dr)X#D(Pc&!L;X?J9HSh0jcQi@wdXsErTN??$oJl{qJnnvlOr-I=g*<3c z?ky}Aw`;eWvs$5TUyg!}1j);Y@$P6g(}_Q?JT7TsdI|HlLV&xk@8YV2>p@av3%bvS zdwn01K!&F?{#n+%N40+$vHZ1 ze(+a#kNmYGcFbH~2X%ice;!z2EOdiDenNopLyP9&jsQ1%+i>0ImrL`D)Arc}HRT zE1U)-m6BucFITGdR$kh?Q0&wve=z@tIVRvV%(m{Df)qtsx1@`!uQE`z8><%*&v>iGs0fXx7ONwYA-iy<%j|^<52+sx z;Na_{H7;rg>w|eej0bJqSRoh(v}ina0y_6|@1&PjC#V|TrzbpVRH|+hx5>UIOlQ0` z{wJM!tPZZovd+*xiPdIx$?4pVS}UdIvD%%TKVu<(aE+?Ir#3AwFqQVr${S~PDB0y^s2`D-&HXy;|U=bbpkbXCoa zi1;udByT(&5@k|jin?yZ>PS9#|1E-m3oxuEBo8Q zDnfN0BRYKNa_OVnQgl4nOR6F}quw&IG|eNeaR&E+>JRezuLJ$Z9jt8{pxID$`fT4kSlif#^*WUiNZD-PB z{rwJ2xxba5<1eT)vBdC8K|ZvoPAf(S|Gi_4bkO(Liorc)=HXA;?kPar z*9&3zA-7Gsz9brViyOqE(}a%4a||Dd&Y&pa6Z%)xXDm5|9 z?Fb{Y5uI;XK6>1ojDR;CgyrcKRtL9wfwAjBy^4+la}9pBOR%n$TE3z<1Ss+HkI{jX z4f+i&>VF58E1|JIBaxyM9v*Jbv1?5@dvR{!7Mq+mbt58B8*ZCk-H7BLSq@;{ia9Rg&W`KBTGfE zMLwGLJ#)#bU&rsTfWF33yw{PG7tc2s9NyX(`#PzKvgrJltGmaqiokLW1N_>y7@1GG zMmi3|xvvV(C#6P4rq-b~0G1&?pR1|WKNNPkm0AIMZyKgLtf?F}hF4fTKI6HQ=Zn(c z7yle*&~i=Zja0z1worK2f_ydKI;yR#wFd$@Pc+hMe!@?4KTo9@%_ zytHZd`H`%Yfm^!@Mr3Eg?#}zds*g7$A4gaxd?Hu+*PJ{Z9|887T%XVQrnCY zwTjfw`r;*wziNvfiyE8k3OsKeccfe`YLv6xbrXG`%`t>$T(Yk&u-+XFBVpSU|#V1NiY z2JCFc7xyHdlK*21^V}D)68nnkiJrpyM)myF#+8*TO;j5lmGd<79~@0s72jFlw5ie7 TCH14W)$4B4_S{khyT< 1401303680 -0400 clone: from git://github.com/jenkinsci/puppet-jenkins.git diff --git a/manifests/modules/jenkins/_git/logs/refs/heads/master b/manifests/modules/jenkins/_git/logs/refs/heads/master deleted file mode 100644 index 6e57345..0000000 --- a/manifests/modules/jenkins/_git/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 ad341c3068cc713898ee58c5c0a6a7bc1715c1c9 Albert Albala 1401303680 -0400 clone: from git://github.com/jenkinsci/puppet-jenkins.git diff --git a/manifests/modules/jenkins/_git/objects/pack/pack-e20b49b8c7838a36b9613416fb9cb1e4d6828eda.idx b/manifests/modules/jenkins/_git/objects/pack/pack-e20b49b8c7838a36b9613416fb9cb1e4d6828eda.idx deleted file mode 100644 index f5b9118349f649f0136218bce069fd6903dd9d54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50800 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

5YZ8dHlj~fDXPK5{(M>0zaxCW{Cx@_X688e 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 M?wynF)Xu8?KOU#KX#fBK diff --git a/manifests/modules/jenkins/_git/objects/pack/pack-e20b49b8c7838a36b9613416fb9cb1e4d6828eda.pack b/manifests/modules/jenkins/_git/objects/pack/pack-e20b49b8c7838a36b9613416fb9cb1e4d6828eda.pack deleted file mode 100644 index af371c20f1319e8ed2dc053712e2fee0a5c9cfde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 466248 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 z zCHN(XT;4mSBSLE2&msB6EE0`xCOW@04h#l#6GB7sT26F0Hd0{yi2b-`;@2m~Lxv6B zMe(=4nIR++h*J=DiR$T^NO+OQi2n{C#;AQ@#P;4EnBtNR8B67E|0(=RdUz+>${Tdy z#=9AI>2KY$xIKV~6is)Op4c)eyniY?DQaR=0UPUI)2PwDaALy1JMbn!D^s<8ve7{x zTs3K*XQOMON;h8x8ASU&KNWOvT++bk_oFo^NgV*){(HvUnudIEm9DPruRdcbhL(Z>q74)^<8l7k z7LU!g4sSy#8x$Dng-Jw8=l)Ez5&a7NZaM+4b~(4d-8xa(S=PEu46c&&b9+APkbNSz zcL`x%4XghyGCop?P}~^*tbzQtMvXQp?4RjvC|)_4eK-2o9>%Jx z+yjIuvNirtBLSu^fAAQ?Z^V@n?wAQxq zeo_=pe3L$u;?LQ(JB!f$n+M;04seXq0W=&SURL%HALR+T=-)mueP0yxuoEDF;*1A3 zkY_A&@hJdD{kOI$4t_1_D5D!oofm0{1n*&gAsCuWEm=x5ybtt!u> zr)Da2X;hs`N6sh*)QqdQBP&|nCs`+^RRrCS>PH6tTQ)D5_^XOHl@mWH(P!_^j*3)k zl~*}A-g!*wV9tpkNM-Ay>FdI7T^?MqtmTFN-AA=|kVf2k>Eti(f9AI%7%Pg%Zr9H& zD4m3$!BsS|lm$>ZIGhc0osboJbF$XAWUTgp@6lKt5w6YM?;Sqy*2&uK8u;!d)J z=SD2lk1Dk;8xi^mVvBasYl^vQhQhTs-kX zLCdWMmiim+b|3x_j$!T+1u$MR(R3|($E*jx4L)kJqxx;Ge^Tdhq_0f$Xom1HGiScg zetUoxjaSC_Pd=Xh{dG|w0Dc8#lrtF4?`bVx`!zRHwBKQ&ryXZZ*qO-NW3yN{NE`0o zT-yv>4UJgAG7>^uYuF5UBT6_{TIQ`}6~9r8u0NVacyj;*DZJ+Sc`30mE%Q`|_$Lfr z72)LOlYiZ^a&{r3Y9du{15?|b!J;Nt5w3F7yYsl0o6AT*^&d%y30>Q}>JTjHBD3(g zk>I5<^R8~`23AEcqFQ>vg;^$=DcxtL_D1-43x6fWbgLYeF38E>&Lt{3*)Ds)`NFqR zpwxf6ev#|QQdrCADG*Y%2RHOm^w=qsx|Y-Lb|O!F0R##z%nw^Xt@(+it64XPVeG(+h6 z@;qZSYZE4$+>2=jvi*nw^lSqa+0by;Go()^3s!=GCsNJg>#sM02Zx8W*zj~=>RvOA z5>C=n3ZIBFLL2-ZAyhU;NKS&hS&eZx8=GQ9L;8Ddlrqa&Xd~u8) z*8~_BZoOqPH<ttRe?i*`4$h0nJ@s+qkdPv$b%Q}O13s1zTCZ}9 zi>G&vD?H_L*Vy`e4MS6Lf<4EfjLs?p7b_2^YE2ON=XYYtW_T*YG?Ssa|B`QrT#Q#9 z6xvJTWpG%HPG;x*(&Lhl^PnlSK_Er{MDC*28kjH7Ki@l;(QRx;*wnf+^zNDn*>zji z)XjfTb6VGgU(RHH0;>{mL6LaNGo!Q<@*?nSLKBmb@!*Jtuetb5EoTNjhAP}fjbRXM zSMKdxj)f{hdfB6nIh}>#YEnx|UHaP{sgZ#OfehaR=Ma^rafo_&#?bKbeOBw_-L&WQU+8xb zkMlH&uWs#e0&210eu5*JUZ^pz*kS!FCjE-Z)-ag|w-SXCXa{gg+d@k(sZ>>JsKX{g z=hH4^q1hBnc4$@wZ>oCfLo9Z*m`auFUahs4>3HQGv03Bn_fsX6*s<%X%~~@7+`kEx zY6%pR?##}YVjFTR2g6p90r*s2M7>ajbN%1XE&^I7s(fdq6_30_Tfc{$9Izo>%f^do z@SS&=mwz^E*Iz>8#2z5}e4rc5KwGK|Ur7+5jeziZpD{^`c@2R3f}=iQGDFN*k2-e2 z{uxSKG5alD67*sKb=EyapB>sp`q982^rASOV39zUYG)Nc=4c?BHt01#;^ z8nF(U!UstK`|Ap^e_oLFOxnmD3TnpEG>!n( z4InHc_e(p;T}X&UbMB!f?#GJV=~KRZzDs~TPMdMCFX$`OTd}-V))1tlmElNJ7-$@! zh+8q!R?ey4uH~y;tcTc&4N)kB=Q&O`TMba2rV74!lIgpn-6=VJ>{niU*#~qDcBNai z4BZ|7OEaoHPpW2S`Rz(YB-Tzs&L=3h=Gzw3H1Olo(lCWNtjX!yU#L!b`{jSH;0NXc zJYzgHa<-TF*i5Ptsd&JqNaR-<7D0h@FD#)1OC8B6xbbHo4JRQZ5j8Lox@d{G&>eNW zczs+QFbY10)GQl|fZ?FL!l1*4)MZRlHOdJd)hCQdkb^^~v8`e1otvKHY?zmlr0*Hg zf}*d9XPQv*U4n|RZ=TbN;CHTZG8d7DO7Oxeb3)`=GTAV(?v&D%E|t)J$#^pW{EWPE zECsuwbiSS*3(UWL7vG@VIW?GJ4zAXef<43#fTfneyyHNezE|#fEp?-6ui1x*2-McY zALkYa1FRu<5}&rkj>H_U<&@#GWRm75`+ znrn=LY5-5xE0Vh1Y`JbBqn_2-P+kq-OKHL6$ZaG4M>-S}2U!qAb_0}l;e+>%pryrNV({Z5!P^t$>21_4r$wBR4MMgy{1+DK zzzBSAdCNA>yD3+Sl|$B-yu_wYSj;?de{jNUXJgJ?Ipj8AuehK)lH;Vg7hBGp&_bX+ zvX(kL*NInMH%06`P+=;y)cjgeCaz#Jwlth{*FQZ3IZ2H>0Y`OU(6#SmQ~XiwKk?gV zq6Ul)=3V{+e+uEl8~tQQ{AJsaxa`!#=$2I}ISuElZ~v3PC4m|Z0SRZS;{;du0ved1 z2PT|hQmv~4o_#n6)x!n;G0awg&}Tpa1sMy{SoN*vA5-(1dBz8xteMO$K%V2&sQ$V1 zN_BKhs7QsPqnw~w*+v~}!bJkPwwI%#ryL0_n;<~k_#=szDqS!eGf6Q@whEw z2-z_u2wrJLn?+NbSA9cnD9))x3!Hh1LF{w*PuElO3Jx!9JTh!CgL^v|bi03^@Bk*~ zH0Vr)T9x(gar{TZZP+cs(@SwL97(jpoHM|7_<;&~9D(Q3NSEdLLyamFGRhVS!d{?~ zUzFsQ1YszAS8jGBP@WcCI-BkskoLEpKIFSRQ1|JoZqBY`A;>bPSFVM!%M zpo5XyA0zb=F2>j3Zv7^K?goJro+Ft_woA-8)FS~_*s!@b%@0@sUIPY0gk6?zxz1`B zOheHjt4Zg3-NW-GF=!K!02UsxFs}BsmzkXhJ^b!MU|_Utpaa;8_esWXjNH7fukO_s zXSmVU1>*|fqDe>%@SMk`bf42*$PbF9^|~hC^Ip%Vw(5%-?LVKZ*X*kyd((_|Jr#ZU z`@h3DTQ7cq*^g*i{O8al2!17ArGUsxVW+jf10=mNOF&$nj;EfoOXIL@AOggC6Mwg_fnEoiYhfdK@kVSJnFyp07iebZ}qxBY+ zoF!F=c}_Z>zfEVgj@6}686KZV?&040Xo)UXsGAe<`>a9SR=#$fUI)exzzbfl0(DP) z|Cs-5Q+GVJW?|yLQjj4RFcTvvWtuAc_E90!ZOc#Z@FpO>SZ~2?Ga|58xIPJ#K|tW|V8Q$Mm5+ldQWf{@=8Pgu@352FaS z*Wig#NduAD;QV2vINy97?m2vZrVu0=)h@MrWg+tIg{l|()Ztoz!1p!K%l9Q57(DgJ?oFK7Qbu--4)v0c>Oc^EPXn5-<^qb> zKyr@(0!;RFfeiEww`!28U=2aL13R~3ZOYP$A(53RGb}+# zBUMC=p+dkBoJ<~-O>L-jIA2z&;t=tWrHcCTi?!;6Mi0CD*>lTenj?n?Aq~C(euc|D zVwR}46<(7^Ya1}<6*z4$(_H($Brxn?;_?g&dXdoFYLw`t{&v`G{YUr zg+2{BE6=+{-8mnz(oIY;dWKZYXkbE{uzUFT`SJB;^LXh41jk!fJ@0FxCn`FH{V(UD z-Z@^L@D80-nLx)$UFimjfb7D;#WpL~9x#DP_nZ3n5k4ZN1dG;+SY~y`;5AZ*#-xV< zr{|BKiB~!`3RQls9I)Uatz{bJmF*IYHBKq`x_=`2#jx^M)#*kUwQD9gMj`sB+Z~C} z-%HyEsC!{)>1&6P{9T54?e_)v$=)kw>KcCEoiQfBs7EJ(a3Np2 za14SD<)9C)KqmH*8vOMR4h-@gW9j%+FdH`;1|PpA)i%gCdd_bXV1T-9ch}2|YN713 zZP^zhd9sDh?*dxakx#@rKmHG-b==|kKY&kGmIvOgk*gOBca6DJs*k1+NetxB6HDEJ z+|r3s##}48)asAN*6nUzKlbjO+_8gb7%jq5G-&Nu#lNkowap1w3pm>_Om?;HESqv% zXVIkMgas*|FoF;PJm1DV7eFyN_odO53*jp29~%C zc$Qy=&|fe4`=Tc?6r~!LdEkX6-OPnu~>Nr z1%P7Pg6WZ2DDZ9tKgO6K@TM9f45BMJ-RpR>VUNxX?I4zhU^$zL2oFKJZo42Kn8>$6~`1*>WLKHkTZQ6^g=sXQ5Yn+ExV#UtqwT+nM zXUv$VCAA+klN4b!FyHSG|3#ok8fAkHYYn?*o#GFxWX6>4o=DcweK)8=dMnzMN{I$X ztI1SASiC%7W6o$td56*#XoN>#er4S|;OBs7RPNE%W*m^y(HE*By2XrVx{HdqCo%<0 zJl%cf4yA0w>~{WvOX%Y=;unC?P+4aixNm!03}Q0h*ROdXcaPD(IAbyJ{zq{9;IhhI zgtvm}1$IJkj2;9|2;uzs9SD~h$x~k%p5)U9CZtoq!4_?MDsT=dLLp2te%8BmiJPLe8Dl1oGqo@i+B9ycO15VGkAxQXIOok+zG(;mB>s39k z9(zJ*t8+Ly!W4A`VSlw{8>uQ5$h2nIw6ufdQm@!h)@>47;dM}bL1#!LowMw^>&UYH zJGKY!o~^NrQ+OG|=6Nw_NaR5{)?r6ox7zz4cODg9RD*Q^@!}*YzX*QXkg3-GxA-ZE zGC9h^+g@%yx#FnexyArA7bcI9%PpN5;}*>Is}R=NWZb=0H=&1%XZcpo3OMzjLAPc6 zACfzr909&rUyEPT|1UFOeOWknn4T13ZyM57+TJ=plFHSEP-3S5ql&rTkt4@hO1%*# ze0*~~$FNjInu1NpdxvZ}M`39YGyq{#1Kv6tzOs^*he8WcjSzqSq{l6@`Ao%}W7M|s z#Mx)Rp%yY5Jev#cZDxIxZ>-g`wn1haRjLuQ{xFNJ`(pQlO)m0#9@Di--K(wKoh+05 z>srO8aA@mBsB3GLX+@rzrpzKoI@#y&Pwk1G{>DUdix<5^1Ar2Jc-A7sOZ@?f;p__v zuLFBmqofyUfc%kB^=Zc$z;57oXuBb;bjX`>w(^kwKb_GQVypJszUy5t>hC}yGeha? z^a#DjwDABK^`HgL6cjMhcEr+{DJ2@kO-B7(i5ULnHtKwDP^3)9fIbXV(I>eU&mR)Z zo{R3ZKb5{2PWh-$Rx_*GZa;KZ>!_d-f?_vf>f!xRZ8lgs)AK5`q3g&zt3GW%5aVR5 z&(1jkkUMB!b0XvHa=zjc*>)64Un%iG2kE}F2Zc6fdNTS<=zG&hsIpv)%>z?Z2aTu? zg?WxktkTlw8Rh=T(EO1nvoMo5Bnl9GL8=$7pnrp1zDay*WD1BfT|Y*Q%WOba25hl} z-DI)NQ^}~q2%%X7Gzzf?F%b4wdW<8S^)CbvU4yc7MU3P_7N%aDo7hGQapx?wq%RIx zpj^izaTRW~*`NHesN(QtNAw2o`c7zm^_Y6$Y&j4JN~w^fm;JxI$p7vNuOwlAiUQwX zkFaR_8zeP#!?Ki8_EP#EW85S_n?0!T6U!K%oyXunVZLc~&2bwV4O?q!Sd56=$qe$l zC6~5CH$6lQPU|nG<=;kP$v)Q{su%;sMY%ARKmM4kJgoydh;zKW2i$hcudh>r7SO-@ zE8oK$+vaz}FZ=PV+0+*6$*CQFwwJqBaYM-_2(wxzYfTj~UnC^)qbT5}g8VUD*f=o? z`W_&xv%B!1+>2!G&$T0tWb&N-6{<-7{iV-_Uj@MJ@HonHf5=jVgShVYiyw@Y&9~a7 zx9fZR8IWf3Q_J5>bEB(EL11(`&nC>TPAFB*$BAZ*%F%i*B30w&J*?B|Q>L+^0eEPZ zj{Dj~_qo~^6O{7nBjEwz}lyM?0IJ`^{BON2GGUnP;IM2cp$Ph-&F zb#@Drutc!_pf)J$A*S)(31Rj}m}HB&)c27{u#}OqbZym5I5NH~+qwwzl+}MBvth}2 z3W5BIk^y568sAM1o*95?O(C`E?@B3i&O5V9<$(>;0Fe|Fn>r^7J5LG`Do8-peJjl% zwoT^XI;!UVO}og29qCq@@MvzAn_C0XIf|z4O&Q}%3_=JBS&CTH^8rx`D=VT9I1|c} z6u*K9UkWzT9InII405_!Co?!Zb+Tgw&K}RIo4m8J^$B8U>ivCB)NnRTI$nzbRchq@ z(wUEz&Akml=R6%_MDxdae(F|8wpV0GO}@;DX?hP~xED{=1c~e+BOcmshle@un(lIh z!tw3}Zhw=RYdlB#E6cHcG_Ugb#35`$!&qmMw~i}oK{-fl)9{lVJaFz)VbZPqOsHo0 zEI4c(J#5x8l7Nu^#c6cWG@k#2Oc${u?=vn zc+)o@P<>pN4W%lAgF!Tc_JhCipX|gJ#T)zUA7HI7exNyD@ljv|1E|*mBN(xQB4}D# z4$>-ee}iUFw0>%mCzp}b=bvc{ikq5bjj_aYp=O0~a_Gi*TV8FB|HJz23A_AzdL}m| zffP=lyHZwFr#r^Vs`QLt-z@*;M^eZHTG?lay9J8)=hb5i zrfs9s_+*C5h3@64_?6Nu+!iK0%x zQ3r7q2uVB#kx8lPs)8ZucZm-dOXWz~iZSkS`*FGi%n|ipny~6+heSv6@OYSt!0sg! zyNFUDq|`zPPe_UZ8oJC6YeeR8gn5Y!G0=$oKkY^X$;=HAxK>$t+)_%1#J{WU;%j0D z4pJ|}IqN&zG+1{Rs%h29%P}d{-*B(_=HZ)f zetyu}$V5rh`M5Birni)@u{cBHRPmgW+Tu(RVsK8;%NiQ^o=#w$cnlMye*tqV{iBC9 zX_9GD)&_);7IU37C$x*}yGmr}eYE3tM_{W#Ue%c4NB7rPL57lq@VjjG zyLB`pN|%Vti!AjVV)^5D5AZ8Ell8OWC=i9odo58Ogekfe4eOfIq4D6^GupMNq;-VR zn_rKIG$Wwcf_FxF`YJZ7$`(_7(5Uzm{QKhA;jq`}HzfX21kDpte9o5FU zBUQ|EJsF3%Tdk78ZHu4Y;>t2=T(H(CDxQ|M7v-pI_k^%jmHnLqa&*OmT^WdrCXuu0M? z?l2SKIDY3%+aw9Q2|wW@3}9%vuV0jn;;kZ6p?s4sL;@CSMiFU{1ZX=6Rsg_qLbBDU zSVvoKkPzpJ&0eufpT5oGxBKNX`_#ss%*BEDjGiONPj5d+4X2WTQl0U*eQ%d;iK4S# zOh%+uCg?}~wiZ*2GeC|noLcNq%N>uQ$+PbF^VP>+U-yBYLDg=tZPtHTJo6=I!4Tg< z0$C)#R|L6ep_QSG^JT1BR;#KwX8YKj?##sq*}Uo$dP__u>MPNbk+cPr<4~lo?lA7a zm}D0@*h$E|f0FFMA6R!rZgEiNW4=9l4Pakih9!X?%UDMa;5QGQ^l)OSbBJ>dEK2ZL z%q$;T#9T;}F31%>V zSBvuj9=8CtpSE_9(G-)uCA= zSjwu%k)AooJy}vD*e&X?HEs^KIpg_;ToMWUU+Y;yNmH~)uHdJt+U@uyqzXb2*UV>* zul6p|#zfE^L$TsUoInw(p9`uIkTtTYrgE=W4&a=1yUEQhkE-onz+AmW(C68$?RI=z zu5=tQZ+~~=&wPCWBjg)aB9r5hp)Z56ET~w1HunGZm=t&S@mGVb20kjOMK#fbHhY&( z$u!)4LK%^cc#ypO?{e!T{U&IT`xY_6Y32yBAc6tQm-3yTxDf-q>Hm8LLt_8%i-h1? zX&5+xk;b8=G13vQN{WIwCEvmoA9iq(ah30_Zliu5UKqj@vUa%D#K z0aP#fF6mnI0KWPBh)HT3kvL2Yp=@FJMRY9Dg&y5I(Qf0XY=k=_f8P2=-}hAK#`?og zozr_rLaAXT{CJnSo@G0lDYiB$;-k|jT&^6to=95urD6w>ZzgL-HL1F{EK`Pppm}I2 z$f;k-*%Xii0Au%no(k`-IzF7B5;59{ZIhEg?^mN77OGf08z*Dmh|AvJ+<5FJ3-LT#;*^(ZIp?W8_ zaJ26L&L%ejO&RdpqlLncZUQgWhsRp$Py7meHiEKch~09`WipmKZLH6QhMT;`Do8l= z;)3>vDXfpu^?5|}$CDRj;sR|7EWd{D3*JTW zn?ve{ikvCSD$|q^Ktow=Lnu$aQ~BqBT86FV8qz53VdRHlrh?{2 zKH+_sf|1LrUSp!L;&(A4^=8!E!^P7)S(KK4Se;gjCu8f#IaFq(7Kt7ygWH?QGbkin z*5Twv;8t`JCok{VHM$5I-n=PO+eu{90m$p%P%K_%6z06{+2;11wT%e)*xygzOIYR= z%t;@!W}3^;H1n21(9^P!19Pnqydn-!7_8* zbu>E4KD`X`ZpixpqfRt|%m<<#Q0x4n<{(!-l0p5JsY(m5(J=o+?m=bC*_+y%aC5Mx zuMGPDRi|nybkoVFshOMf3wGwn1AJF2BE8c0f*VV6P8jexO)xu{dNG*UKhH?1OYl$( z$>f<_mAFtU#0VO4ZfoCAy!-mcx*Pg)<6&oCkL$)jmL8dk= zLL%wR@KLQ&`fNRpk&wv#nqDJz*RH_K-Q7lTdWcyI!NmH%?I>RNORN2&kG&wO)I`oe zt=0e}0T9&PY+b_8DnOXESwP~{Z=7M&vnK_yhT37Iduq9a=E$ZCjc(ANm#Rvqp2$~( z{fAo**TYMTN^(ICdmb@Ns488V3n@#ci}V4lX+rfm!5OGDhb0A+Lpj%;Ur+bFc+#Fp z_%SBcHJ`!XFUZD>>Od>oph_}Kv9ST4gx%=Y|E31(Ma}z=aDm<6seu30r`!)Y`tVaB z$iM7r|6jn*44ll=)@(pv^TKmh$T`s-G~2q7&KHl$pA!AzQ6T+(Mh(QjC=|J5MJ28e zG~Y!!*Q#xDf0dgAF$WH87?i`Z#}B)DEdTl&HbrF>zx@~Gqkj!bL4h?MgIY7~dVwVO zAYgKo9r^!|^$uQ@|6kl_wreKawrv}en_QFa$+m6V#?)l{)a03Js>ycUbNc?C=iasM zKXBIPthe@lX&+)JW+i##v8}}zv#qY9`-r6i+?gTLQR>#Cp_SCQR@?LAU3Bt#mamSY zHc-Bve61a^LU-r;R$DQ3UCjB`Tzzdv`;+8+qNUoVcH^tJ?;72UmXRX9!e$caQ)fOt zRb<`|h(f7Q$*!A;NT3M>cm`Ut&70VW$qZ~60{7AnuM53TfuvTA3XxJPQBUm&9jf4XIoC%H7F*Ya08V&8whR=Jw%0?Q&mx7)m^Pt+T;ODd$Ua^`d@N z0K#{Y@Y6^c`#~j-v9_fvHq*^HD_y45CE|D&)t+2n9TYoh@C_)x0o%FHF<~m0+7l)z zox_(A?5E&Yo!?}zW;i>QAAo$g>-8;;21|w?hBhLQCZ0%X@)-V?AZt=Grfox7pR3zB z$wu-emP=KMFLWYgL^F?KPX@3YqOrPG`B3%1%2Ye(gu1|#Hu*w!j| zUz;x+puo1ajstPRLRPi#_mMBGXB(L>{%LvHI@c2}Cc3}=23U+N5yMK$=%<62+xQQj zA@n}cJ3LXF`+?e>0GARH)-b}9{TCo{^)4U`_sr_Jw? z^ikBcv4mCv#96_|64S?qD6 z<(jsUdqkR4NPz6w&gD0{hg$J>yh`{Zk!|79OeOrkQ!-%)3@(Kd%~GwWAKZrgTjo_rQMZMq`nPY7muj&F5EcUE2;i{~pA@ zz9<)ZTXnI#f$v2j@0SL`nWjD-v(Vpu`DEQXw5b|ZN&O^2`~%_|Np#(zXNd~>o<8_P zh#fvJ0)4z1xo%)0VIyXNoa#w)O9{Fh^b%ry8iQTF6LY3rB^jj>3Y2LveW4koq9!$+ zK5|pwSvNcHZ=FR0@YFZxt7=o75=&&16j>xLx>*spo!Dt|N5-g51!t`?7L>zZV`SMO#J4wcsyO9%af z_yI*AM-F{n_lLWGLdj%Q2Z*amVYj`rU)ukXm@y*lqK-i6i2cj5AOjh3ny|x@mAA-K zGNV(E-WyAl)-08;cm<<+T3CqSwe2P|5=MG-DPhCvJb{w4s+2ntK$}fleUad0a}yYL zo4s|Qg+g1naLxF7X#+Vc%4Dr)GQM@StcHSL2UkGi!%i!4+*G{qxO_(krtViY7Put@{?e^?YB z>sgSRCpg6__lFeLpjr!Ep7)S`2|ai=vj>wpE2R|Ku|OL{M7E#`$$xT=Tl<$(O1Yl~ z>t>Ica>Xi--onCAeDJ4dV!XlxqX=oBuuaXfcTR};9%aS#$ZZN?8^5(>)K4bzhd=)E zde+%GD%^f7@?l6QGe^W^x4oOv+YnQ^cfV(wEUe@Cxxby`{+$u<+0_$_hb?a%1+KrM z$VXQ>rHBB*3epuQ&m+>rR=$Fxi#q;hT;Ot}bCvoA)!PfX)xSBaCxAPJ=NBiTQe98! z;^_2aEom@dj}&k9v;j}#wqT&y%5oiJ5sGH<2M_psF9q2>kBK z58bEz@DLoYD7Pr~sjmIz@cieo)Kk{XF7lk>p^pS4^N2@Jy|n870dd&u6{64441=>A zWEFi2KedWILg(aW$M^I#rt}tU8U9wT7s%#ltt*g!JvY_>R0{)xHlq3h79O(|Ma;0b zdnAJ!$hh(TTn^@wenzTIl8{r(eC_Csfu!TwsMn|3=hg?Z16D_9z zmr`1Jr6+@mXqT6v)Of!a*_jX74C|71ry@tf?sdo8XA(Nk$L<(~**?3I!_!mQ|7J+9 z>-r5ngp4wlNE*J~3Jo^D-OeudeUZ?xlL1Lu86LskwGP=H3L%2h!SNK4_H0F$-$=%2 zEwgfPM=Zc7#IsLG%?QvdHkR)*!6Z_D+}E#qwQ`S&is}TN4?MZC(;tQ+5m2{}ZH4C(pzRh*87VV@pz4Tjx$7An9&@^em19^NQuzg*P4giorkKEabIkCZ0r~SYT`7 z_^q7ET%;`0@6Vw=*0E|1Jdin+0I(;=vEz-?((#RW*j>2YG-7qkamnLfTs8~+Q*XN! zEM>RCuf42@4d=dzpj)=n8%ap7Md1;wC9}X;kPi3FKR1I^(#_E{Z6zCyOh+ z=5HSl9gaw(rOGi$#o}f50Hn~RT&-CMfIuo?b^j!aVfz$>vKn`X!^*4nzoXpt((mV0 z1VmudRxQn|dL5dEl}rG&zkr8)ulp(IuiAhd(FfCgmtg$*uSvQ)jOy?Vr&mu(9VfGj zt`(dfxfe;aY%drOS?lORw6l?T=kiDP(7Uh(?UrV4HY(C-ui@9+@W~&C?cTo-Kzq!=3E5vc;WUfwv?1_REtUbK=eAmlr-|b-L)gZj(-yYRLOUL(^h#n0poNK51X}H|LJxF? zp+|+TxAoD`3HZ^u6F9j2hFdDFgX+86$?W@o{mwbxZIOdoKqTy3Fe~{h%Xmf zJ8ZrnipNbty6#)r3QjC|iKV9TPd`O$Ix=XHmI@oLmK2)>HAz`>U!zY5qyXkWvx{8?>zpQ zU)pKr_OWl3&0nf82LLW%a{0M;FdjT2Rpx=Ze`S6-bk5#1JiPiE*PKJ0degOTpjbhh zJnFh!UbavBWv#s8vt`=L<^pvar7_*0j#JXde;CVEhJG}jr&}x{D@s_TQEf%0QGc3>kY{LcBE+W7g%^0UMVJ7|%s(rz`l)+=I;M#p*?b@2B$!&`T)3G*`C^d}> zZks=Ji|4fb`)o4HPaGd4Q93Se-3gd_M#-s*&%%hRf#tOl@z#dw(GM6d(Hf|$dBzFS z#wA2caPhuCpu@RWitHsks5MFi@n=_Ry~ClN(Ou5O$@%uu4C|K>lYnrl;q3)Gzm8H8=w=fc zOu#YyGw;dzJs{A6&?BJHczWjCK=1*KIh9YtjS7O?lmfj3=b$9`vy}0iRV{#YO6yqy zD>rko!K-XRlthjLZ#lVBhm4SDMn6_y-->A zpQ9vBE@H6o{r$v+cR;%zWf*lG|HeJB|5R&^)%L%AmB$CLVrMzL(~w|2R(RHstm_5- zR~KT#hFVE1a-7Wfme&RdX56)GH7mCz+ZIQ0Xs37{P z@G_Q)R@qS4Z^JM;W`Eo*vHvaVv+wXtL~4HI7hO_31PORSywT*?NcK3B@E0K+tv`@f zb#bmnZj=z4NoYjNo#Mn@c{#{ig}6Dc3n*>myI3&P+0bA9AZX#3W4Ud0XxVJu1)l^h z`<;h2L`7UM%tP5aR+}3BX8i5ZNzbZZhA;4gHRxST~WaLIB%hkfQ}aDo_zU*3T> z{cH6GD9j;LuncNHKm^jh@@EdDC_#V=A-NH0TDD)%{g~hY_9a#)7-lF0=V0H6_a3eZ zBg@}RFy8*8Hk-X!v@f1L%x|9rUbhHCX!i-@(idL9K8ZLzXM!BSu>(K`{=+~8l$o^c zx;3eVqTS~`1jP#4(+|r!5m{<>u@Sn3Nd`I%z4$nAE=!kb>3nK*kR4sHw>;Q}MYd~+_jMp=kR1aHfHj~r> zqmdzE;b^|jrnI@ueK=RBMT;->NcH^3V^cwI;^{CnN-_M6?P>;7UFnAI;CuQe?k&Vx zoL5;?J)}=Dwef@-qicR3VZG#ic-OH|yq%s+F=6dy8lqsFRsJo$1|Wp^bk@Qy6WH#T z#(-WoEEu2^a~(VAJHxLa4CjE8ro$(lrzM<{)iLJr+w25#S6+6O1#rAXQ(4LpS~IKN zQ-SiS?fHgfZ0%X+8Puu%l?oT@3 z3_9jF&u`sw2|Xt+cj)KOB(@U6vH;gtAFq76_bAJ0Hzjkv(DEf zeNmr%*os=*f2gW7zzz#|p8&CU5IGL?(JL88x(&!?wEzWrCzgZA7^8W(^JSEZnaAp^ zS&$8vMm_J8?Gs2rLqxJUX{0=2NFi7;mjp#nGrJLo6qKza6Q$rb;W-TE^JMecb1u%d zuzU0Ou)kEB;(GmWFwNQWFs#XOf}k9%72qj-n)q&cQTXt&H8ilRAnhP^7vr}?!bfeQ(!BcGM&6C8Fjv?IyOc3C-nP7KE;y_bx~48P)71>= z942oDH@oWT+s{7Rbf#UGEgcZGSK;ChJ46UxCwH^=BC<-wTOV(*vbvR47T zH5Ll5B0AI2gH@YVTctKXDXgWJQb2C(pX{3F0`AW`^hQU$CcV2cqO-)N4yXGo;vs4@X>&5O`#Kn)t|J?T7Vf?Nta+o^Q>?l^BLZK=6Z}ra6oWq&B zuG^o$!JpLcL7ZW1JS*t72lM%1;)AjmyX#3#8X}=6|HGz!7T`29etFcV9O4N!?Mgl! zQ-{#v8JR?h9z7CS^k6QjtHD#{i7gmM*ExLwc?6)8+o< zH7va8i)C*aDqSglzs~9$w6>5qTWpUUS9890qtkU|&Mn+gs{su<>A6ZkH9ZT~iAxl< zs)MKd$!Z!JgxX@w4p`gumc0Z9Y(2T1eLQUk*aBAZ{EHkF#E6Wb6d)*e5q(}VMrk51 z$5OhuH6lygiYwomF+dA}bgsBPsWmwaO4}qEY3&T??&N%aAlOnmSo!UA+0Hk|A+TAv#Fhbws%%-m zgDfXBUCc%X-DavF#1#77C8{^Tn43!xg3ORo*2AX4PCT}totsD$XKS?IbZr1BC{udM zL{`56m(^@)AR|#cr9b&TLQ74Jlc-r7Y?VUwa{25T zd~#|ZK7IRNX#0B4$km*eQN74PKm-R5EcFTQ@~N)w?z^Gf1pZq2nM#|Ibr*mc{GaIW zWYD>e{SZR`SFR%N^0bX&h(-9JvbvA7sF}cekgcs_k~)ELy}u9pJgrIgD+3ac(|Q!h z4x?DnS!ZeDiW#lc=|)D_Y&S~jGI-ccCjbg5;#io{43W;PV{9`sz#NL+=UwU|5mJjx zLLGm&>pdPlXZI8ana`P!ao=|gm}>_!HJPuFi8uIr>W(Vzw|(o19KJFPM{wZ6Qd10( zw!8IKO}tP4W_VTqx}FZ*<$LO^?pXv@+WyH~{-}@{-=;5T%5h`KxfUX{If%>3%}Zqa znDuKX;1#8EbN>!<`PQ;AmmIjoR3`0#4(uMpG2s)&^d%BWK^hVoguSJrax#&69j5fp z##yd36Yg()DKD^sPM!SC&FqxygLAZ%%M~~Mgw2P!DamMN(DyvF)G4e=RpXr4Qv`@( z<75cpa_(6pk^4&smK?ma33}_O-UP!eH^_D7O1N(nkmos;LrC?!xYwHB1@grAt_VvO z7AatA^&Y6aN%74inkh8 z`56Zy#PtSh{B+u&{$E|9MT~SCtmpQb=L^_iQWaR}vQ}wqLT1|qv$j=OVu;Byrbk^n zV=Rq1eRrCE{di21i@HND=BfJi(u;2&fZU6AdBwh!>e5H~5B8`!XCviRlTS5f$Pb}4 z*Y$3n#q*87?r3U?)?&B08a{a{2j7s zlwHw^XxK?YI6q(lwxPp?`@qVcaVlVe(wR#d-ryui(no?U-(X6&vOvOPoceN_^K%}( zOt`J&D1oX^uoW#2ehpg%#Q|2#d=W;e@K!RLEx~EhWR|bQk`-qpVv2w+Zx=(6D`xu| zGqh48An(TxXBvdE`r7gSz0iz)KDZ(e%#u&R=1OH)nl;|H{rBwY$jQv#U9sd?ZyTPq z7jw`FO$ulp*G+eWqG5W^1J~vmD|dYM2l;6-&x2)N$C*qla2tX5-!mT^%L5z+bypm~ zY8^-NK=z7+UeGNZ;v+ol&VhC^}4jH|u+$UK$3S5 z!F4YL#3sPYUuE^g;2OUU6BM1=vnNuG@9^|r!pjf@C@%l)O{(J7l*u#LnbKgFy^+c4 z!Dx4wbY3ilEQr-7Wb{jueN1%rgSlo>s$Yo_kQ~ z@%qDBX>ALu?s*?Vaob~)Zv#!t?ifjJt;LR=`M!vFq(^BqHT~!qUr5vNIvphMh?55b zalk}zUP-0`SBz^&bW!Goqcaw8YB{Q)QICG0x5CQUHQh~YPtyt?V`j5(psCQZW{DB!G`vs>o0YBrRi=bZxKee&mai|zAmcEfeE z3O~FEeHYbs68$2`==t~k_0+dAJLQlCph%2 z@)Q`&CF%MLnHy()mn&g)z-j6DPrrCD?2Vk&~MV#^t9+pxGj%;9OkOw7pzy zZyQv07f5+a=S%v>U2Zap5ZI_~B3ZJ{S`PYo4;~KAGqDH}rr7uk1qo{}xp?zi+u6{* zMMk9(Ssk~p?TGbmKcF});R(zd=dTzMA>3mp;0em@rvym{iXk=5T(9!Kzw_#M7~rPa zWfffBkJyC6E&(Gaf3t6`b=+NAm$}8G4$@N6w6d#I=!KiF)tLqkA*K&e#`TERUpNW% z8qI`fu6?Pxzk*E6wD(E;(BH8F;~TLgeDZ_rtKIsy`=z6b^hv@W`gd-rR_Uicclzm5 zP|G}XTw8cUrqGs*i}i>neT4%^H%@8U@cU3_K=|1w?_toH6LF8m(v z^+MUy%d82gXFa`D#Qn1q%gSC^Ei{WlU2Ajx5N`4yqE|38v*mQExNy+LNXPYSLD3*D zzL;CauhKb(Km9lCh=*Co|DrTOl0r~m`u75#5Jw$$)9NoP0iF!<(Dwvimo8n!0GSnTt4o(KxE0OOL*K)HNZ53`>@zinCmE5T65I2y!QPQ@k%VthQGT9-HYbgl z*IwAUlbLO1w1vsiiVp>A4mK#*u7;D}U_|%!JONk!?Ir#?iz*@McjYG`FIERV2Gv%O zXef6^9>l8#fOu8EQn;x>5=~X+IYoOGBzELjMY0%|H(fvdf^~<>W8z%gCUym<^}*-=O0UB6#r7Yf^^zfKE~pQl{J2@6fQ{EYhE33V+-Vz! zSi~zq(dHS8=(w39flxa&39v%bRI48QnX|2)XLTJpmP5biq&nOBUGsQJ)i607nsjKa)ovGRfo&Z=9&M=pyWXZ zK?E43OF)9m4j3i35??7z$B7-`po|J7J9uHef!P$;`a6KXi4-MgXo*5A$$2%*TK-cK zPEkWpQciT~=TZqUTWcR@>J)~f>r;x=%uGAEtUpu(+hO@%&D?bf@ z?RnpNJ6Zg9TH_7woTH!fi?j!r^4_)VO*r_rsHBF_(#wgzGjrzJpI@N2ndi>))0^Q| znmu$9cJ2nEw?>gY5d?Zp@+(0BLC-m%@(XC3{pLoXd+^&3CBZ0pkh}XAS(t%S+79qd zn=L$xfDThxGs!W%xFk-i1Z}l%*intAj53V;o#9B`j4E}RomygP5tLkeBX4RN^psag z#att{N;9`k#24-}%MKSz(yfg|*h+c$$(Po#o39T6 zMs?auyR5h9hRXKEcKL#@G!kuVw>9EM&$Wfh%!~cRU%?gEZU2I7kg2i%^QnA~@&m_P ze{2M@B7t%*5zZCB^wJ)C1B`AtE&*YfNZ1_~-RYPAjK+_)uL!2VO=B}nr}(-QzGPk@Xe!`*@6Lzy4}E}P!!+#upjUxTU0`gX_LvuV3giVIJV%NZc)At=<&(h@j~>p=P*4xzpF$}|AwQpZRj#M3uy0UTB^(19Q}pW_(q6NUQDfRy^0#LP9hb$~Q53E?W)$5Ef7RX^AMQQ*pis=)ik#9!H3HY)TSxEEmm}K=afr(9A?!I@ z$JC$W<|@7(w4P6(mgLcL&^NNyx$&FK%m1xRo>B;A0SEwL1 zL(S50t?}ce!!^&7#w6mp{IQP6f~C!Ewr&)2{(VDyp7bT^IYZUQoZ?6TWvr%Uu0&Cr z1xH>U4QaNI2)STSaN~`1R^XXk|1PEP^M6 zQ56oypkxu>!*^N>sEGW9GzHM6kJ#l)I2yIiTviG%K`1sQ70xoZp34&;=Pn+Qv^ii< zZd+O9FFrJMcnmm}LJg`O^63nPBHmvzcw9ExPW%a|<^3)u+<^-#IQ@CcQAFbuPrUZ3 z9_^Re;k_uDi{AN_7vQ$kjjLdmEzl)Q?9{%<7y#Uq- z36(PO!%3baGI*8gDUADD)ZZI;RN*N$M>Nx6>QXR*;b|n1OvKV7APeJ%$-Wt-AhU** zD+5qxK80%qH4%Hcs1{ZU z2?z3?EGvcd6)z&aMIcnJb1Z4hUj|?GM$pCW2``w^HN&#vOGy1|J9}n*Nb!e*zO+)Z z`zHKDqQEChTIm|s~zz$|>Wl&~G6f1olgE>XwXIGP8;aS7+E|1Kx5dVXV zAe^ND!{P-kGENu$y@@F>oXI=P>NJK6C_@hUqIXBu9t6w~ow$x49B|v8z3mI_LFsV7 zt{j-QSU_kIs3e+SVQe3H?;U1bnV?v2ob;gK9ZIc!fPlA>y#dw6KnW`d?)$Gc&TLeq zhDNVvQ?#%pZA_9HUa$6qeE&NTe6Obo&4nARt>7dfD{IPOiXVQo)9u8vS0aA6w4qpK zh<{`887DJ|k|L`jzo(&`R9m_PR$Y{NNa@VZkUjSP{5h&(1j%9V z@Q%|ShsnYT4|yy>?0p3*j$JKTdWBxl+y)8xb6`yCKxj=f9`~)Rr5dWVj>U(c4#Pl7EvuCQ4)br%V)sNEY7W@p{OWF&ckFQjOFz>rvMTg0eKzYoI9iZD za_~2vdDYjOb<1^G=~co`xB~kD#|0&Ph>yPNSds#&;D8GR+(N+j)#NU;Ky|kyWdYH3 z5kH2Hiz!ADY41VNJLGNpK=9!Qw*?8O4Mj8)bhs#GGFg-S_mKT0twlt&}takr~zcC>z{ye{Ha7Vi23|s1y zWnoGAQ<4ZhoUzm6&4o#At1y*5N4P~w+a_G2oimC#qhDSRG$153`hS=TNbdn_+YW-0 z?p)yf*Dz;!oE!=MK1metf@N1xYxZtMh`@;}!A1|zSqDp#XA*kJ1v#Zg*di%N;)XAC z>hfvFN5U(@bei$uTH8sSCOdJEv6E8G*QfW@D}32jNFEX-s#MJv84Rxc5tqf!|Q zQAM#oSGRL??^SjUS6;#EnRx?YvxXHvz@@7>r7NLwGR$q^-11z`LH=X)ZnPR7+POoU z^4h^ZEYP*sZ3!njDV=^W8bD9U&|oF%~tSY$;e0`k`U2 zj5Qw8tZkkKV$3( z)a@W%U+t`Bj)<0H{t!QZn8^N1cL+#2mq#EA;!oe&q4Zjau%=W*+}aA_cH~Byx*0)= z;1S3f2ae=2FUwHT!xq3mp=JLUa(G!doQF&m4O#^P2J38^%0H+^$5LN2dLtySCKh?VaRI{~*>l z7ZA#ByM!=|9nE|ht-zZK+TTDG8OZ4dOs4_Lp4lEVIY`JlT#k^b#zVBM=&k8IeV`q7 z&Mp}Z9mrH76=lZ1er;>ykrtm8%^^9?LAMsMnQ!uTPA&~rCY7vIXrqWCgRzrh%Yr#g z^3aG*Rl}g$dhX!rkWGF>UTIX+%l8aiB!7QeD2dsK_sX3OaKA89h=GDVZPhZ$E#KOI z5AK@m>4Um6-!AD-TfaH^2Z>pu5R(UDe&YULE!o!~p)_-G9Ee}$`H(FoX|)4MF-uAr z>!U2(V|AXAf>HSBR+gg`BQ!mYhI*x`j*EL{XdP2hu+CUkaXbX86)f7Amgm&u*o3XF zYS34FVLXXnxDe=baKs%a1E?KZ2e3IOlpi*9|K&9FKM(%6k}hr_{qdM}PI7BYRr;Cz zry>q%SoGf^g?DbG?c9PnW{I2hgTD>x0SYU%V4PwkRI`;e338Sj_tk=TE{f3KD{eL9 z!&k~a_wQPLdWCt_|MJgQ^*&@Y?w6`*<=zD>TAp2P_J@-}5u`Sgy7!AZvEWpDtfYVj zZWfSMb;8w*Pq2hNsNF-PC*a~UaeO>?ctnv(rFI26FJuI8=!j?qSw;0?%6JHD!-l6! zew3h^^NfrFubNyafmc1c0g2pHD$l;y<2iA1?bjZtYQhIR{g~k=4M;>CxK52)TswmY zp%uRh58Nid>UOslQ?9drkL>SfE2QT8k16-3lGgb1_`B%KxM*L!R%@qe--u1O4z}`ettKma zF_t4kA~|DD*u8?O9pK&N?VjE)5drfpR(k9f)ruUf6ti4>dqGqUs|5N8c*T5! zDnDLhEFh9Q{39TVLpC`ef~4c(^Z0Ca?5dUtg0~MMP*Q1{;Z+M^2BvLE!aptL(sJUm zau26M+j$aB3PV|oJ*&;yKAnzKO<5hs*s?I*olxisAiUR0GJ)fjM_=J$O0BEN5OO85 z@oL4edhLN>SR&MyGvXO&yzJQmk%`{4N|J@1;HhiJSt+et^iyljF() zVN@}oeF78%_(geu@0TXKdjBV;-8%sOLM~E0JGqO`r%Q;lsGdo2nQ-YrDfh%_KmYa;U)!9|Bgk^`}d2ow!Ar*Y*&{12gXutaVvH0a`vY?C1 zXrj?bG_#@xVcg0yNlWgs!N;`BpZH{3vO}xR}z-6J_TpL!l~(qcRkDF8yYdu*z8-5hf_cW0id5`9iJ8 zL`~_kJ9ReM(j6zT4!Z*`){~!VluwEWWgNw#t-iZ?-PQR|mrV#*(lQ?+m$luuGkojo z8Iy`k_~0QFL8Xe+ecn&M!Wbo2V6|JseF!f_m@RT0+<5OWhTi3KJ^*XvK(-c8Kq=f0 zz7tKq$7&)I$wG>_-_MGly)>ejR%a*4p%#ImaS;g-9Q@A@`75lflVeN})+c}9MgICn zFuo?gsW!jJ;kh#2`5$NfwE;d73PU2bAFp8m-#>v{CjVGXgY~Ii<;iE*rC;K>d7!w2 zkBO^DVfTWe_cC^TN9tk`(0?Et1Dr6zrR0+7oHBpkl}G{=AuT`Nha=%M{1e6g)qTlL zlR_y@%WZfKko%=H&2G(!qGniyj0qhkD@)G=8QXL^^mWueD@y(sx^D=KyNNHw z)gM!^ic^KXSH&g}Cq>@tzz_f||8UI8D$WPJZ!z+qb1gY)@hS7Rg7dv6B4VfENV125 zS+h_m(N)gro+|<$?d;HThA=lGxrLIhse%AHLTLhtjwe{|vnyV+)(*SwU109rfFhbG z@6{KXg_wam87%i{dLmX_tS;ignFGT2duz#y}o}Yfs(gd|A%w)Bg>#td5GW6ZqCb9 zL4K2(i2uy`Z#m#t z0p4AZb-jPvfmSfbQ>l$fS#RgsGDShfC1B#;CG-X|`!r`Zuks}HNl1_&P2>anebac$lw4*k%IUk zrQ|kjCIe-mt@7UtT<;dbwkLfYBBhHCOOuY>ra`w?K)Z{Mp|#QELFL6GOv&9Eopot| z{O{01lgxukttZRgZfYUS8b-^n-Zm|o9StZL>9~e$Gl3m)l=WqHHa8`1E5R9!jDtwY8HTI>;&`emk$4O{Nms8<=2x7Ew-cl&GqAADj*8f8HT$FUcC$(=W=Jku9NRI~5k&7P_RA5BlR zD9Er59NuOkOrnEO3Y1IUz>c7bbZZxxWD zy-ao@1&BleBgf<p*K!LEFS(-&`3dx4`K5`Q7=flpz4)x0E0 z615#0WxByQ-tgg0Fw|hwFG*~;XcSgOem5OhjV4KFF?qq{gD|peCcE8qFenh8I zFYTYqwbr(Pv@+k;=CpQCj6!AIpA0I_WB)I+Y$P)7!G}y%H!QywotSBco>visoGN5FFtapU8g(HcBG=bBoR)Z%92u@X^ zwIo~<)0CwDIWyZh;OFmhd;urVfIqm;`3|UMxaKXTr`hGSHs|~)tE3jikygr@?ys-9 z4*Sn3TWU6T{m?j!eBOKax+{wLc1cz_53~%xgAKAu0K)^2^Oj!}94tkKnh)6-a!ZQH zlQTK9(TTUAbuLb7E9(2iZ($N(>WkZXAxYshnw*6svJrih0((54X14rDmTy zXX$&bVy$wCgwnQeLb^6eZ)5bCBW&A0tElLR4^R)>lbk2TrW156GZgWBFdKDYYF|HGlTmp&<)3 z(BuXNhr^0sM<3+!P^wsf+2p>SmokRPEzoj?Dp5nR&vB(33Pcghy_a!|ch>W{E- z=M~1Z-E9j=G$wj#6Z~v||GHG_$APQw*`Ow^>tE8Jn^U*m^#WmbXOWcv+GBAa4WJaz zM6EB9`NGLg@<+EvB1@rRp#(#H)?pZbtlcYn^w}FTuts_^uNFCOx}t?$7Iwl)ljG0u zV+uul#2`CICE*x3bu>ErrI0YM6sC)#Py#7oW-b{9!B4=sP4MyEKbKHFn&cLg^3ki} z=#+h&UECDX{T_0FW4{AUUFlTsr105XNsU8btyUrh{2yJP44Yr|sKmGEGR;{ZFrMBA zNcP?wW!ukYoi!Q|vHgdtY#d52OKYL>xP1p@?~MTEK+Y5ps^Ew_hn(PxWXb|qw~dkw zJFQI2WJi|lt-Pe;gX#BeQz6SkEEaF5OhCl4JHaesXJw<|+iT@~<#Er{dM)8|#`Mcg zC*JI2GLYl8v*lpoiSO+TfK{=sb_1qq^;J1-3gx(&7b=Kt_Bd2OL9`@D^@3nx(CZBi zjDI`Okqk6#y#l7in9e8CS5SQNi=UCtR=RT#*$7DXLEV@=ocfxORD`Cly+=tW*T$19 zwu*y@ivmyUf&uH+hkUW8KM(zdA-8$0@M?_8thUs|#zfXD^5vMiQq>yMjH%e@&2%-=iA40V`l|Z{+>EOSt3)NzGxw`?w{@=O%)zRhQZH z!oTfL?lbiu>pdZ#zp@bQMkY`ghk%9ebFX~_bR+prCI8MDodM-vmr@u_XcFnI!$;Im@{%m4xshU$d}R&k`v6~vFFGe#f0NHPPlDIy zbPrv;S@jUlmbP=xaaj|`HlydwkyK^3`7u7W+ni(j*LBUrrrWaA)3WTH%ssgiLg@QP zi?bNazR)34tTmXHE&$9NkHkdL8TbJHfiF)^^avP=Z+DL56c1|-ciXebmER}sg! ztyRR`H&M5!C;iH!A)_=O!XP*E-%8hoTj6D+DG<9*LZgDe2|neU2)Hk1cgM#%YE@$0 zrA95>>255612;Nm4Z><4)p?%F;Mv|e#oJG^Gk-{85t}P(uK)CwED1X%-ERtr&b%0| znFP2577wb|R-UZi9UiyDm3uxTOlBKJUE1lY2YKHj!flQYLpyx?Rcx^PVrjsh^4A8% z$|J%ZdB+#5#pipE503y>^Na)6cLpo&<6(Z9))rhfTeT;7%0rKb#-#ohszbGcoFAUN z#>vt*$lt%jbA!BM|5uL%#13U@0<61VqeIY4gfYHYt=n15QLx5^&z`~u^MGZ0XM0m> zI*=}C4)T#lW@M%fPQ9gM3}AViRQ&2wnjvw1We=G=ziDAEK;A*fAZ+*-|1+9B=} zBt`kZ4ha>MX^@qw+F`dJmB@F1nr=yg8uwSTjT1`tpe;w>Q@i*42b9q`{YMHxm1`3;wny3_CzP z{}y^YU8VUHx!dKl?-9nJ`Io`vsS7-hogsU(fI!&%6{cpueiG#GNsZQ#gAxT+WY1Xe zZ*jVM?pnbS55krSx=lGm-pzbIOY3@YL?UoxJLt-E>@>Nmdg`l*2!T0q;F%57@?5&d zsaKJ!tM-Vg*x>n$#)VrX&|18Fv8<|y-X``N)flYCUsbm7u-BB0s(`Qq+ zxgZjR&Yz$Mal;xKNO$X(vq;w!#ePr0v)_Gd)8sJtGQzydtweJABR{LNRwa&dyo1RY zZ@;@xk6rl!(p=uQ0OXDXwygG$q)1@>{Ph>GYk1l$sYO)>boneJvJs!TAQtZL6M)L0 zbRF?eb7^0DyAP$t6tbrYQp6~fg?#eSk`wqGhy+@gBN`iA$aH0#ozsfwD`>>>noE#n z#Yx^cUi8~d`)6J1)_zYh9&0;xoiqE{tZz&9^*@<$D+4fkj3qdnTeMad-u2mu=}1QF zQTpfz2*bGQdhh7RW?cTg(0ErGls`gVyDE)*$R(yHkwI+8d@pc5hsc{h8Ss(K0?y`0 zM59*ya}!U=_w}CZV6zP~5JgS~x0Y(Wd7O#<x`Ol%>nWHj*N?#dZ)F?}iW@B_+8T z89T$z4Ebp(%@wYUeNt8R3TgJv(8DuN8Xw!!IJSkmL-E={uwrH<<%}c+Ti2RdK)+)@ zr?eH&$oVwNHia7(i;=RGXZ08}$%^zT{%y3+(pL8Qqb38Y{Xl@}M>JSwhJ)x(t7qi6 znOLD(G-)0u=qb%$WkJDXW`&Uxg3=(?H9u2{LZr05 z9OkuwQ8+|8-f4EC;-QkmR*%5KKZ2bQ zBGt=AKwTDlmY=k-75!3P~E6N zg&9*4+fsb+_cXxE$FnX8DKKdsW4zH zc3eC@#fhu;ZHyM1uxXj;7v877Jt3}+K6h*S4ae~w_#C3wazy4=8CRHHi{0rI{ea*& zU3$F91&N3J>aA5hQR6Yw8^XTs|A(x1V6QCNwzXp$6w?TNxo~?rpa^ z6F9jjsX$t>Bpt(W67>n!_`VdkoTiQ#w#{qF`ofMQI8n{Vah^ITJoFs~oG zLSFab7CcEoVn^ej^iIQUo1H@#H}YDa!`6N0@2}@?p52{2mZ0xq4vxMYp3*Z(pVi0s ze>>#yu-~6@KW`cI@kOXnKdSmE56@`bKwWKb9^H^6#f72J1ZfyG z=U5X^CV!Y4q*p|%!>j(#Vo)+EK0=oTSo-wasgMW5)nS*OD-F&gaVw7%}{h^pt>~ z?1(fG?rU*!kR&%Gvm_p#knR5Rkzzg6Ho|_QlEP9QgPrsdQd!j%5bK2 zL87RElPk;HoDFnwW7FSp5?AZik8 z$gfZrHmE>}ElW_&aK|L4zduDNZq(8x`C07U0Y#@^jj)nc-=Kwzd_a5U7ZN}_>^aJX2r-kl$ zH$#P8e0tfUfpb}8yxttR9(nGund^@tnIEguztmYSS*x-O zeRQ44a(mPbIRwF94=%dZBK%KSJ&xK-+FjN}i2+HzhvN?c9(&YWC~ij$3scg7Bc0Xw z?2nfOK_rEHE17Ao4y}_w-ucQcfeTKEj`LAwTQ*#?lhnowwG6>@K|={adT!=Fzs@&I zABT>4eXPIJ3aL#kyUL#+av~nW<7nW#z0cE~BM!t#MSNCl07LQTNiXt{)r%n>%$N^g z!)2Fin9kz3buW-o3W=oeKJUTl@Eo0ZAKj6IjNTqUmG%qACyGm>2vbC%mgVgS32>CM z@U0o2Y)%g+nM<&vafmjO#-LmYE)VJBnNA*NZFSTD=Cz)Bd(Z@H*jXb?mp-Xyu2uA9 zDS#v@@g4{`yV`Y4<^_i{op*Z{$B6`@#fH@BkvC1YQW!QLhNO%KTVOmHy@RJ42UcHt zH`$lZ*wt(~tjRp%KU`BxhCh-Z;nS#^>{JX-l=1w)k%_Affn%_jio#rA!UUX_ z6RTwd?tTz&{>|WTDIW<=_h@F0=H){Poiaysek&REB^2Lpu@jAtg54hTuE-n?f{hA4?ht^9PlNYBSq86-}Nw|+RtXSp}L>>tg?$QbP?Xpx3 zf!91wP`h0_wPn9^ABwe(K<_}KziWxN?`4oRdvv!z#?KV_06TH3oW+0s@zGL%goiIp zyPn_Qs~T%)qW%E93Jw7-8@bA1NDk5NGYV~gmE@e`G5~8XI52@zilFLd&=d|cN#c<4 zj9TS{I8}$R!+m(kEh*)fd8F5M%&eVlRSQYJ2Sg59lnL}{x6HP9^TqYUYH20e3 zdTW!X;7Lg5Z36XnnVICzlP&hY;sfpOgdgcez{vI&D|bKA_^d{>r&Tt7k@I z@}P-WS8RrGMJapr$7zH(a6WhRzCI~kjcd9C8I&C~g!2eWbkVG^SriHS5T7rk>$N*g zdam0W9m?~e+7_0~0eGRZW4^pl!hkt}c6a@s;SvB>d&48_=h_T$V}j`I<35}ZJe;lD zYd_FO!^*ELBJtq~WrTCa3QkOTd?!^CR}(Lq@e4}^ zEG-MuEMYn7MiVl(05-*rNbGN<6!J9Z{jtByh+m?7gNMejhwkF|3mK6W1?S+Vc+;>2 zITK8-Q4M^jU6hW`7OGZYEUuaMh2pdBM%g}W+I;5l64Is&fXnjphB2g_L50oKU{i9E zyK9@>(mx|ldRJ@Y?faY8#L|cTbI^K>U)GKdxWCiG7(>$G6g0wB^Vd&neFaE1ZU_yD z;UF#lLrUUrR~+f}gYy%HBf}owj0(0d>dvBh-J;<6ZYQdyTlcXR>A9bq=<63@u`r^w z3Z4CBSFEbYt8$Gt3CcaFgZJgCy*UuDxm3=5rRe7_UcrX5J;n6q=)oIg`o9MJqdGR9 z#o(4}4dCqk+f_%RQhK>AqU}iz(?FDrwNmd{50Mk&R3!HY3OL#5?+sWHPzx!eJ7f%Z zUCkPI$#8Vm7-E}tvZQFdqG8{uRoI&79C!1#tfjHe^Y;JJNMpPPI2Gc}GUR^Tt?#jNFWBCI z@ncEOWq+Pw(O;i6;RnDxi(k4~vkP#DeOuo{qIIS=>UE2vK_%c@*#*%+=RQ`6wt>)< zVyUUxYbZc95o5F z7>whFeO`m>VA(BDT{~u!ZLYJdR{rK5b#{13Y4r0RlwgLd{oG{mrCH}<)xLb_9il&- zos>8L<2G!x=*0x?CA2MzUT&$%7puX&N-TIt#$@TO;T5D(T?OGw`~LsT*9k>)7k&{1 z#dRP~Cum+FaWmC3CGLo2Y7SFdV#6xgnP6*`Z8|ul zz5p3TR&gGilcz;F>}KbQWG09TOgI+$wLQRL-i>hC_u}EwARq_az_-Qenrb zw(*@ig772FRi!OfNL*;eJF?f~DnZZqSCS2wCvu}h2H&G&RksYl?7ukbE216f;yt3{ zP7!iAg&;sta&o*l4pH686$f1F+cjstuigQU2R3HNbT3>C`3^T58YgjliS2U?ZsCIs z13WfZBnqy38ALC z{1Q%IGU%5@6ug@ugGL#t0c$yJm;a&nJX}nUIp>&3?>X4y$T0k+U+-uG4b4+*kT=C;amPAB)Ph-PZzm913Q?6AwdhNH-19aJ-UQ)V_9Sv~0F!;;Hfv!cD7aZx?Tm}t){W+7(3n|U4|NJi>~N!7HCD6Em^3)g5cQppj2vO| zLgBZwZdr4pwuO?v(a*6(FH5Gz8C!3U*DjnOHFjlSGLWrUUEh6&v=Kvt?mBldD(|*r zpBmAw+h1R=&Op;FyR7AH)lq(q?lGITD3;le`?-gd7;1kDTXXH=XSU}^B>w!l{{<-U z^b}Vmu*(DlA=!!HZbB~HlQYWv99?cW-uP_+yFD>HlpESak8+*mEu zNIzOo#iA&yX)$)9>Qg~uNIaPr5#b=^xw;^uD{quUD;-&^DxR%U%zD>poypo492&O? zpAN~{LtO$Eo0xLoevjEa!A$n!G^T;sz#~AAMP@!s2Yj`C_(pnexK8>FGND6V>Rabu zf~;Jk0uFsSeaeL9-Ek~-ziy%!sf$;-LHMzX0Jwu%Klt|?0aJr;La;%5r%uRrTE7xH z(q|Ss?%5^(Oy7{<^ZHXqB!fIg$pGP@yEv#(n)zK}ZDe(@Q0 z+e4IT#e?>wEk{!JV3P)Y6r#lL_N)Vt_CMFGd_4;T53zTXx}NKFgpIb)^!3n`@hu-F zr}ef;yUlN(If$SCCO{r7eDMa8+hU#kuSlj%5US+{+2*v~pS&#Vr`q<(aBnad#c1Kx zjd?jk;I$=gmS~g5Tq4tN#+@RJ6ZGk#yIS>Pd*#kmd3F`~^WA;Zd=gGgT$~f~nGq@n zuWbWzTg=8QJlzvA*mi>|jLshFh;E~-RW~~5W+CxpaBq072i_qHc1x>hl|KE=DYtSf zOSs0?chDo1#c}`r{^{bJ ziZee8LXi{vB_;dNv%5)S0J{MGPZZe zF@vW9r*ti8qt9g3DVe2vVv*l==cXJuB$&hWgPe7tt?W*-$@t>VF2@|pE@Q@+b+(YL zf?rI2Ht8|vG*L3Ez-7@jz?!%Nai~{BEmntC)e1VDwUOG)`Q|uHl4Yfah-_lDLiao% z(}U#;2Q>8V@7$PyN}ohiVljMNXI%qJ8l6-nZ=Kxj!JluI(aNiq9x*WQI{b6?RUbA< z`kXWt%Si!Tg>e54&^o)7Nx|V1RT}?2AVEY0Bn2U}GasKxx%_iA;dy!TE@BLnWQN$0 zX&{;mF%nJX(M0HJ<$$a~*C-yAakcbLl`_oK-(zUMji?`^uM zd>-gyUM*u3e#1>M75cL%Vu(7bURYBV@7C$sGdSuH69QQu!-v$F6;CsmPAPO$jxz_ z1lSVYp96D9d?F!#9s;gH`gBkwM51>*EPh%G?HopB8U34alTBTSdlo#~Gpiw#xB-5M zfnZlb=5l6?Y;?Rad;`eh0@`1 z)6*Rv+k}vqpp1=)o;OayF{3U1JxjAzDj{z6jV;_}Xsk|f2U1ZpKRZcX94aGod{)1m z;X8)eKL$z}Jp8+KrnR~S3g14+eYj=@HaB|YhZAgSv!b?~H6<8Z!sNz6d&@aHbk(rx zI)ke5yWE8H9NCZzpcP=a;tf}*;hP0eFZ)MyDHu2le^y|zC~0k&{Y5>$RsL4~vNY~`>@8F|ut_el$#`9i?y7%0_;Pt@RBfm)0fUCbmj44pbw4q~mhp;w9(=eD?&nQ67xN`=A zAPuEmx-F`id&l9LSxK<(J#&Iwwnl!m(^h5iJ2b^KtOC08wYK(0NNLHpuDIiWn)Puz z!bo^%0DCHW33M8>$);PFbovd}rlM?=Rc);)2-q1`Nt*cssRu*{6QK~{Si6<$cUylB z8^}Dg*0c!~XJBqi*ZP}qR=BNcw@3cOo{sJtB?u{m#oEvZof%1!XnMXUKgzW&5H`&i zI`M}7uA9vp5}h|7Zrm)zPm=f79{+R!a17`YB4aW%DT}Dw{1d7)u!w_GKXLYWa|rc` z{(;?*j!Yf^zmt+x(jao}lCBo&!a^<^nVZeRTL`-jQG;M?2@>MuG(kI9DWL_!o>_X6 zq{EP;hPrQ2w_qgiHeA8%Rroj3XA3rctI$S>cQRzC>Ic!__dS`1r4!I7_@42=_HG%- zs(CxB`1of;VID4K5&yXzl_Aii8Jt_Va0iPdzoSzOv4D_d!r1Xb*qFCxN_a^w@Q3Wj z>UW?`as9@mFZq4X=iU~+c#a6p$*fdOpcSAD&hez|fYi_=v!NYTF~5{Mujg6o0N zuB__JSk9++g@iS1S%kDh=oy|58!}xeQWJ_9Q0QNIX~mTcbHVkvL|%QVISL_n#@4mV zO7hj-Q?x%PGh=hkKGV;?l}**e}}U>+z}7vb1Ay)PPk1JOHo;^+ zil7b&T|9XV_K#uZGO^%QrEr{D58Im~G+@JeD=2fk0l}a%(21>EKYWqnC1oz3Z67%d!+whuU&jVaN8f@vb(kV9V5ex zsyA4&E=xt~PkU(0ZlVG7@)!quE&tyvB#z$Q@R2MavX;^rMqTH{SdoS5i#4re6+INS z^w+c2ViTT&Hl4=09(AW_5pNJMdA)xrjJq3FGH{oF$_aFw_%thC>#ohfPb^>mg3a<|>YD=1~&{QRX zJQ;P>ym+{7DUO_sZq6MG=~S4iUM_S$dTz!rDhM;sDY3CNPWYaD&FdFD!N66tVrm>3 zPV~3PA9S3gQbed88DM8S?JjQrniVkI3cs}?x;~zUjbG){I5$IIus8fB} zsj_td8a@wsc_=ae#pay+v_GPOZLa@vL%E}Kd8H=;6HEf6ak5WN60MLT#E|?wyCQA@ z`X|n;$JmG@P`hrW+~bby%_KV5IZz}Fw*pxcXMECbX!X58Ic!+mA&EPIkrefA1Y%iZ zN8ZXnq&6&v`lk5-B>Nk#XSK<%)-wetTOkVYf<)tnpPom=u%6zN;JTBSF4D@IWxJr~ z$1Hqf?yXaG`!$YqeLPS{!e%TNq*;JTm_g1#o$51%8ND(@fO9VS_uINz)h$tbak9xv zH|X(=>RH~XQI}`jQl|U%pPm*4Q=+gayI_mkVzI%*MJ{`(UZ4glb|x90tdrRPWxFg1 z+Mx^~mI)Fb)@Q;jnE#AjEw{(T6(jPEjqFFP<-BQmh#{vSmLH5z%@d|ex(Gfg5;gP4 zPe007=OU_N)>dFKC}0dUF%bzVwO4erQE)IWu*NwHcRxeh!Lu;?xXrQ(jgh8~R#D$A z(omanlrZn0@}`%erqaIVAZK5=?1WF+WEp^Wc~a#IW!JUH+;+vAJ%Y}VWvSZLbiM2* zHA;>q(sn5EU8w*qM(t^U10{XdG@q~=B6~yw1si*kuINd^*J-#pVB#24Rw3vLV$u?P zLc1Z0Vd;)vNUOG6>uSflfAC|jRCa)!=D@skffmvpf3-FH05!LiJ!*KRP0 zwHXfL5gz+7x2ypv=W7-+M&>`&*Zm`Nnl)Xt`=}G6RQB4WIcB>Q=EL4` zlj_rc)v^MqaSFEg-FZE?Wm$EDL5|-jO>?9%u0z>Dzht0-ee|R50+Hd)L?MFU;G*Zw zW(mF>ARtDRf%(xh|8gD~Zd;h2j*19G-@m3ae zyqy`&Eb}N>-(KoF1UakD@liwqR{Tl*pFc3k36Rh4iW;e`bq*LGKjfUZeN0>MM?tdr z5Y{2rT7hO(VJ={+(q9)s+*888MIXKLOnBZnMCb z{_W|g<&U(BAvrL?hTkEm8+GY(Y595@5qpHRCXwh!_zYDbY*rRyzyH!UoLk|mqW6v# zB!J+ZVf|Ma3;ck88JeV05rzlLKL`A6uXwN$7kgyc1C3H{QnJyMi_y%{sS*Ub>v4M0 zGjP%Vb`*Zy3wn){Tc?@euGhpf>*UJAQdo4fph+e^Wz=Y z%c(^P+hG;S+Eg{1Tk z1=5SDmI3ThI*7Ynx)VRbVNc@pR9wM@@9xmcg?-DK6&&nyG??mFtTt7Eqcys|H~56u zyL;>XabDush+qe~;mV`HPq_BQMt(rm3QOp{EAtQ23?CfPhqRbQX|i;A3LGuOV@6k3DEK3ss-ZCJ#E zCSu2^ulgO;na(D66MTjjLaS3t-8acqYHfS#Tkpi@!N4K8yg8?r#n*FsKi<|+wqP8y z>#ri{KhYEbl>O&8KOss&3CW&zU4T51l1c{W7>&CV9bnP2?i{L#gtFWC6_w*Hu^V_v zv>SvabPsCev;fN$DOfU1oG5s#*xiyJ_$#do+YuvV<~KzNAPpTYlNej`j+fp}L+?3l zQ<7!0z|JeC%Q=PfJY8;+frCI1gL4TGm$=os5L*6fG`<2;h8D**&kD1qvRXBsx&B-> z;$p?iN-F*OCDi-xKnXxXl4i07_-CxHP!f;8sH2mej-RSNN99)=fi*JyOoZ%`QQer1 zf<)wih(aQCm?X~IQz_t^0&=-V#(wlz~8{?gR)Fl1D@YWuB867qi zvT~k<8O3EhJo^MXMH6ECznw9&FCNN&Lt+BK(KpJLU&mTy%6&R*jTbJOK&r zi{<@a!SMe*hfb!8sC)r}T9HXYiQ?@Br;;HjkM31!1V>=kjlhRZ933z!Pn6sa`Xp42 zG4-l^Md3`$0Vs{iL+&!iV+ZU_D|>D75&sCe#C>d(AtX!T;la9x<>&tMeMnLQmO>$y zPGKu02LnTE&~)rl(eNK1`;XtwzeCE&j_8KHCbPODaMOQh)^s;JM?^i7s900 zjwa|wun^1ok}3#%GM4$LJLK|Viu5(Zo>yo@oI>7hf`m9xNb) zprSeB4b5m;59&yj8`Cs@8@KGn!x}v9Td+Z5YYI;;QBz%UjFi$;Elk(nDF>C(_bY-B z_$*U+;kxSCUJh$DA6-5>zg4)dR?`#P#?=;Y2o%Bm5@&>1Znk!A{#fcRXNZ00)`>#* zg3NtYzu1DL>JV4``~m`kH9oBlpEdwUo44W755OLoW)@FSn@*XyO_PoMAJuk1d2E3w z>;I>+LQ534mntaU|OU~k$ojeo{Traef4)$@CI_9jzLsG^$8agW+ z)L&8?5RB+ZI304&+<}Kn)I0i$)hN!aTY1R88e$(BXJB~U|FyjCu7=T$7%tbxSu}!QmieRO}my(%Dfg5#s z;;k+SkZGdX?x9i`{(UYbU?+|NSw29Kv<0OKp`tP0crGTcg$B{49uXXuB_Z~dVsoK# zM>4o@n3D^q`w+wmEc4@WcN+ZUrolu=9VmM6Z#w%+YobiiJo9z$q&-i@v)g^$*>Nw6 zB+l`j%ZJQ`;srfr$9Nz9Fy@Xxb`J>6QK>VI(EF?oZ?QKZ{OiF<<=2R}hpgr5Q2w*f z%winx^EK%FnZ`x@*G^jftSW>z4yducbY%|kk*+juaY+9iRmQhgE=O%#bl3B3u2PnQ zsuV{0!APnn#+9>HWUwc08Cr#D7Yvk#^HAcINS)UsK!x)2&SP4kkj|CD>;ZAe}n$vZF(=o!t z$J7Os5w^l{fR`0RYq@Q$v4VL`;QAcr^R4Cd22RJvr4_-g7OK$}cI|FwM~QCF@%DAK zde(_y?Xe$zg>!f9#(eb|vcujy^FK1Pp3kcc&_{YNtY6j-Yiwwu^)NESA^(Dss)*lw zgshvcv0O(^7{GAfPsO1k<c7WPFZ{})9ppzDkf=S? z&S#J}^M*4_CoPnaAvi%;yq~e~*D5Fd(|g46aY*dU%aq&WQt0fxVGsn05EiniNul!e zizSzNbMWj0uc^tpAdh;jIK|@I4aEAI)NA^uoF`K2%O(P7tl;y**8uLjJIz-pur!*q z)`7S08MhGcGUUu1j6(M5#YTmA;AK3LxhSIJ-(;vb^e0M9ZdEJi+|H2#XocpClYbeH z(NFT0A|+Fv;#o)wkiaWUE#yiCuHGN*z`2&kYlYretsIRwa~3-Ro3zh_Nouf5QWT3z zRT*8pPEvn2bF-t}>4#iekj~DZ&UC%nwb&!?G&Kfog$c6Ywlx47#oPFf6-FI&0tFI0Ssi|q&(O=*bNKZV_MK35m{rELfLJN9y3%SF#>a_X zQl6&0MX?X<5-;zg^fsq%t$(WRD z$&PFcpWk5sj@S+EM@I%Jm~;@g><9>}NOYk~wou6ghs8GqkR0;gJ21DTA zt$Hw#eIJTw@*;5DX~F;V2*e>EBpLrXc8?mC0R?dJKB{}+{q8RtQ6k*Lei+m&BB4?I zwSPv0ng;4}9~;v(?ffn>7eP{z6C~|u3DcX;cuyzvV-KI?PfgoteMcFLfucDRwtrne zw&BD~rJX;Jc)9;-ZZ46K6q6*D;ZhtDAVj@FQPhwwe9LVA*xOe-m2chl#p%k{nN@4G z%PYtCXny}m<8Ct^R*`gMHiQkQ(JgoNG}=T-Dt>`H^K11={?X)j8PwgE!?i)q z5qlZmm?JsS&tUuP&tVoJQpg4Oz}mh3TCboM!{A|0VK@eXzF&UTDoDPvp5E9X27}|% z{-jsp@SGr6n9JsZ-oIitkf;p^AwlREJ;?_# zsMvTAly`($X5$;}uIzE}7&2ATgj=4<^=uO@3K0sC0)KVGk>lcXCRvAXywhN8hO~BL-drXHroU_Vb&)8 zZG|~&5S7^8gF@1IM&|-{Th(X!@h1o*E5JL51vCw~tbCTE@AZp<8&Ot?3E$7{d%)=~2;%><_MPcdFh#RRa4zZyH z&f6Po!BQ&3)-#<4Z;ZFQ^;{QBFryB(1#%&VD+YR>h@P_3SR*JoYt(bCPG2i0H11yM zQhI&hxiapItVc(QJFSZIS2j(vx3>DjJ1?)bp+;-DJH*E;6|v0EWk>z*vV&Rr_<<

UOG^-awVKKvV2{#<>#vT=UxcI-(|;-Y@Kg%kjYHug z8YH#ZJftv7E-B)D(ojxXB9GXQ%}hZEKoL!77{kWkDo~OE?)b})2!R%Bl$AVn*^z4* zXr$XB5u9t~V{Ww64DaF`2&(5+Wzsx%2MZyU2#*#DP%;o_q*k}OdOsL-N-b^-@+^W_ zCBLX_Rg$Hy9xU|uo>Q>j1dgLBeb=N)`AjvA`ht^-yi{&W{E#2p)bjyEH-;rZ)cKno zaH5jeMem3w{$nQThcK_Uv7eyCA~aov;tHFvr{43^WvA4z0yV>G`W7;cRaMcCl^=($ zi;$hz>)gnQKS+gNY3{erio#O~F-Jh}T%|(dxv=I5h#l}YTY}S{wSu)tUYwb$-mc0r zEQuxYK4)g?*l(FtYJE#33t_%TqaR8eIF?jFXLChx4BI4E!JxC!6Mrl;vvN&;6N=R=*Ru^` zSD||ks^YX4CCzOuD_*3-tPy`)PLtaFn9#n#Yq(S69wf|~A}a6O{}J-)o%dbzuUOg$ zjE)1Ry|=G}fAKt=#tKz!^T^DfXOKvArHO>kepJ*y$-&Pt$_v^Hh~#r(5%?3*tq5x- zG`x?@2&;;hXJo;{`Z%tstdif-@3RheoDe^K!&_D&|1HDqp6DH19r50O6A?3iUyzxg z_MyfSQLhq@tZ#)bXK;}FhKF~BhRn1oW~e2HAv?@@W1?YsM4rMYBU&6hliL235_IU8 zHa`aAj~Pq5PSZAr_(FY~jh%Rh;T%xi5TK;yef0TNbahfqyH;>AA@6QfF@-A!HEwW=TPjjEGa^=-43xHY zJqLQYa-r}YssCikPJNsl5o%(e@h->3i=XYkCYNsOhMmLup2l6@S8UC5Ccf)+J_CD3 z1`07KfYxlkp<}$v77m3jM$iLZI#n;Y69XKv_^rX>9q5Bnyf+&lmi>R=54?Vo>$CnM zU>)MWX6->`nT+d8n_UIyH}w?c8%v;|DC|$EhlJG~YJ*%NdHZpro5z%)6OEN2WsYD4 z=O;==v{EwMZp0BNtq`y%vFmf3rnEZ>R@-8rZD%~Www#-8*2?;dFX=m_Db+OPxbMvv zY+%EN!60J+BE)kSTdQF{2nievp=X@$RN$f?Nl!o*j%x^cx|?yW8#}De&{o}yLyK&4 z_e95#q{=GApCx&6ltTHZAQ50g1nhB%Ux{GCK@Y43DVP)aDH7}*C;=M?KpzJ~?uMZdbT_I+v(lNjPjXM*2yHSzaWqSCiW%o1LM?V~8p5Qy4Q!gGM?=ftCP~s1 z@lnrQ*ceorif72dsjaZ_u1HI7(ortDGvf8Av5>~X3qaQp&*Z3QrNe&KAFcE`?u_yG zMN}Sg|4Ba;^A4X-IB%VtzaHl#L;K}B1$di35BV=2&9Ip?_J4X>!%Mu7$zh6rUGR1IPe>2qaKA|lFffr}2rAfWx>@>l&Y~5I&kaJmeq%iB=WY8N zqIg>>x8JWhz3}YrzuMGf5WqFxFBN!W^*Wt=;x_gKVp`+bu)xl4Ueh?@SS4ij8e0Ja zYi$II#Y_0YJDCVKDJExC(|G_bp_lP_!-@S05*K;k9Kg+kCb234q`d-&vuW|2TB(2O zB2YYD*>@q9x!?EuJUf7sf};z~DQTeV%wez{YZ=4$G3WYYaE+XNS8AP@a=fs9A8?Dt zLmLKu+W{+r_cGIJ))psYaZ;;XQD@-m(*CdW)#D1oMCb~b(GP5ZLml2+r1F|Opgptr7|B7>x z;Eh;5YwCu3&g!h50+sg(st)sgN4zcUbJ(#2KH5Viq=7RT(q||Q=@(D3#`koP{ zT?R9lc!}Fp|Mou#z~qSR_EmN?_RX=s@X0MM{02Q?)(?Sd059E)_DBcI(}!Yqu`YOu zHE#aM0l8IR>V!bGqrd9R-_^VOZ_V}E3y_NrPYG@72NGqoIRcpmyZ6ix|%Wc~68y7j8W@+l_F9vzniWkd_v@~+Z9gMe_tb=XL9JWkA-KZ5k0|wLJRCm`ufa;b6y6Qsl(Iif&b)jyh}69i&SM z!$Ng7m7b8TtOU(uOwG86nb0^v+5Ya#2xFzfo3>3JElw$wXe7`8p>ri#K*1H>5G`cp z_OEBP7`YGi0d_4m_pRl2mP=>hz43&b&yQ)nLVsGSC0SQe;kh1nD9?9)JJ^j}y?Md! z$f)qf52q^MK~JBixY(L^ZJuP-%+h+&=^O`R#Wg1^*zFy(1?07tO{Uel_NxK^|KWS& zyiyPnL!GaMc@9rX*tW_jIVv4_d!F=S0FY-l;|v|h{_r6;gvJXo5+xGPm2g7Ef=zT1 z1C6m7E!>rz(=$W{^=n1ApsnB8B@2Ek6!!-ZMM@C4`oq2NbvU6_jz|LBBq*jwiK!Wx ze`EdqV5*t~5q(Y$RyNFw`V-F(DZeD*B}LKZDTPfoj_5v-_(q&9xF zy#hJ^Haq*&(HF;ZAOKFT&kP+tAWc00M_5<{9FO3#jy!&wUGiuDP5vfi67zugEGuzs z4-|6INZ?KcVv*NENZ|sJR%+6>z*vp8G1ac=8A5~l-mf1CP6hapzxo3KKeEuyAC75b zRh`Xk4&=e@=8a=w7UY-e_G{qiQeWqSOH^Z)zm(CGMg=^sJW+iOt&-KPT%gV`w$tCf z$VJ$mPxz0l^LwC6M{*2FU(({w7%((pKn&QKVqHDn(7g^3SV3?Mg7wyrvmvWX!4h(p z91N9aTROAYtP3$F$U}UqAIh+ze9^$)))_%Y>nv4dN_EC1o8Mf?5WR5J9f33>kNl!r)yf6e2*OAN%rV?ZtVYY$5u<`q}KvH|R;5N-bG%{Ac`YdT#4Jg60*u{PS4CBn{KDss+1P=IvXfhdHi~ z1ICV&SMV69YHeOc4!YDdY~Qx5d^K~gxoHp~>wel~bj6@Q@>0?#=CALIvPXXF5V@Ve zM8w84pE9-NFDeqC5&H>k1a98ZlY%0;|Gf)j{f9Y`$R1*KI&78M??1ll;$sys(NqPB z2+Z&134RI5vjJhqI>NSSX4|_VuxgZer@K+aSL<~R`5_JggzS}LQf|q-p&Ty^~^(Z%^FKpD=zQymXbEZxjxLJW{&zzYB zcKRnZsrgr#mN|0tIs>joQtp8EU4xyK-bB95=%>XXIqH7{RlDf`Rz(6NV=--qBdFy-GPVg8V*f!(bIQzNYo*A!@c-s7gaOY z#(@Xn4wQaW>og9~4k?cMY=@wUEMB7nFn!GY`AOl7qKh}kHJ52jnv$C;I~$SVKK>P% zYgvw^#MkT)q2v&^g)s}_c7+XeP>iMeWSmJ#aXlicd4}XMx{>ZbMkS^|Y7}&lhq11t-+q7VIgqZjpBAVYf_YsSk!jC$B= zlH`z3h3K8lk@|iB)_T{j$b*=@hEkw-GFdZ}i&d!91o*=8r*+S3%PB1%oGq4SFYG^1 z4@79%Vu&=JylBdMdVpJQxFZFOz)he<-nwoV^yhAXb#7YZ$JPfwFKa`%K28@O)K!D( zDGoH*S}zi{4r6g2TjX9u`By>*fbj$eLecJUaoJFeOivIUt7J`;{d*xa>!y<@u$Y0~<`7x- z5NSM5YkvAUwqM((x2nmtg@A!{Dp*bSPW2A<{yMT{FSo+i)9eO5yjHCu>(je9F7s(* z;|HW(Ty`qvPO-`Z7nU~L&aDxS+Fsq7VgQfY%m!I78pG3`3Pwfl zl#+>Es0L;=CJG;iw#`8|km(|8Bp{y{L9LtaauxJDOBqb3c~4{(cm%zn%xl{J(H+gYHm>Fk0aEGwfE8l!p z^^$y}at899#XLs5Yf?GP`D*=s4LX4?e1iF=h*#@rgeZe=N$SbiJeOpyIHwLM+icTaXkz6Ziz0}V8GdUpo*VKQ6pnyr zkVC=TFNWq1UqeZ96bt0fg9M_7Hs+B2pjm0aaCFdHT)oY8!CgI~cjTJXLHH?JNeTJ*{`j}|DS~`SPlTlS|2!#>dfoUI#(_bb+w=(DBZR&k zNis8_(IJ3~cq{^=LazZ-%ivQho(*{a0bLUjz~pw>o7yB{m_7_Df{VbAGkrxcQyly1 zt}2Xv^W1?Q_x!M2AA`bifowIYLA_Dfm~`Ap0rdb^IC*aMDDck$FeVlU7+j=O`T(Vg zt?7ku@19go9EfNV--f#wIjv~4aW6kBjcR+bxjajMLK=x<9jb3kqqEzzW;1Hei%Slq zn%nnluH3eiV0)KX#cNp`&JLIMWND<4!LS2Jc?k!_A4F%W^rl!c;_EsS=gB-b0%>A!0Wmh3wH z>};&Oq-$>7j2t?o@T&I7fkbdCd}ibOHVePkII)Ariqj!6y$7$`FsrF^e@Og#;U`Gk zJ@Y^(7Y98jNAq&mOz*SVDU@A!ckv>4fxpcPfMZ9wsJipQ6UY3tf5m$h?hUV z7nS{dew$|E_Cb4YwvD+p&h5WuMg*H%g=^c_ReVBv)?g?5()T)~U}^caxU zbjcPJQ!v(ZDoY4^AWUOg2BollB;_KAE1rFTs8w@^V3 zP>JpNgH~7{h7xg*gL21gS(_MwOxbU&TK#OPFBXYuMwFi<4>^sQ&TYVVEzTa6`-;HW z-Y8aPH%(Rpvq;v(q84KG98vw+pMH>$N|$2BwupK=!F2**MvHqY`#aRkM&^SreU)lW zc{2w`S?B+dbq?Hhg>AQvZQD*`+qR7cjcuEaZQHhO+eTxzL34Kcej4NC7wqgk*1ewR zo^xIk*4pKK3bK7#Y3U0In;on7rLF|;{`2S?PN96s6c9EHeX}S!G4Lc-%{@M2==bay zf5bEP#ldF8(*p_T3>66yv7Pcv9Dn($a!IE;$`*+}MYD%8{-A5V<0~|*W?lkjRjS+< zruPb)puv;acgv9ux?y=IaI?H!o&eVHJ)dp1hQ)7w&z0%)@yAirIaDOgB9o>z(ppVV z->LeU+nas}rfcXowF&P|Qsz}XRQk51z}kyc=l3n{Z76k_`VQ<VaiDciwu2o0ThgidtrH~b^5v`I{VVma`CUl(9#lABanTWhT+yZ~*;k4Gk_?_I- zf9n3$`E)$6xslsRU&=7Yp0StkjKj}JQn8?uIlAX}Z8=*lfHji7-|v|D1~Sk}?1XLi z`Q((n*-?&pkE}%XPp+8il^Xp-fx9LpbeuwW2H4sPl|zZ$+Jskqf5F`iCSH4 z7o9SbmvRk(l@%Uztd*^(}EB72h9)N^z0jhkdqMxK^0 zMaMTonfH74qG{rN9cg*ArJC7A%5?~G!A7YCOz~ds!!L9Xpy^YGqNrGw6k)d%Z+TRF zy`*7-qb#*3iUcmN;OLx3iq%2}Au3MblT~7`pz~=r=yEmfs=7 z`jMqci|6LkhqB3uIoIaIt>dGJ%p%IP^!AGb)ual{0?HeiEiEIYEG~S|Xd;T>#Z5{F za_y|4CQ8&_`_^dGA++KoDAMK0@SE$)2S=`4#=!aMZ#`sn_o|OTb1t$CPP@Dx>Q6Sw zQ~!c%ujh+zTENE^pcX)THTRI52lKnHy0s>X`S?v)TdgTN9pjvc)d zu{h(&Pa*8AUk(469+91qM$Fwbn0&Y=4o&vNC&7mJEAkVPCGX-eKC=>Obx>vVddGE^ zfo|kPy0@7TB<;MtwNrtyA;Wx1f_Dc0s=^J!H(K)N24wXb@ z$2v|f95Xud^!26|D;_?n=*AUoGF~discw~R;RdI}s>?hbxeme;)A|Ot1$Y%Ro`D^x zZ(SpxJB)qC19R5o{HepN5V?73#+9@>)b4SGNdrSZFH?`ZJ?;t^=|ncizd+|wXQTR& z7)>EGx@C^gSB`*#LUithyH;37v-gOmm~?@*sk@Zg-r}sZks%Yi{ZC)Px2Q#&NH}wX zAlSg~C?}Q;_D8dokpZ*cZo7b|;YdyO$Tii;BXwLx|V!-bBfO z$(tM{_4PLVYV^>{Bb^li(BF15Q#k#@m~AAp#>#DL6aY`c@}l3XCo7T=M6DW^z5KZ9&qfMJE0yneLN7Lb zY$)4zkUNK1w+$tzb`r5~#V!NLEX|bi9y$g>RHN;RJKAmb(_B1P8)x1hJO%A;e#owa zE?y+w-#=|X(oTImyj%EZPxIQoq4tgHlMo{ju+|&=X44@3>Mtvf`tJga5VuAcXF&Cx zIAEFL6#i^yq?k2!*Z0JPlI5=^rFNrcM-4q!gekFS3_*=sE5f#?RWAl}{W$NZjx{G?jsaz4XBod8y)cX*dg#NnD_C@lLctUR2muhsNdZ2Z5q-8A|!gD2qrIKRdAxV7JskE_f~>rf@Kr# z^@LEJOJv;CQ?8YXpIdeT9UYznIP`g`SlaX~7N(5ofuRANtoyKSCQ71_!DW3&Tz zsJjK$**la)s3#T?-IVH(v^|Zj8^0+AoF<3WFAWPo5ctBG1FlLTn3C&H=po9|pI+f= z9HQpROwSJQo?!s^%i!b4w{8}gxsrA85$koKu|_pCMlZ(+b4(i}3Z?S9onmNYEbE35 z+Gv0N3rWtmVSjMGF32D$GojiZ{!9Q3h}Q2YT08NS4na3I`_|N5_Dmx z6~5p^&TK(SRC4O@O;l9}5cZa%8!v_HWGHhCWs90QTzo_tZG4y84Jn3#bfzioaqRiJ zz+^{AyIU;I)RZb#w5BF)Fv_F`At-_n2lMR4`&zqT*Frwp;viNEgozL$qq>z*582AA z`HZpKmDK71Y%hZ|Sbad(z4o>y1D$cY^0n&x%cE4RuTHKI-1dH7Ga94AFcGurA1w*qo&5ZJAT1okp9EYf5Jmf8)T@**Ss5@#a9d z(CM}9GV*p9_h!ly>OyVowIRdx!V$(Vbg~z}4NlU>yy=6R_!?`{uKmgC#?FD59n0Xc zaSzMGsk4(p1~>=AF)17Y!Z>g5k(4-Xfyo@ixDvupRB@bo?F-8>d=wC)`s)ajf8TH+ z^<1_AYwn$woy>%Ad#urYB6E~#bg%DKs_HnTHc0RnmJz<~a&^}hnhl3tn_rZ9_{%MC2*qJ1#39qzdMUpO9bOq{RRgFV!Bv5JP znbl7Z*N}TDi`RKpg{saC^@n8+sY;^6u~NX#GBozoD2*1)?to*KMqpE3L70`lB$L*f z+{Op`T@%y{tAKLajg;b5SmAHhleU;gY)XgLU?OL)8U z3pIa0_b_@*QiTEEcA}iZ79+q*_7~krN4-F%VLkSnr4ltAohk^Jj)Sh;B@+|pGmWce zOZexGgFi;iG;t*5!`)QZ;TlT7x1t;sB{@n~HS%8tc*gmsoY_ZOY86~3=*slPn#MBp z16^sdPHgmzAd~4;fU?kJX?lz_g?lajX&3gC#^EF|*ZOC=ZwmWAl_pTUqgvHIpVm2T zq{I)mC0|c~?v2#xBc)O>Z;3O_(haSQjfy_uFh@`N+i=5i5h;RWK(HWNkFKL&-`*B@ zY;1$a!tAGl>nVOqCksL1;2O8qIjJUgkv>`W)I|8&0cQsqsGS-?V$w;Wny*39F08F% z33^qgtv?<=Q%!WI8h9hbD+a9ITna^n*-J(Do*G@GJhE_jW&85Hc{sW@gWUKl0`!II z{>W4REI#2)NCzny#1dw9TM7FD;CxuwI-ya{c~F|+aIAe1lI=iIjzH7t;2ArA-3hF` z{3Bed2GmtV!)g2o!pY;|UkC4w&-ZViyeqjel$OyC$$c`cH2x&||@W+OP4|7n}gP$%44M z-hGoBIyho*jp;ZhYM5Qkwbmok!d)0;C;7TyQ!e9w@Sqq;eM^;Y^sD7KI#gm0$aWfS zKblPF(gNO-K~Y(I7`Y5&o`83+%8KRT8*pZX-zmbbk3=VLpSM|8viikCGa%jmAP-OrD^zIAg`F`_tF3hI6t4HVQHjp)e&cRlaAL^$%^r*f0FqadtH3B77% zr3^`sam7;f5J}{mfDYSn7#}@E=r^ddoBAa{%>Hx2{d1K;nkz%VVFB)#V!wbZ^nnJ4 z>Wx^!{ZV#%VwPZ?UpVaUtcXFP`Sie~0_MdnilKLgl(0#gg<*nPR;2KZQhoAO``7Y{ zZ^&b)lEpB)V`8GNE$nUKGE^A?hwC4z_1pVikbPT1V6tHC>?>;j>T+$HPrmayrtv zIYt>dcW+B^0=2)faCHnK@yi$Z`A8Q%^XqJ&AO$FHMcZDb=5M=hIRO?0TO|p{EZf)k z#w}G$i_Vy{!}<$cw>#kF@99%0l=5T}XW_{0CF&p^ekwxZ#FU{ClcbzjO6a$q{jC5> z`UXKuyk;f{Q`QB%kmMDR6nu5N|Nc3A?KA+Y_JvLT&Gz8dBOxw6!KSQ%p3V zsr@yx-B}5q$TJIFyt{pO1Vbl+0Hg5?+I^}X*6!il23`SEsxT_|i!q^MgC!bQ+gC38 zPOCZ1@oUov_scp1NW5Dd;qik`RzI+$Ly&Lt8zAfHcz2 zPjQV9Yt&?-rz28hqyo{NmGS}&wHES75$KGmgw7oZByBh?O4A$bZ)wF#Q|aQ;LyB8X8$Xm*8fRWs5k?1h-hqRoWDPRD{2EjL61w1f0Q2dbsRegJ z0UP;~#<>b_eUf(<}Q#wX8@lcyA+Q%?>gM8{We z9vT^sea8+j9xJ5w`Vtjk~#*_kP$+En@DTcHuj=_XH}AG@Hj%2T?YK!yh|b z0@j1~Cqa|R-}S?QVf<>lRNd!rl*dW4e6-}r6+Sw6OMP`t^XcWBesOTR<<2ooPTWGd zaC|t2$h6Abfzs1jlc#_2>wo?a?@F=?cchuml`J?;vr)+2KZ4JeHfs$_g0;2uTN_Yj zZE5L?7b-BxGnFNV>^vlsEOlPA>;Ox|bB3|%MsU7kVlyZ;Poe7$7A~8TW5$V2e1Q}3 zpH1!y3W@Vti5@93$`u8AduF`#4OUvOCf0LOCWwV`xUMHBi(Br2&79(XCt4ofM+0B& ziC1DTy4CuoMN%p`U*QFqeSZW8C{rzZKu^HU;LoN+!EMJ-irh?Zm$pW*@?`LaB$xdQ zpJRdW}S#Y9HqC;up!31l|&!tMwKwY4y&2f>Tyoe6rN7mBl!vm8sQs&n5 zaZfvE#6jb-0(|X?&U7VxHO#gPgUIX;@3fC`=?Ak?g?g+z6H7OLM===|8Mn=ttz+FH z$W}J&Epo9e0zBy2B+TPUv_AW+_%g^O$9Cz@xHlep9372UqaS0sE0o2dQ->>` zG367j5fwB#)I(vgbh#c{%Y+RyoBZ!U>@WJ9|I!(gJOD2WpkSP@bSx$qrBn{U9%UDY zkl!0ZP@RiiaNd8E#0z+ms#Q~)%#Nf1zZ=@1g~uNT8zb&WlExs5UKcY{ui@r^T@WJ6 zQ+*c%66uRWj++ac3(7;2j|+#txoTJS0@&d|GcQg5H1lqhup4cXoUK*c>3tpB` zH-bP0Mn}iGI;+rEdLz9pjAk zWS@j1EJI$FdEtimG|n&jyG;q#BhII84&p`oGV%+WeON9z4X`68pZ({Y1KaD&HM#*O z)#^paF9P%YB~w+LOjlP$G#=LFbNtK<2j|ULjTF!*%sgJK8K|Kz#h%ED8o4fM(SEz3 z!}}ZCtFXZ;l@|_w$9<-B`DdFPgb={(E1eO?#il_FhCi(pJ4~Vdt;G z?@l@hHl(pP^T(_Qf1q{E*$n#N2(y^jABl%o%K8vfV0Os~@2c!~M{f-Xt!6Idp$~nj z{en#sQL0d_e+?;~CEIZ*$P9rhSFsV$URDc#21Ri5zk{fvDX}Gey(Qzms*3?IV6Kqo zkp7tLwNT{^HM3n*suot#Uqi%75F3jeSI8Ycv52v?xX4Gu?l`v#ckQx2KsJMmLO{keUzpG@!YaNy4m19KA}J3j+DLtP86fTDutQmh*VK?m#@b zU&;$*1Jc-fVaBv}o0>d5@r5Z&r2DDR&7&{7w51>c*#R z&n9f^-v*oGg&LCrT^hYKc{pBV!z27t+GptJSv&myjNShc&O}yNlrF3WkwCP}P#ptO zmNJ-(bs|Ybyx*cQ+Xc(S&|@{anSxZgMMFeaEN-f!Me5F2ts=3Q>!ruVQy~G1jt%0B zGAvZb2BC4*IOsG}@kBhz zINn+NPPy&l2#zoakMuyKn~ z;-`hsPR~lsFDU7hv(n)2-e1yNuaTi+nNX5l6mH-cF6-kJG zTR52rhWNkFV zm9rVqQV~{&CeAL-p*OY}9(D;THA$z|p!^#qmK({cy)|pSKn;bFNAkaY+Y(0n;>rFYH=-3;YIu zE$SsE4RlMf+`}t;?yP-C_Luc8uK#OWE^^~NoaMO*BMc&1QE|QVh71Y^gF=j)&f59$ zdXLDb)sSmFz7;OWbCt}T=x`SP&KkFj7M2Hro}H}+#U4T>QEyy0kyn&CPA0SPl9a$i z&@8plE@@0=e0o%cD77~}xw40wj@hvnP~AWJtJ51F{Y9rn79RAJ&=Ya<6!t_!dR6oXr1;cmP{3N>SZJ14?1ExEG`SoKo#fI3-QaaWD}0hiyfs7Bb#!~-y5e>L*$-}{7m5=ykfV=Z}vVV<#SAL|)@ z;tMN0LCz1W6@Sqy0L%IRWArDRW(82j_h&?86QSwW5FOs}c6a)hgGLW6Py?`d-(vI% zbg7i`sPydF;^daasG|t#M~&ER8>p60KuL&~cm9lRfyWl3OfZ5?Wi?*u;wv+~FF4rP zn=LEx+aIs&u_E%&&xW)PHkGl?aCaZ4t^R>K8-IpQY_zZI*KXYOSm=H=GqPR8R6V)s zr>U!tIe}D3ly&;@gA@ZSTmW=}=%){i9+4RCrXyTF>z^B9G#!%Asz&$Zqt_?=S2o5e zuPAuraBVHvd1{SXENMRJiBdtdj|3{Ba*ASE+e6}QB1x(J&_GST(jzse+hBeOz4v+J z6iv27xA%rOr#^(w7l z0CsTa5)FEr$i=iFL!k`>ckUG-;5Cn!Q}qVwuc3nSMb#;e?f=ruMFHU85C5S7!@Br_ z3d`B^{IH$X$rABM;y{;$zdqv}kE{z_^HJh3EVAEox|~oUO9J9A!*h~ISAte{3K|kA zW|y|>>`~H^ai0zzkE#j`n^>#|s!km4#)8=YG9w*I;$JtMfS93a{QjU(G>FYW5?GpuTDT>cSS*dXmPyupD-#Tr>EP=KNg#1H~=La=PEX6p8)C zpP|Ff1z#<7)BJ)-M1#qd8~-4M!ss((`sb|7#b>eEUjnEPNmuT(b$);{DB;BF!FoTn zw2nWZxaLa#p-=zsFnI)mCe=#$Mn!HiuZO^tAijpC^PTz6`PE@ge&x-byEn}gQq~J~ z0XzXT=`wZEkBcE2qKk%~*eq-@)4EdbDs>^Dmm{i9{dnbI1~%g>$SF%B+<#6ATvV8y zSKDEAC0$M^v!uZISG#7@0rMOm6)2x>h$l`ZPn=HV)D;dzvCi*|^ zYzbZOf}&Z{3GM;W3F$(cdr>Dd3H9bdSc;P9!7m#yO;~Vl-E$Q->JwdP=3-$~a&``eOQ9gt``M`# ztMbyPQs2uYKUQn~SDy5#`Is|Gca zF2G+s?_Z|>|Ik+=Q#3)Zo(&?A>HyfkD|Vrbaj;R)RWqPtAnOWCrCQa(O5wOmc&%Lg zVWO2hI|{GzVfAV>yn;hz$x>K1K18G_BQmsua49B7hm5iB2s7*A5!D+D!ktgib#zBg zbb;gzU>ebAB2fvMwc~7&p~^Y#xTVd5@uQsQm;nER|kO>eY3wy7M|_HcJgzEkN-id z>V4hugLZu)xdb_W`>oa`ExbG<9Nwj|4LR6nHI3HZ%uzxU2lvdU_p8`T<6>6zcriB8$Yz08}LU73P_DvoKNBttS4PLo1LAhPo5O2?9T$|;x~iMSbA00sOA ze~Q7&TrDgh(%wV^GR01Az?(X>Q_3VU!|%@AU(5=Qe>gUFuq?0E0>N2~!c^##T^v8g zX;a9=uPs{n6F!{|Qt8q1p>ZHvJM$QnqXK=#>X#EA`-Z~gN|Cbdq)zC&E`$Gy7~Xsu z5am^q`iEZXO1U3d#W#bqt0rj)M5^v{_c@(Cp<24xpjY5$eSoS1xRn;iPJbx~!8aa& z774Vv?ifEL;czcPqi#J8>Y)g{(k}jTB!xbUqWLL~40W+S>7gu@xzsokaY99&XD;_? zp*c%}th~c~opNalLx|Eu(dRR&5Li7DO6CH(5-fPFeB#E~6NcG|#viG09_XST(&~Zr zG~8B9nSxYl4U_m+Ho7!f<;_Yx`$3i=t6cc^J?PqR<*emHHXW0w3=g>Gcm*Tj+CsGu z#T|p$rLIK)8vTrNEl9cYKC(QEjl?0COz6eZ2p^v%!vn)-zmNWlOY+KYk+-~Qkn#6` z!^sApdA$xo&pZd?uYFMR49^$z+JAq$lxU7Cx;%^yL`cbbmKZC+5=OLzcJl1$;<4W4 zT$p}Wp-znjDrt^+IE#dqntPg_oVhtZv4rV&KQ4_6tVSkI6l=KAkc|C${hw{KM}4xF zU{3cKVx;-KU6iC&HPVB!rWypUCkG|`%~3=SD^ZB>YT}wJvqH6 zhbC9ha}vS|wpjaht3H%Y)OOYDpOe?tPs@huwj5>8J?k5L{UU&n>%Gr~KB1jWb<32W z*URC}eVu$49&GI!M{b0bjrG42Nvu$$$X7p4P60!l;o(aqBiwh_#x#+p>=Y=apx#7X zft~N_;9_^~#VTOzQZ-3=@M@c&iwf@;mGnU zI#%kN6*bF#L?^8p)y`oe)S%S5kOG^Y$`WA>V5z6(MalbZi!w2de}Jq-wOF&hW>Sn$ z41f+P2AE0odHH$yFXZ~k&?G`Wr2?eEFtH8Ttj;8`` z^H{gb2mGnki-(Gxw|Qhw!=p_tEO{=#Hgh9`LOj*W@;b@euL`1iPA7298s$y?Q56>~>V%7E+A9C*Z~mK4>JzyEE8FHAzEv>#5hG;4l}(C9#}_QzYcNTI%u`HXOmt z3hkpXCGZ?}(T4`PdZYbdp}lH`M~(JPhG2Ni+?R{&`#5DHY*+q6&~DEIzD^*h6LywW z4{@EOZpnR?;&tj*w#Y*Xqo-Yixi;B$1k?W4nP#a`^$di=!eGf{3CiITj&N3?j^i$* z6}2nIK14C?*PZ?``vNCl4@tihJc6k&$r^{Pi>=4~{d>hi>olG|LjL z@BYz+@%uR~5O%Knz-za10FJ)kg#VSmr$BN;(GH0eH7q!|e|@E)WNk{yy`qoFo~RT> z>y)d!MIy6v%|kR>PuLg@+-z!BKhdeEdlt`0G8nJ==ZaY&bG>jfmxN_RX48rcGAF@a z6a2SZ8q=WC*GUHBbla}!<2S{lcessfHdOD*z3;t>a`cDp)ybdb95>(0LOt*ykpAj= zl-rvjds34|m9N11xkw#3l>NNb%{aMw1b(sK!k*BrHP!Y7k~lY$O#hmAQ~zANq5J%$ zSP`KVad*EliG!)mo{<`#)8MVM_Bdg;a{Py&%lOY1D@)ao=lTne3>!GIgoTqE?)fEA zM)^HT#}f=;wg=RtL3!fB)#!C(%(GC1l-J>u3NQxeHOT;0wkP_vw7ET{$|_Cs7Z#M^ zv@n13i0Bn!`%8DL()NJ>9iDMWUO?Pk0RP297tn&KnV}kE(={fSRSGG{9257=^9)h| zZu_si@n+>G(YDa9pM4G4vx>()iOv1Vfx4&AzWIM4JF?xki)*|H1oo2|;G{69rKH~j z>-)%}F=OnyRNPi~;bt6&{?!@)Pz3+A&_`fQjJYCf$xm7^B~~nx6rKI zInRVWFJ+-;Ry^|ktjQ7m+9ML$bIRiYvg^C_?PVCVeyjh52SPe(2QQ&+=b3?EnL2Gg z4&A=D1w(3uqQGeapp>SgAr=y~>o7pD%aI7-x)b8;(>-R7i?r(O+Wl~mfoKGrK%tIK za|mcPmVm#sjR6dFum|O2Aqsdx?RI#dKdgF>j#At&u>G5|M{kWU5cY^8bkf&QiI)5N zkHrBJ20dfYR{UpP(!%hiCzMzUPd*uY%)7vK~l2AX@>9dN-DV_ODYlPmP4 zmT?=QV)o$Bq>w?(ZuK2N@S7yQ1S9eS8PBq%Hs^S!loiN(Ztf&zhnTXyH0uSfBQPwd z+l)a2g2a~oINV%!-wrGzbeWtD@7KR^Q#VfW9TJvj)>UlY~)4fAuO8#>SZWSL+o%F5*YesN0U zR>R@}Bt5%e$$+A!vf**BLg8BqZkD_zlE<`!1d%Y@Sf>zJ09+oPL6eqOHh*|g+n(!a zK$XdH=N#+w965MN-Mlm_2vA7?_;Hfjl|JL0XQ}(K-N174rS=}Kp71fEIJ!_&UpE{9 z^jjW~tnw{Kt$d_nbmm==HLL|$t9D~72TH+kr)so6b{lQ?7awhSorK}QR;yq1n&PN` z96vx1NQ^(vIb{Zb;O@_iJp)7`5KzOgQcrpOZdDc0*gC>z=IbaURvO&+lc>V2899MZ zc!dH{96JKE%VltJn2n@o#2ErjY@P$K*QJ0GBYWl6LFywS^^JXjlaM+Gmt8z>Zme6N(#k<$y~a5q+3)kr>DcTp2Gu`|>;j#}s#H9c3hz zn5v3 zlZo>4MBQ>!)?HoII}=_rLZM5b?T0o_R{0C9Uq4SX1cMdl%w~@g{z4`Cf-Vv z6~*y`+nUZ3AU(+TDsM^{(mt*M+AKg@<%nX{zf0Pk?tB92lB%pH{JVE@eGN4ruO6dH zXZDoQd!Q&HDSVA_$dm;bbz~HnSa9NDP)Fg=)I$fUZJbu%v?_O8^(Lo5 z(J?91icCC+7BHIOj?eVq$VR8q;myiVDDx-OQ!)-?%M^o%-A}+8>fPfE-nUrx>%Rh8 zy$(N`*pvGcYXlr~UY}uBR2qnf0VUYM<_MkDUmrT{#UllqzqfY}JO=tvZ*@nIr>W+) zZE_J|{cZgn*b+%a_usDUYf%m;IlB1^lkIBtoh|+lXt?gGCpL}_8(wiVpwPBg;B!zO zEJ6aLsRoAp5MHcIRzDC4wYI(}L8K#pwnB1Vfkpm$uci}tzF{Ukj%)}?eVMaSB10+6 zfXw8Md?X34_i~=wyK!B_Z~v!Pn3tRg{|un^pe56Frsd;|t@Rawb3TxT9&P_%+>2wO zV?FOL%K^WbYTm{C9(<1L0Ox?3#0{90VrEy4r;DHbp2||(m*f`v&y)~+;|sXZEd)24 ztOG*jp5xR=uMo5rvZA+$xa;J0gs5kzkF1Q|-cxAcP&qc!=2cSs{yNVyKgA^RstBVH zDYQhHmPsPZkzNt>EHx8s@Paq{H?dy#x@wI{cd+F3jAhBK+GnKAsE;EJUNO{Ou?7hY zUZhe)a$r=A#&KgD2n5PoYOXqC^b{anrc zw8$4>hQ~AX4Dg@Q*3ovqmfaJB0NRBirdP^hbb3fyO{$gG5^F#tzLRGU|0op^hDe9N zUL#sIq743Uu{Psbx5ZnwHebT=VASSNy~jcPT@V&@b4J>=UGW|#__vgkVRRA?P%mJ! z`XY|Y*N;jwRI)IA{Oz-hJvENm<7NP)li?>Uw64FmxTMzeBOKoB9x0Qe;o5@j2cLiD#I4p)~C92Ye73*{PmCnEM_AXxc5$yEn-B$#9ZSUJ2 zmXGj?{CfRinT&*Sd3m*(R?ya4GHE^=Gu_TZgfv3G6?D2<4)zS_zgAP1_0_X@<5kxO z63#5XgIu+$%zRzj03ZBo;0k^XTpso6%ikO*!r?V*B=n3eqjCUv)fO~)D`ZXI^?4$J z8%a17x<};vv9J`5o;T~dl|bm;gn5(#HRbV8Xf};VJOZhhs2<38Kkb72u~56Uq;`|z z7`qi8)Ru=OKL5-R}g6(TyeW8AWG~awDRhUDcR4h z;msb~K2%^^ID@4dr^+~s!i@3~(6)YK;n0^-bjG`w$@`zqr|tUtOb=wfKwT2r?Ejc@ zKmQ$6zyTR11OKuQ3qP`8q%A`*$TLoxa0&tmf>=>oL;yg3ZK-m&a$d}z&1rAUBqDn` zBg)}>NwC&KfpMN;b#9Ovk6Sw&6!&HwXV9=x%F(H+?oJEZCo||9Im1Byroq@^)p+X*=k}SVPFl1CXO{?}Q(~*FO;6rNVi_bpsZ9}yS z!v%$+b}jEUD5u9+1GK?0O!a#X!V|nPv{L97Su}a(UtyX5ez!c+oXfyk!jr!uOb9_9 zTE~ji04^C#b$ zj|*%FF1Zy8EBLHAl1NRN^z?Iqt-WwRUGEj&hwf{uz;}1embjB>W4WeR;ak23^o<>y zyMsa3`{?|oCI+&hO+f8W4p}52&QBe>B)~}{`KJ;L2r=3hxuD@+Nl;e%U!xa_qVoJa|qWJ%I9~xKcb9g}m_2 zGSW&d%D%4447&7iliV?izm1P)VJM44M0=b4>%8G-7MBNzz^XWwt6saGZIm5 zd980@_x*QsY^{|30R$$$3bg@l!Ot9$y;35%&Km4+s6ew@4jJZbc@F7he1qA&n;?y! z*hgt?m|}7^6inmYV81}67m$?YtCjKK5_Q_vDnXvnL?IR0p@R}AW1}_H&RI2!YHT_} zJG1b6FKp=T_h-$)<$2}amDg$qBx)YWIuieQ07X$#JT8f^c|PuZJaVmA`l+@?K%F{- zgdF#41bH#NfUjYSA|-zbME?d7niznZe^(Sk9fSWV1w~BE6eu+ek#m)a5gL{cnVRT%__X!4^IznTzDm~TgN`IMrpGj#H1XC#(5TR zL)auIk2Om{17&viLJP5l^_r9vzCw&W?{x(+93h6H49^cHxqP>>&7*CnU;TttY;(2z zf>{I3gO>l=xpE4KCIk9gmK(l9X_+Rg*(w$2&BPs+0-D3GKSocd=|K+INp=nf z(m2JPg^4u}1MfV=aIGaTnF?M@=tsfUQ;!tcpLaOrWiMR^LhwuSKE(NJ3P2+ZM&ALW zxD3(#=4vb!xYcp{^Y&_Iqg%3Ax2AqsLsR`i}aieiCdMlyB~>L;Yi z*GpL@RD5`ih6n)IAY3n784-EQTV*w-Eh-b-mLaSYG@`ZXrm|SoaD=QAKl)_>ZzzaG zVu|aH9W91nvd4yA^I$W+Mb>ZvJ*&U&1@ab^ysZNW>Ky?^<~nrMs98=XM_IyBE&a_o zDxrr2J1yzVc#a!;C4Wyk;|_RiWL|eld3QkU$X6{5wFj^7$8j=+(*U68nM|_@(*A}S z7L!-aa@95z{NGt1id@E$2CG*2Dl`Oxtahc;Dbi>1oRYZ%K&Jj@c9 zil*Z$8P!EYkuI?qCqF+gusGz42}Kl7PzYXEm+~KAX954DIAVbR9blQ-`VTrA&-1dT z9==6OO{s`X8L^y(%_jgjcGcNNu#f#|Gu5*-Y=_1n-S5Qgubm@Vp!%}5D;-C6(vPAR zWNn-XPHJ0DyQYK=0yG#5#)Qc{xO9Y0^~iWwl~(N~RD{Jj=wDve5PC1V)NIUMC+iaK z5i5P0QI&pS3aXK#_V5Asf!z4f284OGVtkK*KM4@J4zti*nzLGP?XpSPL6KJ9!-gMkr62OQCz! z&>u_@oz+58YfUpmntWF2BDP)XphaXC5CgPpmYLVJseZGTn^WBz{`GC^+4-0+C{a@F##h<5SX90I2oah%ph#;w8a|{Ym15-mXJR4K3}%~V z6S#2sa%<{P{~gbtneq_qs0DGXL*9H$Uz9|>5)Yw5%%94%`6&U_Y-^@8c(m@-)3TuO zw&G^pNu-H{_1i_5R2VBL$ne@nQMSblb}#9Vn&aks*R5zjP@bUt@|5Xh{EiAK0(8dtCxh2fe>QVY7;zJ3Pctp^W@Cq)_ zU7A{EmG`fpZZ?x?d6ygdecq;HRmL%QP8@muA6f6foLAe13pciH+iIM~wr$(C8rwD+ z+i7ekjcq55oqeaz`+c)#_T&%TS#zy*o#(+6Pv39(Nsp=$N8A4Jb&)0+LxB7B{xbP2 z-9WcSt?aGqW^P+nQoKppe$A-CTTrr;0v>m8^^WaE#&E`j$-bmZH={axb~!kRVeFlFfIP zqDDQ*XSLzlEKqYgQT^F&@ihk} z;C|vEh~G|M#kzcT`9WVb<<8fXt5bLa)S487W78C>Wr~SosFgCu{lP-88Al#G@5%$b zvjp&I;q($IX>Es;DU6bB()o62!}cpl7Rz2oH>Ri(HD`=B#$TzO!s*sc6<5E$cf>a2 zUSm|dIMjYEM*q9izFl<}Tm=+2xu`%A8BBWwS6#$ts6q^b-n?4hglPZDg9oHNll~Qv zf}E&M*mD{*pGjot#V|%;8MH3=V=1Tf0{jq9Wki(&cV=!S!nK5q%W2RIR%JiQRd(0b z%(PQK=~`K5$GpS4HOI~~o@LbiQTHcB&SPDVA4Npwc%&~;RQSH^u{q_owBy-3JLBePZlp<`?@;Z5_CHm6Nc+$hn@x+ZJVvh)aP zw7A;-Da{%v2)m!AsKf9QEL=Y@O>H@%^3?z&3BCPl|B-7l@NfdVd*$!9teZQswJf_= z?J2n#%sqLnNO)K5CRHO@szvJ0i9f?!({0Keq%Tt_okJE~0@BVYV2h($SEnM|cOT70 zngf8*vIb}Wx^%2yL8=phr&06$TULd3@0X4)dt4`?pAeOQ$4$+Fol(H3`Ue0D>F^gk z0l)LK5y~_lbz-oW2e0VI!_lQ<7GMCj(Dx4G@!OLA0JD*xFm0{Ji>D`Yok#)wy(oE!pwm zKet*S;0)aj&vU|)&ulL*Ca$NH&PVy8KO+wv3C@R z!2<_;0IH@%q2w6H)jg(gWVULkzB6kWwUisoVtFs(uQ+kBT*2;@qRKzi7D((lmOsyc z_#Q&Iv^_U=l0RRn7VaY-RDUBhZN)O0UsTP)Bk2P0z9P*rz;3#~o5aG?yl82L=|_=< z&r{>J9iF$)NeAi@GJnL)W0tesIZ=GE3-LUv)cW5)@XuBC&u@xhZsIk;5icJCDy0hc z;Iiw0Qd~#p7BO-GrKbP*UGHTs&7kKv+%sFgbVkH=h?LdG5ZgK|9uDp@e7(%lJPS;; z1|)ivVy`JSjAyx(#)5h1fyjLWNz0j<$Cw&!O6+Ofc73}k87}8wn!#!m1`ytbE7g(H zL+H7LqzM6W0XRh(OB%tS=@m8HLDH~CwTzuUo&964(!t|C?fk1Xhi^J5oO=0umL{C* zhqR{{qt42{@&DvnlK@=fUsV@~?R`d(g)hG_;(8m>V=5`{37kx3TFG2@7u_yQYlfXZ zUkSQ!+yo3+h}p7Y$4l(97JSe^;M^!Cagn@&1V_181Ro*?Y(PMr}_mKgs;@jqlX?AR91W zWKVq6IUxG)Y?h{h^jMyA_=-a+5XpXPBG?<$9Ii_kwQS?1;sDzCWGAo_)>mPg$|x{T zKvju=iWa*n5-LrYMKARi#bD|a&CYk9TRqq|%1D$}Fmq0_&YF^`A6dyMu1DY5zH$kw zS=EGt@eYM2a4uDSP3GzxaK&kCYC(NszS$Feb_e5>QRTMj3N&jBBd!`(Oh3_^ijw`{ z-0kAiKgt!g=d~nnU1|cWw%W=i+=t!648oL64(Rtq`CTckar!TOI{JUA5Z{Po4yE&Z zJz5pWa2AXOZq4DdW=mw8C8YfeC4kz(hDPdwzeE(*ziYbx2Cm8hI$bu_0sh?IMU}%yTFpdtD@mA>qJ1RKAtNhrq#b<2rGRXCR)YYA%uVkr#BA9yE;vjO|Ae#P!Q z+L=eM5Ui;Qo|5WmTba22H^$k$Le2mZnCWXMEsFG{OiO+;d6(r>;>)lOarpEHLM7GoaiG?0_c@;yw%s_T&UyOFfC zEr~(JllCLiPFJ(4$4~alYht}Q%ij_|hnM-_(=Ryxw&Ok@5kGqa3mG$|ZG-Rt;^ifc zw$3zfw#2$N!JgD>W4*rzyg>PQ_aE1O3k>pBa`jKGbqb}%zH$L(fG(A9h-V)d%R@PW3*KFuu@PLsAU^5Pzka}X`< z;x+-o8ZvgS`~~uG)GRkX@bW4+i5&njqF`WbYb8`}f41E%r@W&%4qy6kCPjZ6`B2>1 zp_prS91_P%-Ajf$LsBmyww}ojo%?M@ABE`mSmcK|$P8Qh zNECvd>4!P~M0|F-*{O>L7f-UFzFD`by5~U)jv`fK3OZMl@yMR7USnRgXT~_#DPd2m zNw-i{9o6kOJ8zsh|5UPV_?bi#Oy%*Uv7HJ+OP0zI3-8{R^8>KcDXm!oA}oBC)FvQE zt*&0`b>~9`35)YX1QlOugPEI$bjj+CNjXP9e1=muX3zzrd(DTOrqWpox5M%%k<-yK zvm#o{PkE+pEK~nTgtm^lT7))^N4hj^MBwA4f1_e^$T}2RBn^I!Nx1OLv~F&zoM)v- z#|X2C0)xiw>*Z=KE(x%^zt@N4I&pa@G`p78h@(T}a>H?~6i%WAcW$Vxxy*FpsC-UFva)?awIqsfSmSveWN zHW!HyRXJs-z)w5&P_sHcDGzwmw}>0%{3dOzk5;N@g=>j#SCMZF4R1abeL~Ltf)BGS z?cx>Gnwmb86rq1T8ZvVf5Jq6HV)T^HA2!wzMpgr#~?4ne{{O+*Ch7E4F= zDK24f;eG^1FjU~+p|kYB4f}B<9yk`%o#K>~9NgUXg<#`6#K{95%m}CTG_@_q9%S$#q^^(wwJ_)VMg0pIFnpv;Xnt8`5dYS zgn3*EsnmbJ^WT6a`o&&g#r8z+fa{~TeX~)r zdb}r`x)&;XRfT_N=;y0-`VwMPu~oWSU9&H;f4)L?YI*Cz zyn4|+3Bo(`&{lm3k*gt9mGL!%18ye3Uh)6`*xl6>`H%%1LVEq--}wdMRpl`I4>L5i zuivlWz(zD`BX^DJ5U+$}2N$)4DkE#84Betk(D^cQP}lNkQ4SgFq&d}ghy!}jkVapb zk9frPsbDgl2?%6P?)mLr(Im}mxOroL>Mb_=7+zf*&D^~Us|sh`_i}j}PFtrmSEn*v zPp@a8S@7TV2yIJT`6JFiSDATphN<#7ymOXz8DpV&RE}`$oKoTMY}D} z^LK~Ky<0TKp|toMRN0&^wJQ;=*i8yVV|JR1LM|LG(u$Hd`6& zGytZZ9Q*$TMgUTCkyfB$P)>sRDI~Fjt6P>b2Z-xP)^0JFUwAg3it`ESNm;-MWw;|%p|KqaHZD8}O=tFEH%8D* z-KCTw?lc9q_>@4VV!Co13H_k&{XO3uI!!(B7f5)XHK9V(#a>N*ijg*QJpCgeGG*x$ z!%S{55L{}=0%6JwyKsu$eI$w5dh{8ox;@P#?g5=1$2Ky5WVvAo3OQkE7Ob_+1H7e@ zC}o|%!G}{|U2;WuE2LED*^&O>hLro9Bth`6&I=V2s09FcN~04>ZxN-Ib|g@)W1v>| z6Zy9T#(q_ZAc_DiJK_s4u5S@Yg5nEngk`htru7|hmC;KA`&UrnZ{IeOr#J^~R6uc8 z5QWJ$jCNUTp(Mq^0}b0Adt>9x88zDjRi@y)?aybu1{X;kQ!4Rcu`ysNjvy84(3$^E z6HwOLH$QK-Q-`+S-)5|wz`Kc%=2q2-pYJ7pDj>JHH^SEqf`Zl84)6Mk1a z@vaAbA6h<+VyRUUD37_}$<<8sJKWGc709EV2UX!iZ)0tpdkGlQPu>g#jl;vxVljNBOf%Sra^3%gz@R^p)@T^~wHf zsPZ>-drKq?_Wagv96gi+WfN{So=)iC6*vI5+%0!CKTi+L*MT`m>QyvgVH`Ci%M2C1 zXB1|Tqo(ql z#LbiX!`V{HssN_=KF-PmC^F3UW)a7oaW>7M^g!+PX!3e7O?g|}L(90E{svS>@*O!; zd+pJv&?UF{k=OmQiT~`LTw@EzX9OWeS6?;pYi5s}1Zdj;I5}WuZ)hf$aZH3K4zyz5 z?cP5!Es5t_T7UwJ=cbUD0FCBWh9(x!tYK49 z>`x>kuoAW!*AUQYItEt3T`F{pr&gT`-t}<{!mdaySS<*^W(_mI@Mc8&ru5a+9;y?g zH^$ja-0gPu(kT0_WFdHxuRfVe4@8GEE^WqA&9(*Yb+rb!1F(j$a}PaeLq5{k?UEQk z&-i5GPMk`sdcs-4I42p`I#Y)^pV#!4m|?Y68%pz)Eh#j&V!eJz^L6icgIr$Ug0>Y*o7}c#0Of{Mt@*qqBJ=Q1&d%94Fr9ypdcjC4=x;* zaHF=hbH|+)qr{zW?A3o_Uw&~A2lC+G0mV{qibr0Qt#vI#<9nUQWS?Wxy;jFpDHE54 z>xwmA%l=U33R8e7rMi$;ePJw{2 z_J~WPMO|V+*~$s>NXO_qg&;b#T+HkJs2*-gM?+G4hkGCNc+ni7K4sP`oSf){j?Qx) zIzXeu3KBNTI3hqy{HYG_N)+PX8Q+sQu0sYMYo6@~np3OZU%$#)I3ldvJ`HaY_|Xwr zp;xaA^%v>{A9u?{JuRut_~g(@MMZC$=qj|;GzQF(CkM4(%RRqNowAnRksKO^wC@R- zl4N3W%LWsS*Vwc!;1W9KR^#nw-g&aJ26^YuvyC6f4o{nN_6X^(%=tVAhr0+b>s8_SFB?SQ9yt8dV#T-%#e zc~n~wT9FNE*xC7}){5ZT#edv)2`IPl>e|+iucqt$xch!fh+vB%4>2t6kg5+yR5F9AjV^3;@PHy&~Lz zQqzPBzM*z0C9(x*$h{xq5zh?_wKI{H zF+SzxSIWpdfLW0p8*^}pB2;K^%ZkA-dj3emq)l+G{5e`YbahW2QjPJ#flqm-yPucp zdDtSd$Q?twg+ZlPvw$`4@=K|!Ks8#Djdq-QDZ=kWb>&Cb^Te#0d{;pAM-1za9Fb!W z2~B#A78&%oB0cgao5N`M6$`N4@D!iodK3H1$1H_Rf;?UQ!x{-{kg@@aeM@V0^)UX6!ZcJU{0Vp-dk<8;6$c+|R`GtUVOm8d3 z=2^bjZy3+f!v1hQ>q^v7~qF@lA zp^Uf^fDGb8!;Y^k3+jeBM*i345mpjm5B4oEtR=@Nz# zHwg$h#OQa|+g_TxMtW^o@XHjZ3kM2N`QUClY(u&|A-7eVdR2)@Q{smG`jlD2?GIP|7TuJeUGW2tF7AexRB+1?vOGiQh^QDh{@6E~s* zjZOBMPTK9pQG^Exb`N^c5}x0BM7COV^MWom4FqTOj6FB>H^1T}*iVFARy|{|Bib~3 zIW(mEaQ`cJr@uX2X)V{tQzy0CB5SyTs_w}lx9PTB^zju*&^nm z&x{NFEc@^%`w7T6Xk;*p4Dgf{CvCGB5xZZh4uP<1dTVPbBW{4>fBib$_|d;Rusnia zztK`dM0#5Mb5nHD`?rx~n0}?O!(t(#qRU|cHmi+Q$Ys$9mqYd0&|)%b1|78lVOgf8 zmV$(l@I2N==H>dG%7$hBux`ep>b;0+m!V}b75P)ZR38Hx?Ix~g@UTX4a!xdH_wp9* z#S3V1Qi08x)LMVgafm+oR0B!hXF!Y3A3Xa9xiAn5FS{lR1R45CM6)sz6_)V z!Tz|t3|Qs|-kpv}tA|hEN$N5h%{-``U}IS@-xq$fOC56CbQdVNLFb>bqg%gwZ5&O3 zQTmUw^9AjlSTAVD+iAshPdXiXaW78e`=>__NPdt`%>Fi_e~suC=iie0hEXl={hn;7 zxAoI7S6(A*ZwE?Lt(^v|nj@9Oap*f1hjG$$o&Qm*giU5gA+Jh&?eP5Uc#s2635{R> zv~#65EF_XQ?VZKhqT>UECD~+T>eq!2Xh{Q1EnkHXlDHgA%T~j()WLCatqua-J-RiS zjS{%H5E)zZ_%A@VPHE{0>G>vmI%mZOJ53glO|f7~HTpsbP! z(CRd(%)}lSffgF8XQG8g<<#dadm}H zCD|TcSyVzuh5^eOi@ZO!;<57ZR&(6CZ1Z1Ax>`z3ej1!LGC>EexK=eys`jFyOhXE; za?UTVE>2ln72fGvZ`E44&FC~+L^VHFpQ-dl;_gc8!T%iz=bQQ)mA)zQM^BeP)FX+S ze>1V#s<>UJklVAcQ86TXxDcb)FFX>$ufPs+%SZh6ecO!ZmLmbdm@{C?fe1s0%6Xe& zrcoNjc%QGUD@T=mQ0DJR?le0oL|p$i$hwWkJN*@9_;j!Pu7iO9aX9HM@Tq~v=%z!S z@!o#_p6`8{0=ePKpXHdWxmBs6aYzeuo}{f%|E; zHT0D>>I>oG9<1YLo{c~Lk?NcnQ0#WC3e4k4&l_pbkf zYuTOxX#gQ)9G6TSLRMKJoftD#1aRfnF#SycYb2Fx^RlW4VNA?u#tlky`tZO6dKv5zBSNN$=kxX!I3~_`j6~aYls0a8EL!9 zAn57D5;OZw)D{}1^nm)lthn0t=%3CY#hmsM!4Y+3l+Re^9xWIb3!wDkkiv{VP z=uJ=ye)@T3eDXas#04CcO%{p3?{`Ic$^LuVB1gJ|TKrZybb|>*N&F{8iC@IKM+DkhLi~Qo zrq3T|*QpSY7E)qJ&PY-z;~gs7Dm`u;5ZJ=uyH$YC zP|}g-f%y8P`_6@Y1DTPumDbrul-4m|GWbnem;7vQVZ<}F?7q+)vdHnT9h3j@IpP&0 zw9#Xuj}T;6)Sl#nAD?|Sy##<6KU_ucU1oulst_{*cP$jg^tSGcK~q!y&=9m?q*{P4 zY?h#GZj-bEQB<7Qr?~3+OAkxj4n(3DRSv-xO$}-lm&U#7R(|bzZlk;7qH)+&Y2U`1 zHUC@h9qAJ0ulx-QtYc%Mf}*%PvMA)DRgP96GyyK(baB5F9fkHPfu57bm9y=^&(xRu zWre&r$veE)sZA*|j^<Mqi6NKqw2tLP*W5_{UCQiT>@X`R%AOTRu_A-<# zq9EY_xJv*v{q}O=z+fHSFw*43nSA-_<>&LQs2&OkF{383E~N^LB&L9*$sbiP3y$x? z<&I0}+>*Z{OmsmTkvI~w;{X{tR+tr3dw6zZ36e4ON!m@)go#Zf1Go?v+u!9Bv|6zX zDa>}R?;kinUX-}UOMA83|3ZQlc9y$K&@9K{5ou(^aEZcRr}rB%NoWUZZ6m7rU}yX6 z?rykU3Vse*DWa$*1TR5NPf7N&dR++y)4NC{ua}5L3KYTfniGgv*$3VpIClLYoT$)X zw=>UOmDPB_yaQ#hf#tA5H%FSOjso83-8H>-8cXuh2m>KM;vJGH1?d)s7A>8VIS#~W zc;ya4n3eV7fMt5I_`UJK1@F|sr~e9*ne>_H*AlaWU~CW@{`ILR1K>b|0IA_o<*qG1 zaLB{l{$S?-heMgIe{N(|@j-}UQwK(F!)^9HsUDrMzhvfYMjoq6QQvlQa<*8sAg7&| zSyO_n<+N?{jH&<^%AkLp1&R)T4Q@N8U=k{c>;7ya$qI|t0u_ck=L47Bb=F+x5gdK) zyhYom}KL6V^)^}w+)Rz7RrS@OE9bpaoUJ)_I37n-wMOirs8VKzneho-<0_211N~6 zsUdKnB?A5B2?Pyczi6eHv7F*S>gd&n_LLcmWdSg}V_`{bm{m^cR~1v`mUHXB;_}2am?bv`njNT~05H z;PDO4DtxU^o;hjk`N5$hLT_49b^VL%2Q+KT0K#0Za84EAbEE_jOc(afpJgh5xmJmE~fefYz%36AAR;_}hqbJ5$ zQrWvcL`Zm3>mlt|6}8?`9gO$oc5SxAw$2MU!|jrtl^w+@{d@P>%{}zt%8ULvABX## zf>nr*PAca~ZpVvxTNcz|{C4K5g#`%ssEq({{%p-nP}JP;la=JO;vW!0ES3GQbdmoh zE(4;?27Uc4ZHBD~5P=0^wSV1wGwUKpBaN9*wej8RLrI&TFldbQ7sD?D)k5~_M$xGx zAxoum+&|7n2o~O~Y#T3#v&XDchPRdORl}j4qx7g1;DJvOq@6+|Pn_mzg8zw!+hTWe zy}AY-`*Jqzq}6S#wdS()a)hFOMMF;Wf9XRwh{?uGoQzmv!6nX5p|CbJ$^A-@cKTz%D&ycQK{!WJ1yRO?A*wNyl9jz&!rW&1=Btj=7T2JrA5ahPH7e+ot zE8=~tOuf$4+LBJKZE0b@pF1t3p|{X{b0X=J#s4t2iWsP7&mT*vsF6a!^1d>NHYZ&- z1()Vp0?Whe_7`k2RO~G6A8GP$1p-tW4W73b)El+nLj?1$e{%>hV3rla<7TcF4sPWR zZv_|kvFI}k;d%;VCXAAfxlyN&)=2N!;iAYfxM)M(u5~*8N z&Iag^!pB!RSu=tvI9sJE?%YG#StUR-0Qw$h9^P0}y^~$2^oA9O#Tc8pbA|Y0?MoG` z22FBG7x#19J@LT_w@H)OUB=etT;&K;viWd|Sr3p>oN?ICcZ6gB;u z18~%U%S?OL2El~VhUnkx+q;^V5=zXj)8KmI`x@MRgfax3SAR%H^RZ2jhOiFI z8|$Mon)13fBtsryym7;6I{!HKIK3V$74Jh3SUq@E{^Jh^*jII2>R+{nJ&+qac@y_ZgIsaKocg;2BA7W>D5 z0t|qIfJEXWjS%*p#M|{Z(qMkrG?MSzMYiSU8PtG!C|(noiOX%=>%u#AJi0#diL{UXdtq( zi9_w{%tOV#wB%Z?b?>jQO;_DNL(y(@$vKEa;a8()%vtIvALO=sV2SCwCvwqM^f+%I z@}=AduV>(|dTgcIbjN9&5?T5JMUg2u{MSw>6#>4f2Y(=63atPp_#wCieV&|` zA+DIHN9f;+y*QkSg1r1=5eE=T;ulC7HcvY*tUw79djXFj7#bf4vfZ&3K=W16nX6b~t|~j%UoXGudnN;|)2+JZEe1tV zZVc}uHavh9ai43Ngyq8nW2w%%k$!-EhEo=E=v7zJ!gyEm428f+Bd+`+hl7veFL0Z_ z(*D2V?MMT_bcQJU`H1TD%?;o^8ubsWWtV4Qlqj*SlaXXLxwxp>2J`;Shrg9L2J4IM zOlZaSE4ZvH#!}FXBf7+mFAqnV*<7hb;egiPXu*6I(P5iUwvepYu!re6k$)4u<{uA=0bf|{b`0bcqB-+0SZP^&|K@q91^$oE zi2YK>qXxfb?Mf_YSP%b32Jlg=VK6*v(<|p(4)(Z>US9QZ5P4WWgWe=E6~g*~rHq7{ zXWpnOten$xh{YHZmO~uuTnVuo5ddCVTAZdWN&;RNTS7dv0R9DXh>A)S%0|)8rxf^C zKo&~Sp)7Uds`^!0KWdw?qbuRsNSPA+NM=ozm(VYnsy4!#*!m~IKva~qI0-lW=HS3-A+iESE zcif{AlVFP#?89|E^EUkT8V`cfgS3?~T@V49IQucgM~F{gDnD10tgpQ~_QD1b`5F4j zGDx?L$v6aXjY}qT@w0PG#LpqtPH~JqH5E$F{3v~tigk_-*U#Xc1SxPbBWNU%GGo|7 zj9Qvir%KMSCL|aLlr9cf7{W3UWDR0ZBq|CI%)am~D&UZeINrqswY?~1_BIk8yB6W( z-ELq;>BITaKmXQ9aLkE!=z8-8Z#h$#VCe|YRbGth&Nh}A{$5@*$1v`099dflK*$bbE$a_m z>Ne*w7kQpWRf|GL9OAgJ{f*7(WJc5j*1X`G4mcV!R?Cz@L)&ELWX*80z>SZ7o;_H@ zI(C5=RlEhKS*`GR%4@@j8Uk?Ki_r@=Da?4^?;N`b=lM!$Hj)Y67)wq-mNK-2^jpoO*hew1PK7b?R;3NGWgW~C^%RRLe01siE+{#<98+JZ zqV;=IYWz&`i1T2MHI`_`_aG_k^r* z^un;gbw2kqBlGwin53p8g?UvT%?Y7NuLGQ5uOZp?8Jtw{x_8sfx6LOgq=@F~@e=FI=n=2@mSkG!^reI9npNOT8!{(A z%3{ALU;bbJj$gE^DQX@nP262dVZ%_qNP1X4NodJ$X$uPDiQA{crJy}e6v zr;RYh?lRoyQ6BD~B7Rjyv{zMNU*4o7IeRK@?tMm_nJ%GEQky>dASB`__=_EF(47-p z{^sLb2WICze?}^v8(C%$c?g8~K?yCZLJ*|)#UbYzl zo5i1Eu3n_#d9YY)4t(CJrc$E40?7m(QTuI3ZFGBs85Z|CsqVFcoges%CpYBHd$AOA3_Tc*yTSj3E%!>rqrL*DyTVW$OkEE&U5dCcXOua_-IZ z@xiage=8e9)2&3F;toICp7NlQqrlW?WwS*sy$Yjd^2HuHiyT3Wr}wBX&3_5o4~ZwX zswY6nUS0rA1r^kVu;mQXj?P(uA!cxkxp#MEV&t-(;rPd!JJx|#HSrD?ihnQ`PyAp6 zzcL`>NL~59HUJVqR(-2&oLLzGG4-hF@>eAN;O{ov3uj#{=T3VUe?SOR;7NYRTSp@+ zZdFhkstD`yE%pZ*n^bz~h8_rL5ODV1d`K`!27a`>xYH*P>b4$x93kLLDRA4ZF(Ph0 z)7VM}sV57cfq@Eu3=2WKTSU0>MTlia3nTASg?#!@tsO7q!vyTPZNUu3LmIESl$;+l z|5~lalTY5zZ%@ItR#u9~CBrU{LI)z@1YchaQohflsWh)JW`~_y=vt7xZ?|k*eTYuM z2BY`BLF!Imvd46bByoRB{>fkf72qzI95FjBCqJsG>Y_Cc^xZ@+h4^zSCH*6lXTfJ{ zYh>3}^y~?x6F)l^bm#r)8Q&fLS)_Dl?c@2NmLc%FKHl4M7u4VR!y*`vTPd>JcNi* z&s3t}S-H5Wa0~E$idnAt`zNgQ?SCv6gm@I0Yt>gAhK^eLaKR)mTnF zMKAWEv4EE_|DGy+;&p0b4~m5%^exAy^#bv2lpJ& zBo47v(_Gx0fd$kR1=@cyq0v3EkBEgKCZ#{=Y{%NG5e)x2^V>XOb;cvxR%LlAwo}OG zr)qR!Dxxm0;?ImcSx5r+kFq}ZP6$@fxJ;IHV(KU=W0aRO?+v)TUs+j>pn5t+Dy zQHgv&XM{rmr$8TvM>Eg@HZkt)?C$U3>C60))eLzpW?T~+l)5mH>F3OzjD4;koDrw| zvTO`Li?H$73K143cGZ&g1_fv$P< zp*y|bBwku(sh;|8A}Nki9U4-8K>W@Mr#}icTt9F63l&nBGov)efbW;^kfMOlg?y3F|0uxXuj4S|yHjamh z8pt0^BZi8vKU;IVGa@|sMH7(WvE}70je^veK9+nQsXb24z^o#v!)~+ED57Ce$(2yn z{!0wWo;Wqn8hRzJK>Dz$^0t|#wofy)&9yY|7#q<&C&%$L-U{6_nx1 z`w9IJky7J)x9)LIZDhPapIhU*{fKc2)CYG)((#W$lPNl0gu&aIgp~0i01_^y! z87R+4CP6m{5p1)}V0RqpU|)W>l6Mp$xI4uD<3H9=33{ZkK%inRa@KWcBPFfpEI(aHElMc{|sxAM}2j>noC01VpZB9;gRAOBbq$ z`AJI;W#<K(PsW48GAcS9KEuE-s^yX=~C=~*M*>eQ~USI2ln9tJ`H>HhmB2`6$dWt*xC zMfC0;b)#ZX_<{o!ma^u9giKAg4MLn|ebU_CJDDn#*w9#)A7Rx;S!dNJ(ncKdwjqnk za7M%SlS*ON(nCEJp4z;<7u~Rr0NymoEXkrUup{VKIZU}-2efVLc2;s#1LypYL77py z;MTB}pv5>!{7T}mq&{q^M3D1QKlR)mu&$|r%MOcq|DD(+SYPxqW5IoA~3fQun zh+A(^tg5Zv_N`_l{{dmLRVEWhsC^{)GZdJWL8p%Q zsVY8X{D<<26_Eo9%;Sap(M5w;1!nf*a95+~6t@)z?6%??bi7C)wti16NP~jI zS*)B*gDBWRoXH$@jJp4@+03~0>Y$rh@*%iZ;@ z=&;Dr9o+43x)%P6Y1me8ka&at!E;VHpRj%S0PZ`d5jNa&pc&!`35H(z&?DzL-L@lYxll({`jdxIdzkK!AqmG9AK`{PnUH>GK;RN8?8(JK!lN2z*V1Apmh# zq2soqs>JtEQ#Hr{YUKd_Dp2I3a9)db7wd7?_Eo=W4h>Kn23+18kB?29Qhw0alB$Jj zsx`~4q>UMc73)Wzjb@iB%VKeK^%fT;=-2@3z+aYR_bN(%ghCn_%JFI$%2Uh6fY&rH zjM-)yMVj#{l+baC)_1i04my>= zlBLZd%JCPJ@|1105>W2wB+WVq*OLwQb}I~G-&+PH<04c8GBS5kuZ8dU@ERyZy_f=P`%f;#d(3?jbS|3;F_VJb zY(v@Vkwy3%Fr#_&z?%0H2*;y18BfTGuHWfNL61CuPtNlQ4r{f0dMe)DE(}?izoP&+ zlDA>2>4_D5_0)k=Y}+fl9dWZ92WxqJr;BjG63ycKIe0TkesnLP2&!)TcFB`<`^g1n zzheH`NrC#eFgZ}(*|`Dw<*SOafpKR?j8a|*&kb*mxY3<3j6YJU(@L5Lm_lux!xg<8 z(%Yds5g3V0B7_ZxoR$a(K)j!HEbT3-cJwB`E7)dts@_Xi1u)T2g1nQ$FKnw`O6jIw*OCI z0sxf+P{{xq)^pG|Pv93>6(||1B%l0CKvScvw9UBnBI0}ZN%^C0sDZw68j;BjAK3T` z+~NV%z?k|NW3c(GQ>D6Cb(kr|j!bV#Pul{MX`h4OFuGpuC9(&(C@yEo7@+OqxC+WU zXS;%d_L``wKl9_|b+y^<(#tC=LwkA~Jaxt+3P_h$(@IinNhA5QEpy!UN3eihksRas zxI;F3S%dv7Io=_+{A^H4G03~iEeQ9q8q0s^@S4S!q8X6v%^D|CPbL+J2poWlIM<_& z?ICi!x+~0Pf_J~}a(Qa+eM~R!7qg@2ym9)Ss_J*VzPh*g@&fLd zun1XL3Z)y2WO!YbG$8%iiI95&1lf(JMXny>0%7aV*`gPwro_oyTDp2uR{~vr>8-K9 za0)-t;}(bq15_&5Pq5^m>+;p}mK?1h8w`l>0!R_tzGYfiP{~Y)Hn215>YGEPK+;k5 zQ@5z0@cR2*?RD{#PizC97{8TqTWPaW5o^@rf*aQlk&_ND1`F6IbHWV7C*B7qfeQ@< z=WXOEc-gI*12-?X#dF)C;W;v(=`viq(rW`e8c=dK{nJ*XSKhVm2KMn^gb zaGt_wHAU1Q-wD#8F!8;=`SKQu=#)qJa`coY< z^>3rs@A7(y7Fq-8cwc%v8QY+*VUceTHxCrH_qz&e(<$69?VdD;JfO83IW1c~A(Zm9 zOVjh`O)r*N65xua6mhLtqd_z!*t$cw;;>F$XtI~?5;X3`0f|_0;Nfblw7di=g^G-F zd^Wp|YTHKKI4g;!?MeY+CETi?k09EMSiGGc9eq@`_v40>aB6115f=6S4_WURWLXzA z372hm*|u%l=&CN;wr$(CZQHiGY#UwQecqXgnTYTIy>TM;+57C3D|2Pe3F1(@uQ=`s zD&1ESPts4Jk^F-+%PGI%1XvV@lMEUQCqL?V=XV@;+B3&ww6r zeS%lXAkUvynvNoki$jK4E?)A#M(|r}_U7m?-Zp6w zr*B`;GY41yNX{Q3T=%&ZdFlV^H+f?nJ1;9XT-at)U#XL$z}&dX`|U#XA( z?^F(H27rdSgCGXPOj?R!T#Gtv{t0keF?@-v<wbT~N{ z+M3J@*t!OSQM+5jGBcp)NiMwO-Sa9F=xUdrzLfC>cBU6m`yW>B|M@Tw3O)Qz1uJQO zf+QnQRIgf%v4Qpgf4)ufCSnXT&@W~Rl9Oa=nsp_fOIGCRM4-o!Q6aS1VH9I=P-kP` z1@kVvI}_sJD2zzYxhGN{50c8|v&>rHAw}={N`=@nYsZDN!OL$geYy&U z>YEf}Ro?%hTB3#iOH}%gmgCQt8^F>mF1K9Kj#Co=x*~5yX+jPt{pXX!@WdXLVUh9+ z?zotijHdu$<8N|z-W?JXHXB496dShLlYK;rOwd)J6Tjhur*KjWG__*=E+4yVGz_9A z?gm0bn-;bA^}ZL6Q%D>&&ec}Xo;nr9F8xDSlBYMg4XdY#bNyX)x3Wm8%b(weekVgb z066qFY0831Lo?(fBX2+K62hrEIpnQ&{CZY{P+o?Xf$w>dd-<4?gVGV_ACN_s)@ueV z;QKuNKQfvB^hp3u!f9jWME~&p$r|^7bx}HYJZO^ittfX;H^0Z;R^@)L1FAA>Cn$|E zdekB^4rSv;{)6`&u^ZFIL+p6lg#k>dn;vH8(*#Qz%X6t#9 zOexD8wW+*q+%>J!=KfnFew+YK!byOp*d|uaE>I9n?Ddgmw^5D+Pyfwwvy_B*lXbYz z+&S!y5Y0p!E9PkpbIa!T;N;|pK6c&Dpwu6G+Jt)w*7PQ0h*1pEiKi*7kQ)PIgxCd^ zhN3bJDuWZBMojg$a1^9053Z8j+F4BUx^VSCr2yQJofzWWMeAN{VMRmQL1jd%H~0=K zt>tBO24f+8NigXH;Ucb+BFK@Jw5lSU25FJ@s1RqIJ~F*N7a~co^uc~w5?kvZvP=r7 ztViPDr~-I;`t1;d5`OL&qas6YJswUzNYfs={ES=p`Y)v zWBj{X+9Ew^4_j3fIAgGGn54ZU9qCm$GiiRs6+Y7X|E}> z#V6mZsoSfmUV}f~W+=AW7mV??YcZyQY3K2CLWOW+HhKnBTk=8p!NFT}!sn%T;qm^P z143ghG#17aF8sxXpsw;Fmi^V9%jF$gX0*5c{BXHB(dc+7mw4H|gKpC7mZ7*HiEsh? zCXb?AyRnFPzTsw!v{Z?JZWZX`2^Hgk*j+Ja@@gi%bMBYhMgv;8wWJw({K7`Jbs>3$FS$|tY;DEg} z%x!0c;I)+A#gEZ!iuD~77iG&7fdpy@*boYMANp6oKu83XDN%GFRPStH{8$89A6~Wd z5=Lonh>%Y-i2EQ$V<UM|s?-6%b4VvNy5LEC+ubZ-+Xx+oR)o*G95?Y_Xj+ z^W@IXLyp6S@^i|1-jR4xP&GDRjE}#rwC%~cVbAa3O+AXU;$>yN6wA#Qp`g=!{v4a} zy$YzFAO1ezE=41&+5cIa34p){aM=Duwpn9}x{8-sR*?@l6js6+Q16&9(i=0v0Au&| z3byrB6?JiPQ*T9d2l1AHQ<>WuQP4a^M@|=1L`nTtoMPo@&Cps6E&X-z(c`bP^4u!& zhx)H5YxQ3o=>_4!f$q9mq~^-7)0{V*A==Jr{0?!1A}dHu8}BzC=$>)#SG3;3;-9w5886u9yqD+QN#NUg$eO@B#* zwOdVd=RoGP!*rjS*mTap8?!@&Fn)<}Fli*Op5{YYnaxdx=!oI)y=WOZf1g+7riDvb z)h*1VbgJGm5y||++o(;^UDk@eoHGg39sI-f@6x#FuU|@^JgF)1AYh`;CRRovNDv?k zex$ikUP^|GBQ{zd*ESRjZY`C;2Q2}IIG-2x>nXA2tGdnJ_Hxv0ETam`9_Ng$NH?dR zP|he;Ehd%6cmpqqR^m4gQkw9Cmyv06&1O zUkjGD?~f5{p7DC^smMMAl1FV_-D-i))IzB1SRz~8KD*EUpj78SNtA6ayb^Ka9TL?| zZEX%~m9@55T^~gS73U^SZ!XgZa&1!8UbE*`#>a5G)Z0*ke^)_K9gC|`&3J=OSq0t} zm(x+!5VjRM`A}7hGM^>+ALQZ~mX(6j75yN(pMt02T<0d7% zvqTSL2=od$&67D^)o?E@SYQ9MCs4V~#wLJ{`Yi4;M2=i$l zes%JDwTm0PnA`X;5|H4`TDF|~_`2t0rmB1tB!+rdnMHd>0_FG)rT`VBQJQk3V8QgX zx~yxVyV{Mmb0gtVxQk>pgPgC=F#07=IGK{Uo`={x4wTb zpxlm`8$cK&1mLvK+`xU0!`?I|>wH(E`FX?b=v(@6Szh<`5WxpOxsLuImF8s*VbQ1n zf05u13}KR2Vch_)k_QhN5spwX1L5YYCqaQV_OXYe@R*>#oYg)pNxhecjms-GG=>{L zd2H%9aQhx**Bm#Fs+=`ZpaQpfsY%Z)?uj0&=Tbx?3%T&aY1X?JTjpF1knY*=BTNVY zTTk%$Zvrxu*6-08mMUd1a^WUjA-lQsw+ZHcPLA`cGD$(E^ZfMOzPOF_O#NBe z{9%{ZO!3pd7B;?0X#ea8c5=v>Z8__J+-y5m$hMTB2VBuG{zY?oe=oIu1J%bQd%p#? zba z=UOM|cW4>uODfO(r?xdyEy+g)UsW|w5s9J;_sKQ$R&44WEq?_Ioh8QvBML@Em1#fc zV44Yk>YX(82sxKIC7zN-xNXRMCG%SoaQyq+(FY{6{?uJeLS&VTC#{s!0Nki9YXqLS zIKVZ}Yq_0`uLAJcPmG5XI-f3oD((k0lovQI9`WkrxYE8docTTEWw_GDs*9s>!0Fz< zX^Gfqv~K<-Zh<}nA*m|w2A=u=)xn+`o`VJeCTn=cUeas^9eawGo4*Q|-=@0{5{I*K zY3RYh|Aag(>x3)@CJ!g7g8$h$m^?rs+>@MBoSU~lY($p@GW&;yb^;>mp8YwsI?Qcw zGJL#6uE9dB;A)U;%$j@EPdR1I?16EJfg!>NS~Afv4Fd#A06WnGfc>l1$1=j|DUMybhPE>ZG2PdSQ9jBe2nwFDQh?$cGmLfkfy(lI7OQ8@i&U8n`L7Hy(CZ}F&l%)frkJP zsW9VUhd7`~sE#fF5FLq6qNlV2Jr_#P_yB$L za6XLgz_rm|6J+Rex-myL6-;LsbT8uU%xWN(a@=cvDULxH$!n~sB)oiK|MH$l2Nr=oHRekXt=s&ERK9AEp;h zHPe-nK0Z-CaUn1pq+8CKyI~gWm^4E=jVB&$u!1{gW^)gov6zSJg=cf}ye)hhD1#J3 zPwip(G~jYP+$?`;)QeCwgkp>dBm@D<1>YYZR7qf!1A*2gIh{Yo2h@m)R>iwr7Ce5B zsH!i-Te0#G8QBi<6y}g+4{+vlqV%6}54Ow7rX@hJL5wEk$l|5Nk# z{Fl)b>?JVbx;BguIE!+wGY=M(#|o7*dpgqE{#f&}icBHK%fkdMB<{?z7cGuDG>jqi zOpG@5oLNTha%P0wuA=3doWI^(#4VK>uIR`S?MRLbZR2|omB=Eu>{RaM9NeuFZh`jL zpl=Dj;`&;6aoSm%`&8@c-rcoVslkaW=wrKI+D>14qJT>t*f_%1DTpr-z-|n*rxJt1 zOZIF;MZTqnyH)=a1BY`uZ!ti5A9$Vri>iJ)ZmxR7^rDkQQ%GudTw5Qp4Tq8s(T|7A z;iE0gD9Z;}Zq?0oAK2f>J=<%{Il*G^x2X!>Py`Mdd9Mu^5a%VtTXzVsd$x<(AG;++ zG@sYHyTf>Mj54b(c}D1l!1(oa|5r-6onhJtk}V+`#4p#Y%Q@$(66xEqj=%^Fr&tpj|k0JraUAJlig*W24)r}MQbVmM%QJ-gP z{YsRZJj;&nZG~zc#Q=s>tps%T8q<<|63oeUsn(hBFxZ8*TZe>%`2Lm#?hO6JtDodK%F0BvkQuX5O_XWjsn$FhwE^2MuC%1H~Ej}frqvI;8I`hg8 zQp^0T{ojbRLNGT<{_#g-@KI+qNRZKO8!vE&WNFDzJouP@=xjX7Kz(Z8fgHingF6N$xx@fdGYS-u0tTihmNxA=d1a*2CX{`LL~GG>8W(fD~z-Q0)+TaHfIKAVAx z1phpQV%I25m zFVZ+z#{4sdr0WaoB<|5}Nlntr31h3yijtGQ9Xt=%GksBe&kpX+XH?(T$c^9mzc}Y8 z=gq#Ws7Kxi_23szy*qE)&Pc2jmuzr-5MQSJgGC09h~vjhh*l zVvtdrV_p2Z{3T~<5}VT`rDd zeIWey@msEwH$$8Ctn!;}BXa&HqlJ$UHdvtUOVd?bZmrF&5dR)R_*)>IFfV`8uw@7vf!g|S z?`0PPAVV}ag5CIPEx|zLGvs=HBES0ncp~5(Tv8*ZwO zwtvEx8*3xNaD$}=GpIc=bDF%=P^bN`f^5jV_{RT25|u0qp2inm@TnU4)I0_Xi|tdF ziyQobx?k~kVe3R~EFz67xXo8mkP?zTnVk4@;7&-eLw!N~D!z}Z&WBe=EB1+U3HrCV z6++_($##wk(e5m7Ah#RQ!dT z6kg~9FC#=lRDf}v(ROrHYe2;X;)*=sR+<`;swW3^U96_XC4_OmZLCRZz_ zW^6daT)N5#&RNw|XO4=e2FkKyzZzQT`-VYMYx)?1@);JF$UoRC#ppvK!F0EfxI>?r znp{i*l3jRw1Z&_y+h9*7*=z`|UT*rXLWl5%fG4j!xoU+yb01&oV949^mc_6 z7Nz$u4=&^^EmKAkxflfcGbTkkaQ;ro(AW8im;URhxNLAM*Xp%Mb}sUeo$(e(dA#^@ zY5g7EFoUs-~lAP6(R1NGS39 z_e7L5sf=>U^Xnfjg!ugK_$a(906bWo$?Y;4R&$N%%G%PtLREEAz|Ux7;U7ExcBwz% zn3>H*x$I()=ChY0R}5NUC6190zHp{nD`^^KcnGK&_~RHRQY$Ny(SbKNO-G#c;+?r4zYwDaqa~@*rz$X3T4ZcSkBa&+q<@hNinc~UH7XqZ$G&txOEH=*7m)}Uq z!dC>iLbOx{#{1`ui{g<{fqZ2kZ<Bxk6ce>B7{@m=-7@a8{vc-7lhp%oUNrTdD-C2p3B^0`=i&(cP50+XD%} zSXo?dGTVqH$d)brVwob1Z-pdkF)@MOWG|W5q5+CP{Xh**0=rY8S~h4`9aXocQ@)R_ zxm2G26MDC+GGC_FsuF@B;2uzy3WBZ&!zjEb9XnUzXd<+3p`lhxyX%QU)ODNxb#Om& zSS|8=D}?}!1NK*}9Y3NM21e*lXNX4h(?;!0zH)d+b@2V9{Gr?fuf6=jG{lVmhh1tS zLsE?B7YwU(g(h${HMe2}ALD>NvsEIQEyOYWd$7&~D-3~C26fLkjKxJF+s4YH-p}X3 z+U1pr;tL`F*d80{p*>P^Sr?At-dPs{?{-BU%(DA=jXKWKu z9dRHku=JX8X2R)ULbf~h>DT7roi zKC$9W>Y|&5FI)0H5|v&)2+k=79isWgLguLTWDb-pq*;RGA1J3Eh3&#JiqJW^ zR3Dk=np%~i>Au#snx;H{hSUq!F?oJ2Xk3jyuOeto8p0quufF9<@*F%DLVq+-? zdWtQO`1O(1q5xUV1;)0ah$pulMcu)cDk!!*(~Fz4*n)-|B&3i8hCcK{|C1Y3 zG^@1@q7BX&4rS@6q#mHN^gZ_$KSJ=y0yAKSInvc8r4=pD)3$ry8UF}0d{dxsneH_m zdg>HoGi|b`^gh>fpNQT_T0()a{~|LjRL$zvTk1?)#prRE|4Vu4%{}gmIP~X!s3r|k zEt}p)JTjss1(Q8)3B)E8!zOCUez}3?ebj*IX}VgluB2_}J5%-S<-BI=XMS{Prz*JU z)2%~0?Z%#>1qM+sdvT0Mr0)zsKgg%0VBBppjT*%1S}lQ}pV*1^w7 zv`%ZEn-sXZ74cX+Rm5F%@|(~!QElUvMMDb`todGeuTi^|0o`e1apuLurY|;P!s%ya zxdo$TGxgbvjT(!5v6?Iv`%e){4Y5x&)l3njUJIV;^Z|dV)`~R^eW)m4O3xuf};7fS_H6=_7awz%Jhq-5|y!0CFU*I?fYDRc?;H(p}^b{g)||@ zcdoE0^SV0ps75+0Bu5cmEU4rXTJUiV?B}JQ_I6{Wk#HWYc*qjzn1wcl@8B-_=;!em zDFu~xU1f*P6D2Y9>*a)nj8MrN%Ja5j&|*ZOL}Ma*&E?P$ZOC4B4j^e|P$N+5mdJG*kK0;d%70qAo@W$;6I z{0~XEw>KT>B#(@$9>U;H=R-IueNfJ-um4#&^Q`?fT(Dfz?=?*g2WQypyjwh1aS+3y z%y$?GJ_-kO1EfDVPa-Vc-2pyhr5n;<^46>AS_$RzyhbF~B+U#qGuRho zfADZrtAud~ioO$-r^i$X;RW4gz_BvCL$uZ}OeqBXjxw3P`uuAEjuDvg2J@{=R{Km2 zOPtm*{1%VOH#IFC8dNu~dmD76{Z;KjLan^y*ig$#`WM#0HTVbxa3 zD-glaK2aZZzzz8W6w9&=Qp1g#Xb`6HZRL9d%()3`E#eri_ktMFt6Yci$ybg6juCgY zhu2q*=}q-U9rm)-3xWuGhf<_}@TbP%4|XJW8O_4BaUpa1+#_e@=grb0Sa{Fw-FGMF z&zyUk@$vHOL{vI5p7it|pa*A>GvZ-Sffi^-B-ih#IJ#p|@TYk5_Ii(rG zqy^SA;x~O2=n^q9M+@y7`nLwdS9+9?$0IYB4>(AL&Xq{&!4G;UknjdN7Qn}Bm9(mu z46)(&7A8(+FB5-PFLz%78sQUG?x<{dumFw$Hj)`tM%jrPU7DFWs>AV7McPRjof#zx zJyBWZ5iL^LNf|YYg5x2B>(ZX=ZmMWgy=+mcjR>Zw@zka9pioA#fkpE(`lgb44%bC_WxY32L5vj@42K2 zCTjna=n`i_#FeZoEe!R96^SKdq^+5n{flY_<>5A^yUV7PNX|3yWG0J)Wtya0CRK4Y zdDA8ibuAEKKKtZXQBW%ST=|N>t+*wWxGJ){_XMBQ>X_*S+gyAa@9qZ_;@C8vI;@gn z2h+Ncqf)Nk_?Da!S4tMMxCh1wBGH{tm$Bju5@hP>gSw>FjL}}T`b;G^-2$H{>e(+v zRs@68cCAc3lkXOU-X7slEl_*tWzI^AC-X*;0bYcybz1Bv99I5Wb0ed z?}m{CxfiuPOvIWW*Lfy2EMvMVh6!#Gvw5Rl>y}ruE`M@kh5Ng$brr^m0-eN`ao7kw z&ob_~1CRh;K<9DzCCOw^02my+eV7$oXC9kAau)nSX9ScRCgR80_T++oPoz`%LU zmfHx}N-L9uM((H8arW=`7mtB4#IbuXw|$^vXeA5s#;;K5UxWahFJfGSDjj4 z=-yhk_YZ1Nb?icp1HBF{0#4K?xf{Vt5Fp z!_8SqQlEEe#Ij`C5GovcyE(yGXO$P6VgN)slD&sL{C+|Yxy zR5j}8<|gb&bUDyC!SkNM+QHITl}hL8hGqYr9=5>4=ND#d=dH^FqW2+mEs&-HsB^eI z+l)5_@jrYdoKHa^as<5+)4Xt;&6hs;kUy&Oh(&Vk7Sh-WYdl#g40|})x5J6ZLnO)v zxki0~JBMnNiU5d$g$W2GWI1wpJ1~3M5aK5$^=HcL+tbG)Xq_H;!Nt~Fw|f-m$^yQk z?*IHYFnjT7ns*1Vr<yRQDQK?a^RlK#OdmxuJPeooT!11`k^M`- z0MB4$WMpAdlANPmprO=fM2Gnz;TrinnDw>SG&NEs+PqbYk%0viAS`8RDpBQrL9ze#(ly7!2A+0;qZyZg;rQ^?1e!af)5jt z`vH`=vQQe6G%{>ru;F$~s-&^B8WsPO<@Yb6qNO?22iTXUSUi9b>;mLZofHI>>kgYOOWX;?hS# zt}YaZ)Zc8n{MbcJ0&m_D$(DX53}yQRA>2v{y;0M^;6z#0v##M@TyHI+G<_JrLkXux z_C|sQ|CnSvy|&Aw<)><+lXw^cB|LW?EIQu!fzD(2>OG<~J+0!%>~QQmY9QB~u0*c3 z5P$E@k=FkAuKw?u%goNnOv$cQjn6E|&`BFA+QM9Xy!&X?|7@6qbiMJsM0%rbf&#fI zqGY!L-1}nU<^JPW#a_SBh2m?YU5TcNWA-9pT?%StWb>Hfdy`31-rvQ$(hG>&ySJTg zg2u@EQi$9DF1pi8(Xq-Plr|Mg$LH_O_+Q1Sx%JvAo>9&0F^gvvivhOIob;|E`p~-E ze;_Yg)odiAj(WmnS-4A!@D-p8V34w*?qTNE@AN!VMs!oVQtJP%2^>s-w)^@= zRRfd>|7RVf=0%VcawLG4S~R5 zM#w6_@2M&*GCVTKGTuE#;0mR5`>5Ajn|PVMb=yDvRQ} zxw%l+IcGh*G~OQc0+GNmt@Z&f;TGkI|Ds)c<}`R6Cq@{;DWf9`384-hkr4UL9T(+G zxnEFz4g0k(qx<~Or<55V8A)=Su+-584kr#c8(6=CtD zV1`aG_*;d#Wa0*f><3X#CD4GeDOL{+aXW@nRwD}Qc>en6(eN~DM;;72n4dT@S!SKY zV%m;^nGGaMDGh!2Bb8e8Gq66c zLFEDjNMAdqG_~BH5XyqK-vag4H(Xt2ec-8ovAGmACsbB3+inUxw+&;>YOPWoIAsG> zaF&dlasV}?VZvl*Px>B33@yZU6up{*5k5XDd;Da-Ae>L#Lw$Xu4VH4SjIk+4&?Y|R z%s}mVK_lDd14JJG{}&v=Krcl{xk@8#$T@=E_*$Uy3ifZlp9V+6#MW?Az=|HI6bZGU z%@uI#s>#J|0l0-r4Y+J@Qb?Nj8`GqG$=}6-QTUlpNTd*CF|@T*iN|3%Gn+p>A`#Bk zV_NxqwCcoWyGz|3EbMP_rw~N9?Si-M^&X~+8>uz$N%V7#R~o%y&{1=@`DWT+5Th~^ z$Jo1Ty4;8azI|GPRm?!wIUyT!q4YyNYtC-4wWk;mFF>(2!XLF1y}v^CG}owaE9Kp{ zmOd{fJDH`q+Jp2cYdbY`FzQ;AUe{$g+MY(nvpLM2JX>|GOmy6&7%ar0m6}Xh$fnp~{nGH?l2|1q;#Zl7>g6B^&2R&pE}WK`VOubrd0V5;Je|9?_hU<#cfTfLcTmmNE!EHWIm=%$=yrb}%8{!@%^Qw&+uund zTNRnJ@e3!jhLIir-ZU$SIf9eH`-Vn9d%t2Tz-tu2=wC!o!9E`0P*m#j=C@qD9=pA+ z%pvkar@FE({<%36C3u}`(fUk4^tYhHFIP}u0sYx2)Br8D^|iX}SR{GXi{PeA6%MS2FJT9J4`VKyi`AB)(#=linWS+|;MFttkhuKYR|FK41r;@9 zxtqDCkTf)N)Q`ZW^*HlJt*$FS*InUlW5Fmv5#1!>Tr9$aj8M&1cS^T&bXj*}*5O-N zcj6qqy2C;Sh^dp>w-!L}{m-+Kmq{VoE@xrUYJLr>ymnf#TzB!4(E1LU07a(5+|&ZJ z1awqX2%G9UNEyn< z+6q|+30X$i+WJac$XDt*KnyAHG`w@f5XkM?YWZzj+j5PKh!95=cup!uF)vZOG$}te z25h<%h%<8j+wPb(aPeA3sFks4Xyq{@Wvb~lg>ryXl@OA*@HEBO5mCEn&>YdeL`aLU z(T{(v2>DxPu1k)&Xg ztAzbL%xR8l#0SaXxV56jy+ObVDuG~XJ7unfiLzo;5zdRH*`$27m0z&8%slPy;WZdOt@HAxV`9eMM{t*Nk>Ey1!tY--{baXMzBd$4h5a~@0!K5 zkc0Qv{E?C57|4b%5X=^1A5%;z&q8?HdegHbsCZbxSW|K7SSsG@2G-58{V6xvsKk9Ab@ARLqH>h?E=HN@E(HmGlQK5=gsf_rvI3_yh_2nY7*H9{Qe%+&248>mOhC4&{n z(CDM|;6=EMyklOH!-Zeosl*^%%~JhhC0%hbe$zfVPRDTY7n60EDZX4Z>wMq??U@`{ z1zy7G6aMaucDzXTZonAkXb^!eK<>%Mo6ugq+M|xuO}yyoGsDY+sSyiN$@+U~{VjQS zXh{C43m!m4#SHxC5Gs269?8^0@c}*(b+8ijnriF9W-G!@75R3X3IpSbV;cHe8N!h1 zJ#`=@Uc9&)^B4c}`(@u#J~PL@93DwVeH|~jTqFoi!RcPGEnx`UFP2vg-qR8f;0<4i zuvP%uf9NPJH^m^WCOL7ctY}Y3IXNdSM<2m3Ime(>uVxQ8S1U3#l#aYO%Z6*gM`C^R%27v_x!CN-58e3t5Pi(KCC$LTfTRx#sq8y`F=-W zq>3@5{h@)Y9-@lz=QvOJJN#Y}q(X3m_%rBs8zQVm9)7M(P_KHd;YTj9MIv($9e?)R zb!{kK{jo8r>4gA4!<-G5p`rY-5uFyUP4wj7`8x7%R)ua+%xP5U5BGtBjbffKQFL#T zO@_X8{t^zd29K9+gA|gf*#%a-2B_c~_ANM-dLycN(YNoXq;%GF!2a_aH0j)!U|vv!K&sR zu51izSm^%Zv_fTdHJ7r8u4@4Qv^Fgx22yavZMs?w^OC_@UhWWYeKjD|N+e#R;N$BT z6llga270fn$7b^X_6{y~)WXCn3YKCBnOL}zX)!Z7`0(FtIlUg1#>m~VVZW%FY{=RtJ_K{N!F`-^}3WFVKlA zPEql{0Rjh`+PL)0)a*3PD&4FUt&EAi2i`}^d4RL&cujQ4i`tCiZsd(65ThsJ@8(s& ze_AxJ?Y7uaf2_aJIX27>h$Cx^FIzEe|FYYr(mR0AP~qx(&ex}Y2rVla%UxbHfW4q3ypJ)svH zXlw?3m=VSem`Dn8>j~Cmj_f%!XXRcgk>JjpQ@6q07WhaR247zRBP;H;ib-U;35#VQH)nBR;M zMDh_K-3oLD!dMM>*Z6T`;w+G^@aXSy+w;#E-Qb#ZWf zx<7J03OoeU*rei9<2-v<&@fJ%Zt-ej?1aXILjlJP3WPW{$ZSlP{eZC@J3A~SLqx2f zDl7q7EB}pWlkGHNC=T+m(@9ILES3sAVmJ;y9;l*r$0-YLMmcjWTRXb6@5srIotqn{ zbPe48$f!2WX3N?*Hco-!ol6Vzj#ZZFjglA12r+7hO7#X~RE4{2!t=W#T$pxqZ#Q0{ z9W;xH<+FXZ*EIdeo*S3oGOp_H{kVZekcHef``G4*%GC+9g|Bp`N-%#M`R!Z~GG*#1 zv>woJ7!VH9aZ3L!MRFpLSVu*{%xP!C7kjm963dK0InJP^eLij@CLid>KnYbFOTAmu z=r;$+m1&V;qxw%*ps-J$TeYrVOk<=Yq-T|4|24MA#tmehfwgR5V^8+ zPiRSPQo-&g!-kb@Ze6nknJQcJ8L}Xgq%%-|Bn>VKjv8~4_dYcT8EG7~^Al01#HWgM z#=Z8VMvJl&Nx7Emu_U8aFrSf^{UQYYm!btP^66rBd)8vI*^q2{m- z$$eLCM32_OZ{jIs7Y5&Q8v~78)tw>PIwjF8sts&LvEk^9Q}?hZkyGgy0x2rghOZi< z72wc}*!wr`{7x~8m^yg!(G0R9c}BZ!KoV!ra?Vsm<1pgqtJEdQ9b^jQmpwQ2F(meD zU|n#IMG%FWMhG!`aq=hFcoD3>53HMrc9v-`=JoX)TG(P9+Fl#Z&ZekuJiyg4J^DTQ zF=X#^vXR0gix6(DI7Q#XdHb^SVqW|g%@7%-SiztwFwcDEA{7JosZ5p&KyvnBx}WZspOzC#&hxP`2cmK(L5&%|cv0<#lRU4dhZ-Szl|cDg zTVkM+jQArxYA%y?S8SPNR75n`@&JPL0|5?IK@L zkDM?I4v_=YBcqhX3zCwmq=iLmwuwJ^;l|i?w-d5OwmN~)4j?v{v{?y=thQwVYM3pn z*(CTWdy)~oo;6*E4ENK_CD@$nZ4;aecID6Q1w1jq8hq-#=&R8* z9H9MpnU=cfG{60bqg&oBjNm2BQ5|^djJn}hg^h4x{1X&gP4R1|JG%1vB>gax4>K&p z5lLQMYqH$0X_hCE(#MMZq0zTSLvgsZc!%2e_OQ76-I2vj?P&{m68yP z3+Vy}ldrmg7nY65qGMsQm`gGsTihmlU(6@owX40yZ|?ow?k+7J#P9nnosj*ReakYT zY&riCK~Ju41$KSN&+bWw<_}_P3CJ=PEeVS8>>6W%!?tB#TW`QG`+%LqZYz-xq$0-> zuEd94jNC#IYWCw*q8=oK)@b zcs(-d6mjiW_22X0Z6qX@4tS2JKWRIgEAP@FurJsWvll-MZ$oI@Wb;82L1AN&KUuJL zdbrgLOJOGBzx3OF7QUc~!W2yk|Lgw#2PglRgfw*hAD5dQ1lH?l&ZRr&vvBOmU^<%% zxjuj+0SB)jgKKTueCM}oR(*Z%xF2{4Q8{H}o#p`t3Q5H4DE+@Z&Hq!f02(~*j}DJn zm_0-%r%h;I)W79-+bgGe;eqgwky2Wo0Pr$I!v;{WApN`1b7&G67q>RC2(UpqFxAS^ zKhMlJZ@d;U1#5P0#gZwOR(9wv{k-OpZl$U5Q>Q;?@z;bIYTq}MVs4mrVwYk(Q5lGV^0$cr6nZGt=qHGFvYc+!J?`1C!@WS&qkxpPIQe+5StUnH@{%b2Yc5Jur5Ym?ZxVhMezy zVnbY*QrSNe$>JoCtw?Z8s0Emmez;P*{GWiRhlagybJhmg`Wo=9192s_Wy%lDErgsn zaTKw|EgvVnKlFiSJ%Fj5_X_?-@C7Q<4KM;tb!OOT)f00AgSl)kO18>xm(E&!)}Y74 zO>M@cO?hWE&_CL8P=XB!mWP-w+MG0pD{PVfSb3ug70ON2u|bT&E7vhAt+>cQWl`pJ zGqBi)z59##qme|bitzH8vtcXJ&%^t$t@im|@ii+z-y~84$qNYg=M;(`Rh2YB#W(7G z*3<%x-Q)G8noQ!uEHyQ&ju2lVYSwEmqjCOdL`-T`e&;UC3S`iVX`Qwq`APu>F3Xly z!E%5HZb5H@nb$$g-gD-;vb~TIlPnPzLlKOd1y4dZKVfQ6FpND=QnEUiXBQO zU>?}WTv+YE0qxD&>Z7ivy4yPSK7%qc$5ebcqKT9@JU;JBSZKP${IA?^KVQ*3b!!xG zDkoMDPP;dhUdCLR9QM}veU}K==-zVnJV)r8MVVhvQQl;`sZF2k-S;6tQ%H(k!`oxw z3f*(sYsS#*EoX+@bJn#JOqYksnCTa5QuFfWX!O@p(`!)1dPvU$%qK{`1TQ>^`Q+6o zUX4RS_JB*Ho~%p9bl-eXkhFeReA1(ih}B$*GPsvv7(Sz_7nC zzElF{pxAvf-|uYso5QKFvshcw6u#il^cPa^a$oP@UNq|G=F^dTSJ`{H(wJD_T3A_P z<&hk9CkdcuL{bp|qPbY&2Dcq6c_hMQ=Dek3KiV91H%+kSPAWgf5$ie$+h~(#jvsu~ zF&$ItBSEk4P$(4oH3GIZ*fzX7-dGh`{?sdF9fAfJ^NXeIQHXSNu=@lDi7UcBhlmFHHF#;(hB=Ib#Fed$0T#+tL0mpy4csgY8h9`fVc&r>P8Xt{hPnVp7TV;Yqa7c85Rn_aoS)#r9yvB9y3=fhrU!BY zZts7qn#se2yxi$;(0?kJhK50<=SHUIh=zte5jQ_k6B#pQI6TN3192o zqGgk?plm;7cX!6c)iajfR*iRR$(LAN^e`wW3knK2?SDPSOI-R((HaH*Yf`J@Smz89 zhM-eB%BWG_eK_noQ3VMUw|FV%F0H2i+|JyMQ}qNN;W@aV9T5%Cbuz`GsuGRG1Hqv6 z;athR>t4#3$7vM2-sZow0g_M70m3b2;`@w3k??t0IGFSB%C*Y-7~{!v}) z2nn)yhS1spTo}L}KLs@IqKN@ltRyy4NpVqVOUy$W&WpRlR4VwO?oKw)3+nuS(t>O) zW-Vr=pAGxg9(EZJs>YD`&C~*fhC$Utto=!mgI8*yGLYRe^&7b~hXS z6-2Rph@K@QdM{BLzz_i5m6F<0lHKY=NRwE5`PG5qgh{6IqT8L;Qnf6u2hZSD_^}7i z3y`vu2E4!!Gmkb7aAH+OxfrfXharcUJTRo6FkQ+a(0+QNb(J|UZ{X=MYUU&FCcJ%a z|9IGb1Ic65>FdM;N>)l;_F&A&7uU2DMNEk#9|2Uybw$=C|XwJ4ich{g(|OYu>jcfOyj;&T@CbFRcq;ZjM!p8zDn+l{-f?8=Ut3Gt^* zU6sr1D7s~GbxnLxVS}Doe|@v#7&x!U8X@3b{f<5~ zUY1Q}@4gE?wq)qYKai4yGQefxk0_c4TCtX(SoJ?f@3L|X)PE;p722CN$u_@_Q#&Qi z2IC=RXnh5bY;Z)D(T-BeERO@#wP8I3`bIlX?uGpyaJv{=G4lTGtw4f&uo8T06!XaO zRgt&BWkv>ye0&$dsNc{;3h1Nl;HddinnSoaO{dM+ZD@a_wkB~blRaOr=xmNiClfR; z&Srbw9S{A!me3w;FW7FvN3yl7$nLc8|LFHBZ+=DQs++QK{)VL{!9jj_9Do#X|4GUN zCF<}(kR%SyG^qHfOS+HXL?iph&c!I&A=)M(Ex}tp)p~(nN~YNae|(5zvpPzAY{mJ9 zU8*R0}=vro3;+r)KN^?E77r=vUA)vUt=jkG1DUvu|p20y5f4-8|9SdRBr>$ zlwnmj%|wTK5E`!zHqxzz6+IJn=e;$r9bgO$c4W?Go@Hp!wlg~9y zt;%346lZXkP4EyxeO14Fk|fy>QF8f2^LU0U2I4*V%5~ttqKMJ>?R$ z|BHH%5sTloVTx{ZwNkJ;egD&qs4)_{_9*b(H(s==H5zpz;TYkow zB0Zw}6E~!S&Fl1l=m*_Chq*jJa|e(HJ0Zsc0flSd0BTG@f0#7IyrEK-qHE43FKEP%j<@xe)v!I-fFjhd15iZ;F<2LfzEE{C|%$37J~ z*TaZljBLDr>4PQe)nOUyg$Q+EEW5<9F!0)G>OMIfUkM<+$$0tyvRbmE7ihlv0cfV~ zt>t?ISLpQZ|EJddJ%t7hA6*RJtA%+l(on8!iIFN)M6($*&2{5)pNf3}=?DKZ_n&1k zp~v^;3|gVBoICvtsgb>_O#gj&5)Ndg7Gbu78%T;!?Xm}A6Sv>d#aK!pt*d7jMXZv0 zRKqSpZI@bcv{fFY_WBs#Me;?%J(%QfJ@`~C!>^TjLSkcAO^jAEo4(I8(@rK^eM=@g zrxl#QiL1F+%0lE{o^`>?+eD_bl&ff*!@=IvGFz;_^zAkGoNeW{39U_8mfti=*RWF7 ztdNZqNadj~Mkj-sn#;Qkn&)1RhJTE(xTXf4Dji@ix2B@HL+3|@ z%|)eN$Y|WMENqA8A`%^D|HVKlzvR(Ky=1)(Ww33qkAW@+F<40k4bH!;=p(D-!ieA= zX5YkUgLoc|BN?#jk!B%I~`XSvrOc~^=)nPN$rEV5@1PX+`*+dx+!GGXE)$u1cE@R&}* zmjVRe8+@{t$OF_BxOfvdVhp+Vi$X8>gEELuow`|-x+%J%i=?d1ZL!4FfY2fz5#{l; zY4k$MPHB|!Y~iljo;tmWf>;qRvekH`l8{pJjoy4`;E)WwF4d&7anE|41ja`9yOE_e zq<#FILxM=L;$8n+a7Z3dk{obOP1(+Dw!xN1^Wd?vA^U(+ z*FOSdFK|iUKU>B@qC>AU!Xxtq#nRn}W_~*)XR|ipMSnaEc z7TYFpHwz;^<%tCo)-YiWTaLzG;B($>quLXh5gC#mmylk=1Olnb=cWL&+yu@1%(8-{ zoQ&jT9kl%He3h(Zoc#2R?5YB*>Vlx@x?>fPpyuIeJcQEu1GUgMw$PxW=|&*NBlIX| z_`n1*)jFY5C$vO{2z9Anhg`k@wna}!gq*fPYCuC+zpuGn%HQNcT_SV>H0Bj#K$`KT z0pA1ES4rL`7-)mKuEVNlf(!FfmntX;Opq=0pcPJ=A+tAC%V{Bu`0P2)@*5UK>SDT{ z@$U8{C}~W^WLb71;Zt&TT`uoV(CbHe0ezF?nXF|CByS^qsg-xs5AI~{ti_T;kb`o8 ze49ufd8bvkwCfluIdKs3SOwe>Gs;WPGk0jGCUrn}MO3M#4zf@A5! zsZbLpre|iA6s2zFQYRLaFD#&HqH7>iC+4PXXx9=h#VM)PAg)9iA7-o9kVdr*2{gaTnI zb9RMraNf5nS`G$$9;iqpvRU$O#Q>C~N^|P>Yl>5q(4HB-AJb13G#dM|n+|rSTQvAb z?%&cQPs{|9@EQkK@#38%J_B^TjqD$bV@ZgyL2zx8nS-H!p0Q13+(n{IPd~Kh7De$OwaKma(kgCvlSi|N1QeO}%C0MjSUX57H(T8m7!X~g+ut7U zC{o0hUIsL&NLVXdNH>W5`{hB;`_ux(na|^3<1F_nqwh71wyIlz=;*&Fv)7`nQ7A$X#dbvQ(;e^5I zV1=~I+}BEr1bKeXio9`e83q`U7MU?!#G*QITB|9+EJ3-lN$Jb%&34&bB4XlJTYdH* zC8sHs#`Uk0f{Q}e5um2O7Up4$84`nnTBWL}>76h)G)KLnZaw2Cu=>@ zWG{F}_u)O2x6cu`#osIE*afo%S*atW+JJTr^SGJ6E{YJK&DRvy3SK~XAbD<&d$_%2 z;XeHP*OH?h`CF1pJ2%V6YiIS1vv2xr}J4?cAZ6#?pS2rjOl73gERgXDgq$PT< z)naU{aMb0`?jx#s?342Yl9Trnn37vaSn#b!-Fs)#OVu zNw0S{281g+YYyv)j}4=x_F8*lFRebMw7I;X(LJ9KI*ikV90&bOskuxG9c(pe(6zUV zMnOUp=SK%c23V?kt}TqL8&j_jYU4mnUtls}XUOS_7L}VPvLmJ4G)?rb)~y`+19`NW z{Zy>I6em~SypnbU?N$V#vls{JzS{>AlKiUt(=Bt9c!$oChCe#Av)`0{sp`VSqzf_V&FigGh@1&t$0a#JUak*HH0FWZBwUo z-s0jeLI;sOEI#QIXSB% zn&kd@|BBeJ56#w9h{w&WW?ZTA-$f$9W=I$$rLm5YLa&5k3eY8-mRd#(!ymKe>u^*P z%lQ~N6EzL_J6zL&N16A*_Y>$zl80yUvuiPuSaA4THe+T(&bYsO2I%VW#az_QQp?|$x$zWg#J-R|9{O!*16!U(s02J;thmEHeinAzCB=l`Cf zvt&VG7ExH8*-}KJRndPh0hwYfQz)`3=t<5f(b4E?Rl;Z1=t(qza&q7{sceB1%TAz? z(`=9kkm$74|GFq-QXOj<#{>$3V9qE~`PY2+Z{N1G{G{CU63{OH=cMV+jqSPnSd;H% znt&(I(9E3~crdY;vNfWVaPIfM3%9v7TrvK26;pb619>gOBWQIcJ)ImlmET^G;=7%6 zadPQzB>{(qfAZETeVd>`q>-q3x@kOec9eSleqDJps@p^^3lfmxV6KZN32glA9Aw1l zh~hr(x?78#9qk$WPCkO}W92bqLA;wPx+o+C?jmRzHh}$s0?!vPXH?k6FB%AJHq58R z$VXKL$OH-Zk76!#0gf^fm$qA$&{FoyUIQJBb$~j3n%vHBKXORisJ|fFv{qf3bSkwb8;i~Z662s~#^J82M zbr3DlR4r)P_4B&M*Shf%$dFD?(mbd*E-0T$drHWoP7Nty`b0 zqG}=9ounaWr)Fi2m(8>ZYR1%L|670!oD={uRU(ekG71(bm{Mf&!XuM#wT$H@TEd_u zD#FAK1;dk7Tz$<{-0Foo!uVBUJ5e?!0odvGK!c%*LRBZ?+Au?CWHmYE9V@j0huQB; zy)iP=A`qw+Jth|Ra<0_+!n6#El_9wbNN#p!MsCtR7-`%p&YJH@Dod<)%Qa}$=q{K2 zt=vJj@K9SA2yL_&6teT(YI)sg366R~=N;`JQ}ecC2e1LUf6v{mfxbbizW=YD;r}Zj z|7jZ3LJyKsa;8gSKU!ntTJ*9hneB}rKmv@J>)MDta(dzi;DVXHgaMOYK(GPFRBhm8 z<|rj05Z9n3(v@@FSlrFnVPg2Fd!Y%2-UXR{SVVnru+)PcniC zh?@I4aqT)(uje90SR^Q)_el{rRL6Iz98e(zTOT|H%Y-$^E6fZ)+3Xf4o#?lUlLj!fZ5CEPOr4pKbRJd(T;Zh|{r=7y~iGmfdeiP(#@N)xLs(9{bC*0lQ%cf3z!${lrX^F!D{$N|LTR$I=qqoa7~ zM`z;P75%;QpeJv~?4Ro&N<^bIPlcu)4v7vn1|1ndy?ICL(te8r?ftrZFULrRXG^Aw zkT%BLP(H9!cgYCgS1K$d+dLyd!k!pqgXQ;>E30DInhdSAKtXc-wBM1g62Jb;?Vavu zT6R&b6t9wbx@`6){Ur`c^5RYfC+)m>5`uM5Bh*-ZfQyyZ;FxZrL@8~?HL}JCq2;%9 z3hF2WJ?%(eDqX0t8+DL;qB?wKhKBvMDAQy|1CtYT`YMJ6&KT`873IJ=S0buD!+}~B z$>I5*q1Wm@hC2HL+~3bbEqA9eUsEhI4Tu%Vg8H*=*~DQ>jcD}+!g_eFWHw7{>2!Zn zR$`Dbhe{pkQQS1Tl}WMsQ|~x~c+AM=!P{cDxjs$Os+m8Y5!j5{=d86jJH5{B%FXQ* zYuXTZEh1Q)jsqatHCU#!qIA|TN7xTjSA<9w>U9*?e|FDyww_UY9AYExDPx36Q4){I z2p~(;U2Q!1sRuw3@9M&2h{z`_a1_YyaJwtF_iW30LeV+*mRZq!9sL$U+0;@ilV-)9 z9{p8ToLEi916+fWnWRz%uqMWyxB^S!N!kKO?JIFquR645Y7Djn`>KUK*-tTr=o7uF zdm@e=N~rnQSadmb#XW}mEYk0wq>pq z?Ny2EvOh(pxzE1kqOaheHy6Qrw;fp%2QVf(^ITGyXZoNe<2$g_I9?*d!5C=neSg54 zz1e$i9DV-j>fM$U%<(n= zqJSRZ!g8q544K(UUwwoHuY`AOJ!Wqn%(Y|EFQ1pki%V^`-=oH2+^TuUDhpMlB@4@N zfS;jT7A&6z#{J<9Z;84&Gg0pYQ%UbRb2sOlPtHXl@1@M1<K*@~S|+84DK8Gy}WHs|B${-@Z26DO} z=LcoCh+5ypHruIWi$!=z4ejQ4bRTI21UT2?b{d^W_r?SOpj;PwS$U3xO37?H9bqXA)ayV9P!T4zY`fV9PE9A&l*>8SENyX_9?l;%fz(8s~_J5Aoi81kNbTp(SP)Lxl)!EaL%>nVV+@m8A@7_ zCaykx>iG>J92qtSaWaV?cG8?@<6|nz)*Rd4Zdaxw-Ni6k-NyiaVmWrhvS4N)!iZ|t z&(unzx%zfxS3by3t-9YS@B1uGz9Qx!>4++fcN$ zj1{U&IV6FVsj(x~O2OH^fri)@aBN2NAOsIoP)h%N^CzOPecg~3I<_x+{4L9<6GW4?HCgXxYD|c4=?Un1 zWr%;mVs?K85BrVRr!;cMNq+a4?sQT&>Ft5NytF4S*Bbl@l424heq;~>yc1ZXHe-V- zj7BN4)lM=|13e;;oMI=NL=Qs%))GOer1VwByFQI&quZ`5cX~K?!#b87#(*zkT#w)T zmgHz}pee#X(qrCVc-M~%u;SrlWU=Af_vGF8#Qa+Hw&d+l_M%e{zI|GO!C=R04$>Y6 z8XdA=gx0PSichi4qi2ee1v4&>JG9`g74Zf0jX4P5i~x(tgpfP#Fg91an8gXn5JW=h za}+Ig2Tmp!$h_SAdbDcnmn|C-AQWZ_|EQ)KoOJN5;VMOXc~vFaMuLx5Cps7Wi)X*D zMSGIyOeOZ}nnPfGq4XupSfLoqIg<7|sNuI1iM9PW7F^_m;E3?`DiKx|At$nz3U+v; zK>f=$jrIg2+nB4}R)H&Jsv!)L@1rId$)oX**6070<*wiT zO&i>?G}Yl31H$EWATQ}j9}rpMXu2@mk;xYe>~yZBs6Z3}yxIhz=LEiq z9O+56zba zP5^t5)JvFV>N3O~wlGa>*dplpH^+?A_Bi=jGw?>u*Ak`N(f}1nw(@!>$n5}lIj9jo zy6HrD{uCEWRo(BDExwG?3!F7Y^NQ~>I~7~vOE-M+5=7epM;`4rrLynz{Qz z7U>jjUD6A!dPuR?EsNfJmxbFWDr-rD?OUR4X3GcY@F4^_-x}-So7yOk5^MK+BIKvi z)6?TKDJ-mV4@&*$;S0}Gf3)XFmX7otJEuRt+NP^mGhfVGy;JvPlN4}47N)_rRE4=EJRXofax zfzq5nhBg2v;=cc-2fm}n+ZJ}!&wCemDT#H%U?|qg*iv>au+e9`6sCN*FR@*DAu|#W zJy{(0XzxqF+i(j^_)wd2w*}Yekgf=5le0^k|3V65Ure6oHx6b7T~ufXhbzfx(}Q7hep#JcXCqq%L6L;1sC-1!49XQuy?Y1`jG}*5vJ^>)F}qR{J2~0`ra;T zhYj7j+a-r1o5S_DJ*o$Um25rd+&^I+>mf$S%gIktHeTIGak8+Gctv#}Wa;ovtS1i> z*AHmq_-I8O8t(eOH3Pah>@&)>sU295A> z2>d(V9QVoa3_6!cr;)$_DsT)Sw672jMZjA<{}>tnir@#pn{mpiPr1tJ*o-f1?%#Kv(zOh5yM$8~OCn{yd-*6W6g2mj--QiX17Y zzeu;P>g8f2K{-I@tesyS0Aa33XDgjVHE9JvL8cg7Cl0pnkiI zc{@W9_W+fnHt)Urz+x$BX1*Hpygl-A`ir3KA|jq7n`n1cC8K44QhH#geW(3z&Ens_%s9o_ zi5Nx(Dfz#C1F1irXXn1bGrX9~-eqi#c6K{msg^y&S-=0{167Cdwq8%Fr(F zli#r~W>#ACA>}1?ER?N1O%xFI;yEo<8}4f(uQl)!d_9dkOQ5Ev=Kxk-PN>ncD$#p9 z1eiNsZf09}x80T(fx;6iUsw!26^l=)q~vB5+wk@PtZl~N;w{-r_Z?V|%nyF>X|dvGx2lOHP&>5h zD<`)l2pX7gP8ShnqO-FT?wTxtseWE5#uBJ@P8j!KJBVL0II4;D-0crl^)_RDHG_mM zwZP+Q9O$9CJ$uU0b?c;kDQztT&?wN;Ovy=DRZ4F@yt{gAv_79^V#0Z|xX*OynZ?$U zQ#O1l+&>mbA9`(bd~W9#ct4LxU0b%B4j&1`nUK8~h9a$SRP}zS{Y}E82&7xhh^eR8F8~HiO>x zAilK%m7^AAc}3vJv*$_kPnv>A^dBzu;*(r%Qg!X~+CVnyNcfuh58lYNLNI7V81X2S zt!sY4t;4Ab{va6eRrPQSA;!k<14`b$5x11hbDL10u~Q&lR1BJ{%IX83Jm4^OR%}>@ zd|?kjvwKJ!(T5Tx5?jZZpa=bOLs4@YrPO(4xxPjWbO8+hXzoMlvg5qSfmlUF6*?CK zXwD;KQ-N3kHJInpr6Mj=`yx4!Lc7MgX@@MHy&^Q52glL@DCnQ+{*L3J>L*|g(S8bZ zN4GgxIVZypGne?fuM4!x1nB#Wc7DNht2%Z}n7`FM0r$cTS`+h^%h`fMZ2&CKfu;sq zv8(TA4ScWL{4yUCpYzhiv`gDXaD6|$ET@kDynr+WY-?PC{$=>-j&=sS29+hm1*uD$ z3s(izY`Uj>Wpz~OrT{_ppr1+8OUcqo;?WLJ9swi4%1_kFDg66?Y6`A?p(7LS5uz0# z{!Ax7PALoGJ`0l=r8t9YA>!Hyla}E4myE_G(A(aKWs(3T@_Ga*cL1_;O3%{@B#Fx> z3;;Lr0mUItIAu76!f*pGYTJ_rQ^SIa)@^HN+qtw;nTamNi8SReX$gM5FP9>QvG9SF znHQ%r#H!5tCl#B6mNouLZ%0}P?aQDLNgsO)GCTdEbHwWL;NiU8-9g+*-lEL9^t4c+ zhowKN=Wn({zmA5cz~|aV#f7ToM9@m+kp^lia(*c^JZHGHGuDR;W$Vm|vYIBBUd(iS z7#<25GLaw1W0cpVYn}*8k!XUa*q>9T*4<5kglFoJS7*bi?2M7!dRPhWqrFDzNJ$19f7=4Hd+$LAkBqXMM?qotIbl z5Ph_;K`X#@+!?e{>=cC(g_hU5Qm}iZWY3vh#}3Y!ChSb)8assz zA^0awVmw9SjbyAHos=z9{@Nf-oZL*j&W5#LlHW7A&n2%l+86Ty!w~);)0$(yr?_bRB^g z8tjJICVn;rb0!suuY37cjX!}97&jSf-*YeZpUn!QimZi9P|?w%&RJ8}P%lsa0gnQ$ z7KV)ERRZruOJegxay$Q>a^q zh+FmsO2^ZR8Zu)mE78N7sdj_!70U7`d6>$`lMvNf3SmoVyIQ$K{LlG>E58JP+ceSal$*0VGvPxfC- zvrGh$cte-_UV`$4jZyK-k8RM%j}s>Oka5QgjFxX`1;DPHJQ}lX3K^0=zogiVyV1cB zf%jC8c7oGs5*aAz{v0vEDyyqB+~$(p)H1PwTR)w3ro393H&L^Fk3$?@e>2ob5TNx^ z{iy~$*KUdYb&IGJn~KW6bjtLla?Ks1pJ-*l8e%`g2o{g`Ex7JD!Lm_ZnVl`%)J?ly zeZPRwed%fIK2r-2ZeqYQ^dVe#3$HP!0kQ`cpbylBhc`!ggjLlf2dYXMiPQ&H6$WVj z5s1}^$^DDdp;19{y3RQK4`9;f<&>OPDsoOGJO?-1p)jJ~E$Er>Eg zl@rd=;+5^r8)#9?YJeXj31Uu=ze%X^ES+7h{QD!$(I+Xma^x4Y2tgg0J&Q>c+f#Ea zg7q%q3nDnY!H;v@O8SnMZ0`wt=}%2-J}Qt1vijC)x%FPZ_GFHQw*3@-1S35wLv|9fkj+DiO`D=lp8mg3ybo1WrTv;j$TLV}$%GOn*lOft$qRLYyC{f~qq4m7Y1D2UD-&>SM$6RrbgW zPSe+XmyoctLX<-hlrg||qIXeKb0q1WqVQq^(HT#}SI5-{Lslvqf*UWTx?yhr35?i% z#pn1C0#X5lRQ!b!IVlNJc{q6quDoSk21o@SWLNIQMqcu`Ni7@eU7id$405d;4_SCX zb5G|Ms3)$qTFyp{MSdTQ;ddt%6_xAAhh_n0i_uBhi;rt1BX3r0yV-B`RR)(ep2^SN z7ISXI_@)c2df2Kl`7a+3qE$P=z&0Fs`0SeX5AzQytN4#^r%FWTLt;+A4Wxk$ZsiIh z6adUzbB#IXgJ072fQy47@5s2`;8pH|WP|l7uM&-@p(6F0%qpwss4{#K7J^6#sbEoZ z@cJ{t#qR}+!9bG9@sI0)hOp8EVTEC)FXB)9q3(9%!=N++S?rh@+R!rNI#;aO^|`%fRnG zc8W{n_;L*`u`bsg87Q4Y({ACY%}H`cFLrD0gQZVuAi&f5Rwh#Cz6s$PQ*z-jL-)Xf z7{pR9B?aql%{%wSa&nem9+t0Fo<@-CHU=qVZcL+D4tv1ZsH>K(cBhOC*S^5#C*E4I zesgf(6&Vh>dw|nb>=TV*U{0bPH++a9V1}s)|6^E-f8_Tpu#r~hBZx_n-+l!e-~g08 z`nRW9gjKcvGus;%A3I8bq@cl0x7q@8z1mLnk`S{0wITC25tVw(<+lFkq9CN8gF;2< z4tcl5n4OE!9ce+Xm%gs-veWQ2EhYFu7uLSa-7y#X58DY6vDyKWY9)57;t8=LTDqSf zZ_sCufb=qjNOB3H88fgw8e?UP(1jSAFsXh3K3Y&H%UvzOTR2u8-(<6lh!E@|c5OpM zF}KsP#^9=UAq=~u>bgNlN(oaO$`J{%ybo=%-<7ST!n$N_CUq)e)OP{MzQl1p<;;i(bWW6RXRK=I%(4W!`f^+cGcM2ddY9=Ftk`J zP)=$V>{mnA_+C2tbXkXH8ay^%hD22|aR?P=WWX-%2Z=91pij`~7F47K#92109Z58) z!r4I4oN}NVTe7{p6cZlBGbJr4e|5F>w~DVnGQ8&&`Azqz?#73ay}~F>Im`RnvMKmi z!1)ww*6oV<6Zy>8|KT7V=4pboDz@~a6|2TFxOfR^p4|v*>SsS|l(YRaG~2Dd8IKo{ zcP@kU9#nhvx_dP5usWmqtAqd6AK5$M1^uVwKyXQ%1vT7{E(bko>)$qn)7R04FQ)#u z&pr>^c6g>cJ-#O={=I@FOAlUQ-z--~qD}fAGW*b;F;kX8K&M2%Xb@BK5I$pZJ zSW(sxg=6d3*uIt8;T*BOBm2pF>+K=u8QRbY3wD%)d4OJOP5j+R(DO= z%LH?1)BWg2y1YfP)$+5XF`XBUBe0`J@aE=!1kF>*>m_3E~@cr5hw|==RVV zJXwpPlk}9Dz+KABW$qZu#|wJur)O$Ee<_$y>ezK{1|B>Gtv?mTI*cOqwuUAPbZx4o zi1~wk*W|g?QtL$+JD?ol+^wWiy!gZuYt-E~_%6pvF?4Pted2{v|(&y9GBY?o@F4Fp4ue{D;>)=mNB_$!M;C?&F2 zdz?^|J9$6r57;3(+EbP7&@Cb8!%96m3cJ0Mbs z6=w)D{a_jnEP=LQ!*tq*WOn7{qvFRXX$r*7FH_J=Bh?!QVCjL*mL8KiPn5rZCz57( zrNAI(Q32ht6!#kfi^q#`%VpI2`V~be&^_0qED!84uq?#+ ztu`qLM37I2DwH<{_%1rGTrm;{JX;FwEHiRTg)m@W+A$dQgKF_R(LBzN$YF{I=M`^P zzel=77Knlm-StlM5#Gp6YPIvwwii^#sm-6l#XHV~YLbF4S|{JU+=CIaq;s|ADUU|4 zPj3AAx;0R)^>u7Qep@H<(EB^Lc*lhp7tIFaD)Ff!flnC5?mj6GGm$yE`6ey zB0nRZ$dPUzz6*iE@)6$G{qS$1*q2MODF&t7Lg`#MB8=!a7gN3xg-$`K>co_S-E?#- zu8|nTM(0Z#Y#XLuZHfgXlk?i>7`*`$$45DxP+JL-E(Zm!y_lh za#ZOri}6>K?h#W@XGnpnz&?PEUDqQR`Ou6N<6{OcCObp>_ni|Br6F*q2VFk{SxI3Y z@JyVWscCDju9RY^Tv^Mloh_~)1X>~mN{3>YV5r8zogl6OGoW7IqRPa^!jVbWPZKtV zMO^`l{X9Yr7wJiD1*PyYLBdc~Y!te(ZAF0}I`OnIoFN+}Ly6hg8~m-560^Zg{$nzY zxTrWGCqF$c(?BN$2e_0oMJF#KnUy=&_bbq9^_La}0}djat=tGad2s=MH$9)Qhe+=K zxz`!>^ydbtb%ggX|KFy#{}PxP$EoG#rlbE$W8U+f=H@5+tWsa5Un|HFb)|QpkmI(F zs48x68JtloaU(CJJ1mBo;p=>OPidn?XSDwec)d{E<%Ng zT17S3sd(AU*bb~RzsCRg0AEQ~YNg}dU<@JYe}EAx%1%+>id5AC2N(vW_gftK4tgfr zxiE4w)!?C5RfP`2Ql~AWsqkS?Q)j+J$bv=Lp`L(YN<8g^bn6$kS-H79%d}gF;OvkD zKD%@6e4l&3m5-S32`0y(y$?8cVfT^XAl9o(Sx&Kp)vCfN29YV-N~7u34<(R;&#W}A zR_J?U)zcra4n%B}JB?u<&tnN2jnrW}L&iDjY@~XLdrh5D{lrG?;D{E1DyCIwEJHQp zSe9%-lc>f>jebZ?SYY3Ib2L0TY1SpD5FUxgCIF2ouPzR2<-n_BPm%sw=riH)yYAz= z-PrAdBN9Iz5%mEBLpIo3KC_S!30iZIb7X;y)M-@;qV?qV&mk}L@Ec+844z@K4`vsr zmQ{dfwm&cbqYU6_^;i7H4ap-%e0BgahzYn` zmsfE?_ZD61-j?qL%&ZZy$~4=CnRwXUo$9RNGW~eYC7%nmfb48D(5EOy-CVkJI=!(^ zrQI>1EK$1?3-1YrLjxqQ**;}u2@#{;QGuHxWjjwK3n=AA~QNABX<8 zL-K^hHj_Y1KFqF5PMdKHWD^FFXij%1N^zLz`MT;kw^D4mBJGx5QqfCE(@C$$E6mVJ(=SfZ%+8tCA5Xu$Ds|sH6P%n)J;h1y*mk9PK!W%i zpl&q&*aaG~rh87E&c~A|{`lvC`A*uhEP1qpY_qEaiYk25D|+lV*pVTmBZ!2IiN$wn zHM9Ni3=Rptu7iU{sp-0FtjaSB1$;$(f-w|)!X#Z;dhPsqs$g#1^&|gttGM9 z6Qe0azQl{xgFI4i1JgUK4)ewRZj`+N}{!EBN_G;?kW5Z&S{#(uWxt$ zADD4;u1f~}jDsis;)HWI&?jfZDr*?q6?5v~nikloJKyJTfhbQBVNw(QL)X(Nz0Zf; z8=rU8rQA^Hbb_)l+V@KT!J%00Zl0Al*+JJ71lb_vtrh)=>@$?&P_Z;nO}-rwYbp4x zdOnZA0{;hHp8n{*QeA&5oU41ri6(OOEUwlbJ=xl0EFlydlGD}2Z?^FM2n01=i)yaq zYkB93`+JQSUN)-ne*LMVt)!<_)v}LTCL#U0wDsMQogw7)m6zmzUd?JEJ9*cNY^f1L z=b;s&06L?e74TZOb^i-TK)ApE3Q3fDVZVPlx{1H~090{3b-sY_;Oi>a>hSM_+}dk& z_moDZR(m5~{_qV)AN2|U0)y1M9pp}J4xc85H}!|?jWC8a%+6O=HV&4|rA@8^FBq z6%-lQ80kY;5O6q5(^*s5sYoH?0^QN=~TX1UZp`}HPNj+e`k)hrbitA3fe^y@P?B;7pp-fxeXsiTkoJaO{zG*@vfnl1`%^1!P_>Q5P5`B;>sbd&`y;+-PSQ};#O(*=} z;6SBo+7r+i7{67i7qNc+D}uuq1>g4Flq|sR-6$(9+`wgJPp&ap2)_LkwH}eh(!EHwUeuA&8@@f3CYP$+v}Pa5yVD)dtLMsN zQno~E-crbNA6X@DLG%rd7}EE)l_2J?4J7iwgx5ashv9E!)x=t3TPgHZYw~VbwjVn? z!ZF_AaTwe#o%mdxDo~<84wrBcxRUP~rs2VKh(8a4dm;DpP;t+E#dUq-X?Eq|}lJ2(n@%LTQ@jicPJIs*mzetL@3n=m_D7 zjnWiJ4LqgspeTFw#$KbAdk{67!h@*m9e%x4j~2(P8>69K96zk5$BoC2yT|HlTv)QJ z+e$!EkEs%(rbjt~KuN!aAf)F_ng2__(m^PgV#PNs4s}f+1pf++Cm=Sc(6E=Iu4FIJ zJX3)(V;Inri>MRc7OP!0X!GTvX=~G%eBhvTKn9c2@$bXG|0EK^enx^nYi#&FK|ymp9B@E0 zgJaEiQU22B=nx4#M?xdZ{qc81cyc;cq(CKK;i>3O?G>ng^k@oC%#k!)6Aci5u_B#$ zIB(M*F;i-+tDf%=d&XtE02wym9WeU}WXAL1fORFXkHMnyVHZ$w&9HNfVK35QYzXo- zl7N1AGdp6zGIJdG4p37=Vovnmgq)7VPqJLRG7spX_) zg|>c~Ogy=@a5+|RGQ$=lw;pQBUbDT|Py|~{tsPAxZ>K@6w}Iy~JWOytD-GdJ^W=2f zSBEpdZS%97b>nE?J;02Y9J-&s%8&e7dOhpMia7mH08a_ z?2)O7!or@JU0%Lb1^=Q7*xiGF>&hl7AdkiF?dwbP@uFAj4_4QAuX1B@baG+M{jS@H zvlwq;Td`Q?K~+M6>^P61@H_~3KyU{ApY{H8?CiuUn5CWIf@sSUbAVDqb*A3gmIJO& zhW2&!?4+hQm;KlIg?((Qr$-m1L#>nL09V3SwA;&%?JCI;u2qdM+;=^Dermw~I_r$) z?VD09*ZvKaj)m%|{{zLMgY0UqZXSu1ttfFc2}4b+DTXe1V#?$W>5xBpxEADa8AjVlXl@;~8~ z&W9HK*zck3jCAKe$td znz6|dp4qzH8A%^o3Z@IPa`XwhUY>;-3uJtAV^l3~YFq1j+y9Jl`@G`7Kt<*HH|ZS` zxh3DX35*STBI@95-V*2xD_ytHBo3RERQJ2Nf{a(rk3PVwdOD+1Js-!_)kE+H9{o_1913{`f%ePtM#p(}v3`@238R z&q3dOE&9mw%Wr91W~K@(djgZ2>A;T;|41jCTg&6zaEAv1ip@!QmlYX)FuY^#o1U>S z^)kGH0#meBQ4)88h2x_)vdDmK*2;tH-tFM@<{^GR2BEBXT`QQeTmc3tJc2;>X1cjT zFMnJ%$>@IUV#PA!+6#_L&*UzgS@M}fe+Vzq9*<*B1WEO5b6sKAmir5J4QG>~Rdhct z_vBmB0sB@;_Io~TSYSeH63}ph)+hR-m?`mFig-aHp}2(j2fw$M=9oSDYGcA5$$W;p z#6nv`eX0p6Rj$xB>tdl@8E4G{^J%Gd&$Lo&IXydnJXl>leNJY*xX{K{x|>FwUSO3F zBQu;4;(%sbNOAP+Sj^)5@Zz7hftK}8z9{$ni6nQob(0QN$8i5S1n$u%dWZjPjp&j9-wh$!&tr-Du=WD)Xy^I;gao z8roFSR6Ok@A3Z6ajqVuYO#wpU$KVPHxTwS@3#X%s-WBTZKpzaOyXb`Ey_Za6FPvKJ zwB)`}fu7bW%Uz7}5q&TJ$fE{tzWh|jt;_XTQx;RzeVY$I+O_HJRqx&RIthyusT9s` zN52PerNlhpUHG}!A3VJjK0W5-I5!;i0f>9AyKq7U@raBpEgY$)Y%5E16%IbvgRzLE z2c8Z@x1=LC<2LO(I*I-nuIuA}ED0w`_HODClzhwZh|6&Uj^wV7_(B>9uEabH2am=W zQ}SseGD7NQrTtLDxPdt0Mj11gy&GtT(AYsO-wf&0gv@Q-ncm&}Nh_U~#^dh$rEIr5 zr>({3xsiw+@K?E`>xKVN;(ux6T;w`o8e4L^a81DWm+Dfz1d3nVo-sDE41aAeC7I?e z)xOX0`p8kOk!r?tpres=&&HD6erAmf4TI0;u<0^yICa>on6QEW1B_7=5388)RTI9s zmtGmLdXmwtV961qXQ&~Lj>+{0xxLq@?J1(@SU!W(hcwV+sWtW*x!$CBaLip=6(Dlu z9j2O$Cr_5Wr@_nOtloIP7Aj_Teo}RWGTeT>9`}>4|=0 zsR|So-69bloO=)wnv>;7nr)4ZiSMW$Nr)@j+<8O*e$Ua)xg^WwbPTGLWd%J-N?Ytv zstNq2wPv|aJm*c^&2zW%0zH4!bB6X<{~S0?^<=S9Zr(rI*R5N3XlijDzv56)qU+sJ z@#oPF_+I8-;PC#I%0GR2DY2ju{jHcaaGUVA-s^MQ)5{Oc?aK4nHw#_3o;}m8@?6y% zyU1+r#*OW7#GAXYAr#x&wlg*~lIRz5vF{j~EtE;BT77(XVOhp$<8nGV_8;zF zt_PQAaiLDERH7@D*h+V_MMOkI(_Z)mo{_(ulqY_rM1Q5kekEqA+&YHs00$4aCJK7) zSVHXj}wAH@0YT@#lBM4z%y|t zRglqY*luNLd;9nm*QP)2ro^NCtRjus#cu|ygB9)JYwt!+4Oc3%zdIyPp@Cl2FcpRy z1XMa=_mTpdpMsSSg8f|)2fJIo^K^W7wQPM1&+Oi-t$bb{Tpv6iUp^)F_t?6&V#DD6 zDxp{Sk)wnmIe}#cqQR>ro!ztV?b!bLCif!rhA z)0+zSP%>Gx7Z#3<;S4t?ug#@eiD$$A=^FgvAAi#ov$VD%{-a+e9-?IOS2o7SC@E4m zzyfHY`d@DrIfU}9h%*5(dvS2+Y1aJprB?cAPd;Ck$1j&h zah9a(+HrfN4zLEhG-1KTk{7x)7-4a1ya zxuK()68g2ID-z0fU_rHxbgy8xBxX)Hk$RBw54oB**BN#8;-tcDwwTf5v=umquw+a@ zl>iR(x^Vv$IEU~d?T4DY*NEBOS?5q=6X#Ix9x+=@#OXE{eJc(&%9OQy|T${ z_d@Yz5}F}@!AO~6YLkYd{1({y}@Sjd~$5RYl`})xBAz2cPD4h=0(3`4xbyz&4Rsy9gj!) zlqw-+@QLR++tzQ)9vMF&eaEqVYg7hq&T$!&p!qk8VL>HWfTB$3Ao1Lg|W2-M+h&adTew}rdz!L5Ha z7~MUMhF>S2&y)Mx>8dlYEt;K0qLw>O$6ci)ODn8O;*Im_lD3o__V5xsJSIM+3pn8LQDT{26%k#EJ70eRD4xpKTU&ZcJ;lc z$mLmJfh^b8uHrfK+2M=VY*%%6^3|&LI@-%s;^0orYAfD}KZC?7;se|m1-@j^!wowx zEKH2#_O?2eHgycwOBl$(P-U!6&55I1y!6&S`;v6bzQj=s=)A+&CKEfXI8%e|l^{@H z9*_%@PL^NMn6SpeZeXrFU`{x&fJ+i)v9M|}1-66RO*7B%#$DDU(;%DdAAi$1rFT*+ z#bRFz@GO!<->X%Wyck~U^@z4j1yeCVyMS%8o^N^MZ{=Iw;bVw?(a4YA6}b9O<>g1M z+VzgTtEW|`G-d}R; z6HKvSIYrw*!;Eapp8D|`8L(JX-INa5g>2&8J4`EqCPN2!B6kj~uYxl#34c7SN!r0cqa-0U5A@HUD|UeA;pvau`>YQK zPuB|1yjF5Egu)}+jTdDLq-3K4#j8#o1sdrJ8HdW54$+-b{Gy3h(A_mT$9uQdI5$cwe`h#2p z3vW-(2X$#j{`h<&@1;5!Y0mBI^x^7e_*(bQTeJK7yTwgynb2X`q;}kb*;8Qe?FN5y zw%qP^Jjxyz+h}2{0t%ACY?`)Pw(M1orH8d__2OE#xw_P?$Vw3xASfHu(6j2EkvhdP zvtRL)e{!M1u88V<4%y9FH7Lwj=tZQUU|n#kt^%znh|03>Qje==OOmPjmWCP7L6G5( z00+!5CR0yRSL*sVVjO{>jme(>L4vm+f2Iv8(VugBiK>x=`Kc5e3I4mlQz88FUqqb> zo?+Qi;FcxI+7cE`vZJIwXPDuEd4TYBtZUb%`p73EEK>utHa25gi2sjYQy)xz_aDFh zN*b;p$6)E&o~NdOY1};#%Wz^KK~hE}y+WQ7R@&8x^QX}j_-&d6Zos;4t)D&?{nCeV z@}aHXt<^|=zWI7;$celt5i@Kn+M;!{poi!yKN$*%Ew4nQBCx#Vw%~&r!1YjWT_Ay$ zgGp5I(BlUTCOv>XG3V%BQ{4nEbT#sym}vw2jL{6t1l$;v4D~E7DimEOvp#?-{=L4W zryHtc9BnLXD6?n4Y1OJIA}>xr)Pm~1E(6dLBuJ2-Do8iN=a7`!dhYSpRK!)OCkOYh z)f0DRPE=JcoeVy1-?aDpYi%`4%L=Y*cj>^VR4EyQkDRlQQF%MA$7d$^9P#(ch=ix{ z%C|8emPG-#VECCmbrCx+TD9I)=VG894$i&S@vu7|f<;o3js97Z z*^>m^F-ta0ttyf?Gs@Ze>)Qf}kw=%p`NxuC)zQeHx?0&FDX9p^m7@z1Tc*8jqPS&f zwyEZ(`Jlf?Gi`q~)FTo`94VM)GO#FIkRSoOORVDVUK3er+gh4~r)jr$ce;2k9XvM2 zxA*GJa&gdFSkqbp9j9Q9+cA~knIt+X`a)jN5)RZdMV)ea%pm8+jSDlR)6f}er64Qg z3?}f>);eeg)D%qr6ar!89~mU2fTAd1)=549f-DYhEw4QPNn2g?zs|bH5B}?m^5s0Y zYI9|jJTid3eMa9Mb)<+ycu>SY^NxruPYBa2vFXM&CE&-kYq*&HYm%dbn>~^n2o5=Y zq`K3gtvaJJW)l}kq_UAfayAepxrNL}!W-(9dP06T->I5O%!U}12n4&>`H`68N_8x- z)Lr2ZHY)XI{H77nuS66~E=)t5`2e~=NRLkZwy|N2hJNAPnh9EXsyi!OO=klLMaMVI zIO-LA#9X7wR2^Ho!}Iz)!VS_Xxh#SoHHgO@4#To-5sxKNnd!xtGqfKPjfw9ce@8>a zSo|G*`SAWV{MMnfPG3F-N*(|u!|wzG=`Y@19fse5ztfi=fVk2?qqs}K_u_->1~df1 zo1Phqg*OBPvv zh7Tu>x|mj^UZ{JR>)(l=4AEexx@v+UL{EsopA|+QSy56P;t{O-@Wt$aRdz!xNHecv{ZFG6m9KIwowmr*>9SJ4H_iC4Wx4&U#JIXTgb zC0uDmRwFl-D#UT>xOTS2cG zAvzV2*no75Mv07IwLAK*)zfvhZ`!HCU>lJ_YE0xA$FQ#zF-#BT}VPI7tBy-hT zXZ+jYPgWQi0xviH6z>;9J>EoNV{puP>JAPs;1jT?X_7Rb-|P$~eLNVRGZ_xZD`|+s zg7IEFuxXs>x7546kpA?ozmkud+ty596G)cegb&!HQYkLENx#`Wg^RBj^s)(AAtBi* z<`DNqz+}iSx)>Yz8I?+R`((+xHf}N1Hm}(H<}=3G8{cO;PR{J0$H2)CR(B zVv|EZ;*-5phF}CEo4+*>{t|5qh;oeJO^s%Ry2RTS3c;IL2cnt?o2=l_|FRCx2A7l7 z^8UP2yXyB(O~vtC{s$)*vHPKF867Tz{G zT;1QX1DLGtH%9vUw&mJz>fR91*@*KQarS`qc+KA8%pTBDglq#RR}(v&faO^e<;K>< z*fuRJ)FWL|L=EEFVopC_xB4yT;&gPWf6ngZ=ab8+qjcWgyX9+=(_N#F3)`8^8G)*V zmeFTUaJs%dle5VtI6QN@9qYpYZFY-*^!vm#fVT^HG_6Qtm65towW&^K4jLr+0Q)vm z90R5XMrG0I;_v_|3QJ~7WJ|(C5@nd}J`>P~N?!M=Gq@0Cc`n7FhL~3L8Fu^R%dN-Q4N&4S6 zaFE*H)Q*&5Ma5mhJC5((bb#lRxH3%Jl?L<%5d`=QAtgO&VY@Ert;1?2BPpO>2^y_8 z*Hp1#f>7?CZDPz2f@76U!iGr*0(m7OU8GIQ8ar5SBP^uO2L`WW@Je;IwnE4x<7pDs zn&#%Yp1$CsKsL4xh4a+&xR3zfflhBx54!lyS|t%F3hz?Q*zCW9Dn2&#Pvrmdg0`?N zy188}twVJY6IJ+B4mz3~sEe});Hrdm0fQ+)eROq+O$QTlRhD~8{tjXW>_YT45`&0+ zlcn9N8mnar6KW0GKk7e^*~)|Fy=)8rFIwdk{XBaRA|+w4Jo@#_6Z_HIpy{OuZW}yy zlMN%rf|+K%5g;13`BajBg>@Zr16`4gy0DH(+!wXcy05P0M*q>89GIOq$7^W!tL4e? z+)u{Htu@QDjZtFzEg`l-(#bb5@^g)RVb&~t7H6F}Lf76DRQU45*4;8F`*YRf)PRzf zzKv}yl_oK&_|&YH!YcN?VOt|q7uEwX!WOI`WwY;t|R zbdMjW>SzD$QhmEUsXd(~6RmQmw=IwH9!Jxw_}6kXs}>8@v~5?-t-G)US=W)58q^Lu zOdJL zsdopuWrNmssg7;SQ?sUdu$L29%DFv6r#8F^GpuR-lldLMBEK2@mMWFXUu=h`PT=Od#Pwby=qP~W}keW~+kTp5<^wY|r_c4=NtPLe+FV12uuASpUk39U23 zJjmv)TWtr=jvph7#b^z$&d}U)=&%X_AuIS_HrL?bb-8i|T=XueEB9 z(L8!sF8bZN?p;=!vernk>mggDbQml$C^?IDp4owovqTI^jd<5`YK$l=8a6laH`g1r z4aD�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�FQ`WqD1cFxgd5t9^AIM;h~Nx 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
*g!!G%Qz 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 eh=#t7bpRDSHXGESqx{G|q1ECu5 $real_content, + jobname => $jobname, + enabled => $enabled, + ensure => $ensure, + } +} diff --git a/manifests/modules/jenkins/contrib/examples/job-configuration/templates/build.xml.erb b/manifests/modules/jenkins/contrib/examples/job-configuration/templates/build.xml.erb new file mode 100755 index 0000000..26eab7b --- /dev/null +++ b/manifests/modules/jenkins/contrib/examples/job-configuration/templates/build.xml.erb @@ -0,0 +1,17 @@ + + + + + false + + + true + false + false + false + + false + + + + diff --git a/manifests/modules/jenkins/contrib/examples/plugin-configuration/git.pp b/manifests/modules/jenkins/contrib/examples/plugin-configuration/git.pp old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/contrib/examples/plugin-configuration/templates/git.config.xml.erb b/manifests/modules/jenkins/contrib/examples/plugin-configuration/templates/git.config.xml.erb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/features/deb_support.feature b/manifests/modules/jenkins/features/deb_support.feature old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/features/step_definitions/deb_support_steps.rb b/manifests/modules/jenkins/features/step_definitions/deb_support_steps.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/features/support/boxes/deb/Vagrantfile b/manifests/modules/jenkins/features/support/boxes/deb/Vagrantfile old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/features/support/boxes/deb/verify-plugin-install b/manifests/modules/jenkins/features/support/boxes/deb/verify-plugin-install old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/features/support/env.rb b/manifests/modules/jenkins/features/support/env.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/features/support/vagrant.rb b/manifests/modules/jenkins/features/support/vagrant.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/files/jenkins-slave b/manifests/modules/jenkins/files/jenkins-slave.Debian similarity index 97% rename from manifests/modules/jenkins/files/jenkins-slave rename to manifests/modules/jenkins/files/jenkins-slave.Debian index 44eb55f..a0b9985 100755 --- a/manifests/modules/jenkins/files/jenkins-slave +++ b/manifests/modules/jenkins/files/jenkins-slave.Debian @@ -82,7 +82,6 @@ do_start() # --user in daemon doesn't prepare environment variables like HOME, USER, LOGNAME or USERNAME, # so we let su do so for us now -echo " $SU -l $JENKINS_SLAVE_USER --shell=/bin/bash -c $DAEMON $DAEMON_ARGS -- $JAVA $JAVA_ARGS -jar $JENKINS_SLAVE_JAR $JENKINS_SLAVE_ARGS" $SU -l $JENKINS_SLAVE_USER --shell=/bin/bash -c "$DAEMON $DAEMON_ARGS -- $JAVA $JAVA_ARGS -jar $JENKINS_SLAVE_JAR $JENKINS_SLAVE_ARGS" || return 2 } diff --git a/manifests/modules/jenkins/templates/jenkins-slave.erb b/manifests/modules/jenkins/files/jenkins-slave.RedHat old mode 100644 new mode 100755 similarity index 50% rename from manifests/modules/jenkins/templates/jenkins-slave.erb rename to manifests/modules/jenkins/files/jenkins-slave.RedHat index fbdfa56..f8d2adc --- a/manifests/modules/jenkins/templates/jenkins-slave.erb +++ b/manifests/modules/jenkins/files/jenkins-slave.RedHat @@ -8,8 +8,15 @@ # RETVAL=0 -PID_FILE=/var/run/jenkins-slave.pid -LOCK_FILE=/var/lock/jenkins-slave +NAME=jenkins-slave +JENKINS_CONFIG=/etc/sysconfig/$NAME +LOCK_FILE=/var/lock/$NAME + +# Source function library. +. /etc/init.d/functions + +# Read config +[ -f "$JENKINS_CONFIG" ] && . "$JENKINS_CONFIG" if [ -x /sbin/runuser ] ; then RUNUSER=runuser @@ -19,16 +26,27 @@ fi slave_start() { echo Starting Jenkins Slave... - $RUNUSER - <%= @slave_user -%> -c 'java -jar <%= @slave_home -%>/<%= @client_jar -%> <%= @ui_user_flag -%> <%= @ui_pass_flag -%> -mode <%= @slave_mode -%> -name <%= @fqdn || @hostname -%> -executors <%= @executors -%> <%= @masterurl_flag -%> <%= @labels_flag -%> <%= @disable_ssl_verification_flag -%> <%= @fsroot_flag -%> &' - pgrep -f -u <%= @slave_user -%> <%= @client_jar -%> > $PID_FILE + + # the default location is /var/run/jenkins/jenkins.pid but the parent directory needs to be created + mkdir `dirname $PIDFILE` > /dev/null 2>&1 || true + chown $JENKINS_SLAVE_USER `dirname $PIDFILE` + + # create log directory + mkdir -p `dirname $JENKINS_SLAVE_LOG` > /dev/null 2>&1 || true + chown $JENKINS_SLAVE_USER -R `dirname $JENKINS_SLAVE_LOG` + + # --user in daemon doesn't prepare environment variables like HOME, USER, LOGNAME or USERNAME, + # so we let su do so for us now + $RUNUSER - $JENKINS_SLAVE_USER -c "$JAVA $JAVA_ARGS -jar $JENKINS_SLAVE_JAR $JENKINS_SLAVE_ARGS &>> $JENKINS_SLAVE_LOG &" + pgrep -f -u $JENKINS_SLAVE_USER $JENKINS_SLAVE_JAR > $PIDFILE RETVAL=$? [ $RETVAL -eq 0 ] && touch $LOCK_FILE } slave_stop() { echo Stopping Jenkins Slave... - pid=`cat $PID_FILE` + pid=`cat $PIDFILE` - killproc -p $PID_FILE + killproc -p $PIDFILE # Wait until the monitor exits while (checkpid $pid) @@ -50,7 +68,7 @@ slave_restart() { } slave_status() { echo Jenkins Slave status: - status -p $PID_FILE + status -p $PIDFILE RETVAL=$? } case "$1" in diff --git a/manifests/modules/jenkins/files/puppet_helper.groovy b/manifests/modules/jenkins/files/puppet_helper.groovy new file mode 100755 index 0000000..f170405 --- /dev/null +++ b/manifests/modules/jenkins/files/puppet_helper.groovy @@ -0,0 +1,271 @@ +// Copyright 2010 VMware, Inc. +// Copyright 2011 Fletcher Nichol +// Copyright 2013-2014 Chef Software, Inc. +// Copyright 2014 RetailMeNot, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import com.cloudbees.jenkins.plugins.sshcredentials.impl.* +import com.cloudbees.jenkins.plugins.sshcredentials.impl.*; +import com.cloudbees.plugins.credentials.* +import com.cloudbees.plugins.credentials.*; +import com.cloudbees.plugins.credentials.common.* +import com.cloudbees.plugins.credentials.domains.* +import com.cloudbees.plugins.credentials.domains.*; +import com.cloudbees.plugins.credentials.impl.* +import com.cloudbees.plugins.credentials.impl.*; +import hudson.plugins.sshslaves.*; +import jenkins.model.*; + +class InvalidAuthenticationStrategy extends Exception{} + +/////////////////////////////////////////////////////////////////////////////// +// Actions +/////////////////////////////////////////////////////////////////////////////// + +class Actions { + Actions(out) { this.out = out } + def out + + private credentials_for_username(String username) { + def username_matcher = CredentialsMatchers.withUsername(username) + def available_credentials = + CredentialsProvider.lookupCredentials( + StandardUsernameCredentials.class, + Jenkins.getInstance(), + hudson.security.ACL.SYSTEM, + new SchemeRequirement("ssh") + ) + + return CredentialsMatchers.firstOrNull( + available_credentials, + username_matcher + ) + } + + ///////////////////////// + // create or update user + ///////////////////////// + void create_or_update_user(String user_name, String email, String password="", String full_name="", String public_keys="") { + def user = hudson.model.User.get(user_name) + user.setFullName(full_name) + + def email_param = new hudson.tasks.Mailer.UserProperty(email) + user.addProperty(email_param) + + def pw_param = hudson.security.HudsonPrivateSecurityRealm.Details.fromPlainPassword(password) + user.addProperty(pw_param) + + if ( public_keys != "" ) { + def keys_param = new org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl(public_keys) + user.addProperty(keys_param) + } + + user.save() + } + + ///////////////////////// + // delete user + ///////////////////////// + void delete_user(String user_name) { + def user = hudson.model.User.get(user_name, false) + if (user != null) { + user.delete() + } + } + + ///////////////////////// + // current user + ///////////////////////// + void user_info(String user_name) { + def user = hudson.model.User.get(user_name, false) + + if(user == null) { + return null + } + + def user_id = user.getId() + def name = user.getFullName() + + def email_address = null + def emailProperty = user.getProperty(hudson.tasks.Mailer.UserProperty) + if(emailProperty != null) { + email_address = emailProperty.getAddress() + } + + def keys = null + def keysProperty = user.getProperty(org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl) + if(keysProperty != null) { + keys = keysProperty.authorizedKeys.split('\\s+') + } + + def token = null + def tokenProperty = user.getProperty(jenkins.security.ApiTokenProperty.class) + if (tokenProperty != null) { + token = tokenProperty.getApiToken() + } + + def builder = new groovy.json.JsonBuilder() + builder { + id user_id + full_name name + email email_address + api_token token + public_keys keys + } + + out.println(builder) + } + + ///////////////////////// + // create credentials + ///////////////////////// + void create_or_update_credentials(String username, String password, String description="", String private_key="") { + def global_domain = Domain.global() + def credentials_store = + Jenkins.instance.getExtensionList( + 'com.cloudbees.plugins.credentials.SystemCredentialsProvider' + )[0].getStore() + + def credentials + if (private_key == "" ) { + credentials = new UsernamePasswordCredentialsImpl( + CredentialsScope.GLOBAL, + null, + description, + username, + password + ) + } else { + def key_source + if (private_key.startsWith('-----BEGIN')) { + key_source = new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(private_key) + } else { + key_source = new BasicSSHUserPrivateKey.FileOnMasterPrivateKeySource(private_key) + } + credentials = new BasicSSHUserPrivateKey( + CredentialsScope.GLOBAL, + null, + username, + key_source, + password, + description + ) + } + + // Create or update the credentials in the Jenkins instance + def existing_credentials = credentials_for_username(username) + + if(existing_credentials != null) { + credentials_store.updateCredentials( + global_domain, + existing_credentials, + credentials + ) + } else { + credentials_store.addCredentials(global_domain, credentials) + } + } + + ////////////////////////// + // delete credentials + ////////////////////////// + void delete_credentials(String username) { + def existing_credentials = credentials_for_username(username) + + if(existing_credentials != null) { + def global_domain = com.cloudbees.plugins.credentials.domains.Domain.global() + def credentials_store = + Jenkins.instance.getExtensionList( + 'com.cloudbees.plugins.credentials.SystemCredentialsProvider' + )[0].getStore() + credentials_store.removeCredentials( + global_domain, + existing_credentials + ) + } + } + + //////////////////////// + // current credentials + //////////////////////// + void credential_info(String username) { + def credentials = credentials_for_username(username) + + if(credentials == null) { + return null + } + + def current_credentials = [ + id:credentials.id, + description:credentials.description, + username:credentials.username + ] + + if ( credentials.hasProperty('password') ) { + current_credentials['password'] = credentials.password.plainText + } else { + current_credentials['private_key'] = credentials.privateKey + current_credentials['passphrase'] = credentials.passphrase.plainText + } + + def builder = new groovy.json.JsonBuilder(current_credentials) + out.println(builder) + } + + //////////////////////// + // set_security + //////////////////////// + /* + * Set up security for the Jenkins instance. This currently supports + * only a small number of configurations. If authentication is enabled, it + * uses the internal user database. + */ + void set_security(String security_model) { + def instance = Jenkins.getInstance() + + if (security_model == 'disabled') { + instance.disableSecurity() + return null + } + + def strategy + def realm + switch (security_model) { + case 'full_control': + strategy = new hudson.security.FullControlOnceLoggedInAuthorizationStrategy() + realm = new hudson.security.HudsonPrivateSecurityRealm(false, false, null) + break + case 'unsecured': + strategy = new hudson.security.AuthorizationStrategy.Unsecured() + realm = new hudson.security.HudsonPrivateSecurityRealm(false, false, null) + break + default: + throw new InvalidAuthenticationStrategy() + } + instance.setAuthorizationStrategy(strategy) + instance.setSecurityRealm(realm) + } +} // class Actions + +/////////////////////////////////////////////////////////////////////////////// +// CLI Argument Processing +/////////////////////////////////////////////////////////////////////////////// + +actions = new Actions(out) +action = args[0] +if (args.length < 2) { + actions."$action"() +} else { + actions."$action"(*args[1..-1]) +} diff --git a/manifests/modules/jenkins/lib/facter/jenkins.rb b/manifests/modules/jenkins/lib/facter/jenkins.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/lib/puppet/features/jpm.rb b/manifests/modules/jenkins/lib/puppet/features/jpm.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/lib/puppet/jenkins.rb b/manifests/modules/jenkins/lib/puppet/jenkins.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/lib/puppet/jenkins/facts.rb b/manifests/modules/jenkins/lib/puppet/jenkins/facts.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/lib/puppet/jenkins/okjson.rb b/manifests/modules/jenkins/lib/puppet/jenkins/okjson.rb new file mode 100755 index 0000000..59d5055 --- /dev/null +++ b/manifests/modules/jenkins/lib/puppet/jenkins/okjson.rb @@ -0,0 +1,600 @@ +# encoding: UTF-8 +# +# Copyright 2011, 2012 Keith Rarick +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# See https://github.com/kr/okjson for updates. + +require 'stringio' + +# Some parts adapted from +# http://golang.org/src/pkg/json/decode.go and +# http://golang.org/src/pkg/utf8/utf8.go +module OkJson + Upstream = '43' + extend self + + + # Decodes a json document in string s and + # returns the corresponding ruby value. + # String s must be valid UTF-8. If you have + # a string in some other encoding, convert + # it first. + # + # String values in the resulting structure + # will be UTF-8. + def decode(s) + ts = lex(s) + v, ts = textparse(ts) + if ts.length > 0 + raise Error, 'trailing garbage' + end + v + end + + + # Encodes x into a json text. It may contain only + # Array, Hash, String, Numeric, true, false, nil. + # (Note, this list excludes Symbol.) + # X itself must be an Array or a Hash. + # No other value can be encoded, and an error will + # be raised if x contains any other value, such as + # Nan, Infinity, Symbol, and Proc, or if a Hash key + # is not a String. + # Strings contained in x must be valid UTF-8. + def encode(x) + case x + when Hash then objenc(x) + when Array then arrenc(x) + else + raise Error, 'root value must be an Array or a Hash' + end + end + + + def valenc(x) + case x + when Hash then objenc(x) + when Array then arrenc(x) + when String then strenc(x) + when Numeric then numenc(x) + when true then "true" + when false then "false" + when nil then "null" + else + raise Error, "cannot encode #{x.class}: #{x.inspect}" + end + end + + +private + + + # Parses a "json text" in the sense of RFC 4627. + # Returns the parsed value and any trailing tokens. + # Note: this is almost the same as valparse, + # except that it does not accept atomic values. + def textparse(ts) + if ts.length <= 0 + raise Error, 'empty' + end + + typ, _, val = ts[0] + case typ + when '{' then objparse(ts) + when '[' then arrparse(ts) + else + raise Error, "unexpected #{val.inspect}" + end + end + + + # Parses a "value" in the sense of RFC 4627. + # Returns the parsed value and any trailing tokens. + def valparse(ts) + if ts.length <= 0 + raise Error, 'empty' + end + + typ, _, val = ts[0] + case typ + when '{' then objparse(ts) + when '[' then arrparse(ts) + when :val,:str then [val, ts[1..-1]] + else + raise Error, "unexpected #{val.inspect}" + end + end + + + # Parses an "object" in the sense of RFC 4627. + # Returns the parsed value and any trailing tokens. + def objparse(ts) + ts = eat('{', ts) + obj = {} + + if ts[0][0] == '}' + return obj, ts[1..-1] + end + + k, v, ts = pairparse(ts) + obj[k] = v + + if ts[0][0] == '}' + return obj, ts[1..-1] + end + + loop do + ts = eat(',', ts) + + k, v, ts = pairparse(ts) + obj[k] = v + + if ts[0][0] == '}' + return obj, ts[1..-1] + end + end + end + + + # Parses a "member" in the sense of RFC 4627. + # Returns the parsed values and any trailing tokens. + def pairparse(ts) + (typ, _, k), ts = ts[0], ts[1..-1] + if typ != :str + raise Error, "unexpected #{k.inspect}" + end + ts = eat(':', ts) + v, ts = valparse(ts) + [k, v, ts] + end + + + # Parses an "array" in the sense of RFC 4627. + # Returns the parsed value and any trailing tokens. + def arrparse(ts) + ts = eat('[', ts) + arr = [] + + if ts[0][0] == ']' + return arr, ts[1..-1] + end + + v, ts = valparse(ts) + arr << v + + if ts[0][0] == ']' + return arr, ts[1..-1] + end + + loop do + ts = eat(',', ts) + + v, ts = valparse(ts) + arr << v + + if ts[0][0] == ']' + return arr, ts[1..-1] + end + end + end + + + def eat(typ, ts) + if ts[0][0] != typ + raise Error, "expected #{typ} (got #{ts[0].inspect})" + end + ts[1..-1] + end + + + # Scans s and returns a list of json tokens, + # excluding white space (as defined in RFC 4627). + def lex(s) + ts = [] + while s.length > 0 + typ, lexeme, val = tok(s) + if typ == nil + raise Error, "invalid character at #{s[0,10].inspect}" + end + if typ != :space + ts << [typ, lexeme, val] + end + s = s[lexeme.length..-1] + end + ts + end + + + # Scans the first token in s and + # returns a 3-element list, or nil + # if s does not begin with a valid token. + # + # The first list element is one of + # '{', '}', ':', ',', '[', ']', + # :val, :str, and :space. + # + # The second element is the lexeme. + # + # The third element is the value of the + # token for :val and :str, otherwise + # it is the lexeme. + def tok(s) + case s[0] + when ?{ then ['{', s[0,1], s[0,1]] + when ?} then ['}', s[0,1], s[0,1]] + when ?: then [':', s[0,1], s[0,1]] + when ?, then [',', s[0,1], s[0,1]] + when ?[ then ['[', s[0,1], s[0,1]] + when ?] then [']', s[0,1], s[0,1]] + when ?n then nulltok(s) + when ?t then truetok(s) + when ?f then falsetok(s) + when ?" then strtok(s) + when Spc, ?\t, ?\n, ?\r then [:space, s[0,1], s[0,1]] + else + numtok(s) + end + end + + + def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end + def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end + def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end + + + def numtok(s) + m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s) + if m && m.begin(0) == 0 + if !m[2] && !m[3] + [:val, m[0], Integer(m[0])] + elsif m[2] + [:val, m[0], Float(m[0])] + else + [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))] + end + else + [] + end + end + + + def strtok(s) + m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s) + if ! m + raise Error, "invalid string literal at #{abbrev(s)}" + end + [:str, m[0], unquote(m[0])] + end + + + def abbrev(s) + t = s[0,10] + p = t['`'] + t = t[0,p] if p + t = t + '...' if t.length < s.length + '`' + t + '`' + end + + + # Converts a quoted json string literal q into a UTF-8-encoded string. + # The rules are different than for Ruby, so we cannot use eval. + # Unquote will raise an error if q contains control characters. + def unquote(q) + q = q[1...-1] + a = q.dup # allocate a big enough string + # In ruby >= 1.9, a[w] is a codepoint, not a byte. + if rubydoesenc? + a.force_encoding('UTF-8') + end + r, w = 0, 0 + while r < q.length + c = q[r] + if c == ?\\ + r += 1 + if r >= q.length + raise Error, "string literal ends with a \"\\\": \"#{q}\"" + end + + case q[r] + when ?",?\\,?/,?' + a[w] = q[r] + r += 1 + w += 1 + when ?b,?f,?n,?r,?t + a[w] = Unesc[q[r]] + r += 1 + w += 1 + when ?u + r += 1 + uchar = begin + hexdec4(q[r,4]) + rescue RuntimeError => e + raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}" + end + r += 4 + if surrogate? uchar + if q.length >= r+6 + uchar1 = hexdec4(q[r+2,4]) + uchar = subst(uchar, uchar1) + if uchar != Ucharerr + # A valid pair; consume. + r += 6 + end + end + end + if rubydoesenc? + a[w] = '' << uchar + w += 1 + else + w += ucharenc(a, w, uchar) + end + else + raise Error, "invalid escape char #{q[r]} in \"#{q}\"" + end + elsif c == ?" || c < Spc + raise Error, "invalid character in string literal \"#{q}\"" + else + # Copy anything else byte-for-byte. + # Valid UTF-8 will remain valid UTF-8. + # Invalid UTF-8 will remain invalid UTF-8. + # In ruby >= 1.9, c is a codepoint, not a byte, + # in which case this is still what we want. + a[w] = c + r += 1 + w += 1 + end + end + a[0,w] + end + + + # Encodes unicode character u as UTF-8 + # bytes in string a at position i. + # Returns the number of bytes written. + def ucharenc(a, i, u) + if u <= Uchar1max + a[i] = (u & 0xff).chr + 1 + elsif u <= Uchar2max + a[i+0] = (Utag2 | ((u>>6)&0xff)).chr + a[i+1] = (Utagx | (u&Umaskx)).chr + 2 + elsif u <= Uchar3max + a[i+0] = (Utag3 | ((u>>12)&0xff)).chr + a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr + a[i+2] = (Utagx | (u&Umaskx)).chr + 3 + else + a[i+0] = (Utag4 | ((u>>18)&0xff)).chr + a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr + a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr + a[i+3] = (Utagx | (u&Umaskx)).chr + 4 + end + end + + + def hexdec4(s) + if s.length != 4 + raise Error, 'short' + end + (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3]) + end + + + def subst(u1, u2) + if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3 + return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself + end + return Ucharerr + end + + + def surrogate?(u) + Usurr1 <= u && u < Usurr3 + end + + + def nibble(c) + if ?0 <= c && c <= ?9 then c.ord - ?0.ord + elsif ?a <= c && c <= ?z then c.ord - ?a.ord + 10 + elsif ?A <= c && c <= ?Z then c.ord - ?A.ord + 10 + else + raise Error, "invalid hex code #{c}" + end + end + + + def objenc(x) + '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}' + end + + + def arrenc(a) + '[' + a.map{|x| valenc(x)}.join(',') + ']' + end + + + def keyenc(k) + case k + when String then strenc(k) + else + raise Error, "Hash key is not a string: #{k.inspect}" + end + end + + + def strenc(s) + t = StringIO.new + t.putc(?") + r = 0 + + while r < s.length + case s[r] + when ?" then t.print('\\"') + when ?\\ then t.print('\\\\') + when ?\b then t.print('\\b') + when ?\f then t.print('\\f') + when ?\n then t.print('\\n') + when ?\r then t.print('\\r') + when ?\t then t.print('\\t') + else + c = s[r] + # In ruby >= 1.9, s[r] is a codepoint, not a byte. + if rubydoesenc? + begin + # c.ord will raise an error if c is invalid UTF-8 + if c.ord < Spc.ord + c = "\\u%04x" % [c.ord] + end + t.write(c) + rescue + t.write(Ustrerr) + end + elsif c < Spc + t.write("\\u%04x" % c) + elsif Spc <= c && c <= ?~ + t.putc(c) + else + n = ucharcopy(t, s, r) # ensure valid UTF-8 output + r += n - 1 # r is incremented below + end + end + r += 1 + end + t.putc(?") + t.string + end + + + def numenc(x) + if ((x.nan? || x.infinite?) rescue false) + raise Error, "Numeric cannot be represented: #{x}" + end + "#{x}" + end + + + # Copies the valid UTF-8 bytes of a single character + # from string s at position i to I/O object t, and + # returns the number of bytes copied. + # If no valid UTF-8 char exists at position i, + # ucharcopy writes Ustrerr and returns 1. + def ucharcopy(t, s, i) + n = s.length - i + raise Utf8Error if n < 1 + + c0 = s[i].ord + + # 1-byte, 7-bit sequence? + if c0 < Utagx + t.putc(c0) + return 1 + end + + raise Utf8Error if c0 < Utag2 # unexpected continuation byte? + + raise Utf8Error if n < 2 # need continuation byte + c1 = s[i+1].ord + raise Utf8Error if c1 < Utagx || Utag2 <= c1 + + # 2-byte, 11-bit sequence? + if c0 < Utag3 + raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max + t.putc(c0) + t.putc(c1) + return 2 + end + + # need second continuation byte + raise Utf8Error if n < 3 + + c2 = s[i+2].ord + raise Utf8Error if c2 < Utagx || Utag2 <= c2 + + # 3-byte, 16-bit sequence? + if c0 < Utag4 + u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx) + raise Utf8Error if u <= Uchar2max + t.putc(c0) + t.putc(c1) + t.putc(c2) + return 3 + end + + # need third continuation byte + raise Utf8Error if n < 4 + c3 = s[i+3].ord + raise Utf8Error if c3 < Utagx || Utag2 <= c3 + + # 4-byte, 21-bit sequence? + if c0 < Utag5 + u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx) + raise Utf8Error if u <= Uchar3max + t.putc(c0) + t.putc(c1) + t.putc(c2) + t.putc(c3) + return 4 + end + + raise Utf8Error + rescue Utf8Error + t.write(Ustrerr) + return 1 + end + + + def rubydoesenc? + ::String.method_defined?(:force_encoding) + end + + + class Utf8Error < ::StandardError + end + + + class Error < ::StandardError + end + + + Utagx = 0b1000_0000 + Utag2 = 0b1100_0000 + Utag3 = 0b1110_0000 + Utag4 = 0b1111_0000 + Utag5 = 0b1111_1000 + Umaskx = 0b0011_1111 + Umask2 = 0b0001_1111 + Umask3 = 0b0000_1111 + Umask4 = 0b0000_0111 + Uchar1max = (1<<7) - 1 + Uchar2max = (1<<11) - 1 + Uchar3max = (1<<16) - 1 + Ucharerr = 0xFFFD # unicode "replacement char" + Ustrerr = "\xef\xbf\xbd" # unicode "replacement char" + Usurrself = 0x10000 + Usurr1 = 0xd800 + Usurr2 = 0xdc00 + Usurr3 = 0xe000 + + Spc = ' '[0] + Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t} +end \ No newline at end of file diff --git a/manifests/modules/jenkins/lib/puppet/jenkins/plugins.rb b/manifests/modules/jenkins/lib/puppet/jenkins/plugins.rb old mode 100644 new mode 100755 index 3a1049e..e0b6b9a --- a/manifests/modules/jenkins/lib/puppet/jenkins/plugins.rb +++ b/manifests/modules/jenkins/lib/puppet/jenkins/plugins.rb @@ -1,4 +1,3 @@ -require 'json' require 'puppet/jenkins' module Puppet @@ -85,14 +84,28 @@ def self.exists? # # @return [Hash] Parsed version of the update center JSON def self.plugins_from_updatecenter(filename) - File.open(filename, 'rb') do |fd| + parser = nil + begin + # Using Kernel#require to make it easier to test this from RSpec + ::Kernel.require 'json' + parser = Proc.new { |s| JSON.parse(s) } + rescue LoadError + # swallow the exception and embed okjson, see: + # + # + ::Kernel.require 'puppet/jenkins/okjson' + parser = Proc.new { |s| OkJson.decode(s) } + end + + + File.open(filename, 'r') do |fd| buffer = fd.read return {} if (buffer.nil? || buffer.empty?) buffer = buffer.split("\n") # Trim off the first and last lines, which are the JSONP gunk buffer = buffer[1 ... -1] - data = JSON.parse(buffer.join("\n")) + data = parser.call(buffer.join("\n")) return data['plugins'] || {} end return {} diff --git a/manifests/modules/jenkins/lib/puppet/parser/functions/jenkins_port.rb b/manifests/modules/jenkins/lib/puppet/parser/functions/jenkins_port.rb new file mode 100755 index 0000000..608a96a --- /dev/null +++ b/manifests/modules/jenkins/lib/puppet/parser/functions/jenkins_port.rb @@ -0,0 +1,21 @@ + +module Puppet::Parser::Functions + newfunction(:jenkins_port, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Return the configurad Jenkins port value + (corresponds to /etc/defaults/jenkins -> JENKINS_PORT + + Example: + + $port = jenkins_port() + ENDHEREDOC + + config_hash = lookupvar('::jenkins::config_hash') + if config_hash && \ + config_hash['HTTP_PORT'] && \ + config_hash['HTTP_PORT']['value'] + return config_hash['HTTP_PORT']['value'] + else + return lookupvar('::jenkins::params::port') + end + end +end diff --git a/manifests/modules/jenkins/lib/puppet/provider/package/jpm.rb b/manifests/modules/jenkins/lib/puppet/provider/package/jpm.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/manifests/cli.pp b/manifests/modules/jenkins/manifests/cli.pp old mode 100644 new mode 100755 index ca44571..e402bd7 --- a/manifests/modules/jenkins/manifests/cli.pp +++ b/manifests/modules/jenkins/manifests/cli.pp @@ -8,8 +8,8 @@ fail("Use of private class ${name} by ${caller_module_name}") } - $jar = '/usr/lib/jenkins/jenkins-cli.jar' - $extract_jar = 'unzip /usr/lib/jenkins/jenkins.war WEB-INF/jenkins-cli.jar' + $jar = "${jenkins::libdir}/jenkins-cli.jar" + $extract_jar = "jar -xf ${jenkins::libdir}/jenkins.war WEB-INF/jenkins-cli.jar" $move_jar = "mv WEB-INF/jenkins-cli.jar ${jar}" $remove_dir = 'rm -rf WEB-INF' @@ -18,7 +18,34 @@ path => ['/bin', '/usr/bin'], cwd => '/tmp', creates => $jar, - require => Package['jenkins'], + require => Service['jenkins'], } + file { $jar: + ensure => file, + require => Exec['jenkins-cli'], + } + + $port = jenkins_port() + + # The jenkins cli command with required parameter(s) + $cmd = "java -jar ${jar} -s http://localhost:${port}" + + # Reload all Jenkins config from disk (only when notified) + exec { 'reload-jenkins': + command => "${cmd} reload-configuration", + tries => 10, + try_sleep => 2, + refreshonly => true, + require => File[$jar], + } + + # Do a safe restart of Jenkins (only when notified) + exec { 'safe-restart-jenkins': + command => "${cmd} safe-restart && /bin/sleep 10", + tries => 10, + try_sleep => 2, + refreshonly => true, + require => File[$jar], + } } diff --git a/manifests/modules/jenkins/manifests/cli_helper.pp b/manifests/modules/jenkins/manifests/cli_helper.pp new file mode 100755 index 0000000..2beb86a --- /dev/null +++ b/manifests/modules/jenkins/manifests/cli_helper.pp @@ -0,0 +1,36 @@ +# Class jenkins::cli_helper +# +# A helper script for creating resources via the Jenkins cli +# +class jenkins::cli_helper ( + $ssh_keyfile = undef, +){ + include ::jenkins + include ::jenkins::cli + + $libdir = $::jenkins::libdir + $cli_jar = $::jenkins::cli::jar + $port = jenkins_port() + + $helper_groovy = "${libdir}/puppet_helper.groovy" + file {$helper_groovy: + source => 'puppet:///modules/jenkins/puppet_helper.groovy', + owner => 'jenkins', + group => 'jenkins', + mode => '0444', + require => Class['jenkins::cli'], + } + + if $ssh_keyfile { + $auth_arg = "-i ${ssh_keyfile}" + } else { + $auth_arg = '' + } + $helper_cmd = join([ + '/usr/bin/java', + "-jar ${::jenkins::cli::jar}", + "-s http://127.0.0.1:${port}", + $auth_arg, + "groovy ${helper_groovy}", + ], ' ') +} diff --git a/manifests/modules/jenkins/manifests/config.pp b/manifests/modules/jenkins/manifests/config.pp old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/manifests/credentials.pp b/manifests/modules/jenkins/manifests/credentials.pp new file mode 100755 index 0000000..7d46734 --- /dev/null +++ b/manifests/modules/jenkins/manifests/credentials.pp @@ -0,0 +1,60 @@ +# Copyright 2014 RetailMeNot, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Type jenkins::credentials +# +# Jenkins credentials (via the CloudBees Credentials plugin +# +define jenkins::credentials ( + $password, + $description = 'Managed by Puppet', + $private_key_or_path = '', + $ensure = 'present', +){ + validate_string($ensure) + + include ::jenkins::cli_helper + + case $ensure { + 'present': { + validate_string($password) + validate_string($description) + validate_string($private_key_or_path) + exec { "create-jenkins-credentials-${title}": + command => join([ + $::jenkins::cli_helper::helper_cmd, + 'create_or_update_credentials', + $title, + "'${password}'", + "'${description}'", + "'${private_key_or_path}'", + ], ' '), + require => Class['::jenkins::cli_helper'], + } + } + 'absent': { + exec { "delete-jenkins-credentials-${title}": + command => join([ + $::jenkins::cli_helper::helper_cmd, + 'delete_credentials', + $title, + ], ' '), + require => Class['::jenkins::cli_helper'], + } + } + default: { + fail "ensure must be 'present' or 'absent' but '${ensure}' was given" + } + } +} diff --git a/manifests/modules/jenkins/manifests/firewall.pp b/manifests/modules/jenkins/manifests/firewall.pp old mode 100644 new mode 100755 index b2c4764..024e60e --- a/manifests/modules/jenkins/manifests/firewall.pp +++ b/manifests/modules/jenkins/manifests/firewall.pp @@ -11,7 +11,7 @@ firewall { '500 allow Jenkins inbound traffic': action => 'accept', state => 'NEW', - dport => [$::jenkins::port], + dport => [jenkins_port()], proto => 'tcp', } } diff --git a/manifests/modules/jenkins/manifests/init.pp b/manifests/modules/jenkins/manifests/init.pp old mode 100644 new mode 100755 index 22056bb..93b4c56 --- a/manifests/modules/jenkins/manifests/init.pp +++ b/manifests/modules/jenkins/manifests/init.pp @@ -2,6 +2,7 @@ # version = 'installed' (Default) # Will NOT update jenkins to the most recent version. +# # version = 'latest' # Will automatically update the version of jenkins to the current version available via your package manager. # @@ -11,6 +12,9 @@ # lts = true # Use LTS verison of jenkins # +# port = 8080 (default) +# Sets firewall port to 8080 if puppetlabs-firewall module is installed +# # repo = true (Default) # install the jenkins repo. # @@ -19,16 +23,22 @@ # this module. # This is for folks that use a custom repo, or the like. # +# service_enable = true (default) +# Enable (or not) the jenkins service +# +# service_ensure = 'running' (default) +# Status of the jenkins service. running, stopped +# # config_hash = undef (Default) -# Hash with config options to set in sysconfig/jenkins defaults/jenkins +# Hash with config options to set in sysconfig/jenkins defaults/jenkins # # Example use # -# class{ 'jenkins::config': +# class{ 'jenkins': # config_hash => { # 'HTTP_PORT' => { 'value' => '9090' }, 'AJP_PORT' => { 'value' => '9009' } # } -# } +# V # # plugin_hash = undef (Default) # Hash with config plugins to install @@ -37,7 +47,7 @@ # # class{ 'jenkins::plugins': # plugin_hash => { -# 'git' -> { version => '1.1.1' }, +# 'git' => { version => '1.1.1' }, # 'parameterized-trigger' => {}, # 'multiple-scms' => {}, # 'git-client' => {}, @@ -68,6 +78,25 @@ # - use puppetlabs-java module to install the correct version of a JDK. # - Jenkins requires a JRE # +# +# cli = false (default) +# - force installation of the jenkins CLI jar to $libdir/cli/jenkins-cli.jar +# - the cli is automatically installed when needed by components that use it, +# such as the user and credentials types, and the security class +# - CLI installation (both implicit and explicit) requires the unzip command +# +# +# proxy_host = undef (default) +# proxy_port = undef (default) +# If your environment requires a proxy host to download plugins it can be configured here +# +# +# no_proxy_list = undef (default) +# List of hostname patterns to skip using the proxy. +# - Accepts input as array only. +# - Only effective if "proxy_host" and "proxy_port" are set. +# +# class jenkins( $version = $jenkins::params::version, $lts = $jenkins::params::lts, @@ -76,11 +105,15 @@ $service_ensure = $jenkins::params::service_ensure, $config_hash = {}, $plugin_hash = {}, + $job_hash = {}, $configure_firewall = undef, $install_java = $jenkins::params::install_java, $proxy_host = undef, $proxy_port = undef, + $no_proxy_list = undef, $cli = undef, + $port = $jenkins::params::port, + $libdir = $jenkins::params::libdir, ) inherits jenkins::params { validate_bool($lts, $install_java, $repo) @@ -90,6 +123,10 @@ validate_bool($configure_firewall) } + if $no_proxy_list { + validate_array($no_proxy_list) + } + anchor {'jenkins::begin':} anchor {'jenkins::end':} @@ -100,14 +137,13 @@ } if $repo { - class {'jenkins::repo':} + include jenkins::repo } - class {'jenkins::package': } - - class { 'jenkins::config': } - - class { 'jenkins::plugins': } + include jenkins::package + include jenkins::config + include jenkins::plugins + include jenkins::jobs if $proxy_host and $proxy_port { class { 'jenkins::proxy': @@ -116,26 +152,36 @@ } } - class {'jenkins::service':} + include jenkins::service if defined('::firewall') { if $configure_firewall == undef { fail('The firewall module is included in your manifests, please configure $configure_firewall in the jenkins module') } elsif $configure_firewall { - class {'jenkins::firewall':} + include jenkins::firewall } } + if $cli { - class {'jenkins::cli':} + include jenkins::cli } Anchor['jenkins::begin'] -> Class['jenkins::package'] -> Class['jenkins::config'] -> - Class['jenkins::plugins']~> + Class['jenkins::plugins'] ~> Class['jenkins::service'] -> + Class['jenkins::jobs'] -> Anchor['jenkins::end'] + if $cli { + Anchor['jenkins::begin'] -> + Class['jenkins::service'] -> + Class['jenkins::cli'] -> + Class['jenkins::jobs'] -> + Anchor['jenkins::end'] + } + if $install_java { Anchor['jenkins::begin'] -> Class['java'] -> diff --git a/manifests/modules/jenkins/manifests/job.pp b/manifests/modules/jenkins/manifests/job.pp new file mode 100755 index 0000000..6568b82 --- /dev/null +++ b/manifests/modules/jenkins/manifests/job.pp @@ -0,0 +1,38 @@ +# Define: jenkins::job +# +# This class create a new jenkins job given a name and config xml +# +# Parameters: +# +# config +# the content of the jenkins job config file (required) +# +# jobname = $title +# the name of the jenkins job +# +# enabled = true +# whether to enable the job +# +# ensure = 'present' +# choose 'absent' to ensure the job is removed +# +define jenkins::job( + $config, + $jobname = $title, + $enabled = 1, + $ensure = 'present', +){ + + if ($ensure == 'absent') { + jenkins::job::absent { $title: + jobname => $jobname, + } + } else { + jenkins::job::present { $title: + config => $config, + jobname => $jobname, + enabled => $enabled, + } + } + +} diff --git a/manifests/modules/jenkins/manifests/job/absent.pp b/manifests/modules/jenkins/manifests/job/absent.pp new file mode 100755 index 0000000..be22c75 --- /dev/null +++ b/manifests/modules/jenkins/manifests/job/absent.pp @@ -0,0 +1,39 @@ +# Define: jenkins::job::absent +# +# Removes a jenkins build job +# +# Parameters: +# +# config +# the content of the jenkins job config file (required) +# +# jobname = $title +# the name of the jenkins job +# +define jenkins::job::absent( + $jobname = $title, +){ + include jenkins::cli + + if $jenkins::service_ensure == 'stopped' or $jenkins::service_ensure == false { + fail('Management of Jenkins jobs requires \$jenkins::service_ensure to be set to \'running\'') + } + + $tmp_config_path = "/tmp/${jobname}-config.xml" + $job_dir = "/var/lib/jenkins/jobs/${jobname}" + $config_path = "${job_dir}/config.xml" + + # Temp file to use as stdin for Jenkins CLI executable + file { $tmp_config_path: + ensure => absent, + } + + # Delete the job + exec { "jenkins delete-job ${jobname}": + command => "${jenkins::cli::cmd} delete-job ${jobname}", + logoutput => false, + onlyif => "test -f ${config_path}", + require => Exec['jenkins-cli'], + } + +} diff --git a/manifests/modules/jenkins/manifests/job/present.pp b/manifests/modules/jenkins/manifests/job/present.pp new file mode 100755 index 0000000..b78c50f --- /dev/null +++ b/manifests/modules/jenkins/manifests/job/present.pp @@ -0,0 +1,100 @@ +# Define: jenkins::job::present +# +# Creates or updates a jenkins build job +# +# Parameters: +# +# config +# the content of the jenkins job config file (required) +# +# jobname = $title +# the name of the jenkins job +# +# enabled = 1 +# if the job should be enabled +# +define jenkins::job::present( + $config, + $jobname = $title, + $enabled = 1, +){ + include jenkins::cli + + if $jenkins::service_ensure == 'stopped' or $jenkins::service_ensure == false { + fail('Management of Jenkins jobs requires \$jenkins::service_ensure to be set to \'running\'') + } + + $jenkins_cli = $jenkins::cli::cmd + $tmp_config_path = "/tmp/${jobname}-config.xml" + $job_dir = "/var/lib/jenkins/jobs/${jobname}" + $config_path = "${job_dir}/config.xml" + + Exec { + logoutput => false, + path => '/bin:/usr/bin:/sbin:/usr/sbin', + tries => 5, + try_sleep => 5, + } + + # + # When a Jenkins job is imported via the cli, Jenkins will + # re-format the xml file based on its own internal rules. + # In order to make job management idempotent, we need to + # apply that formatting before the import, so we can do a diff + # on any pre-existing job to determine if an update is needed. + # + # Jenkins likes to change single quotes to double quotes + $a = regsubst($config, 'version=\'1.0\' encoding=\'UTF-8\'', + 'version="1.0" encoding="UTF-8"') + # Change empty tags into self-closing tags + $b = regsubst($a, '<([a-z]+)><\/\1>', '<\1/>', 'IG') + # Change " to " since Jenkins is weird like that + $c = regsubst($b, '"', '"', 'MG') + + # Temp file to use as stdin for Jenkins CLI executable + file { $tmp_config_path: + content => $c, + require => Exec['jenkins-cli'], + } + + # Use Jenkins CLI to create the job + $cat_config = "cat ${tmp_config_path}" + $create_job = "${jenkins_cli} create-job ${jobname}" + exec { "jenkins create-job ${jobname}": + command => "${cat_config} | ${create_job}", + creates => [$config_path, "${job_dir}/builds"], + require => File[$tmp_config_path], + } + + # Use Jenkins CLI to update the job if it already exists + $update_job = "${jenkins_cli} update-job ${jobname}" + exec { "jenkins update-job ${jobname}": + command => "${cat_config} | ${update_job}", + onlyif => "test -e ${config_path}", + unless => "diff -b -q ${config_path} ${tmp_config_path}", + require => File[$tmp_config_path], + notify => Exec['reload-jenkins'], + } + + # Enable or disable the job (if necessary) + if ($enabled == 1) { + exec { "jenkins enable-job ${jobname}": + command => "${jenkins_cli} enable-job ${jobname}", + onlyif => "cat ${config_path} | grep 'true'", + require => [ + Exec["jenkins create-job ${jobname}"], + Exec["jenkins update-job ${jobname}"], + ], + } + } else { + exec { "jenkins disable-job ${jobname}": + command => "${jenkins_cli} disable-job ${jobname}", + onlyif => "cat ${config_path} | grep 'false'", + require => [ + Exec["jenkins create-job ${jobname}"], + Exec["jenkins update-job ${jobname}"], + ], + } + } + +} diff --git a/manifests/modules/jenkins/manifests/jobs.pp b/manifests/modules/jenkins/manifests/jobs.pp new file mode 100755 index 0000000..3a6aa0d --- /dev/null +++ b/manifests/modules/jenkins/manifests/jobs.pp @@ -0,0 +1,11 @@ +# Class: jenkins::jobs +# +class jenkins::jobs { + + if $caller_module_name != $module_name { + fail("Use of private class ${name} by ${caller_module_name}") + } + + create_resources('jenkins::job',$::jenkins::job_hash) + +} diff --git a/manifests/modules/jenkins/manifests/master.pp b/manifests/modules/jenkins/manifests/master.pp old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/manifests/package.pp b/manifests/modules/jenkins/manifests/package.pp old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/manifests/params.pp b/manifests/modules/jenkins/manifests/params.pp old mode 100644 new mode 100755 index f32de2f..22864d1 --- a/manifests/modules/jenkins/manifests/params.pp +++ b/manifests/modules/jenkins/manifests/params.pp @@ -2,13 +2,24 @@ # # class jenkins::params { - $version = 'installed' - $lts = false - $repo = true - $service_enable = true - $service_ensure = 'running' - $install_java = true - $swarm_version = '1.9' + $version = 'installed' + $lts = false + $repo = true + $service_enable = true + $service_ensure = 'running' + $install_java = true + $swarm_version = '1.17' + $default_plugins_host = 'http://updates.jenkins-ci.org' + $port = '8080' + + case $::osfamily { + 'Debian': { + $libdir = '/usr/share/jenkins' + } + default: { + $libdir = '/usr/lib/jenkins' + } + } } diff --git a/manifests/modules/jenkins/manifests/plugin.pp b/manifests/modules/jenkins/manifests/plugin.pp old mode 100644 new mode 100755 index fd0dd86..ebbc4a4 --- a/manifests/modules/jenkins/manifests/plugin.pp +++ b/manifests/modules/jenkins/manifests/plugin.pp @@ -6,35 +6,59 @@ # Content of the config file for this plugin. It is up to the caller to # create this content from a template or any other mean. # +# update_url = undef +# define jenkins::plugin( - $version=0, + $version = 0, $manage_config = false, $config_filename = undef, $config_content = undef, + $update_url = undef, ) { + include ::jenkins::params $plugin = "${name}.hpi" $plugin_dir = '/var/lib/jenkins/plugins' $plugin_parent_dir = inline_template('<%= @plugin_dir.split(\'/\')[0..-2].join(\'/\') %>') - validate_bool ($manage_config) + validate_bool($manage_config) + # TODO: validate_str($update_url) if ($version != 0) { - $base_url = "http://updates.jenkins-ci.org/download/plugins/${name}/${version}/" + $plugins_host = $update_url ? { + undef => $::jenkins::params::default_plugins_host, + default => $update_url, + } + $base_url = "${plugins_host}/download/plugins/${name}/${version}/" $search = "${name} ${version}(,|$)" } else { - $base_url = 'http://updates.jenkins-ci.org/latest/' + $plugins_host = $update_url ? { + undef => $::jenkins::params::default_plugins_host, + default => $update_url, + } + $base_url = "${plugins_host}/latest/" $search = "${name} " } if (!defined(File[$plugin_dir])) { - file { [$plugin_parent_dir, $plugin_dir]: + if (!defined(File[$plugin_parent_dir])) { + file { $plugin_parent_dir: + ensure => directory, + owner => 'jenkins', + group => 'jenkins', + mode => '0755', + require => [Group['jenkins'], User['jenkins']], + } + } + + file { $plugin_dir: ensure => directory, owner => 'jenkins', group => 'jenkins', mode => '0755', require => [Group['jenkins'], User['jenkins']], } + } if (!defined(Group['jenkins'])) { @@ -59,8 +83,7 @@ } if (empty(grep([ $::jenkins_plugins ], $search))) { - - if ($jenkins::proxy_host){ + if ($jenkins::proxy_host) { Exec { environment => [ "http_proxy=${jenkins::proxy_host}:${jenkins::proxy_port}", @@ -69,11 +92,23 @@ } } + # create a pinned file if the plugin has a .jpi extension + # to override the builtin module versions + exec { "create-pinnedfile-${name}" : + command => "touch ${plugin_dir}/${name}.jpi.pinned", + cwd => $plugin_dir, + require => File[$plugin_dir], + path => ['/usr/bin', '/usr/sbin', '/bin'], + onlyif => "test -f ${plugin_dir}/${name}.jpi -a ! -f ${plugin_dir}/${name}.jpi.pinned", + before => Exec["download-${name}"], + } + + exec { "download-${name}" : - command => "rm -rf ${name} ${name}.* && wget --no-check-certificate ${base_url}${plugin}", - cwd => $plugin_dir, - require => [File[$plugin_dir], Package['wget']], - path => ['/usr/bin', '/usr/sbin', '/bin'], + command => "rm -rf ${name} ${name}.hpi ${name}.jpi && wget --no-check-certificate ${base_url}${plugin}", + cwd => $plugin_dir, + require => [File[$plugin_dir], Package['wget']], + path => ['/usr/bin', '/usr/sbin', '/bin'], } file { "${plugin_dir}/${plugin}" : @@ -99,3 +134,4 @@ } } } + diff --git a/manifests/modules/jenkins/manifests/plugins.pp b/manifests/modules/jenkins/manifests/plugins.pp old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/manifests/proxy.pp b/manifests/modules/jenkins/manifests/proxy.pp old mode 100644 new mode 100755 index 71e53b9..6c9b09f --- a/manifests/modules/jenkins/manifests/proxy.pp +++ b/manifests/modules/jenkins/manifests/proxy.pp @@ -5,6 +5,11 @@ fail("Use of private class ${name} by ${caller_module_name}") } + # Bring variables from Class['::jenkins'] into local scope. + $proxy_host = $::jenkins::proxy_host + $proxy_port = $::jenkins::proxy_port + $no_proxy_list = $::jenkins::no_proxy_list + file { '/var/lib/jenkins/proxy.xml': content => template('jenkins/proxy.xml.erb'), owner => 'jenkins', diff --git a/manifests/modules/jenkins/manifests/repo.pp b/manifests/modules/jenkins/manifests/repo.pp old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/manifests/repo/debian.pp b/manifests/modules/jenkins/manifests/repo/debian.pp old mode 100644 new mode 100755 index fe4ca3c..43f0f87 --- a/manifests/modules/jenkins/manifests/repo/debian.pp +++ b/manifests/modules/jenkins/manifests/repo/debian.pp @@ -7,6 +7,7 @@ } include stdlib + include apt if $::jenkins::lts { apt::source { 'jenkins': diff --git a/manifests/modules/jenkins/manifests/repo/el.pp b/manifests/modules/jenkins/manifests/repo/el.pp old mode 100644 new mode 100755 index 7e1d4f1..9b0ab63 --- a/manifests/modules/jenkins/manifests/repo/el.pp +++ b/manifests/modules/jenkins/manifests/repo/el.pp @@ -13,6 +13,7 @@ baseurl => 'http://pkg.jenkins-ci.org/redhat-stable/', gpgcheck => 1, gpgkey => 'http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key', + enabled => 1, } } @@ -22,6 +23,7 @@ baseurl => 'http://pkg.jenkins-ci.org/redhat/', gpgcheck => 1, gpgkey => 'http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key', + enabled => 1, } } } diff --git a/manifests/modules/jenkins/manifests/repo/suse.pp b/manifests/modules/jenkins/manifests/repo/suse.pp old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/manifests/security.pp b/manifests/modules/jenkins/manifests/security.pp new file mode 100755 index 0000000..d482435 --- /dev/null +++ b/manifests/modules/jenkins/manifests/security.pp @@ -0,0 +1,34 @@ +# Copyright 2014 RetailMeNot, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Class jenkins::security +# +# Jenkins security configuration +# +class jenkins::security ( + $security_model = undef, +){ + validate_string($security_model) + + include ::jenkins::cli_helper + + exec { "jenkins-security-${security_model}": + command => join([ + $::jenkins::cli_helper::helper_cmd, + 'set_security', + $security_model, + ], ' '), + require => Class['::jenkins::cli_helper'], + } +} diff --git a/manifests/modules/jenkins/manifests/service.pp b/manifests/modules/jenkins/manifests/service.pp old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/manifests/slave.pp b/manifests/modules/jenkins/manifests/slave.pp old mode 100644 new mode 100755 index 9c1b5fc..89eb316 --- a/manifests/modules/jenkins/manifests/slave.pp +++ b/manifests/modules/jenkins/manifests/slave.pp @@ -8,6 +8,9 @@ # # === Parameters # +# [*slave_name*] +# Specify the name of the slave. Not required, by default it will use the fqdn. +# # [*masterurl*] # Specify the URL of the master server. Not required, the plugin will do a UDP autodiscovery. If specified, the autodiscovery will be skipped. # @@ -62,6 +65,7 @@ # # Copyright 2013 Matthew Barr , but can be used for anything by anyone.. class jenkins::slave ( + $slave_name = undef, $masterurl = undef, $ui_user = undef, $ui_pass = undef, @@ -75,6 +79,7 @@ $disable_ssl_verification = false, $labels = undef, $install_java = $jenkins::params::install_java, + $ensure = 'running', $enable = true ) inherits jenkins::params { @@ -87,14 +92,6 @@ } } - #If disable_ssl_verification is set to true - if $disable_ssl_verification { - #disable SSL verification to the init script - $disable_ssl_verification_flag = '-disableSslVerification' - } else { - $disable_ssl_verification_flag = '' - } - #add jenkins slave user if necessary. if $manage_slave_user and $slave_uid { user { 'jenkins-slave_user': @@ -118,81 +115,49 @@ } exec { 'get_swarm_client': - command => "wget -O ${slave_home}/${client_jar} ${client_url}/${client_jar}", - path => '/usr/bin:/usr/sbin:/bin:/usr/local/bin', - user => $slave_user, + command => "wget -O ${slave_home}/${client_jar} ${client_url}/${client_jar}", + path => '/usr/bin:/usr/sbin:/bin:/usr/local/bin', + user => $slave_user, #refreshonly => true, - creates => "${slave_home}/${client_jar}", + creates => "${slave_home}/${client_jar}", ## needs to be fixed if you create another version.. } - if $ui_user { - $ui_user_flag = "-username ${ui_user}" - } - else {$ui_user_flag = ''} - - if $ui_pass { - $ui_pass_flag = "-password ${ui_pass}" - } else { - $ui_pass_flag = '' - } - - if $masterurl { - $masterurl_flag = "-master ${masterurl}" - } else { - $masterurl_flag = '' - } - - if $labels { - $labels_flag = "-labels \"${labels}\"" - } else { - $labels_flag = '' - } - - if $slave_home { - $fsroot_flag = "-fsroot ${slave_home}" - } - - # choose the correct init functions + # customizations based on the OS family case $::osfamily { Debian: { - file { '/etc/init.d/jenkins-slave': - ensure => 'file', - mode => '0700', - owner => 'root', - group => 'root', - source => "puppet:///modules/${module_name}/jenkins-slave", - notify => Service['jenkins-slave'], - require => File['/etc/default/jenkins-slave'], - } - - file { '/etc/default/jenkins-slave': - ensure => 'file', - mode => '0600', - owner => 'root', - group => 'root', - content => template("${module_name}/jenkins-slave-defaults.${::osfamily}"), - require => Package['daemon'], - } + $defaults_location = '/etc/default' package {'daemon': ensure => present, + before => Service['jenkins-slave'], } } default: { - file { '/etc/init.d/jenkins-slave': - ensure => 'file', - mode => '0700', - owner => 'root', - group => 'root', - content => template("${module_name}/jenkins-slave.erb"), - notify => Service['jenkins-slave'], - } + $defaults_location = '/etc/sysconfig' } } + file { '/etc/init.d/jenkins-slave': + ensure => 'file', + mode => '0755', + owner => 'root', + group => 'root', + source => "puppet:///modules/${module_name}/jenkins-slave.${::osfamily}", + notify => Service['jenkins-slave'], + } + + file { "${defaults_location}/jenkins-slave": + ensure => 'file', + mode => '0600', + owner => 'root', + group => 'root', + content => template("${module_name}/jenkins-slave-defaults.erb"), + notify => Service['jenkins-slave'], + } + service { 'jenkins-slave': - ensure => running, + ensure => $ensure, enable => $enable, hasstatus => true, hasrestart => true, diff --git a/manifests/modules/jenkins/manifests/sysconfig.pp b/manifests/modules/jenkins/manifests/sysconfig.pp old mode 100644 new mode 100755 index 73124d4..3c85c56 --- a/manifests/modules/jenkins/manifests/sysconfig.pp +++ b/manifests/modules/jenkins/manifests/sysconfig.pp @@ -3,17 +3,17 @@ define jenkins::sysconfig ( $value ) { $path = $::osfamily ? { - RedHat => '/etc/sysconfig', - Suse => '/etc/sysconfig', - Debian => '/etc/default', - default => fail( "Unsupported OSFamily ${::osfamily}" ) + 'RedHat' => '/etc/sysconfig', + 'Suse' => '/etc/sysconfig', + 'Debian' => '/etc/default', + default => fail( "Unsupported OSFamily ${::osfamily}" ) } file_line { "Jenkins sysconfig setting ${name}": - path => "${path}/jenkins", - line => "${name}=\"${value}\"", - match => "^${name}=", - notify => Service['jenkins'], + path => "${path}/jenkins", + line => "${name}=\"${value}\"", + match => "^${name}=", + notify => Service['jenkins'], } } diff --git a/manifests/modules/jenkins/manifests/user.pp b/manifests/modules/jenkins/manifests/user.pp new file mode 100755 index 0000000..ffede9d --- /dev/null +++ b/manifests/modules/jenkins/manifests/user.pp @@ -0,0 +1,63 @@ +# Copyright 2014 RetailMeNot, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Type jenkins::user +# +# A Jenkins user account +# +define jenkins::user ( + $email, + $password, + $full_name = 'Managed by Puppet', + $public_key = '', + $ensure = 'present', +){ + validate_string($ensure) + + include ::jenkins::cli_helper + + case $ensure { + 'present': { + validate_re($email, '^[^@]+@[^@]+$', "An email address is required, not '${email}'") + validate_string($password) + validate_string($full_name) + validate_string($public_key) + exec { "create-jenkins-user-${title}": + command => join([ + $::jenkins::cli_helper::helper_cmd, + 'create_or_update_user', + $title, + $email, + "'${password}'", + "'${full_name}'", + "'${public_key}'", + ], ' '), + require => Class['::jenkins::cli_helper'], + } + } + 'absent': { + exec { "delete-jenkins-user-${title}": + command => join([ + $::jenkins::cli_helper::helper_cmd, + 'delete_user', + $title, + ], ' '), + require => Class['::jenkins::cli_helper'], + } + } + default: { + fail "ensure must be 'present' or 'absent' but '${ensure}' was given" + } + } +} diff --git a/manifests/modules/jenkins/metadata.json b/manifests/modules/jenkins/metadata.json new file mode 100755 index 0000000..f3576a5 --- /dev/null +++ b/manifests/modules/jenkins/metadata.json @@ -0,0 +1,27 @@ +{ + "name": "rtyler-jenkins", + "version": "1.3.0", + "author": "R. Tyler Croy ", + "license": "Licensed under (Apache 2.0)", + "summary": "Manage the Jenkins continuous integration service with Puppet", + "source": "https://github.com/jenkinsci/puppet-jenkins", + "project_page": "(https://forge.puppetlabs.com/rtyler/puppet-jenkins)", + "issues_url": "https://github.com/jenkinsci/puppet-jenkins/issues", + "tags": ["jenkins", "jenkinsci"], + "operatingsystem_support": [ + { + "operatingsystem":"RedHat", + "operatingsystemrelease": [ "7.0", "6.0" ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ "14.04", "12.04", "10.04" ] + } + ], + "dependencies": [ + { "name": "puppetlabs/stdlib", "version_requirement": ">= 2.0.0" }, + { "name": "puppetlabs/apt", "version_requirement": ">= 0.0.3" }, + { "name": "puppetlabs/java", "version_requirement": ">= 1.0.1" }, + { "name": "darin/zypprepo", "version_requirement": ">= 1.0.1" } + ] +} diff --git a/manifests/modules/jenkins/spec/classes/jenkins_cli_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_cli_spec.rb old mode 100644 new mode 100755 index 60f9caf..baab5e6 --- a/manifests/modules/jenkins/spec/classes/jenkins_cli_spec.rb +++ b/manifests/modules/jenkins/spec/classes/jenkins_cli_spec.rb @@ -9,9 +9,14 @@ end context '$cli => true' do - let(:params) { { :cli => true } } + let(:params) {{ :cli => true, + :config_hash => { 'HTTP_PORT' => { 'value' => '9000' } } + }} it { should create_class('jenkins::cli') } it { should contain_exec('jenkins-cli') } + it { should contain_exec('reload-jenkins').with_command(/http:\/\/localhost:9000/) } + it { should contain_exec('safe-restart-jenkins') } + it { should contain_jenkins__sysconfig('HTTP_PORT').with_value('9000') } end end diff --git a/manifests/modules/jenkins/spec/classes/jenkins_config_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_config_spec.rb old mode 100644 new mode 100755 index e29e10a..3b84650 --- a/manifests/modules/jenkins/spec/classes/jenkins_config_spec.rb +++ b/manifests/modules/jenkins/spec/classes/jenkins_config_spec.rb @@ -10,7 +10,7 @@ context 'create config' do let(:params) { { :config_hash => { 'AJP_PORT' => { 'value' => '1234' } } }} - it { should contain_jenkins__sysconfig('AJP_PORT') } + it { should contain_jenkins__sysconfig('AJP_PORT').with_value('1234') } end end diff --git a/manifests/modules/jenkins/spec/classes/jenkins_firewall_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_firewall_spec.rb old mode 100644 new mode 100755 index 4b09827..b8390dc --- a/manifests/modules/jenkins/spec/classes/jenkins_firewall_spec.rb +++ b/manifests/modules/jenkins/spec/classes/jenkins_firewall_spec.rb @@ -8,5 +8,4 @@ context 'firewall' do it { should contain_firewall('500 allow Jenkins inbound traffic') } end - end diff --git a/manifests/modules/jenkins/spec/classes/jenkins_jobs_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_jobs_spec.rb new file mode 100755 index 0000000..58f87db --- /dev/null +++ b/manifests/modules/jenkins/spec/classes/jenkins_jobs_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'jenkins', :type => :module do + let(:facts) { { :osfamily => 'RedHat', :operatingsystem => 'RedHat' } } + + context 'jobs' do + context 'default' do + it { should contain_class('jenkins::jobs') } + end + + context 'with one job' do + let(:params) { { :job_hash => { 'build' => { 'config' => '' } } } } + it { should contain_jenkins__job('build').with_config('') } + end + + context 'with cli disabled' do + let(:params) { { :service_ensure => 'stopped', + :cli => false, + :job_hash => { 'build' => { 'config' => '' } } } } + it { expect { should compile }.to raise_error } + end + + end + +end diff --git a/manifests/modules/jenkins/spec/classes/jenkins_master_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_master_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/classes/jenkins_package_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_package_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/classes/jenkins_plugins_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_plugins_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/classes/jenkins_proxy_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_proxy_spec.rb old mode 100644 new mode 100755 index 45ca344..c1d1c50 --- a/manifests/modules/jenkins/spec/classes/jenkins_proxy_spec.rb +++ b/manifests/modules/jenkins/spec/classes/jenkins_proxy_spec.rb @@ -8,12 +8,22 @@ it { should_not contain_class('jenkins::proxy') } end - context 'with proxy config' do + context 'with basic proxy config' do let(:params) { { :proxy_host => 'myhost', :proxy_port => 1234 } } it { should create_class('jenkins::proxy') } it { should contain_file('/var/lib/jenkins/proxy.xml') } it { should contain_file('/var/lib/jenkins/proxy.xml').with(:content => /myhost<\/name>/) } it { should contain_file('/var/lib/jenkins/proxy.xml').with(:content => /1234<\/port>/) } + it { should contain_file('/var/lib/jenkins/proxy.xml').without(:content => //) } + end + + context 'with "no_proxy_list" proxy config' do + let(:params) { { :proxy_host => 'myhost', :proxy_port => 1234, :no_proxy_list => ['example.com','test.host.net'] } } + it { should create_class('jenkins::proxy') } + it { should contain_file('/var/lib/jenkins/proxy.xml') } + it { should contain_file('/var/lib/jenkins/proxy.xml').with(:content => /myhost<\/name>/) } + it { should contain_file('/var/lib/jenkins/proxy.xml').with(:content => /1234<\/port>/) } + it { should contain_file('/var/lib/jenkins/proxy.xml').with(:content => /example\.com\ntest\.host\.net<\/noProxyHost>/) } end end diff --git a/manifests/modules/jenkins/spec/classes/jenkins_repo_debian_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_repo_debian_spec.rb old mode 100644 new mode 100755 index cbb2958..3fdcceb --- a/manifests/modules/jenkins/spec/classes/jenkins_repo_debian_spec.rb +++ b/manifests/modules/jenkins/spec/classes/jenkins_repo_debian_spec.rb @@ -12,12 +12,18 @@ end context 'repo::debian' do + shared_examples 'an apt catalog' do + it { should contain_class('apt') } + end + describe 'default' do + it_behaves_like 'an apt catalog' it { should contain_apt__source('jenkins').with_location('http://pkg.jenkins-ci.org/debian') } end describe 'lts = true' do let(:params) { { :lts => true } } + it_behaves_like 'an apt catalog' it { should contain_apt__source('jenkins').with_location('http://pkg.jenkins-ci.org/debian-stable') } end end diff --git a/manifests/modules/jenkins/spec/classes/jenkins_repo_el_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_repo_el_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/classes/jenkins_repo_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_repo_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/classes/jenkins_repo_suse_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_repo_suse_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/classes/jenkins_service_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_service_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/classes/jenkins_slave_spec.rb b/manifests/modules/jenkins/spec/classes/jenkins_slave_spec.rb old mode 100644 new mode 100755 index bd23509..206d8e8 --- a/manifests/modules/jenkins/spec/classes/jenkins_slave_spec.rb +++ b/manifests/modules/jenkins/spec/classes/jenkins_slave_spec.rb @@ -5,10 +5,11 @@ shared_context 'a jenkins::slave catalog' do it { should contain_exec('get_swarm_client') } it { should contain_file('/etc/init.d/jenkins-slave') } - it { should contain_service('jenkins-slave') } + it { should contain_service('jenkins-slave').with(:enable => true, :ensure => 'running') } it { should contain_user('jenkins-slave_user').with_uid(nil) } # Let the different platform blocks define `slave_runtime_file` separately below - it { should contain_file(slave_runtime_file).with_content(/-fsroot \/home\/jenkins-slave/) } + it { should contain_file(slave_runtime_file).with_content(/^FSROOT="\/home\/jenkins-slave"$/) } + it { should contain_file(slave_runtime_file).without_content(/ -name /) } describe 'with ssl verification disabled' do let(:params) { { :disable_ssl_verification => true } } @@ -23,15 +24,29 @@ describe 'with a non-default $slave_home' do let(:home) { '/home/rspec-runner' } let(:params) { {:slave_home => home } } - it { should contain_file(slave_runtime_file).with_content(/-fsroot #{home}/) } + it { should contain_file(slave_runtime_file).with_content(/^FSROOT="#{home}"$/) } end + + describe 'with service disabled' do + let(:params) { {:enable => false, :ensure => 'stopped' } } + it { should contain_service('jenkins-slave').with(:enable => false, :ensure => 'stopped') } + end + end + + shared_examples 'using slave_name' do + it { should contain_file(slave_runtime_file).with_content(/^CLIENT_NAME="jenkins-slave"$/) } end describe 'RedHat' do let(:facts) { { :osfamily => 'RedHat', :operatingsystem => 'CentOS' } } - let(:slave_runtime_file) { '/etc/init.d/jenkins-slave' } + let(:slave_runtime_file) { '/etc/sysconfig/jenkins-slave' } it_behaves_like 'a jenkins::slave catalog' + + describe 'with slave_name' do + let(:params) { { :slave_name => 'jenkins-slave' } } + it_behaves_like 'using slave_name' + end end describe 'Debian' do @@ -39,6 +54,11 @@ let(:slave_runtime_file) { '/etc/default/jenkins-slave' } it_behaves_like 'a jenkins::slave catalog' + + describe 'with slave_name' do + let(:params) { { :slave_name => 'jenkins-slave' } } + it_behaves_like 'using slave_name' + end end describe 'Unknown' do diff --git a/manifests/modules/jenkins/spec/defines/jenkins_job_spec.rb b/manifests/modules/jenkins/spec/defines/jenkins_job_spec.rb new file mode 100755 index 0000000..f8d8bd6 --- /dev/null +++ b/manifests/modules/jenkins/spec/defines/jenkins_job_spec.rb @@ -0,0 +1,96 @@ +require 'spec_helper' + +describe 'jenkins::job' do + let(:title) { 'myjob' } + + describe 'with defaults' do + let(:params) {{ :config => '' }} + it { should contain_exec('jenkins create-job myjob') } + it { should contain_exec('jenkins update-job myjob') } + it { should contain_exec('jenkins enable-job myjob') } + it { should_not contain_exec('jenkins disable-job myjob') } + it { should_not contain_exec('jenkins delete-job myjob') } + end + + describe 'with job enabled' do + let(:params) {{ :enabled => 1 , :config => '' }} + it { should contain_exec('jenkins create-job myjob') } + it { should contain_exec('jenkins update-job myjob') } + it { should contain_exec('jenkins enable-job myjob') } + it { should_not contain_exec('jenkins disable-job myjob') } + it { should_not contain_exec('jenkins delete-job myjob') } + end + + describe 'with job disabled' do + let(:params) {{ :enabled => 0 , :config => '' }} + it { should contain_exec('jenkins create-job myjob') } + it { should contain_exec('jenkins update-job myjob') } + it { should_not contain_exec('jenkins enable-job myjob') } + it { should contain_exec('jenkins disable-job myjob') } + it { should_not contain_exec('jenkins delete-job myjob') } + end + + describe 'with job present' do + let(:params) {{ :ensure => 'present', :config => '' }} + it { should contain_exec('jenkins create-job myjob') } + it { should contain_exec('jenkins update-job myjob') } + it { should contain_exec('jenkins enable-job myjob') } + it { should_not contain_exec('jenkins disable-job myjob') } + it { should_not contain_exec('jenkins delete-job myjob') } + end + + describe 'with job absent' do + let(:params) {{ :ensure => 'absent', :config => '' }} + it { should_not contain_exec('jenkins create-job myjob') } + it { should_not contain_exec('jenkins update-job myjob') } + it { should_not contain_exec('jenkins enable-job myjob') } + it { should_not contain_exec('jenkins disable-job myjob') } + it { should contain_exec('jenkins delete-job myjob') } + end + + describe 'with unformatted config' do + unformatted_config = < + + ... + + "..." + +eos + formatted_config = < + + ... + + "..." + +eos + + let(:params) {{ :ensure => 'present', + :config => unformatted_config }} + it { should contain_file('/tmp/myjob-config.xml')\ + .with_content(formatted_config) } + end + + describe 'with config with single quotes' do + quotes = "" + let(:params) {{ :ensure => 'present', :config => quotes }} + it { should contain_file('/tmp/myjob-config.xml')\ + .with_content(/version="1\.0" encoding="UTF-8"/) } + end + + describe 'with config with empty tags' do + empty_tags = '' + let(:params) {{ :ensure => 'present', :config => empty_tags }} + it { should contain_file('/tmp/myjob-config.xml')\ + .with_content('') } + end + + describe 'with config with "' do + quotes = "the dog said "woof"" + let(:params) {{ :ensure => 'present', :config => quotes }} + it { should contain_file('/tmp/myjob-config.xml')\ + .with_content('the dog said "woof"') } + end + +end diff --git a/manifests/modules/jenkins/spec/defines/jenkins_plugin_spec.rb b/manifests/modules/jenkins/spec/defines/jenkins_plugin_spec.rb old mode 100644 new mode 100755 index 0de3099..66418a8 --- a/manifests/modules/jenkins/spec/defines/jenkins_plugin_spec.rb +++ b/manifests/modules/jenkins/spec/defines/jenkins_plugin_spec.rb @@ -3,14 +3,29 @@ describe 'jenkins::plugin' do let(:title) { 'myplug' } - it { should contain_file('/var/lib/jenkins') } - it { should contain_file('/var/lib/jenkins/plugins') } + shared_examples 'manages plugins dirs' do + it { should contain_file('/var/lib/jenkins') } + it { should contain_file('/var/lib/jenkins/plugins') } + end + + include_examples 'manages plugins dirs' it { should contain_group('jenkins') } it { should contain_user('jenkins').with('home' => '/var/lib/jenkins') } + context 'with my plugin parent directory already defined' do + let(:pre_condition) do + [ + "file { '/var/lib/jenkins' : ensure => directory, }", + ] + end + + include_examples 'manages plugins dirs' + end + + describe 'without version' do it { should contain_exec('download-myplug').with( - :command => 'rm -rf myplug myplug.* && wget --no-check-certificate http://updates.jenkins-ci.org/latest/myplug.hpi', + :command => 'rm -rf myplug myplug.hpi myplug.jpi && wget --no-check-certificate http://updates.jenkins-ci.org/latest/myplug.hpi', :environment => nil )} it { should contain_file('/var/lib/jenkins/plugins/myplug.hpi')} @@ -20,7 +35,7 @@ let(:params) { { :version => '1.2.3' } } it { should contain_exec('download-myplug').with( - :command => 'rm -rf myplug myplug.* && wget --no-check-certificate http://updates.jenkins-ci.org/download/plugins/myplug/1.2.3/myplug.hpi', + :command => 'rm -rf myplug myplug.hpi myplug.jpi && wget --no-check-certificate http://updates.jenkins-ci.org/download/plugins/myplug/1.2.3/myplug.hpi', :environment => nil ) } it { should contain_file('/var/lib/jenkins/plugins/myplug.hpi')} @@ -54,4 +69,60 @@ it { should contain_exec('download-myplug').with(:environment => ["http_proxy=proxy.company.com:8080", "https_proxy=proxy.company.com:8080"]) } end + + describe 'with a custom update center' do + shared_examples 'execute the right fetch command' do + it 'should wget the plugin' do + expect(subject).to contain_exec('download-git').with({ + :command => "rm -rf git git.hpi git.jpi && wget --no-check-certificate #{expected_url}", + }) + end + end + + let(:title) { 'git' } + + context 'by default' do + context 'with a version' do + let(:version) { '1.3.3.7' } + let(:params) { {:version => version} } + let(:expected_url) do + "http://updates.jenkins-ci.org/download/plugins/#{title}/#{version}/#{title}.hpi" + end + + include_examples 'execute the right fetch command' + end + + context 'without a version' do + let(:expected_url) do + "http://updates.jenkins-ci.org/latest/#{title}.hpi" + end + + include_examples 'execute the right fetch command' + end + end + + context 'with a custom update_url' do + let(:update_url) { 'http://rspec' } + + context 'without a version' do + let(:params) { {:update_url => update_url} } + let(:expected_url) do + "#{update_url}/latest/#{title}.hpi" + end + + include_examples 'execute the right fetch command' + end + + context 'with a version' do + let(:version) { '1.2.3' } + let(:params) { {:update_url => update_url, :version => version} } + let(:expected_url) do + "#{update_url}/download/plugins/#{title}/#{version}/#{title}.hpi" + end + + include_examples 'execute the right fetch command' + end + end + end + end diff --git a/manifests/modules/jenkins/spec/defines/jenkins_sysconfig_spec.rb b/manifests/modules/jenkins/spec/defines/jenkins_sysconfig_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/functions/jenkins_port_spec.rb b/manifests/modules/jenkins/spec/functions/jenkins_port_spec.rb new file mode 100755 index 0000000..c777b50 --- /dev/null +++ b/manifests/modules/jenkins/spec/functions/jenkins_port_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'jenkins_port' do + let(:facts) { { :osfamily => 'RedHat', :operatingsystem => 'RedHat' } } + let(:pre_condition) { 'include ::jenkins' } + # Lazily loaded function call to be used in examples. Not overwriting + # `subject` since rspec-puppet is already defining that to return the + # function + let(:port) { + subject.call([]) + } + + it 'should default to 8080' do + expect(port).to eql '8080' + end + + context 'with overwritten configuration' do + let(:pre_condition) do + <<-ENDPUPPET + class { 'jenkins': + config_hash => {'HTTP_PORT' => {'value' => '1337'}}, + } + ENDPUPPET + end + + it 'should be our overwritten port' do + expect(port).to eql('1337') + end + end +end diff --git a/manifests/modules/jenkins/spec/helpers/rspechelpers.rb b/manifests/modules/jenkins/spec/helpers/rspechelpers.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/serverspec/spec_helper.rb b/manifests/modules/jenkins/spec/serverspec/spec_helper.rb new file mode 100755 index 0000000..6f572bc --- /dev/null +++ b/manifests/modules/jenkins/spec/serverspec/spec_helper.rb @@ -0,0 +1,12 @@ +require_relative '../spec_helper' + +########################################################################### +# Required for :type => :serverspec +require 'serverspec' +require 'pathname' +require 'net/ssh' +# Need to upll these into the global scope before we evaluate all our RSpec +# files :( +include SpecInfra::Helper::Ssh +include SpecInfra::Helper::DetectOS +########################################################################### diff --git a/manifests/modules/jenkins/spec/serverspec/ubuntu-precise/config.yml b/manifests/modules/jenkins/spec/serverspec/ubuntu-precise/config.yml new file mode 100755 index 0000000..b1290b4 --- /dev/null +++ b/manifests/modules/jenkins/spec/serverspec/ubuntu-precise/config.yml @@ -0,0 +1,3 @@ +ami: "ami-69db9b59" +region: "us-west-2" +username: "ubuntu" diff --git a/manifests/modules/jenkins/spec/serverspec/ubuntu-precise/manifests/default.pp b/manifests/modules/jenkins/spec/serverspec/ubuntu-precise/manifests/default.pp new file mode 100755 index 0000000..591aa0f --- /dev/null +++ b/manifests/modules/jenkins/spec/serverspec/ubuntu-precise/manifests/default.pp @@ -0,0 +1,9 @@ + +node default { + class { + 'jenkins': + cli => true, + } + + notice("Hello world from ${::hostname}}") +} diff --git a/manifests/modules/jenkins/spec/serverspec/ubuntu-precise/precise_spec.rb b/manifests/modules/jenkins/spec/serverspec/ubuntu-precise/precise_spec.rb new file mode 100755 index 0000000..98929df --- /dev/null +++ b/manifests/modules/jenkins/spec/serverspec/ubuntu-precise/precise_spec.rb @@ -0,0 +1,29 @@ +require_relative '../spec_helper' + +describe 'Ubuntu 12.04 (Precise)', :type => :serverspec do + describe 'non-Jenkins properties' do + describe port(22) do + it { should be_listening } + end + + describe port(80) do + it { should_not be_listening } + end + end + + describe 'Jenkins-specific configuration' do + describe port(8080) do + it { pending "Jenkins probably isn't running"; + should be_listening } + end + + describe file('/usr/share/jenkins/jenkins-cli.jar') do + it { should be_file } + end + + describe service('jenkins') do + it { should be_running } + it { should be_enabled } + end + end +end diff --git a/manifests/modules/jenkins/spec/spec_helper.rb b/manifests/modules/jenkins/spec/spec_helper.rb old mode 100644 new mode 100755 index 70b8bea..ad1348b --- a/manifests/modules/jenkins/spec/spec_helper.rb +++ b/manifests/modules/jenkins/spec/spec_helper.rb @@ -11,6 +11,7 @@ # Override puppetlabs_spec_helper's stupid setting of mock_with to :mocha, # which is a totally piece of garbage mocking library c.mock_with :rspec + c.deprecation_stream = '/dev/null' c.include(Jenkins::RSpecHelpers) end diff --git a/manifests/modules/jenkins/spec/unit/facter/plugins_spec.rb b/manifests/modules/jenkins/spec/unit/facter/plugins_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/unit/jenkins_plugins_spec.rb b/manifests/modules/jenkins/spec/unit/jenkins_plugins_spec.rb old mode 100644 new mode 100755 index 4dd7bc1..0d1a266 --- a/manifests/modules/jenkins/spec/unit/jenkins_plugins_spec.rb +++ b/manifests/modules/jenkins/spec/unit/jenkins_plugins_spec.rb @@ -141,6 +141,15 @@ it { should be_instance_of Hash } # Our fixture file currently has 870 plugins in it its(:size) { should eql 870 } + + context 'when json is not available' do + before :each do + expect(::Kernel).to receive(:require).with('json').and_raise(LoadError) + expect(::Kernel).to receive(:require).with('puppet/jenkins/okjson').and_call_original + end + + it { should be_instance_of Hash } + end end let(:git_plugin) do diff --git a/manifests/modules/jenkins/spec/unit/jenkins_provider_spec.rb b/manifests/modules/jenkins/spec/unit/jenkins_provider_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/spec/unit/jenkins_spec.rb b/manifests/modules/jenkins/spec/unit/jenkins_spec.rb old mode 100644 new mode 100755 diff --git a/manifests/modules/jenkins/templates/jenkins-slave-defaults.Debian b/manifests/modules/jenkins/templates/jenkins-slave-defaults.erb old mode 100644 new mode 100755 similarity index 56% rename from manifests/modules/jenkins/templates/jenkins-slave-defaults.Debian rename to manifests/modules/jenkins/templates/jenkins-slave-defaults.erb index 42a6936..b78000b --- a/manifests/modules/jenkins/templates/jenkins-slave-defaults.Debian +++ b/manifests/modules/jenkins/templates/jenkins-slave-defaults.erb @@ -29,6 +29,10 @@ RUN_STANDALONE=true # log location. this may be a syslog facility.priority JENKINS_SLAVE_LOG=/var/log/$NAME/$NAME.log +# slave mode, can be either 'normal' (utilize this slave as much as possible) +# or 'exclusive' (leave this machine for tied jobs only). +JENKINS_SLAVE_MODE=<%= @slave_mode -%> + # OS LIMITS SETUP # comment this out to observe /etc/security/limits.conf # this is on by default because http://github.com/jenkinsci/jenkins/commit/2fb288474e980d0e7ff9c4a3b768874835a3e92e @@ -36,11 +40,39 @@ JENKINS_SLAVE_LOG=/var/log/$NAME/$NAME.log # descriptors are forced to 1024 regardless of /etc/security/limits.conf MAXOPENFILES=8192 -MASTER_URL="<%= @masterurl_flag -%> <%= @labels_flag -%>" +MASTER_URL="<%= @masterurl -%>" +LABELS="<%= @labels -%>" EXECUTORS=<%= @executors -%> -CLIENT_NAME=<%= @fqdn -%> +CLIENT_NAME="<%= @slave_name -%>" + +FSROOT="<%= @slave_home -%>" + +# credentials +JENKINS_USERNAME="<%= @ui_user -%>" +JENKINS_PASSWORD="<%= @ui_pass -%>" + +OTHER_ARGS="<%= '-disableSslVerification' if @disable_ssl_verification -%>" + +if [ -n "$JENKINS_USERNAME" ]; then + CREDENTIALS_ARG="-username $JENKINS_USERNAME -password $JENKINS_PASSWORD" +fi + +if [ -n "$CLIENT_NAME" ]; then + NAME_ARG="-name $CLIENT_NAME" +fi + +if [ -n "$MASTER_URL" ]; then + MASTER_URL_ARG="-master $MASTER_URL" +fi + +if [ -n "$LABELS" ]; then + LABELS_ARG="-labels '$LABELS'" +fi +if [ -n "$FSROOT" ]; then + FSROOT_ARG="-fsroot '$FSROOT'" +fi -JENKINS_SLAVE_ARGS="<%= @ui_user_flag -%> <%= @ui_pass_flag -%> -name $CLIENT_NAME <%= @disable_ssl_verification_flag -%> -executors $EXECUTORS $MASTER_URL <%= @fsroot_flag -%>" +JENKINS_SLAVE_ARGS="-mode $JENKINS_SLAVE_MODE -executors $EXECUTORS $CREDENTIALS_ARG $NAME_ARG $MASTER_URL_ARG $LABELS_ARG $FSROOT_ARG $OTHER_ARGS" diff --git a/manifests/modules/jenkins/templates/proxy.xml.erb b/manifests/modules/jenkins/templates/proxy.xml.erb old mode 100644 new mode 100755 index 1941f3c..38c0878 --- a/manifests/modules/jenkins/templates/proxy.xml.erb +++ b/manifests/modules/jenkins/templates/proxy.xml.erb @@ -1,4 +1,7 @@ - <%= scope.lookupvar('::jenkins::proxy_host') %> - <%= scope.lookupvar('::jenkins::proxy_port') %> + <%= @proxy_host %> + <%= @proxy_port %> +<% if @no_proxy_list -%> + <%= @no_proxy_list.join("\n") %> +<% end -%> diff --git a/manifests/modules/jenkins/tests/RedHatEnterpriseServer.pp b/manifests/modules/jenkins/tests/RedHatEnterpriseServer.pp old mode 100644 new mode 100755 index 82da077..c9afbce --- a/manifests/modules/jenkins/tests/RedHatEnterpriseServer.pp +++ b/manifests/modules/jenkins/tests/RedHatEnterpriseServer.pp @@ -5,4 +5,25 @@ 'ansicolor' : version => '0.3.1'; } + + jenkins::job { + 'build' : + config => ' + + + + false + + + true + false + false + false + + false + + + +'; + } } diff --git a/manifests/modules/jenkins/tests/Ubuntu.pp b/manifests/modules/jenkins/tests/Ubuntu.pp old mode 100644 new mode 100755 index 82da077..c9afbce --- a/manifests/modules/jenkins/tests/Ubuntu.pp +++ b/manifests/modules/jenkins/tests/Ubuntu.pp @@ -5,4 +5,25 @@ 'ansicolor' : version => '0.3.1'; } + + jenkins::job { + 'build' : + config => ' + + + + false + + + true + false + false + false + + false + + + +'; + } } From c7978876576cd13673cf319d0df28be2134685f4 Mon Sep 17 00:00:00 2001 From: Kevin Horst Date: Mon, 22 Dec 2014 15:20:59 +0100 Subject: [PATCH 4/4] remove iptables disable service call and add jenkins firewall configuration --- manifests/init.pp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/manifests/init.pp b/manifests/init.pp index b8c8369..67758eb 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -37,7 +37,6 @@ Managed by Puppet.\n" } -include jenkins include git include drush include imagemagick @@ -83,11 +82,8 @@ class { '::mysql::server': } -# don't use a firewall, see http://stackoverflow.com/questions/5984217 -service { iptables: - ensure => stopped, - hasstatus => "true", - status => "true" +class { 'jenkins': + configure_firewall => true } # Install git and dependencies, see