From 6d6fe4c030760b7079659d04f7c591e2bcdedb56 Mon Sep 17 00:00:00 2001 From: Kalle Kiviaho Date: Tue, 11 Nov 2014 08:39:12 +0100 Subject: [PATCH 1/3] Add support for MaxStartups and MaxSessions in sshd_config MaxStartups and MaxSessions control how many connections can be made to a ssh server. Corrected faulty commented value for MaxStartups. --- README.md | 12 +++++++ manifests/init.pp | 13 ++++++++ spec/classes/init_spec.rb | 70 +++++++++++++++++++++++++++++++++++++++ templates/sshd_config.erb | 13 +++++++- 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0edb4e9..29cc0e5 100644 --- a/README.md +++ b/README.md @@ -365,6 +365,18 @@ Array of users for the AllowUsers setting in sshd_config. - *Default*: undef +sshd_config_maxstartups +----------------------- +Specifies the maximum number of concurrent unauthenticated connections to the SSH daemon. + +- *Default*: undef + +sshd_config_maxsessions +----------------------- +Specifies the maximum number of open sessions permitted per network connection. + +- *Default*: undef + keys ---- Hash of keys for user's ~/.ssh/authorized_keys diff --git a/manifests/init.pp b/manifests/init.pp index da08605..aee55c3 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -47,6 +47,8 @@ class ssh ( $sshd_config_denygroups = undef, $sshd_config_allowusers = undef, $sshd_config_allowgroups = undef, + $sshd_config_maxstartups = undef, + $sshd_config_maxsessions = undef, $sshd_banner_content = undef, $sshd_banner_owner = 'root', $sshd_banner_group = 'root', @@ -434,6 +436,17 @@ class ssh ( validate_string($sshd_config_authkey_location) } + if $sshd_config_maxstartups != undef { + validate_string($sshd_config_maxstartups) + } + + if $sshd_config_maxsessions != undef { + $is_int_sshd_config_maxsessions = is_integer($sshd_config_maxsessions) + if $is_int_sshd_config_maxsessions == false { + fail("sshd_config_maxsessions must be an integer. Detected value is ${sshd_config_maxsessions}.") + } + } + 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 eebd65d..9eb5050 100644 --- a/spec/classes/init_spec.rb +++ b/spec/classes/init_spec.rb @@ -86,6 +86,8 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^\s*GSSAPIKeyExchange no$/) } it { should_not contain_file('sshd_config').with_content(/^AuthorizedKeysFile/) } 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(/^AcceptEnv L.*$/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } @@ -205,6 +207,8 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^\s*AcceptEnv L.*$/) } it { should_not contain_file('sshd_config').with_content(/^AuthorizedKeysFile/) } 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(/^ServerKeyBits 768$/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } @@ -307,6 +311,8 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^\s*AcceptEnv L.*$/) } it { should_not contain_file('sshd_config').with_content(/^AuthorizedKeysFile/) } 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(/^ServerKeyBits 768$/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } @@ -408,6 +414,8 @@ describe 'ssh' do it { should_not contain_file('sshd_config').with_content(/^\s*AcceptEnv L.*$/) } it { should_not contain_file('sshd_config').with_content(/^AuthorizedKeysFile/) } 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(/^ServerKeyBits 768$/) } it { should contain_file('sshd_config').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } @@ -517,6 +525,8 @@ describe 'ssh' do it { should contain_file('sshd_config').with_content(/^AcceptEnv L.*$/) } it { should_not contain_file('sshd_config').with_content(/^AuthorizedKeysFile/) } 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('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/) } @@ -625,6 +635,8 @@ describe 'ssh' do it { should contain_file('sshd_config').with_content(/^AcceptEnv L.*$/) } it { should_not contain_file('sshd_config').with_content(/^AuthorizedKeysFile/) } 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').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } it { should contain_file('sshd_config').without_content(/^\s*DenyUsers/) } @@ -733,6 +745,8 @@ describe 'ssh' do it { should contain_file('sshd_config').with_content(/^AcceptEnv L.*$/) } it { should_not contain_file('sshd_config').with_content(/^AuthorizedKeysFile/) } 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').without_content(/^\s*Ciphers/) } it { should contain_file('sshd_config').without_content(/^\s*MACs/) } it { should contain_file('sshd_config').without_content(/^\s*DenyUsers/) } @@ -935,6 +949,8 @@ describe 'ssh' do it { should contain_file('sshd_config').with_content(/^HostKey \/etc\/ssh\/ssh_host_rsa_key/) } it { should contain_file('sshd_config').with_content(/^HostKey \/etc\/ssh\/ssh_host_dsa_key/) } 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(/^\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$/) } @@ -2219,6 +2235,60 @@ describe 'ssh' do end end + describe 'with parameter sshd_config_maxstartups specified' do + context 'as a valid string' do + let(:params) { { :sshd_config_maxstartups => '10:30:100' } } + let(:facts) do + { :fqdn => 'monkey.example.com', + :osfamily => 'RedHat', + :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' + } + end + it { should contain_file('sshd_config').with_content(/^MaxStartups 10:30:100$/) } + end + + context 'as an invalid type' do + let(:params) { { :sshd_config_maxstartups => true } } + let(:facts) do + { :fqdn => 'monkey.example.com', + :osfamily => 'RedHat', + :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' + } + end + it 'should fail' do + expect { should raise_error(Puppet::Error) } + end + end + end + +#MAX + describe 'with parameter sshd_config_maxsessions specified' do + context 'as a valid integer' do + let(:params) { { :sshd_config_maxsessions => 10 } } + let(:facts) do + { :fqdn => 'monkey.example.com', + :osfamily => 'RedHat', + :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' + } + end + it { should contain_file('sshd_config').with_content(/^MaxSessions 10$/) } + end + + context 'as an invalid type' do + let(:params) { { :sshd_config_maxsessions => 'BOGUS' } } + let(:facts) do + { :fqdn => 'monkey.example.com', + :osfamily => 'RedHat', + :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' + } + end + it 'should fail' do + expect { should raise_error(Puppet::Error) } + end + end + end +#MAX + describe 'with parameter sshd_acceptenv specified' do ['true',true].each do |value| context "as #{value}" do diff --git a/templates/sshd_config.erb b/templates/sshd_config.erb index 7e04039..fac430d 100644 --- a/templates/sshd_config.erb +++ b/templates/sshd_config.erb @@ -154,7 +154,18 @@ ClientAliveCountMax <%= @sshd_client_alive_count_max %> UseDNS <%= @sshd_config_use_dns_real %> <% end -%> #PidFile /var/run/sshd.pid -#MaxStartups 10 +<% if @sshd_config_maxstartups %> +MaxStartups <%= sshd_config_maxstartups %> +<% else %> +#MaxStartups 10:30:100 +<% end %> +<% if @sshd_config_maxsessions %> +MaxSessions <%= sshd_config_maxsessions %> +<% else %> +#MaxSessions 10 +<% end %> + + #PermitTunnel no #ChrootDirectory none From d462f6f0b2e7a0b715aa8c30cea494ffcc5b3a06 Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Wed, 12 Nov 2014 16:02:14 -0800 Subject: [PATCH 2/3] Improve validation of sshd_config_maxstartups and add spec tests --- manifests/init.pp | 3 ++- spec/classes/init_spec.rb | 43 ++++++++++++++++++++++++++++----------- templates/sshd_config.erb | 13 ++++++------ 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/manifests/init.pp b/manifests/init.pp index aee55c3..92a0e84 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -437,7 +437,8 @@ class ssh ( } if $sshd_config_maxstartups != undef { - validate_string($sshd_config_maxstartups) + validate_re($sshd_config_maxstartups,'^(\d+)+(\d+?:\d+?:\d+)?$', + "ssh::sshd_config_maxstartups may be either an integer or three integers separated with colons, such as 10:30:100. Detected value is <${sshd_config_maxstartups}>.") } if $sshd_config_maxsessions != undef { diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb index 9eb5050..0a5e14a 100644 --- a/spec/classes/init_spec.rb +++ b/spec/classes/init_spec.rb @@ -2236,15 +2236,36 @@ describe 'ssh' do end describe 'with parameter sshd_config_maxstartups specified' do - context 'as a valid string' do - let(:params) { { :sshd_config_maxstartups => '10:30:100' } } - let(:facts) do - { :fqdn => 'monkey.example.com', - :osfamily => 'RedHat', - :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' - } + ['10','10:30:100'].each do |value| + context "as a valid string - #{value}" do + let(:params) { { :sshd_config_maxstartups => value } } + let(:facts) do + { :fqdn => 'monkey.example.com', + :osfamily => 'RedHat', + :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' + } + end + + it { should contain_file('sshd_config').with_content(/^MaxStartups #{value}$/) } + end + end + + ['10a',true,'10:30:1a'].each do |value| + context "as an invalid string - #{value}" do + let(:params) { { :sshd_config_maxstartups => value } } + let(:facts) do + { :fqdn => 'monkey.example.com', + :osfamily => 'RedHat', + :sshrsakey => 'AAAAB3NzaC1yc2EAAAABIwAAAQEArGElx46pD6NNnlxVaTbp0ZJMgBKCmbTCT3RaeCk0ZUJtQ8wkcwTtqIXmmiuFsynUT0DFSd8UIodnBOPqitimmooAVAiAi30TtJVzADfPScMiUnBJKZajIBkEMkwUcqsfh630jyBvLPE/kyQcxbEeGtbu1DG3monkeymanOBW1AKc5o+cJLXcInLnbowMG7NXzujT3BRYn/9s5vtT1V9cuZJs4XLRXQ50NluxJI7sVfRPVvQI9EMbTS4AFBXUej3yfgaLSV+nPZC/lmJ2gR4t/tKvMFF9m16f8IcZKK7o0rK7v81G/tREbOT5YhcKLK+0wBfR6RsmHzwy4EddZloyLQ==' + } + end + + it 'should fail' do + expect { + should contain_class('ssh') + }.to raise_error(Puppet::Error,/^ssh::sshd_config_maxstartups may be either an integer or three integers separated with colons, such as 10:30:100. Detected value is <#{value}>./) + end end - it { should contain_file('sshd_config').with_content(/^MaxStartups 10:30:100$/) } end context 'as an invalid type' do @@ -2256,12 +2277,11 @@ describe 'ssh' do } end it 'should fail' do - expect { should raise_error(Puppet::Error) } + expect { should contain_class('ssh') }.to raise_error(Puppet::Error) end end end -#MAX describe 'with parameter sshd_config_maxsessions specified' do context 'as a valid integer' do let(:params) { { :sshd_config_maxsessions => 10 } } @@ -2283,11 +2303,10 @@ describe 'ssh' do } end it 'should fail' do - expect { should raise_error(Puppet::Error) } + expect { should contain_class('ssh') }.to raise_error(Puppet::Error) end end end -#MAX describe 'with parameter sshd_acceptenv specified' do ['true',true].each do |value| diff --git a/templates/sshd_config.erb b/templates/sshd_config.erb index fac430d..43f0231 100644 --- a/templates/sshd_config.erb +++ b/templates/sshd_config.erb @@ -154,17 +154,16 @@ ClientAliveCountMax <%= @sshd_client_alive_count_max %> UseDNS <%= @sshd_config_use_dns_real %> <% end -%> #PidFile /var/run/sshd.pid -<% if @sshd_config_maxstartups %> +<% if @sshd_config_maxstartups -%> MaxStartups <%= sshd_config_maxstartups %> -<% else %> +<% else -%> #MaxStartups 10:30:100 -<% end %> -<% if @sshd_config_maxsessions %> +<% end -%> +<% if @sshd_config_maxsessions -%> MaxSessions <%= sshd_config_maxsessions %> -<% else %> +<% else -%> #MaxSessions 10 -<% end %> - +<% end -%> #PermitTunnel no #ChrootDirectory none From 9232254a1b1f50140c9c9b937932b25be0c5e32f Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Wed, 12 Nov 2014 16:05:37 -0800 Subject: [PATCH 3/3] Support Ruby v2.1.0 --- .travis.yml | 8 ++++++++ README.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 00d4941..08e6e8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,14 @@ rvm: - 1.8.7 - 1.9.3 - 2.0.0 +- 2.1.0 +matrix: + fast_finish: true + exclude: + - rvm: 2.1.0 + env: PUPPET_VERSION=3.3.2 + - 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' diff --git a/README.md b/README.md index 29cc0e5..e79ed7b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The module uses exported resources to manage ssh keys and removes ssh keys that # Compatability -This module has been tested to work on the following systems with Puppet v3 and Ruby versions 1.8.7, 1.9.3 and 2.0.0. +This module has been tested to work on the following systems with Puppet v3 and Ruby versions 1.8.7, 1.9.3, 2.0.0 and 2.1.0. * Debian 7 * EL 5