From ffde12030ce62d92a1a2466ee08ed6133a507e52 Mon Sep 17 00:00:00 2001 From: "Phil Friderici (ephifre)" Date: Thu, 2 Apr 2015 17:06:15 +0200 Subject: [PATCH 1/5] parameterize sftp_server related settings --- Gemfile | 2 + README.md | 29 +++++++++ manifests/init.pp | 17 ++++++ spec/classes/init_spec.rb | 125 ++++++++++++++++++++++++++++++++++++++ templates/sshd_config.erb | 16 +++++ 5 files changed, 189 insertions(+) diff --git a/Gemfile b/Gemfile index 3b888ac..a205cc7 100644 --- a/Gemfile +++ b/Gemfile @@ -5,3 +5,5 @@ gem 'puppet', puppetversion gem 'puppetlabs_spec_helper', '>= 0.1.0' gem 'puppet-lint', '>= 1.0.0' gem 'facter', '>= 1.7.0' +gem 'rspec-puppet', '~>1.0' +gem 'rspec', '~>2.0' diff --git a/README.md b/README.md index 1808806..e4432ea 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,35 @@ Specifies the maximum number of open sessions permitted per network connection. - *Default*: undef +sshd_config_chrootdirectory +--------------------------- +String with absolute path for the ChrootDirectory directive for the SSH daemon. + +- *Default*: undef + +sshd_config_forcecommand +--------------------------- +String with command for the ForceCommand directive for the SSH daemon. + +- *Default*: undef + +sshd_config_match +----------------- +Hash for matches with nested arrays for options for the Match directive for the SSH daemon. +Match directive is supported on SSH >= 5.x. + +- *Default*: undef + +- *Hiera example*: +
+ssh::sshd_config_match:
+  'User JohnDoe':
+    - 'AllowTcpForwarding yes'
+  'Address 2.4.2.0':
+    - 'X11Forwarding yes'
+    - 'PasswordAuthentication no'
+
+ keys ---- Hash of keys for user's ~/.ssh/authorized_keys diff --git a/manifests/init.pp b/manifests/init.pp index 986252e..963deb7 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -49,6 +49,9 @@ class ssh ( $sshd_config_allowgroups = undef, $sshd_config_maxstartups = undef, $sshd_config_maxsessions = undef, + $sshd_config_chrootdirectory = undef, + $sshd_config_forcecommand = undef, + $sshd_config_match = undef, $sshd_banner_content = undef, $sshd_banner_owner = 'root', $sshd_banner_group = 'root', @@ -452,6 +455,20 @@ class ssh ( } } + if $sshd_config_chrootdirectory != undef { + validate_absolute_path($sshd_config_chrootdirectory) + } + + if $sshd_config_forcecommand != undef { + if is_string($sshd_config_forcecommand) == false { + fail("ssh::sshd_config_forcecommand must be a string. Detected value is ${sshd_config_forcecommand}.") + } + } + + if $sshd_config_match != undef { + validate_hash($sshd_config_match) + } + if $sshd_config_strictmodes != undef { validate_re($sshd_config_strictmodes, '^(yes|no)$', "ssh::sshd_config_strictmodes may be either 'yes' or 'no' and is set to <${sshd_config_strictmodes}>.") } diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb index 4a263a1..0a3e687 100644 --- a/spec/classes/init_spec.rb +++ b/spec/classes/init_spec.rb @@ -99,6 +99,9 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^StrictModes/) } it { should_not contain_file('sshd_config').with_content(/^MaxStartups/) } it { should_not contain_file('sshd_config').with_content(/^MaxSessions/) } + it { should contain_file('sshd_config').with_content(/^#ChrootDirectory none/) } + it { should contain_file('sshd_config').without_content(/^ForceCommand/) } + it { should contain_file('sshd_config').without_content(/^Match/) } it { should contain_file('sshd_config').with_content(/^AcceptEnv L.*$/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } @@ -230,6 +233,9 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^StrictModes/) } it { should_not contain_file('sshd_config').with_content(/^MaxStartups/) } it { should_not contain_file('sshd_config').with_content(/^MaxSessions/) } + it { should contain_file('sshd_config').with_content(/^#ChrootDirectory none/) } + it { should contain_file('sshd_config').without_content(/^ForceCommand/) } + it { should contain_file('sshd_config').without_content(/^Match/) } it { should contain_file('sshd_config').with_content(/^ServerKeyBits 768$/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } @@ -344,6 +350,9 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^StrictModes/) } it { should_not contain_file('sshd_config').with_content(/^MaxStartups/) } it { should_not contain_file('sshd_config').with_content(/^MaxSessions/) } + it { should contain_file('sshd_config').with_content(/^#ChrootDirectory none/) } + it { should contain_file('sshd_config').without_content(/^ForceCommand/) } + it { should contain_file('sshd_config').without_content(/^Match/) } it { should contain_file('sshd_config').with_content(/^ServerKeyBits 768$/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } @@ -457,6 +466,9 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^StrictModes/) } it { should_not contain_file('sshd_config').with_content(/^MaxStartups/) } it { should_not contain_file('sshd_config').with_content(/^MaxSessions/) } + it { should contain_file('sshd_config').with_content(/^#ChrootDirectory none/) } + it { should contain_file('sshd_config').without_content(/^ForceCommand/) } + it { should contain_file('sshd_config').without_content(/^Match/) } it { should contain_file('sshd_config').with_content(/^ServerKeyBits 768$/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } @@ -578,6 +590,9 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^StrictModes/) } it { should_not contain_file('sshd_config').with_content(/^MaxStartups/) } it { should_not contain_file('sshd_config').with_content(/^MaxSessions/) } + it { should contain_file('sshd_config').with_content(/^#ChrootDirectory none/) } + it { should contain_file('sshd_config').without_content(/^ForceCommand/) } + it { should contain_file('sshd_config').without_content(/^Match/) } it { should contain_file('ssh_config').without_content(/^\s*Ciphers/) } it { should contain_file('ssh_config').without_content(/^\s*MACs/) } it { should contain_file('ssh_config').without_content(/^\s*DenyUsers/) } @@ -698,6 +713,9 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^StrictModes/) } it { should_not contain_file('sshd_config').with_content(/^MaxStartups/) } it { should_not contain_file('sshd_config').with_content(/^MaxSessions/) } + it { should contain_file('sshd_config').with_content(/^#ChrootDirectory none/) } + it { should contain_file('sshd_config').without_content(/^ForceCommand/) } + it { should contain_file('sshd_config').without_content(/^Match/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } it { should contain_file('sshd_config').without_content(/^\s*DenyUsers/) } @@ -818,6 +836,9 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^StrictModes/) } it { should_not contain_file('sshd_config').with_content(/^MaxStartups/) } it { should_not contain_file('sshd_config').with_content(/^MaxSessions/) } + it { should contain_file('sshd_config').with_content(/^#ChrootDirectory none/) } + it { should contain_file('sshd_config').without_content(/^ForceCommand/) } + it { should contain_file('sshd_config').without_content(/^Match/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } it { should contain_file('sshd_config').without_content(/^\s*DenyUsers/) } @@ -934,6 +955,9 @@ describe 'ssh' do :sshd_config_syslog_facility => 'DAEMON', :sshd_config_login_grace_time => '60', :permit_root_login => 'no', + :sshd_config_chrootdirectory => '/chrootdir', + :sshd_config_forcecommand => '/force/command --with-parameter 242', + :sshd_config_match => { 'User JohnDoe' => [ 'AllowTcpForwarding yes', ], }, :sshd_config_challenge_resp_auth => 'no', :sshd_config_print_motd => 'no', :sshd_config_use_dns => 'no', @@ -1024,6 +1048,9 @@ describe 'ssh' do it { should contain_file('sshd_config').with_content(/^StrictModes yes$/) } it { should_not contain_file('sshd_config').with_content(/^MaxStartups/) } it { should_not contain_file('sshd_config').with_content(/^MaxSessions/) } + it { should contain_file('sshd_config').with_content(/^ChrootDirectory \/chrootdir$/) } + it { should contain_file('sshd_config').with_content(/^ForceCommand \/force\/command --with-parameter 242$/) } + it { should contain_file('sshd_config').with_content(/^Match User JohnDoe\n AllowTcpForwarding yes\Z/) } it { should contain_file('sshd_config').with_content(/^\s*Ciphers aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,arcfour,aes192-cbc,aes256-cbc$/) } it { should contain_file('sshd_config').with_content(/^\s*MACs hmac-md5-etm@openssh.com,hmac-sha1-etm@openssh.com$/) } it { should contain_file('sshd_config').with_content(/^\s*DenyUsers root lusers$/) } @@ -1045,6 +1072,104 @@ describe 'ssh' do } end + describe 'sshd_config_chrootdirectory param' do + let :facts do + { + :fqdn => 'monkey.example.com', + :osfamily => 'RedHat', + :root_home => '/root', + :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' + } + end + ['/chrootdir/subdir','/baby/one/more/test',].each do |value| + context "set to valid #{value} (as #{value.class})" do + let (:params) {{'sshd_config_chrootdirectory' => value }} + + it { should contain_file('sshd_config').with_content(/^ChrootDirectory #{value}$/) } + end + end + + [true,'invalid','invalid/path/',3,2.42,['array'],a = { 'ha' => 'sh' }].each do |value| + context "set to invalid #{value} (as #{value.class})" do + let (:params) {{'sshd_config_chrootdirectory' => value }} + + it 'should fail' do + expect { + should + }.to raise_error(Puppet::Error, /is not an absolute path/) + end + end + end + + end + + describe 'sshd_config_forcecommand param' do + let :facts do + { + :fqdn => 'monkey.example.com', + :osfamily => 'RedHat', + :root_home => '/root', + :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' + } + end + ['/bin/command','/bin/command -parameters','/bin/command --parameters','/bin/command /parameters'].each do |value| + context "set to valid #{value} (as #{value.class})" do + let (:params) {{'sshd_config_forcecommand' => value }} + + it { should contain_file('sshd_config').with_content(/^ForceCommand #{value}$/) } + end + end + + [true,3,2.42,['array'],a = { 'ha' => 'sh' }].each do |value| + context "set to invalid #{value} (as #{value.class})" do + let (:params) {{'sshd_config_forcecommand' => value }} + + it 'should fail' do + expect { + should + }.to raise_error(Puppet::Error, /ssh::sshd_config_forcecommand must be a string. Detected value is #{value}/) + end + end + end + + end + + describe 'sshd_config_match param' do + # match and rules get alphabetically sorted by template, matches should be the last options in sshd_config (regex verify with= \Z) + let :facts do + { + :fqdn => 'monkey.example.com', + :osfamily => 'RedHat', + :root_home => '/root', + :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' + } + end + + context 'set to valid hash containing nested arrays' do + let(:params) do + { :sshd_config_match => { + 'User JohnDoe' => [ 'AllowTcpForwarding yes', ], + 'Addresss 2.4.2.0' => [ 'X11Forwarding yes', 'PasswordAuthentication no', ], + }, + } + end + + it { should contain_file('sshd_config').with_content(/^Match Addresss 2.4.2.0\n PasswordAuthentication no\n X11Forwarding yes\nMatch User JohnDoe\n AllowTcpForwarding yes\Z/) } + end + + [true,'string',3,2.42,['array']].each do |value| + context "set to invalid #{value} (as #{value.class})" do + let (:params) {{'sshd_config_match' => value }} + it 'should fail' do + expect { + should + }.to raise_error(Puppet::Error, /is not a Hash/) + end + end + end + + end + describe 'sshd_listen_address param' do context 'when set to an array' do let :facts do diff --git a/templates/sshd_config.erb b/templates/sshd_config.erb index 43f0231..23a1a70 100644 --- a/templates/sshd_config.erb +++ b/templates/sshd_config.erb @@ -166,7 +166,14 @@ MaxSessions <%= sshd_config_maxsessions %> <% end -%> #PermitTunnel no +<% if @sshd_config_chrootdirectory -%> +ChrootDirectory <%= @sshd_config_chrootdirectory %> +<% else -%> #ChrootDirectory none +<% end -%> +<% if @sshd_config_forcecommand -%> +ForceCommand <%= @sshd_config_forcecommand %> +<% end -%> # no default banner path #Banner none @@ -196,3 +203,12 @@ AllowUsers <%= @sshd_config_allowusers_real.join(' ') %> <% if @sshd_config_allowgroups -%> AllowGroups <%= @sshd_config_allowgroups_real.join(' ') %> <% end -%> + +<% if @sshd_config_match -%> +<% @sshd_config_match.sort.each do |key, hash| -%> +Match <%= key %> +<% hash.sort.each do |values| -%> + <%= values %> +<% end -%> +<% end -%> +<% end -%> From 6d90e568b6874ca7e08fd834d79785914a325bfc Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Tue, 7 Apr 2015 13:51:30 -0400 Subject: [PATCH 2/5] Deprecate Modulefile in favor of metadata.json --- Modulefile | 12 ------------ metadata.json | 11 ++++------- 2 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 Modulefile diff --git a/Modulefile b/Modulefile deleted file mode 100644 index 575d1fb..0000000 --- a/Modulefile +++ /dev/null @@ -1,12 +0,0 @@ -name 'ghoneycutt-ssh' -version '3.24.0' -source 'git://github.com/ghoneycutt/puppet-module-ssh.git' -author 'ghoneycutt' -license 'Apache License, Version 2.0' -summary 'Manages SSH' -description 'Manage SSH' -project_page 'https://github.com/ghoneycutt/puppet-module-ssh' - -dependency 'puppetlabs/stdlib', '>= 3.2.0' -dependency 'ghoneycutt/common', '>= 1.0.2' -dependency 'puppetlabs/firewall', '>= 0.2.1' diff --git a/metadata.json b/metadata.json index 7cf70b4..c3f0465 100644 --- a/metadata.json +++ b/metadata.json @@ -3,7 +3,7 @@ "version": "3.24.0", "author": "ghoneycutt", "summary": "Manages SSH", - "license": "Apache License, Version 2.0", + "license": "Apache-2.0", "source": "git://github.com/ghoneycutt/puppet-module-ssh.git", "project_page": "https://github.com/ghoneycutt/puppet-module-ssh", "issues_url": "https://github.com/ghoneycutt/puppet-module-ssh/issues", @@ -78,12 +78,9 @@ } ], "description": "Manage SSH", - "types": [ - - ], "dependencies": [ - {"name":"puppetlabs/stdlib","version_requirement":">= 3.2.0"}, - {"name":"ghoneycutt/common","version_requirement":">= 1.0.2"}, - {"name":"puppetlabs/firewall","version_requirement":">= 0.2.1"} + {"name":"puppetlabs/stdlib","version_requirement":">= 3.2.0 < 5.0.0"}, + {"name":"ghoneycutt/common","version_requirement":">= 1.0.2 < 2.0.0"}, + {"name":"puppetlabs/firewall","version_requirement":">= 0.2.1 < 2.0.0"} ] } From bc15c75c7777b546d1bd0de4c4d9028a094c8440 Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Tue, 7 Apr 2015 13:51:45 -0400 Subject: [PATCH 3/5] Update testing environment and test metadata --- .travis.yml | 50 ++++++++++++++++++++++++++++++++------------------ Gemfile | 16 ++++++++++++---- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 08e6e8e..7c3cee8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,39 @@ --- -env: -- PUPPET_VERSION=3.3.2 -- PUPPET_VERSION=3.4.2 -- PUPPET_VERSION=3.5.1 -- PUPPET_VERSION=3.6.0 -- PUPPET_VERSION=3.7.0 -notifications: -email: false +language: ruby + rvm: -- 1.8.7 -- 1.9.3 -- 2.0.0 -- 2.1.0 + - 1.8.7 + - 1.9.3 + - 2.0.0 + - 2.1.0 + +env: + matrix: + - PUPPET_GEM_VERSION="~> 3.1.0" + - PUPPET_GEM_VERSION="~> 3.2.0" + - PUPPET_GEM_VERSION="~> 3.3.0" + - PUPPET_GEM_VERSION="~> 3.4.0" + - PUPPET_GEM_VERSION="~> 3.5.1" + - PUPPET_GEM_VERSION="~> 3.6.0" + - PUPPET_GEM_VERSION="~> 3.7.0" + +sudo: false + +script: 'bundle exec metadata-json-lint metadata.json && bundle exec rake validate && bundle exec rake lint && SPEC_OPTS="--format documentation" bundle exec rake spec' + matrix: fast_finish: true exclude: + - rvm: 2.0.0 + env: PUPPET_GEM_VERSION="~> 3.1.0" - rvm: 2.1.0 - env: PUPPET_VERSION=3.3.2 + env: PUPPET_GEM_VERSION="~> 3.1.0" - rvm: 2.1.0 - env: PUPPET_VERSION=3.4.2 -language: ruby -before_script: 'gem install --no-ri --no-rdoc bundler' -script: 'bundle exec rake validate && bundle exec rake lint && SPEC_OPTS="--format documentation" bundle exec rake spec' -gemfile: Gemfile + env: PUPPET_GEM_VERSION="~> 3.2.0" + - rvm: 2.1.0 + env: PUPPET_GEM_VERSION="~> 3.3.0" + - rvm: 2.1.0 + env: PUPPET_GEM_VERSION="~> 3.4.0" + +notifications: + email: false diff --git a/Gemfile b/Gemfile index a205cc7..0c37c0d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,17 @@ source 'https://rubygems.org' -puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 3.3'] -gem 'puppet', puppetversion +if puppetversion = ENV['PUPPET_GEM_VERSION'] + gem 'puppet', puppetversion, :require => false +else + gem 'puppet', :require => false +end + +gem 'metadata-json-lint' gem 'puppetlabs_spec_helper', '>= 0.1.0' gem 'puppet-lint', '>= 1.0.0' gem 'facter', '>= 1.7.0' -gem 'rspec-puppet', '~>1.0' -gem 'rspec', '~>2.0' + +# rspec must be v2 for ruby 1.8.7 +if RUBY_VERSION >= '1.8.7' and RUBY_VERSION < '1.9' + gem 'rspec', '~> 2.0' +end From 80f77a20007d50f8c8dac3ed1748fb22df596e2f Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Tue, 7 Apr 2015 13:55:49 -0400 Subject: [PATCH 4/5] Work with Phil on PR Numbers are treated as strings, so removed those from the test. Simplified validation of sshd_config_forcecommand to simply check if it is a string. --- manifests/init.pp | 4 +--- spec/classes/init_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/manifests/init.pp b/manifests/init.pp index 963deb7..063aa6a 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -460,9 +460,7 @@ class ssh ( } if $sshd_config_forcecommand != undef { - if is_string($sshd_config_forcecommand) == false { - fail("ssh::sshd_config_forcecommand must be a string. Detected value is ${sshd_config_forcecommand}.") - } + validate_string($sshd_config_forcecommand) } if $sshd_config_match != undef { diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb index 0a3e687..cdbf06d 100644 --- a/spec/classes/init_spec.rb +++ b/spec/classes/init_spec.rb @@ -1120,14 +1120,14 @@ describe 'ssh' do end end - [true,3,2.42,['array'],a = { 'ha' => 'sh' }].each do |value| + [true,['array'],a = { 'ha' => 'sh' }].each do |value| context "set to invalid #{value} (as #{value.class})" do let (:params) {{'sshd_config_forcecommand' => value }} it 'should fail' do expect { should - }.to raise_error(Puppet::Error, /ssh::sshd_config_forcecommand must be a string. Detected value is #{value}/) + }.to raise_error(Puppet::Error, /is not a string/) end end end From 8d80bd6c40667ee01e2206a96c3b4c3a3c98f18f Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Tue, 7 Apr 2015 13:59:09 -0400 Subject: [PATCH 5/5] Pin rspec-puppet to v1.0 --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 0c37c0d..3d3e1de 100644 --- a/Gemfile +++ b/Gemfile @@ -10,6 +10,7 @@ gem 'metadata-json-lint' gem 'puppetlabs_spec_helper', '>= 0.1.0' gem 'puppet-lint', '>= 1.0.0' gem 'facter', '>= 1.7.0' +gem 'rspec-puppet', '~>1.0' # rspec must be v2 for ruby 1.8.7 if RUBY_VERSION >= '1.8.7' and RUBY_VERSION < '1.9'