From 925f81e0ea3381a701fd691438c3d2b8bae2baea Mon Sep 17 00:00:00 2001 From: Brian Dwyer Date: Fri, 7 Apr 2017 11:16:08 -0400 Subject: [PATCH] Add support for extra configuration options and improve readability with block syntax Signed-off-by: Brian Dwyer --- README.md | 21 ++++++ attributes/default.rb | 108 +++++++++++++++------------- spec/recipes/client_spec.rb | 27 +++++++ spec/recipes/server_spec.rb | 27 +++++++ templates/default/openssh.conf.erb | 7 ++ templates/default/opensshd.conf.erb | 7 ++ 6 files changed, 148 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 1517f20..54a7197 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ override['ssh-hardening']['ssh']['server']['listen_to'] = node['ipaddress'] * `['ssh-hardening']['ssh']['client']['remote_hosts']` - `[]` - one or more hosts, to which ssh-client can connect to. * `['ssh-hardening']['ssh']['client']['password_authentication']` - `false`. Set to `true` if password authentication should be enabled. * `['ssh-hardening']['ssh']['client']['roaming']` - `false`. Set to `true` if experimental client roaming should be enabled. This is known to cause potential issues with secrets being disclosed to malicious servers and defaults to being disabled. +* `['ssh-hardening']['ssh']['client']['extras']` - `{}`. Add extra configuration options, see [below](#extra-configuration-options) for details * `['ssh-hardening']['ssh']['server']['host_key_files']` - `nil` to calculate best hostkey configuration based on server version, otherwise specify an array with file paths (e.g. `/etc/ssh/ssh_host_rsa_key`) * `['ssh-hardening']['ssh']['server']['dh_min_prime_size']` - `2048` - Minimal acceptable prime length in bits in `/etc/ssh/moduli`. Primes below this number will get removed. (See [this](https://entropux.net/article/openssh-moduli/) for more information and background) * `['ssh-hardening']['ssh']['server']['dh_build_primes']` - `false` - If own primes should be built. This rebuild happens only once and takes a lot of time (~ 1.5 - 2h on the modern hardware for 4096 length). @@ -76,6 +77,8 @@ override['ssh-hardening']['ssh']['server']['listen_to'] = node['ipaddress'] * `['ssh-hardening']['ssh']['server']['sftp']['group']` - `sftponly`. Sets the `Match Group` option of SFTP to allow SFTP only for dedicated users * `['ssh-hardening']['ssh']['server']['sftp']['chroot']` - `/home/%u`. Sets the directory where the SFTP user should be chrooted +* `['ssh-hardening']['ssh']['server']['extras']` - `{}`. Add extra configuration options, see [below](#extra-configuration-options) for details + ## Usage @@ -124,6 +127,24 @@ Configure attributes: This will enable the SFTP Server and chroot every user in the `sftpusers` group to the `/home/sftp/%u` directory. +## Extra Configuration Options +Extra configuration options can be appended to the client or server configuration files. This can be used to override statically set values, or add configuration options not otherwise available via attributes. + +The syntax is as follows: +``` +# => Extra Server Configuration +default['ssh-hardening']['ssh']['server']['extras'].tap do |extra| + extra['#Some Comment'] = 'Heres the Comment' + extra['AuthenticationMethods'] = 'publickey,keyboard-interactive' +end + +# => Extra Client Configuration +default['ssh-hardening']['ssh']['client']['extras'].tap do |extra| + extra['PermitLocalCommand'] = 'no' + extra['Tunnel'] = 'no' +end +``` + ## Local Testing For local testing you can use vagrant and Virtualbox of VMWare to run tests locally. You will have to install Virtualbox and Vagrant on your system. See [Vagrant Downloads](http://downloads.vagrantup.com/) for a vagrant package suitable for your system. For all our tests we use `test-kitchen`. If you are not familiar with `test-kitchen` please have a look at [their guide](http://kitchen.ci/docs/getting-started). We are writing our test with [InSpec](https://github.com/chef/inspec). diff --git a/attributes/default.rb b/attributes/default.rb index 8ce83b2..128ae2c 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -52,55 +52,65 @@ default['ssh-hardening']['ssh']['ports'] = [22] # ssh client -default['ssh-hardening']['ssh']['client']['mac'] = nil # nil = calculate best combination for client -default['ssh-hardening']['ssh']['client']['kex'] = nil # nil = calculate best combination for client -default['ssh-hardening']['ssh']['client']['cipher'] = nil # nil = calculate best combination for client -default['ssh-hardening']['ssh']['client']['cbc_required'] = false -default['ssh-hardening']['ssh']['client']['weak_hmac'] = false -default['ssh-hardening']['ssh']['client']['weak_kex'] = false -default['ssh-hardening']['ssh']['client']['remote_hosts'] = [] -default['ssh-hardening']['ssh']['client']['password_authentication'] = false # ssh -# http://undeadly.org/cgi?action=article&sid=20160114142733 -default['ssh-hardening']['ssh']['client']['roaming'] = false -default['ssh-hardening']['ssh']['client']['send_env'] = ['LANG', 'LC_*', 'LANGUAGE'] +default['ssh-hardening']['ssh']['client'].tap do |client| + client['mac'] = nil # nil = calculate best combination for client + client['kex'] = nil # nil = calculate best combination for client + client['cipher'] = nil # nil = calculate best combination for client + client['cbc_required'] = false + client['weak_hmac'] = false + client['weak_kex'] = false + client['remote_hosts'] = [] + client['password_authentication'] = false # ssh + # http://undeadly.org/cgi?action=article&sid=20160114142733 + client['roaming'] = false + client['send_env'] = ['LANG', 'LC_*', 'LANGUAGE'] + + # extra client configuration options + client['extras'] = {} +end # sshd -default['ssh-hardening']['ssh']['server']['kex'] = nil # nil = calculate best combination for server version -default['ssh-hardening']['ssh']['server']['cipher'] = nil # nil = calculate best combination for server version -default['ssh-hardening']['ssh']['server']['mac'] = nil # nil = calculate best combination for server version -default['ssh-hardening']['ssh']['server']['cbc_required'] = false -default['ssh-hardening']['ssh']['server']['weak_hmac'] = false -default['ssh-hardening']['ssh']['server']['weak_kex'] = false -default['ssh-hardening']['ssh']['server']['dh_min_prime_size'] = 2048 -default['ssh-hardening']['ssh']['server']['dh_build_primes'] = false -default['ssh-hardening']['ssh']['server']['dh_build_primes_size'] = 4096 -default['ssh-hardening']['ssh']['server']['host_key_files'] = nil -default['ssh-hardening']['ssh']['server']['client_alive_interval'] = 600 # 10min -default['ssh-hardening']['ssh']['server']['client_alive_count'] = 3 # ~> 3 x interval -default['ssh-hardening']['ssh']['server']['allow_root_with_key'] = false -default['ssh-hardening']['ssh']['server']['allow_tcp_forwarding'] = false -default['ssh-hardening']['ssh']['server']['allow_agent_forwarding'] = false -default['ssh-hardening']['ssh']['server']['allow_x11_forwarding'] = false -default['ssh-hardening']['ssh']['server']['use_pam'] = true -default['ssh-hardening']['ssh']['server']['challenge_response_authentication'] = false -default['ssh-hardening']['ssh']['server']['deny_users'] = [] -default['ssh-hardening']['ssh']['server']['allow_users'] = [] -default['ssh-hardening']['ssh']['server']['deny_groups'] = [] -default['ssh-hardening']['ssh']['server']['allow_groups'] = [] -default['ssh-hardening']['ssh']['server']['print_motd'] = false -default['ssh-hardening']['ssh']['server']['print_last_log'] = false -default['ssh-hardening']['ssh']['server']['banner'] = nil # set this to nil to disable banner or provide a path like '/etc/issue.net' -default['ssh-hardening']['ssh']['server']['os_banner'] = false # (Debian OS family) -default['ssh-hardening']['ssh']['server']['use_dns'] = nil # set this to nil to let us use the default OpenSSH in case it's not set by the user -default['ssh-hardening']['ssh']['server']['use_privilege_separation'] = nil # set this to nil to let us detect the attribute based on the node platform -default['ssh-hardening']['ssh']['server']['login_grace_time'] = '30s' -default['ssh-hardening']['ssh']['server']['max_auth_tries'] = 2 -default['ssh-hardening']['ssh']['server']['max_sessions'] = 10 -default['ssh-hardening']['ssh']['server']['password_authentication'] = false -default['ssh-hardening']['ssh']['server']['log_level'] = 'verbose' -default['ssh-hardening']['ssh']['server']['accept_env'] = ['LANG', 'LC_*', 'LANGUAGE'] +default['ssh-hardening']['ssh']['server'].tap do |server| # rubocop: disable BlockLength + server['kex'] = nil # nil = calculate best combination for server version + server['cipher'] = nil # nil = calculate best combination for server version + server['mac'] = nil # nil = calculate best combination for server version + server['cbc_required'] = false + server['weak_hmac'] = false + server['weak_kex'] = false + server['dh_min_prime_size'] = 2048 + server['dh_build_primes'] = false + server['dh_build_primes_size'] = 4096 + server['host_key_files'] = nil + server['client_alive_interval'] = 600 # 10min + server['client_alive_count'] = 3 # ~> 3 x interval + server['allow_root_with_key'] = false + server['allow_tcp_forwarding'] = false + server['allow_agent_forwarding'] = false + server['allow_x11_forwarding'] = false + server['use_pam'] = true + server['challenge_response_authentication'] = false + server['deny_users'] = [] + server['allow_users'] = [] + server['deny_groups'] = [] + server['allow_groups'] = [] + server['print_motd'] = false + server['print_last_log'] = false + server['banner'] = nil # set this to nil to disable banner or provide a path like '/etc/issue.net' + server['os_banner'] = false # (Debian OS family) + server['use_dns'] = nil # set this to nil to let us use the default OpenSSH in case it's not set by the user + server['use_privilege_separation'] = nil # set this to nil to let us detect the attribute based on the node platform + server['login_grace_time'] = '30s' + server['max_auth_tries'] = 2 + server['max_sessions'] = 10 + server['password_authentication'] = false + server['log_level'] = 'verbose' + server['accept_env'] = ['LANG', 'LC_*', 'LANGUAGE'] -# sshd sftp options -default['ssh-hardening']['ssh']['server']['sftp']['enable'] = false -default['ssh-hardening']['ssh']['server']['sftp']['group'] = 'sftponly' -default['ssh-hardening']['ssh']['server']['sftp']['chroot'] = '/home/%u' + # extra server configuration options + server['extras'] = {} + + # sshd sftp options + server['sftp']['enable'] = false + server['sftp']['group'] = 'sftponly' + server['sftp']['chroot'] = '/home/%u' +end diff --git a/spec/recipes/client_spec.rb b/spec/recipes/client_spec.rb index 42905af..fbbfcf2 100644 --- a/spec/recipes/client_spec.rb +++ b/spec/recipes/client_spec.rb @@ -191,6 +191,33 @@ end end + describe 'extra configuration values' do + context 'without custom extra config value' do + cached(:chef_run) do + ChefSpec::ServerRunner.new.converge(described_recipe) + end + + it 'does not have any extra config options' do + expect(chef_run).to render_file('/etc/ssh/ssh_config') + expect(chef_run).not_to render_file('/etc/ssh/ssh_config'). + with_content(/^# Extra Configuration Options/) + end + end + + context 'with custom extra config value' do + cached(:chef_run) do + ChefSpec::ServerRunner.new do |node| + node.normal['ssh-hardening']['ssh']['client']['extras']['#ExtraConfig'] = 'Value' + end.converge(described_recipe) + end + + it 'uses the extra config attributes' do + expect(chef_run).to render_file('/etc/ssh/ssh_config').with_content(/^# Extra Configuration Options/) + expect(chef_run).to render_file('/etc/ssh/ssh_config').with_content(/^#ExtraConfig Value/) + end + end + end + context 'chef-solo' do cached(:chef_run) do ChefSpec::SoloRunner.new.converge(described_recipe) diff --git a/spec/recipes/server_spec.rb b/spec/recipes/server_spec.rb index 151dff4..8376e58 100644 --- a/spec/recipes/server_spec.rb +++ b/spec/recipes/server_spec.rb @@ -403,6 +403,33 @@ end end + describe 'extra configuration values' do + context 'without custom extra config value' do + cached(:chef_run) do + ChefSpec::ServerRunner.new.converge(described_recipe) + end + + it 'does not have any extra config options' do + expect(chef_run).to render_file('/etc/ssh/sshd_config') + expect(chef_run).not_to render_file('/etc/ssh/sshd_config'). + with_content(/^# Extra Configuration Options/) + end + end + + context 'with custom extra config value' do + cached(:chef_run) do + ChefSpec::ServerRunner.new do |node| + node.normal['ssh-hardening']['ssh']['server']['extras']['#ExtraConfig'] = 'Value' + end.converge(described_recipe) + end + + it 'uses the extra config attributes' do + expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content(/^# Extra Configuration Options/) + expect(chef_run).to render_file('/etc/ssh/sshd_config').with_content(/^#ExtraConfig Value/) + end + end + end + it 'disables the challenge response authentication' do expect(chef_run).to render_file('/etc/ssh/sshd_config'). with_content(/ChallengeResponseAuthentication no/) diff --git a/templates/default/openssh.conf.erb b/templates/default/openssh.conf.erb index b1b7729..3d7281b 100644 --- a/templates/default/openssh.conf.erb +++ b/templates/default/openssh.conf.erb @@ -117,3 +117,10 @@ UseRoaming <%= @node['ssh-hardening']['ssh']['client']['roaming'] ? 'yes' : 'no' # Send locale environment variables SendEnv <%= @node['ssh-hardening']['ssh']['client']['send_env'].join(' ') %> <% end %> + +<%- unless @node['ssh-hardening']['ssh']['client']['extras'].empty? %> +# Extra Configuration Options + <%- @node['ssh-hardening']['ssh']['client']['extras'].each do |key, value| %> +<%= key %> <%= value %> + <% end -%> +<% end -%> diff --git a/templates/default/opensshd.conf.erb b/templates/default/opensshd.conf.erb index b6c35f5..dc7dd12 100644 --- a/templates/default/opensshd.conf.erb +++ b/templates/default/opensshd.conf.erb @@ -229,3 +229,10 @@ X11Forwarding no #PermitRootLogin no #X11Forwarding no <% end %> + +<%- unless @node['ssh-hardening']['ssh']['server']['extras'].empty? %> +# Extra Configuration Options + <%- @node['ssh-hardening']['ssh']['server']['extras'].each do |key, value| %> +<%= key %> <%= value %> + <% end -%> +<% end -%>